package org.music.player; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.media.AsyncPlayer; import android.media.AudioManager; import android.net.Uri; import android.os.Build; import android.os.SystemClock; import android.telephony.TelephonyManager; import android.view.KeyEvent; /** * Receives media button events and calls to PlaybackService to respond * appropriately. */ public class MediaButtonReceiver extends BroadcastReceiver { /** * If another button event is received before this time in milliseconds * expires, the event with be considered a double click. */ private static final int DOUBLE_CLICK_DELAY = 400; /** * Whether the headset controls should be used. 1 for yes, 0 for no, -1 for * uninitialized. */ private static int sUseControls = -1; /** * Whether the phone is currently in a call. 1 for yes, 0 for no, -1 for * uninitialized. */ private static int sInCall = -1; /** * Time of the last play/pause click. Used to detect double-clicks. */ private static long sLastClickTime = 0; /** * Whether a beep should be played in response to double clicks be used. * 1 for yes, 0 for no, -1 for uninitialized. */ private static int sBeep = -1; /** * Lazy-loaded AsyncPlayer for beep sounds. */ private static AsyncPlayer sBeepPlayer; /** * Lazy-loaded URI of the beep resource. */ private static Uri sBeepSound; /** * Play a beep sound. */ private static void beep(Context context) { if (sBeep == -1) { SharedPreferences settings = PlaybackService.getSettings(context); sBeep = settings.getBoolean(PrefKeys.MEDIA_BUTTON_BEEP, true) ? 1 : 0; } if (sBeep == 1) { if (sBeepPlayer == null) { sBeepPlayer = new AsyncPlayer("BeepPlayer"); sBeepSound = Uri.parse("android.resource://org.music.player/raw/beep"); } sBeepPlayer.play(context, sBeepSound, false, AudioManager.STREAM_MUSIC); } } /** * Reload the preferences and enable/disable buttons as appropriate. * * @param context A context to use. */ public static void reloadPreference(Context context) { sUseControls = -1; sBeep = -1; if (useHeadsetControls(context)) { registerMediaButton(context); } else { unregisterMediaButton(context); } } /** * Return whether headset controls should be used, loading the preference * if necessary. * * @param context A context to use. */ public static boolean useHeadsetControls(Context context) { if (sUseControls == -1) { SharedPreferences settings = PlaybackService.getSettings(context); sUseControls = settings.getBoolean(PrefKeys.MEDIA_BUTTON, true) ? 1 : 0; } return sUseControls == 1; } /** * Return whether the phone is currently in a call. * * @param context A context to use. */ private static boolean isInCall(Context context) { if (sInCall == -1) { TelephonyManager manager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); sInCall = (byte)(manager.getCallState() == TelephonyManager.CALL_STATE_IDLE ? 0 : 1); } return sInCall == 1; } /** * Set the cached value for whether the phone is in a call. * * @param value True if in a call, false otherwise. */ public static void setInCall(boolean value) { sInCall = value ? 1 : 0; } /** * Process a media button key press. * * @param context A context to use. * @param event The key press event. * @return True if the event was handled and the broadcast should be * aborted. */ public static boolean processKey(Context context, KeyEvent event) { if (event == null || isInCall(context) || !useHeadsetControls(context)) return false; int action = event.getAction(); String act = null; switch (event.getKeyCode()) { case KeyEvent.KEYCODE_HEADSETHOOK: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: // single click: pause/resume. // double click: next track if (action == KeyEvent.ACTION_DOWN) { long time = SystemClock.uptimeMillis(); if (time - sLastClickTime < DOUBLE_CLICK_DELAY) { beep(context); act = PlaybackService.ACTION_NEXT_SONG_AUTOPLAY; } else { act = PlaybackService.ACTION_TOGGLE_PLAYBACK; } sLastClickTime = time; } break; case KeyEvent.KEYCODE_MEDIA_NEXT: if (action == KeyEvent.ACTION_DOWN) act = PlaybackService.ACTION_NEXT_SONG_AUTOPLAY; break; case KeyEvent.KEYCODE_MEDIA_PREVIOUS: if (action == KeyEvent.ACTION_DOWN) act = PlaybackService.ACTION_PREVIOUS_SONG_AUTOPLAY; break; case KeyEvent.KEYCODE_MEDIA_PLAY: if (action == KeyEvent.ACTION_DOWN) act = PlaybackService.ACTION_PLAY; break; case KeyEvent.KEYCODE_MEDIA_PAUSE: if (action == KeyEvent.ACTION_DOWN) act = PlaybackService.ACTION_PAUSE; break; default: return false; } if (act != null) { Intent intent = new Intent(context, PlaybackService.class); intent.setAction(act); context.startService(intent); } return true; } /** * Request focus on the media buttons from AudioManager if media buttons * are enabled. * * @param context A context to use. */ public static void registerMediaButton(Context context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO || !useHeadsetControls(context)) return; AudioManager audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); ComponentName receiver = new ComponentName(context.getPackageName(), MediaButtonReceiver.class.getName()); CompatFroyo.registerMediaButtonEventReceiver(audioManager, receiver); } /** * Unregister the media buttons from AudioManager. * * @param context A context to use. */ public static void unregisterMediaButton(Context context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) return; AudioManager audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); ComponentName receiver = new ComponentName(context.getPackageName(), MediaButtonReceiver.class.getName()); CompatFroyo.unregisterMediaButtonEventReceiver(audioManager, receiver); } @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) { KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); boolean handled = processKey(context, event); if (handled && isOrderedBroadcast()) abortBroadcast(); } } }