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

Commit dbec5b9f authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add locking around MediaSession.Callback.mSession" into main

parents 848acbcf b6e19667
Loading
Loading
Loading
Loading
+126 −87
Original line number Diff line number Diff line
@@ -240,15 +240,15 @@ public final class MediaSession {
        synchronized (mLock) {
            if (mCallback != null) {
                // We're updating the callback, clear the session from the old one.
                mCallback.mCallback.mSession = null;
                mCallback.removeCallbacksAndMessages(null);
                mCallback.mCallback.clearSession();

            }
            if (callback == null) {
                mCallback = null;
                return;
            }
            Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
            callback.mSession = this;
            callback.setSession(this);
            CallbackMessageHandler msgHandler = new CallbackMessageHandler(looper, callback);
            mCallback = msgHandler;
        }
@@ -916,6 +916,7 @@ public final class MediaSession {
     */
    public abstract static class Callback {

        private final Object mSessionLock = new Object();
        private MediaSession mSession;
        private CallbackMessageHandler mHandler;
        private boolean mMediaPlayPauseKeyPending;
@@ -923,6 +924,20 @@ public final class MediaSession {
        public Callback() {
        }

        private void setSession(MediaSession session) {
            synchronized (mSessionLock) {
                mSession = session;
            }
        }

        private void clearSession() {
            synchronized (mSessionLock) {
                mSession = null;
                mMediaPlayPauseKeyPending = false;
                mHandler.removeCallbacksAndMessages(null);
            }
        }

        /**
         * Called when a controller has sent a command to this session.
         * The owner of the session may handle custom commands but is not
@@ -950,25 +965,39 @@ public final class MediaSession {
         * @return True if the event was handled, false otherwise.
         */
        public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
            if (mSession != null && mHandler != null
                    && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
                KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT, android.view.KeyEvent.class);
                if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) {
            if (mHandler == null
                    || !Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
                return false;
            }
            KeyEvent ke = mediaButtonIntent.getParcelableExtra(
                    Intent.EXTRA_KEY_EVENT, android.view.KeyEvent.class);
            if (ke == null || ke.getAction() != KeyEvent.ACTION_DOWN) {
                return false;
            }
            // Store some results into locals so we can invoke any app-implemented methods (like
            // onSkipToNext()) outside the synchronized block and avoid the risk of a deadlock.
            long validActions;
            boolean eventHandled/* = false*/;
            Runnable doubleTapHandlingAction = null;
            synchronized (mSessionLock) {
                if (mSession == null) {
                    return false;
                }
                PlaybackState state = mSession.mPlaybackState;
                    long validActions = state == null ? 0 : state.getActions();
                validActions = state == null ? 0 : state.getActions();
                switch (ke.getKeyCode()) {
                        case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
                        case KeyEvent.KEYCODE_HEADSETHOOK:
                    case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_HEADSETHOOK -> {
                        if (ke.getRepeatCount() > 0) {
                            // Consider long-press as a single tap.
                                handleMediaPlayPauseKeySingleTapIfPending();
                            doubleTapHandlingAction =
                                    this::handleMediaPlayPauseKeySingleTapIfPending;
                        } else if (mMediaPlayPauseKeyPending) {
                            // Consider double tap as the next.
                            mHandler.removeMessages(CallbackMessageHandler
                                    .MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
                            mMediaPlayPauseKeyPending = false;
                            if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
                                    onSkipToNext();
                                doubleTapHandlingAction = this::onSkipToNext;
                            }
                        } else {
                            mMediaPlayPauseKeyPending = true;
@@ -980,70 +1009,80 @@ public final class MediaSession {
                                            .getDoubleTapTimeoutMillis()
                                            : ViewConfiguration.getDoubleTapTimeout());
                        }
                            return true;
                        default:
                        eventHandled = true;
                    }
                    default -> {
                        // If another key is pressed within double tap timeout, consider the
                        // pending play/pause as a single tap to handle media keys in order.
                            handleMediaPlayPauseKeySingleTapIfPending();
                            break;
                        doubleTapHandlingAction = this::handleMediaPlayPauseKeySingleTapIfPending;
                        eventHandled = false;
                    }
                }
            }
            if (doubleTapHandlingAction != null) {
                doubleTapHandlingAction.run();
            }
            if (eventHandled) {
                return true;
            }

            switch (ke.getKeyCode()) {
                        case KeyEvent.KEYCODE_MEDIA_PLAY:
                case KeyEvent.KEYCODE_MEDIA_PLAY -> {
                    if ((validActions & PlaybackState.ACTION_PLAY) != 0) {
                        onPlay();
                        return true;
                    }
                            break;
                        case KeyEvent.KEYCODE_MEDIA_PAUSE:
                }
                case KeyEvent.KEYCODE_MEDIA_PAUSE -> {
                    if ((validActions & PlaybackState.ACTION_PAUSE) != 0) {
                        onPause();
                        return true;
                    }
                            break;
                        case KeyEvent.KEYCODE_MEDIA_NEXT:
                }
                case KeyEvent.KEYCODE_MEDIA_NEXT -> {
                    if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
                        onSkipToNext();
                        return true;
                    }
                            break;
                        case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
                }
                case KeyEvent.KEYCODE_MEDIA_PREVIOUS -> {
                    if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
                        onSkipToPrevious();
                        return true;
                    }
                            break;
                        case KeyEvent.KEYCODE_MEDIA_STOP:
                }
                case KeyEvent.KEYCODE_MEDIA_STOP -> {
                    if ((validActions & PlaybackState.ACTION_STOP) != 0) {
                        onStop();
                        return true;
                    }
                            break;
                        case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
                }
                case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD -> {
                    if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
                        onFastForward();
                        return true;
                    }
                            break;
                        case KeyEvent.KEYCODE_MEDIA_REWIND:
                }
                case KeyEvent.KEYCODE_MEDIA_REWIND -> {
                    if ((validActions & PlaybackState.ACTION_REWIND) != 0) {
                        onRewind();
                        return true;
                    }
                            break;
                    }
                }
            }
            return false;
        }

        private void handleMediaPlayPauseKeySingleTapIfPending() {
            if (!mMediaPlayPauseKeyPending) {
            PlaybackState state;
            synchronized (mSessionLock) {
                if (!mMediaPlayPauseKeyPending || mSession == null) {
                    return;
                }
                mMediaPlayPauseKeyPending = false;
            mHandler.removeMessages(CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
            PlaybackState state = mSession.mPlaybackState;
                mHandler.removeMessages(
                        CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
                state = mSession.mPlaybackState;
            }
            long validActions = state == null ? 0 : state.getActions();
            boolean isPlaying = state != null
                    && state.getState() == PlaybackState.STATE_PLAYING;