Optimize how AudioService receives media button events

AudioService maintains a stack of registered media button event
  receivers.
This change modifies the broadcasters of ACTION_MEDIA_BUTTON intents
  let AudioService directly handle the corresponding key event instead
  of trapping the intent sent by PhoneWindowManager, KeyguardViewBase
  and PhoneFallbackEventHandler.
Because the key event may be sent through a PendingIntent,
  AudioService now also implements the OnFinished interface to be
  notified when the event was consumed so it can release the wake
  lock held if it was held when the key event needed to be sent
  (see where PassHeadsetKey was instanciated in PhoneWindowManager).

Change-Id: I2e8614df94af9d54edbf714ef443cc372d21827a
This commit is contained in:
Jean-Michel Trivi
2012-04-30 11:15:03 -07:00
parent d6be37d4d9
commit c68022258e
5 changed files with 154 additions and 72 deletions

View File

@@ -21,10 +21,12 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL;
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
import android.app.Activity;
import android.app.ActivityManagerNative;
import android.app.KeyguardManager;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.app.PendingIntent.OnFinished;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
@@ -48,6 +50,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -85,7 +88,7 @@ import java.util.Stack;
*
* @hide
*/
public class AudioService extends IAudioService.Stub {
public class AudioService extends IAudioService.Stub implements OnFinished {
private static final String TAG = "AudioService";
@@ -289,10 +292,6 @@ public class AudioService extends IAudioService.Stub {
// Broadcast receiver for device connections intent broadcasts
private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
// Broadcast receiver for media button broadcasts (separate from mReceiver to
// independently change its priority)
private final BroadcastReceiver mMediaButtonReceiver = new MediaButtonBroadcastReceiver();
// Used to alter media button redirection when the phone is ringing.
private boolean mIsRinging = false;
@@ -383,6 +382,9 @@ public class AudioService extends IAudioService.Stub {
mVoiceCapable = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_voice_capable);
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "mediaKeyEvent");
// Intialized volume
MAX_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL] = SystemProperties.getInt(
"ro.config.vc_call_vol_steps",
@@ -434,13 +436,6 @@ public class AudioService extends IAudioService.Stub {
pkgFilter.addDataScheme("package");
context.registerReceiver(mReceiver, pkgFilter);
// Register for media button intent broadcasts.
intentFilter = new IntentFilter(Intent.ACTION_MEDIA_BUTTON);
// Workaround for bug on priority setting
//intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
intentFilter.setPriority(Integer.MAX_VALUE);
context.registerReceiver(mMediaButtonReceiver, intentFilter);
// Register for phone state monitoring
TelephonyManager tmgr = (TelephonyManager)
context.getSystemService(Context.TELEPHONY_SERVICE);
@@ -3492,54 +3487,109 @@ public class AudioService extends IAudioService.Stub {
//==========================================================================================
// RemoteControl
//==========================================================================================
public void dispatchMediaKeyEvent(KeyEvent keyEvent) {
dispatchMediaKeyEvent(keyEvent, false /*needWakeLock*/);
}
public void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) {
dispatchMediaKeyEvent(keyEvent, true /*needWakeLock*/);
}
/**
* Receiver for media button intents. Handles the dispatching of the media button event
* to one of the registered listeners, or if there was none, resumes the intent broadcast
* to the rest of the system.
* Handles the dispatching of the media button events to one of the registered listeners,
* or if there was none, broadcast a ACTION_MEDIA_BUTTON intent to the rest of the system.
*/
private class MediaButtonBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (!Intent.ACTION_MEDIA_BUTTON.equals(action)) {
private void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
// sanity check on the incoming key event
if (!isValidMediaKeyEvent(keyEvent)) {
Log.e(TAG, "not dispatching invalid media key event " + keyEvent);
return;
}
// event filtering
synchronized(mRingingLock) {
if (mIsRinging || (getMode() == AudioSystem.MODE_IN_CALL) ||
(getMode() == AudioSystem.MODE_IN_COMMUNICATION) ||
(getMode() == AudioSystem.MODE_RINGTONE) ) {
return;
}
KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (event != null) {
// if in a call or ringing, do not break the current phone app behavior
// TODO modify this to let the phone app specifically get the RC focus
// add modify the phone app to take advantage of the new API
synchronized(mRingingLock) {
if (mIsRinging || (getMode() == AudioSystem.MODE_IN_CALL) ||
(getMode() == AudioSystem.MODE_IN_COMMUNICATION) ||
(getMode() == AudioSystem.MODE_RINGTONE) ) {
return;
}
}
if (needWakeLock) {
mMediaEventWakeLock.acquire();
}
Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
synchronized(mRCStack) {
if (!mRCStack.empty()) {
// send the intent that was registered by the client
try {
mRCStack.peek().mMediaIntent.send(mContext,
needWakeLock ? WAKELOCK_RELEASE_ON_FINISHED : 0 /*code*/,
keyIntent, AudioService.this, mAudioHandler);
} catch (CanceledException e) {
Log.e(TAG, "Error sending pending intent " + mRCStack.peek());
e.printStackTrace();
}
synchronized(mRCStack) {
if (!mRCStack.empty()) {
// create a new intent to fill in the extras of the registered PendingIntent
Intent targetedIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
Bundle extras = intent.getExtras();
if (extras != null) {
targetedIntent.putExtras(extras);
// trap the current broadcast
abortBroadcast();
//Log.v(TAG, " Sending intent" + targetedIntent);
// send the intent that was registered by the client
try {
mRCStack.peek().mMediaIntent.send(context, 0, targetedIntent);
} catch (CanceledException e) {
Log.e(TAG, "Error sending pending intent " + mRCStack.peek());
e.printStackTrace();
}
}
}
} else {
// legacy behavior when nobody registered their media button event receiver
// through AudioManager
if (needWakeLock) {
keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
}
mContext.sendOrderedBroadcast(keyIntent, null, mKeyEventDone,
mAudioHandler, Activity.RESULT_OK, null, null);
}
}
}
private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) {
if (keyEvent == null) {
return false;
}
final int keyCode = keyEvent.getKeyCode();
switch (keyCode) {
case KeyEvent.KEYCODE_MUTE:
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_PLAY:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
case KeyEvent.KEYCODE_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
case KeyEvent.KEYCODE_MEDIA_REWIND:
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
case KeyEvent.KEYCODE_MEDIA_CLOSE:
case KeyEvent.KEYCODE_MEDIA_EJECT:
break;
default:
return false;
}
return true;
}
private PowerManager.WakeLock mMediaEventWakeLock;
private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; //magic number
// only set when wakelock was acquired, no need to check value when received
private static final String EXTRA_WAKELOCK_ACQUIRED =
"android.media.AudioService.WAKELOCK_ACQUIRED";
public void onSendFinished(PendingIntent pendingIntent, Intent intent,
int resultCode, String resultData, Bundle resultExtras) {
if (resultCode == WAKELOCK_RELEASE_ON_FINISHED) {
mMediaEventWakeLock.release();
}
}
BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if (intent.getExtras().containsKey(EXTRA_WAKELOCK_ACQUIRED)) {
mMediaEventWakeLock.release();
}
}
};
private final Object mCurrentRcLock = new Object();
/**
* The one remote control client which will receive a request for display information.

View File

@@ -23,6 +23,7 @@ import android.media.IRemoteControlClient;
import android.media.IRemoteControlDisplay;
import android.media.IRingtonePlayer;
import android.net.Uri;
import android.view.KeyEvent;
/**
* {@hide}
@@ -102,6 +103,9 @@ interface IAudioService {
void unregisterAudioFocusClient(String clientId);
oneway void dispatchMediaKeyEvent(in KeyEvent keyEvent);
void dispatchMediaKeyEventUnderWakelock(in KeyEvent keyEvent);
oneway void registerMediaButtonIntent(in PendingIntent pi, in ComponentName c);
oneway void unregisterMediaButtonIntent(in PendingIntent pi, in ComponentName c);

View File

@@ -24,12 +24,17 @@ import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.IAudioService;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import android.view.View;
import android.view.Gravity;
import android.widget.FrameLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Slog;
/**
* Base class for keyguard views. {@link #reset} is where you should
@@ -194,9 +199,7 @@ public abstract class KeyguardViewBase extends FrameLayout {
case KeyEvent.KEYCODE_MEDIA_REWIND:
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
intent.putExtra(Intent.EXTRA_KEY_EVENT, event);
getContext().sendOrderedBroadcast(intent, null);
handleMediaKeyEvent(event);
return true;
}
@@ -240,9 +243,7 @@ public abstract class KeyguardViewBase extends FrameLayout {
case KeyEvent.KEYCODE_MEDIA_REWIND:
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
intent.putExtra(Intent.EXTRA_KEY_EVENT, event);
getContext().sendOrderedBroadcast(intent, null);
handleMediaKeyEvent(event);
return true;
}
}
@@ -250,6 +251,20 @@ public abstract class KeyguardViewBase extends FrameLayout {
return false;
}
void handleMediaKeyEvent(KeyEvent keyEvent) {
IAudioService audioService = IAudioService.Stub.asInterface(
ServiceManager.checkService(Context.AUDIO_SERVICE));
if (audioService != null) {
try {
audioService.dispatchMediaKeyEvent(keyEvent);
} catch (RemoteException e) {
Log.e("KeyguardViewBase", "dispatchMediaKeyEvent threw exception " + e);
}
} else {
Slog.w("KeyguardViewBase", "Unable to find IAudioService for media key event");
}
}
@Override
public void dispatchSystemUiVisibilityChanged(int visibility) {
super.dispatchSystemUiVisibilityChanged(visibility);

View File

@@ -23,8 +23,12 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.media.IAudioService;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.telephony.TelephonyManager;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import android.view.View;
import android.view.HapticFeedbackConstants;
@@ -99,9 +103,7 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler {
case KeyEvent.KEYCODE_MEDIA_REWIND:
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
intent.putExtra(Intent.EXTRA_KEY_EVENT, event);
mContext.sendOrderedBroadcast(intent, null);
handleMediaKeyEvent(event);
return true;
}
@@ -213,9 +215,7 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler {
case KeyEvent.KEYCODE_MEDIA_REWIND:
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
intent.putExtra(Intent.EXTRA_KEY_EVENT, event);
mContext.sendOrderedBroadcast(intent, null);
handleMediaKeyEvent(event);
return true;
}
@@ -285,5 +285,19 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler {
void sendCloseSystemWindows() {
PhoneWindowManager.sendCloseSystemWindows(mContext, null);
}
private void handleMediaKeyEvent(KeyEvent keyEvent) {
IAudioService audioService = IAudioService.Stub.asInterface(
ServiceManager.checkService(Context.AUDIO_SERVICE));
if (audioService != null) {
try {
audioService.dispatchMediaKeyEvent(keyEvent);
} catch (RemoteException e) {
Log.e(TAG, "dispatchMediaKeyEvent threw exception " + e);
}
} else {
Slog.w(TAG, "Unable to find IAudioService for media key event.");
}
}
}

View File

@@ -3299,20 +3299,19 @@ public class PhoneWindowManager implements WindowManagerPolicy {
public void run() {
if (ActivityManagerNative.isSystemReady()) {
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
intent.putExtra(Intent.EXTRA_KEY_EVENT, mKeyEvent);
mContext.sendOrderedBroadcast(intent, null, mBroadcastDone,
mHandler, Activity.RESULT_OK, null, null);
IAudioService audioService = getAudioService();
if (audioService != null) {
try {
audioService.dispatchMediaKeyEventUnderWakelock(mKeyEvent);
} catch (RemoteException e) {
Log.e(TAG, "dispatchMediaKeyEvent threw exception " + e);
}
}
mBroadcastWakeLock.release();
}
}
}
BroadcastReceiver mBroadcastDone = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
mBroadcastWakeLock.release();
}
};
BroadcastReceiver mDockReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) {