From fb53c329bb7e04198c24fdba15c59d74bba829a2 Mon Sep 17 00:00:00 2001 From: Christopher Eby Date: Tue, 11 Oct 2011 13:30:10 -0500 Subject: [PATCH] Refactor MediaButtonHandler Merge into MediaButtonReceiver, make static-only, avoid using reflection --- src/org/kreed/vanilla/MediaButtonHandler.java | 255 ------------------ .../kreed/vanilla/MediaButtonReceiver.java | 171 +++++++++++- src/org/kreed/vanilla/PlaybackActivity.java | 10 +- src/org/kreed/vanilla/PlaybackService.java | 16 +- 4 files changed, 178 insertions(+), 274 deletions(-) delete mode 100644 src/org/kreed/vanilla/MediaButtonHandler.java diff --git a/src/org/kreed/vanilla/MediaButtonHandler.java b/src/org/kreed/vanilla/MediaButtonHandler.java deleted file mode 100644 index 8bf468fa..00000000 --- a/src/org/kreed/vanilla/MediaButtonHandler.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (C) 2010, 2011 Christopher Eby - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.kreed.vanilla; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.media.AudioManager; -import android.os.SystemClock; -import android.telephony.TelephonyManager; -import android.util.Log; -import android.view.KeyEvent; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import junit.framework.Assert; - -/** - * Handle a provided MediaButton event and take the appropriate action in - * PlaybackService. - */ -public class MediaButtonHandler { - /** - * 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; - - /** - * The current global instance of this class. - */ - private static MediaButtonHandler mInstance; - /** - * Whether the headset controls should be used. 1 for yes, 0 for no, -1 for - * uninitialized. - */ - private static int mUseControls = -1; - - private final Context mContext; - /** - * Whether the phone is currently in a call. 1 for yes, 0 for no, -1 for - * uninitialized. - */ - private int mInCall = -1; - /** - * Time of the last play/pause click. Used to detect double-clicks. - */ - private long mLastClickTime; - - private static final AudioManager mAudioManager; - private static Method mRegisterMediaButtonEventReceiver; - private static Method mUnregisterMediaButtonEventReceiver; - public static final ComponentName mButtonReceiver; - - /** - * Retrieve the MediaButtonHandler singleton, creating it if necessary. - * Returns null if headset controls are not enabled. - */ - public static MediaButtonHandler getInstance(Context context) - { - if (useHeadsetControls(context)) { - if (mInstance == null) - mInstance = new MediaButtonHandler(context.getApplicationContext()); - return mInstance; - } - return null; - } - - /** - * Construct a MediaButtonHandler. - */ - private MediaButtonHandler(Context context) - { - mContext = context; - mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); - mButtonReceiver = new ComponentName(context.getPackageName(), MediaButtonReceiver.class.getName()); - try { - mRegisterMediaButtonEventReceiver = AudioManager.class.getMethod("registerMediaButtonEventReceiver", ComponentName.class); - mUnregisterMediaButtonEventReceiver = AudioManager.class.getMethod("unregisterMediaButtonEventReceiver", ComponentName.class); - } catch (NoSuchMethodException nsme) { - // Older Android; just use receiver priority - } - } - - /** - * Reload the preference and enable/disable buttons as appropriate. - * - * @param context A context to use. - */ - public static void reloadPreference(Context context) - { - mUseControls = -1; - if (useHeadsetControls(context)) { - getInstance(context).registerMediaButton(); - } else { - unregisterMediaButton(); - } - } - - /** - * 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 (mUseControls == -1) { - SharedPreferences settings = PlaybackService.getSettings(context); - mUseControls = settings.getBoolean("media_button", true) ? 1 : 0; - } - - return mUseControls == 1; - } - - /** - * Return whether the phone is currently in a call. - */ - private boolean isInCall() - { - if (mInCall == -1) { - TelephonyManager manager = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE); - mInCall = (byte)(manager.getCallState() == TelephonyManager.CALL_STATE_IDLE ? 0 : 1); - } - return mInCall == 1; - } - - /** - * Set the cached value for whether the phone is in a call. - * - * @param value True if in a call, false otherwise. - */ - public void setInCall(boolean value) - { - mInCall = value ? 1 : 0; - } - - /** - * Send the given action to the playback service. - * - * @param action One of the PlaybackService.ACTION_* actions. - */ - private void act(String action) - { - Intent intent = new Intent(mContext, PlaybackService.class); - intent.setAction(action); - mContext.startService(intent); - } - - /** - * Process a media button key press. - */ - public boolean processKey(KeyEvent event) - { - if (event == null || isInCall() || !useHeadsetControls(mContext)) - return false; - - int action = event.getAction(); - - 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 - mLastClickTime < DOUBLE_CLICK_DELAY) - act(PlaybackService.ACTION_NEXT_SONG_AUTOPLAY); - else - act(PlaybackService.ACTION_TOGGLE_PLAYBACK); - mLastClickTime = 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; - default: - return false; - } - - return true; - } - - /** - * Process a MediaButton broadcast. - * - * @param intent The intent that was broadcast - * @return True if the intent was handled and the broadcast should be - * aborted. - */ - public boolean process(Intent intent) - { - KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); - return processKey(event); - } - - /** - * Request focus on the media buttons from AudioManager. - */ - public void registerMediaButton() - { - Assert.assertEquals(mUseControls, 1); - if (mRegisterMediaButtonEventReceiver != null) { - try { - mRegisterMediaButtonEventReceiver.invoke(mAudioManager, mButtonReceiver); - } catch (InvocationTargetException e) { - Log.w("VanillaMusic", e); - } catch (IllegalAccessException e) { - Log.w("VanillaMusic", e); - } - } - } - - /** - * Unregister the media buttons from AudioManager. - */ - public static void unregisterMediaButton() - { - if (mUnregisterMediaButtonEventReceiver != null) { - try { - mUnregisterMediaButtonEventReceiver.invoke(mAudioManager, mButtonReceiver); - } catch (InvocationTargetException e) { - Log.w("VanillaMusic", e); - } catch (IllegalAccessException e) { - Log.w("VanillaMusic", e); - } - } - } -} diff --git a/src/org/kreed/vanilla/MediaButtonReceiver.java b/src/org/kreed/vanilla/MediaButtonReceiver.java index ca2e7d16..0116516d 100644 --- a/src/org/kreed/vanilla/MediaButtonReceiver.java +++ b/src/org/kreed/vanilla/MediaButtonReceiver.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Christopher Eby + * Copyright (C) 2010, 2011 Christopher Eby * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,16 +22,183 @@ package org.kreed.vanilla; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.BroadcastReceiver; +import android.content.SharedPreferences; +import android.media.AudioManager; +import android.os.Build; +import android.os.SystemClock; +import android.telephony.TelephonyManager; +import android.view.KeyEvent; 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; + + /** + * Reload the preference and enable/disable buttons as appropriate. + * + * @param context A context to use. + */ + public static void reloadPreference(Context context) + { + sUseControls = -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("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) + 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; + 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()); + audioManager.registerMediaButtonEventReceiver(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()); + audioManager.unregisterMediaButtonEventReceiver(receiver); + } + @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) { - boolean handled = MediaButtonHandler.getInstance(context).process(intent); + KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); + boolean handled = processKey(context, event); if (handled && isOrderedBroadcast()) abortBroadcast(); } diff --git a/src/org/kreed/vanilla/PlaybackActivity.java b/src/org/kreed/vanilla/PlaybackActivity.java index a35724fb..c3a9c7a8 100644 --- a/src/org/kreed/vanilla/PlaybackActivity.java +++ b/src/org/kreed/vanilla/PlaybackActivity.java @@ -119,13 +119,11 @@ public class PlaybackActivity extends Activity public void onResume() { super.onResume(); + MediaButtonReceiver.registerMediaButton(this); + MediaButtonReceiver.setInCall(false); if (PlaybackService.hasInstance()) { PlaybackService service = PlaybackService.get(this); service.userActionTriggered(); - - MediaButtonHandler buttons = MediaButtonHandler.getInstance(this); - if (buttons != null) - buttons.setInCall(false); } } @@ -137,7 +135,7 @@ public class PlaybackActivity extends Activity case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: case KeyEvent.KEYCODE_MEDIA_NEXT: case KeyEvent.KEYCODE_MEDIA_PREVIOUS: - return MediaButtonHandler.getInstance(this).processKey(event); + return MediaButtonReceiver.processKey(this, event); } return super.onKeyDown(keyCode, event); @@ -151,7 +149,7 @@ public class PlaybackActivity extends Activity case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: case KeyEvent.KEYCODE_MEDIA_NEXT: case KeyEvent.KEYCODE_MEDIA_PREVIOUS: - return MediaButtonHandler.getInstance(this).processKey(event); + return MediaButtonReceiver.processKey(this, event); } return super.onKeyUp(keyCode, event); diff --git a/src/org/kreed/vanilla/PlaybackService.java b/src/org/kreed/vanilla/PlaybackService.java index 1b81410f..cd432bc5 100644 --- a/src/org/kreed/vanilla/PlaybackService.java +++ b/src/org/kreed/vanilla/PlaybackService.java @@ -313,9 +313,7 @@ public final class PlaybackService extends Service implements Handler.Callback, pause(); } - MediaButtonHandler buttons = MediaButtonHandler.getInstance(this); - if (buttons != null) - buttons.registerMediaButton(); + MediaButtonReceiver.registerMediaButton(this); } } @@ -335,7 +333,7 @@ public final class PlaybackService extends Service implements Handler.Callback, mMediaPlayer = null; } - MediaButtonHandler.unregisterMediaButton(); + MediaButtonReceiver.unregisterMediaButton(this); try { unregisterReceiver(mReceiver); @@ -383,7 +381,7 @@ public final class PlaybackService extends Service implements Handler.Callback, if (mMediaPlayer != null) mMediaPlayer.setVolume(volume, volume); } else if ("media_button".equals(key)) { - MediaButtonHandler.reloadPreference(this); + MediaButtonReceiver.reloadPreference(this); } else if ("use_idle_timeout".equals(key) || "idle_timeout".equals(key)) { mIdleTimeout = settings.getBoolean("use_idle_timeout", false) ? settings.getInt("idle_timeout", 3600) : 0; userActionTriggered(); @@ -833,9 +831,7 @@ public final class PlaybackService extends Service implements Handler.Callback, switch (state) { case TelephonyManager.CALL_STATE_RINGING: case TelephonyManager.CALL_STATE_OFFHOOK: { - MediaButtonHandler buttons = MediaButtonHandler.getInstance(PlaybackService.this); - if (buttons != null) - buttons.setInCall(true); + MediaButtonReceiver.setInCall(true); if (!mPlayingBeforeCall) { synchronized (mStateLock) { @@ -846,9 +842,7 @@ public final class PlaybackService extends Service implements Handler.Callback, break; } case TelephonyManager.CALL_STATE_IDLE: { - MediaButtonHandler buttons = MediaButtonHandler.getInstance(PlaybackService.this); - if (buttons != null) - buttons.setInCall(false); + MediaButtonReceiver.setInCall(false); if (mPlayingBeforeCall) { setFlag(FLAG_PLAYING);