Monday 27 May 2013

Android Sound Management

SOUND IS AUDIBLE IN A LOCKED SCREEN

By default, Android does not mute application sounds when a user locks the screen. This means that, if you don't handle phone state changes properly, application sounds will be audible even if the screen is locked. The simplest way to avoid this is to write a receiver for 2 intents:
  • ACTION_USER_PRESENT to catch actions to resume playing application sounds
  • ON_SCREEN_OFF to catch actions to stop playing application sounds
ANDROIDMANIFEST.XML
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.samsung.lockscreenreceiver"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk android:minSdkVersion="8" />
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:label="@string/app_name"
            android:name=".MyActivity" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver android:name=".LockScreenReceiver" >
            <intent-filter >
              <action android:name="android.intent.action.ACTION_USER_PRESENT"/>
              <action android:name="android.intent.action.SCREEN_OFF"/>
            </intent-filter>
        </receiver>
       </application>
</manifest>
LOCK SCREEN RECEIVER
Your receiver should extend the BroadcastReceiver class. The ACTION_USER_PRESENT intent should be received every time the user unblocks the device. Every time the user turns the screen off, the SCREEN_OFF intent should be received. This is the proper time to resume and stop playing your application’s sounds.

package com.samsung.lockscreenreceiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class LockScreenReceiver extends BroadcastReceiver {

 @Override
 public void onReceive(Context context, Intent intent) {
  
  final String action = intent.getAction();
  if(action == Intent.ACTION_USER_PRESENT) {
   //here add your code to resume playing sounds
  } else if(action == Intent.ACTION_SCREEN_OFF) {
   //here add your code to stop playing sounds
  }
 }
}
ACTIVITY
Your receiver should be registered during the application’s life and unregistered on its termination.
package com.samsung.lockscreenreceiver;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;

public class MyActivity extends Activity {

    private BroadcastReceiver mReceiver;

    @Override
    public void onCreate(Bundle savedInstanceState) {

         super.onCreate(savedInstanceState);
         setContentView(R.layout.main);
         mReceiver = new LockScreenReceiver();
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_USER_PRESENT);
         intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
         registerReceiver(mReceiver, intentFilter);
    }
 
    @Override
    protected void onDestroy() 
    {
         super.onDestroy();
         unregisterReceiver(mReceiver);
    }
}

SOUND IS AUDIBLE IN SILENT MODE

By default, Android does not mute application sounds when a user selects silent mode on the device. This means that, if you don't handle ringer mode changes properly, application sounds are audible even if silent mode is on.
The simplest way to avoid this is to ask users if they want to turn on the application sound in 2 cases:
  • When silent mode is set when the application starts
  • When ringer mode is changed to silent while the application lives
To do this, the AndroidManifest.xml file should declare the receiver with RINGER_MODE_CHANGED_ACTION.
ANDROIDMANIFEST.XML
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.samsung.silentmodechecker"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk android:minSdkVersion="8" />
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:label="@string/app_name"
            android:name=".SilentModeCheckerActivity" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver android:name=".SilentModeReceiver" >
            <intent-filter >
              <action android:name="android.media.RINGER_MODE_CHANGED_ACTION"/>
            </intent-filter>
        </receiver>
    </application>
</manifest>
SILENT MODE RECEIVER
Your receiver should extend the BroadcastReceiver class. The RINGER_MODE_CHANGED_ACTION intent will be broadcast every time a user changes the ringer mode. If there was a change to silent or vibrate mode, you should ask the user if he wants to turn the application sound off even though the sound shouldn't be audible.
package com.samsung.silentmodechecker;

import android.app.Activity;
import android.app.alertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Bundle;

public class SilentModeCheckerActivity extends Activity {
 
    SilentModeReceiver mReceiver;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
     
   mReceiver = new SilentModeReceiver();
             registerReceiver(mReceiver, 
                                new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION));
     
     AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
     final int mode = audioManager.getRingerMode();
     if(mode == AudioManager.RINGER_MODE_SILENT 
                                              || mode == AudioManager.RINGER_MODE_VIBRATE) 
            {
      showSilentModeDialog(this);
     }
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
    }
 
    @Override
    protected void onDestroy() {
            super.onDestroy();
            unregisterReceiver(mReceiver);
    }

    static alertDialog salertDialog = null;
 
    public static void showSilentModeDialog(Context context) 
    {
 if(salertDialog != null) {
     return;
 }

             alertDialog.Builder builder = new alertDialog.Builder(context);
 builder.setMessage( "Silent mode is set, do you want to play application
                                         sounds anyway? " ).setPositiveButton("Yes", 
                                         new DialogInterface.OnClickListener() 
                          {
         public void onClick(DialogInterface dialog, int id) 
                                {
                 // Turn on Your application sounds
     salertDialog = null;
         }
   } ).setNegativeButton( "No", new DialogInterface.OnClickListener() 
                          {
         public void onClick(DialogInterface dialog, int id) 
                                 {
     // Turn off Your application sounds
   salertDialog = null;
          }
   });

 salertDialog = builder.create();
 salertDialog.show();
    }
 
    public static void hideSilentModeDialog() 
    {
  
 if(salertDialog != null) {
  salertDialog.dismiss();
  salertDialog = null;
 }
     }
}

APPLICATION SOUNDS OVERLAP WITH THE CALL SOUND

By default, Android doesn't mute application sounds when a user receives an incoming call. This means that, if you don't handle phone state changes properly, the application sounds and call sounds may overlap when receiving a call. The simplest way to avoid this is to write a receiver for incoming calls. You have to register this for thePHONE_STATE intent. Make sure that you also have the READ_PHONE_STATE permission in your manifest file.
ANDROIDMAIFEST.XML
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="yourpackage.namespace"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk android:minSdkVersion="10" />
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:label="@string/app_name"
            android:name=".YourActivity" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver android:name=".IncomingCallReceiver" >
            <intent-filter >
                <action android:name="android.intent.action.PHONE_STATE" />
            </intent-filter>
        </receiver>
    </application>
    <uses-permission android:name="android.permission.PHONE_STATE" />
</manifest>
INCOMING CALL RECEIVER
Your receiver should extend the BroadcastReceiver class. The PHONE_STATE intent will be broadcast every time the phone state changes. The intent will have a string variable EXTRA_STATE. If there is an incoming call it will take the value:
EXTRA_STATE_RINGING
package yourpackage.namespace;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.TelephonyManager;

public class IncomingCallReceiver extends BroadcastReceiver {

 @Override
 public void onReceive(Context context, Intent intent) {
       Bundle bundle = intent.getExtras();
       if (bundle == null){
   return;
       }

       String state = bundle.getString(TelephonyManager.EXTRA_STATE); 

            if (state.equalsIgnoreCase(TelephonyManager.EXTRA_STATE_RINGING)) 
                   {
  //here add your code to stop playing sounds
        }
 }
}

SOUND OVERLAPS WITH ANOTHER APPLICATION’S SOUND

The Android platform allows overlapping sounds by default. Before API level 8, there was no built-in mechanism to address this issue, which could lead to a bad user experience in some cases. API level 8 offers a way for applications to negotiate their use of the device's audio output. This mechanism is called Audio Focus. Your application should listen for audio focus changes. Request audio focus before starting to play sounds and mute sound when your application looses the audio focus. Remember to release the audio focus when you no longer use audio output.
Using Audio Focus isn't obligatory, but it's good practice. If you don't use it, your application will still work, but a user is more likely to uninstall it when its sound overlaps with the sounds of other applications.
HANDLING THE AUDIO FOCUS
An application should request the Audio Focus when it needs to utilize the audio output, either with music or notification sounds. Once it has the priority of the Audio Focus, it can use the sound output freely, while listening for focus changes. If the Audio Focus loses the focus, it should either kill the audio immediately or decrease it to softer level and only resume playing loudly when it receives the focus again. If an application needs to output music, the Audio Focus should be requested. The requestAudioFocus() method should be called from the AudioManager.
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
 // could not get audio focus.
}
The first parameter to the requestAudioFocus() method is OnAudioFocusChangeListener, whose onAudioFocusChange() method is called whenever there is a change in audio focus. Therefore, you should also implement this interface on your service and activities.
class OnAudioFocus extends  Service 
              implements  AudioManager.OnAudioFocusChangeListener 
{
 // ....
 public void onAudioFocusChange(int focusChange) {
  // Do something based on focus change...
 }
}
The focusChange parameter gives information about the changes in the audio focus.
Below are the values obtained based on the audio focus.
  • AUDIOFOCUS_GAIN
    When the audio focus is gained
  • AUDIOFOCUS_LOSS
    You have lost the audio focus presumably for a long time. You must stop all audio playback and because you may not have the focus back for some time, this is a great opportunity to clean up your resources as much as possible. For example, you should release the MediaPlayer.
  • AUDIOFOCUS_LOSS_TRANSIENT
    You have temporarily lost the audio focus, but should receive it back shortly. You must stop all audio playback, but you can keep your resources because you will probably get the focus back shortly.
  • AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
    You have temporarily lost the audio focus, but you are allowed to continue playing audio quietly (at a low volume) instead of killing the audio completely.
public void onAudioFocusChange(int focusChange) {
 switch (focusChange) {
 case AudioManager.AUDIOFOCUS_GAIN:
  // TODO : resume playback
 case AudioManager.AUDIOFOCUS_LOSS:
  // TODO : stop playback and release media player
 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
  // TODO : pause palyback
 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
  // TODO : continue playing at an attenuated level
 }
}
If an application no longer needs to output audio, the audio focus should be abandoned: audioManager.abandonAudioFocus(this). These audio focus APIs are only available with API Level 8 (Android 2.2) and above. Backward compatibility can be achieved by calling the audio focus methods by reflection or by implementing all the audio focus features in a separate class.
You can only create an instance of this class if you detect that the system is running API level 8 or above. For example:
if (android.os.Build.VERSION.SDK_INT >= 8) {
 // TODO: separate class implementing all audio focus features
}

EXAMPLE OF HANDLING THE AUDIO FOCUS

package com.audio.handling;

import android.app.Activity;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class AudioHandling extends Activity implements OnClickListener {

 public Button playm = null;
 public Button pausem = null;
 public Button stopm = null; 
 public MediaPlayer mp = null;
 public AudioManager audioManager = null;
 public boolean playing;

 /** Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  audioManager = (AudioManager) 
                                                 getSystemService(Context.AUDIO_SERVICE);
  // PLAYER BUTTONS
  playm = (Button) findViewById(R.id.Button01);
  playm.setOnClickListener(this); 
  pausem = (Button) findViewById(R.id.Button02);
  pausem.setOnClickListener(this); 
  stopm = (Button) findViewById(R.id.Button03);
  stopm.setOnClickListener(this);
 }

 @Override
 public void onClick(View v) {
                         // PLAYER
  if (v == playm) {
        OnAudioFocus onAudioFocus = new OnAudioFocus(this);
       audioManager.requestAudioFocus(onAudioFocus, 
    AudioManager.STREAM_MUSIC, 
    AudioManager.AUDIOFOCUS_GAIN);
   playSong();
  }
  if (v == pausem) {
   pauseSong();
  }
  if (v == stopm) {
   stopSong();
  }
 }

 public void playSong() {
  if (mp == null) {
   mp = MediaPlayer.create(this, R.raw.mysong);
   try {
    mp.start();
    playing = true;
   } catch (IllegalArgumentException e) {
    e.printStackTrace();
   } catch (IllegalStateException e) {
    e.printStackTrace();
   }
  }
 }

 public void stopSong() {
  if (mp != null) {
   mp.stop();
   mp.release();
   playing = false;
   mp = null;
  }
 }

 public void pauseSong() {
  if (mp != null) {
   mp.pause();
  }
 }
}

class OnAudioFocus implements AudioManager.OnAudioFocusChangeListener {

 AudioHandling onAudioFocus;

 public OnAudioFocus(AudioHandling onAudioFocus) {
  this.onAudioFocus = onAudioFocus;
 }

 public void onAudioFocusChange(int focusChange) {
  switch (focusChange) {
  case AudioManager.AUDIOFOCUS_GAIN:
   if (onAudioFocus.mp == null) {
    onAudioFocus.playSong();
   } else if (onAudioFocus.mp.isPlaying()) {
    onAudioFocus.mp.start();
   }
   break; 
  case AudioManager.AUDIOFOCUS_LOSS:
   if (onAudioFocus.mp.isPlaying())
    onAudioFocus.stopSong();

   break;
  case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
   if (onAudioFocus.mp.isPlaying())
    onAudioFocus.pauseSong();
   break;
  case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
   if (onAudioFocus.mp.isPlaying())
    onAudioFocus.mp.setVolume(0.1f, 0.1f);
   break;

  }
 }
}

No comments:

Post a Comment

Disqus for Android