Loading media/java/android/media/session/MediaSession.java +126 −87 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -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; Loading @@ -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 Loading Loading @@ -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; Loading @@ -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; Loading Loading
media/java/android/media/session/MediaSession.java +126 −87 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -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; Loading @@ -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 Loading Loading @@ -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; Loading @@ -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; Loading