From c68022258ebd3dd97a5079ba99f4f3cd12b223b0 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Mon, 30 Apr 2012 11:15:03 -0700 Subject: [PATCH] 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 --- media/java/android/media/AudioService.java | 150 ++++++++++++------ media/java/android/media/IAudioService.aidl | 4 + .../policy/impl/KeyguardViewBase.java | 27 +++- .../impl/PhoneFallbackEventHandler.java | 26 ++- .../policy/impl/PhoneWindowManager.java | 19 ++- 5 files changed, 154 insertions(+), 72 deletions(-) diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index dc9496fe22828..c0b17d982b49a 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -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. diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 1a2714eef9172..48f091c796e66 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -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); diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java b/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java index 39fdf92acb8db..5aa764b980906 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java +++ b/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java @@ -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); diff --git a/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java b/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java index 83f77880836dc..b88d84bee0fbd 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java +++ b/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java @@ -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."); + } + } } diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index d2053ff26d8fe..ed54a5d8c03a7 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -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())) {