Refactor MediaButtonHandler
Merge into MediaButtonReceiver, make static-only, avoid using reflection
This commit is contained in:
parent
556351df6c
commit
fb53c329bb
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user