Refactor MediaButtonHandler

Merge into MediaButtonReceiver, make static-only, avoid using reflection
This commit is contained in:
Christopher Eby 2011-10-11 13:30:10 -05:00
parent 556351df6c
commit fb53c329bb
4 changed files with 178 additions and 274 deletions

View File

@ -1,255 +0,0 @@
/*
* Copyright (C) 2010, 2011 Christopher Eby <kreed@kreed.org>
*
* 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);
}
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Christopher Eby <kreed@kreed.org>
* Copyright (C) 2010, 2011 Christopher Eby <kreed@kreed.org>
*
* 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();
}

View File

@ -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);

View File

@ -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);