Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c6802225 authored by Jean-Michel Trivi's avatar Jean-Michel Trivi
Browse files

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
parent d6be37d4
Loading
Loading
Loading
Loading
+100 −50
Original line number Diff line number Diff line
@@ -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,23 +3487,25 @@ 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;
        }
            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
        // event filtering
        synchronized(mRingingLock) {
            if (mIsRinging || (getMode() == AudioSystem.MODE_IN_CALL) ||
                    (getMode() == AudioSystem.MODE_IN_COMMUNICATION) ||
@@ -3516,30 +3513,83 @@ public class AudioService extends IAudioService.Stub {
                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()) {
                        // 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);
                    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();
                }
            } 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.
+4 −0
Original line number Diff line number Diff line
@@ -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);

+21 −6
Original line number Diff line number Diff line
@@ -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);
+20 −6
Original line number Diff line number Diff line
@@ -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.");
        }
    }
}
+9 −10
Original line number Diff line number Diff line
@@ -3299,19 +3299,18 @@ 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);
                    }
                }

    BroadcastReceiver mBroadcastDone = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
                mBroadcastWakeLock.release();
            }
    };
        }
    }

    BroadcastReceiver mDockReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {