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

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

MediaFocusControl: priority to playing players for media button

When registering a media button event receiver (through
 AudioManager.registerMediaButtonEventReceiver()), do not
 always push the receiver to the top of the stack of event
 receivers:
 - only push to the top if the associated RemoteControlClient
   is in a playing state
 - otherwise push it below the entries at the top of the stack
   that are in a playing state
When changing the playstate of a RemoteControlClient:
 - push to the top of the stack the corresponding PlayerRecord
   is the state is a playing state
 - otherwise push it below the entries at the top of the stack
   that are in a playing state

When AudioService starts (e.g. after boot) and the last media
 button receiver is restored, it goes in the stack.
After this CL, this entry is not "orphaned" anymore after the
 same application registers itself to receive media buttons:
 the entry from the restoration is now properly associated with
 the registration from the application.

Bug 10749554

Change-Id: I985f9cc17b64a60ed4f2f2f6d03e117fb4e27570
parent 7085d138
Loading
Loading
Loading
Loading
+118 −94
Original line number Diff line number Diff line
@@ -327,13 +327,12 @@ public class MediaFocusControl implements OnFinished {
    private static final int MSG_REEVALUATE_REMOTE = 3;
    private static final int MSG_RCC_NEW_PLAYBACK_INFO = 4;
    private static final int MSG_RCC_NEW_VOLUME_OBS = 5;
    private static final int MSG_PROMOTE_RCC = 6;
    private static final int MSG_RCC_NEW_PLAYBACK_STATE = 7;
    private static final int MSG_RCC_SEEK_REQUEST = 8;
    private static final int MSG_RCC_UPDATE_METADATA = 9;
    private static final int MSG_RCDISPLAY_INIT_INFO = 10;
    private static final int MSG_REEVALUATE_RCD = 11;
    private static final int MSG_UNREGISTER_MEDIABUTTONINTENT = 12;
    private static final int MSG_RCC_NEW_PLAYBACK_STATE = 6;
    private static final int MSG_RCC_SEEK_REQUEST = 7;
    private static final int MSG_RCC_UPDATE_METADATA = 8;
    private static final int MSG_RCDISPLAY_INIT_INFO = 9;
    private static final int MSG_REEVALUATE_RCD = 10;
    private static final int MSG_UNREGISTER_MEDIABUTTONINTENT = 11;

    // sendMsg() flags
    /** If the msg is already queued, replace it with this one. */
@@ -406,10 +405,6 @@ public class MediaFocusControl implements OnFinished {
                            (Rating) msg.obj /* value */);
                    break;

                case MSG_PROMOTE_RCC:
                    onPromoteRcc(msg.arg1);
                    break;

                case MSG_RCDISPLAY_INIT_INFO:
                    // msg.obj is guaranteed to be non null
                    onRcDisplayInitInfo((IRemoteControlDisplay)msg.obj /*newRcd*/,
@@ -1212,56 +1207,91 @@ public class MediaFocusControl implements OnFinished {
            mediaButtonIntent.setComponent(eventReceiver);
            PendingIntent pi = PendingIntent.getBroadcast(mContext,
                    0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/);
            registerMediaButtonIntent(pi, eventReceiver, null);
            registerMediaButtonIntent(pi, eventReceiver, null /*token*/);
        }
    }

    /**
     * Helper function:
     * Set the new remote control receiver at the top of the RC focus stack.
     * Push the new media button receiver "near" the top of the PlayerRecord stack.
     * "Near the top" is defined as:
     *   - at the top if the current PlayerRecord at the top is not playing
     *   - below the entries at the top of the stack that correspond to the playing PlayerRecord
     *     otherwise
     * Called synchronized on mPRStack
     * precondition: mediaIntent != null
     * @return true if mPRStack was changed, false otherwise
     * @return true if the top of mPRStack was changed, false otherwise
     */
    private boolean pushMediaButtonReceiver_syncPrs(PendingIntent mediaIntent,
            ComponentName target, IBinder token) {
        // already at top of stack?
        if (!mPRStack.empty() && mPRStack.peek().hasMatchingMediaButtonIntent(mediaIntent)) {
        if (mPRStack.empty()) {
            mPRStack.push(new PlayerRecord(mediaIntent, target, token));
            return true;
        } else if (mPRStack.peek().hasMatchingMediaButtonIntent(mediaIntent)) {
            // already at top of stack
            return false;
        }
        if (mAppOps.noteOp(AppOpsManager.OP_TAKE_MEDIA_BUTTONS, Binder.getCallingUid(),
                mediaIntent.getCreatorPackage()) != AppOpsManager.MODE_ALLOWED) {
            return false;
        }
        PlayerRecord oldTopPrse = mPRStack.lastElement(); // top of the stack before any changes
        boolean topChanged = false;
        PlayerRecord prse = null;
        boolean wasInsideStack = false;
        int lastPlayingIndex = mPRStack.size();
        int inStackIndex = -1;
        try {
            // go through the stack from the top to figure out who's playing, and the position
            // of this media button receiver (note that it may not be in the stack)
            for (int index = mPRStack.size()-1; index >= 0; index--) {
                prse = mPRStack.elementAt(index);
                if (prse.isPlaybackActive()) {
                    lastPlayingIndex = index;
                }
                if (prse.hasMatchingMediaButtonIntent(mediaIntent)) {
                    // ok to remove element while traversing the stack since we're leaving the loop
                    mPRStack.removeElementAt(index);
                    wasInsideStack = true;
                    inStackIndex = index;
                    // found it, ok to stop here
                    break;
                }
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            // not expected to happen, indicates improper concurrent modification
            Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
        }
        if (!wasInsideStack) {

            if (inStackIndex == -1) {
                // is not in stack
                prse = new PlayerRecord(mediaIntent, target, token);
                // it's new so it's not playing (no RemoteControlClient to give a playstate),
                // therefore it goes after the ones with active playback
                mPRStack.add(lastPlayingIndex, prse);
            } else {
                // is in the stack
                if (mPRStack.size() > 1) { // no need to remove and add if stack contains only 1
                    prse = mPRStack.elementAt(inStackIndex);
                    // remove it from its old location in the stack
                    mPRStack.removeElementAt(inStackIndex);
                    if (prse.isPlaybackActive()) {
                        // and put it at the top
                        mPRStack.push(prse);
                    } else {
                        // and put it after the ones with active playback
                        mPRStack.add(lastPlayingIndex, prse);
                    }
                }
            }
        mPRStack.push(prse); // prse is never null

            topChanged = (oldTopPrse != mPRStack.lastElement());
            // post message to persist the default media button receiver
        if (target != null) {
            if (topChanged && (target != null)) {
                mEventHandler.sendMessage( mEventHandler.obtainMessage(
                        MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, target/*obj*/) );
            }

        // RC stack was modified
        return true;
        } catch (ArrayIndexOutOfBoundsException e) {
            // not expected to happen, indicates improper concurrent modification or bad index
            Log.e(TAG, "Wrong index (inStack=" + inStackIndex + " lastPlaying=" + lastPlayingIndex
                    + " size=" + mPRStack.size()
                    + " accessing media button stack", e);
        }

        return (topChanged);
    }

    /**
@@ -1514,48 +1544,6 @@ public class MediaFocusControl implements OnFinished {
        updateRemoteControlDisplay_syncPrs(infoChangedFlags);
    }

    /**
     * Helper function:
     * Post a message to asynchronously move the media button event receiver associated with the
     * given remote control client ID to the top of the remote control stack
     * @param rccId
     */
    private void postPromoteRcc(int rccId) {
        sendMsg(mEventHandler, MSG_PROMOTE_RCC, SENDMSG_REPLACE,
                rccId /*arg1*/, 0, null, 0/*delay*/);
    }

    private void onPromoteRcc(int rccId) {
        if (DEBUG_RC) { Log.d(TAG, "Promoting RCC " + rccId); }
        synchronized(mPRStack) {
            // ignore if given RCC ID is already at top of remote control stack
            if (!mPRStack.isEmpty() && (mPRStack.peek().getRccId() == rccId)) {
                return;
            }
            int indexToPromote = -1;
            try {
                for (int index = mPRStack.size()-1; index >= 0; index--) {
                    final PlayerRecord prse = mPRStack.elementAt(index);
                    if (prse.getRccId() == rccId) {
                        indexToPromote = index;
                        break;
                    }
                }
                if (indexToPromote >= 0) {
                    if (DEBUG_RC) { Log.d(TAG, "  moving RCC from index " + indexToPromote
                            + " to " + (mPRStack.size()-1)); }
                    final PlayerRecord prse = mPRStack.remove(indexToPromote);
                    mPRStack.push(prse);
                    // the RC stack changed, reevaluate the display
                    checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                // not expected to happen, indicates improper concurrent modification
                Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
            }
        }//synchronized(mPRStack)
    }

    /**
     * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c)
     * precondition: mediaIntent != null
@@ -2151,30 +2139,66 @@ public class MediaFocusControl implements OnFinished {
        if(DEBUG_RC) Log.d(TAG, "onNewPlaybackStateForRcc(id=" + rccId + ", state=" + state
                + ", time=" + newState.mPositionMs + ", speed=" + newState.mSpeed + ")");
        synchronized(mPRStack) {
            // iterating from top of stack as playback information changes are more likely
            //   on entries at the top of the remote control stack
            if (mPRStack.empty()) {
                return;
            }
            PlayerRecord oldTopPrse = mPRStack.lastElement(); // top of the stack before any changes
            PlayerRecord prse = null;
            int lastPlayingIndex = mPRStack.size();
            int inStackIndex = -1;
            try {
                // go through the stack from the top to figure out who's playing, and the position
                // of this RemoteControlClient (note that it may not be in the stack)
                for (int index = mPRStack.size()-1; index >= 0; index--) {
                    final PlayerRecord prse = mPRStack.elementAt(index);
                    prse = mPRStack.elementAt(index);
                    if (prse.isPlaybackActive()) {
                        lastPlayingIndex = index;
                    }
                    if (prse.getRccId() == rccId) {
                        inStackIndex = index;
                        prse.mPlaybackState = newState;
                    }
                }

                if (inStackIndex != -1) {
                    // is in the stack
                    prse = mPRStack.elementAt(inStackIndex);
                    synchronized (mMainRemote) {
                        if (rccId == mMainRemote.mRccId) {
                            mMainRemoteIsActive = isPlaystateActive(state);
                            postReevaluateRemote();
                        }
                    }
                        // an RCC moving to a "playing" state should become the media button
                        //   event receiver so it can be controlled, without requiring the
                        //   app to re-register its receiver
                        if (isPlaystateActive(state)) {
                            postPromoteRcc(rccId);
                    if (mPRStack.size() > 1) { // no need to remove and add if stack contains only 1
                        // remove it from its old location in the stack
                        mPRStack.removeElementAt(inStackIndex);
                        if (prse.isPlaybackActive()) {
                            // and put it at the top
                            mPRStack.push(prse);
                        } else {
                            // and put it after the ones with active playback
                            mPRStack.add(lastPlayingIndex, prse);
                        }
                    }

                    if (oldTopPrse != mPRStack.lastElement()) {
                        // the top of the stack changed:
                        final ComponentName target =
                                mPRStack.lastElement().getMediaButtonReceiver();
                        if (target != null) {
                            // post message to persist the default media button receiver
                            mEventHandler.sendMessage( mEventHandler.obtainMessage(
                                    MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, target/*obj*/) );
                        }
                        // reevaluate the display
                        checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
                    }
                }
                }//for
            } catch (ArrayIndexOutOfBoundsException e) {
                // not expected to happen, indicates improper concurrent modification
                Log.e(TAG, "Wrong index on mPRStack in onNewPlaybackStateForRcc, lock error? ", e);
                // not expected to happen, indicates improper concurrent modification or bad index
                Log.e(TAG, "Wrong index (inStack=" + inStackIndex + " lastPlaying=" + lastPlayingIndex
                        + " size=" + mPRStack.size()
                        + "accessing PlayerRecord stack in onNewPlaybackStateForRcc", e);
            }
        }
    }
@@ -2249,7 +2273,7 @@ public class MediaFocusControl implements OnFinished {
     * @param playState the playback state to evaluate
     * @return true if active, false otherwise (inactive or unknown)
     */
    private static boolean isPlaystateActive(int playState) {
    protected static boolean isPlaystateActive(int playState) {
        switch (playState) {
            case RemoteControlClient.PLAYSTATE_PLAYING:
            case RemoteControlClient.PLAYSTATE_BUFFERING:
+16 −1
Original line number Diff line number Diff line
@@ -58,6 +58,9 @@ class PlayerRecord implements DeathRecipient {

    private int mRccId = RemoteControlClient.RCSE_ID_UNREGISTERED;

    /**
     * A non-null token implies this record tracks a "live" player whose death is being monitored.
     */
    private IBinder mToken;
    private String mCallingPackageName;
    private int mCallingUid;
@@ -267,7 +270,19 @@ class PlayerRecord implements DeathRecipient {
    }

    protected boolean hasMatchingMediaButtonIntent(PendingIntent pi) {
        if (mToken != null) {
            return mMediaIntent.equals(pi);
        } else {
            if (mReceiverComponent != null) {
                return mReceiverComponent.equals(pi.getIntent().getComponent());
            } else {
                return false;
            }
        }
    }

    protected boolean isPlaybackActive() {
        return MediaFocusControl.isPlaystateActive(mPlaybackState.mState);
    }

    //---------------------------------------------