Merge "Disable translation of dynamic grouping strings" into pi-androidx-dev
diff --git a/media-widget/src/androidTest/java/androidx/media/widget/VideoView2Test.java b/media-widget/src/androidTest/java/androidx/media/widget/VideoView2Test.java
index 29809a9..0e5c8a8 100644
--- a/media-widget/src/androidTest/java/androidx/media/widget/VideoView2Test.java
+++ b/media-widget/src/androidTest/java/androidx/media/widget/VideoView2Test.java
@@ -52,6 +52,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -213,6 +214,46 @@
                 any(MediaController2.class), eq(BaseMediaPlayer.PLAYER_STATE_PAUSED));
     }
 
+    @Test
+    public void testSetSpeed() throws Throwable {
+        // Don't run the test if the codec isn't supported.
+        if (!hasCodec()) {
+            Log.i(TAG, "SKIPPING testSetSpeed(): codec is not supported");
+            return;
+        }
+
+        final float targetSpeed1 = 1.5f;
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    mVideoView.setSpeed(targetSpeed1);
+                } catch (IllegalStateException e) {
+                    Assert.fail("This should not happen. :" + e);
+                }
+                mVideoView.setMediaItem2(mMediaItem);
+            }
+        });
+        verify(mControllerCallback, timeout(TIME_OUT).atLeastOnce()).onConnected(
+                any(MediaController2.class), any(SessionCommandGroup2.class));
+        verify(mControllerCallback, timeout(TIME_OUT).atLeastOnce()).onPlayerStateChanged(
+                any(MediaController2.class), eq(BaseMediaPlayer.PLAYER_STATE_PAUSED));
+        assertEquals(targetSpeed1, mController.getPlaybackSpeed(), 0.05f);
+
+        final float targetSpeed2 = 0.5f;
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mVideoView.setSpeed(targetSpeed2);
+            }
+        });
+        mController.play();
+        verify(mControllerCallback, timeout(TIME_OUT).atLeastOnce()).onPlaybackSpeedChanged(
+                any(MediaController2.class), eq(targetSpeed2));
+        assertEquals(targetSpeed2, mVideoView.getSpeed(), 0.05f);
+        assertEquals(targetSpeed2, mController.getPlaybackSpeed(), 0.05f);
+    }
+
     private void setKeepScreenOn() throws Throwable {
         mActivityRule.runOnUiThread(new Runnable() {
             @Override
diff --git a/media-widget/src/main/java/androidx/media/widget/VideoView2ImplBase.java b/media-widget/src/main/java/androidx/media/widget/VideoView2ImplBase.java
index cd6faea..c2716da 100644
--- a/media-widget/src/main/java/androidx/media/widget/VideoView2ImplBase.java
+++ b/media-widget/src/main/java/androidx/media/widget/VideoView2ImplBase.java
@@ -120,6 +120,7 @@
     int mTargetState = STATE_IDLE;
     int mCurrentState = STATE_IDLE;
     long mSeekWhenPrepared;  // recording the seek position while preparing
+    float mSpeed;
 
     int mVideoWidth;
     int mVideoHeight;
@@ -169,6 +170,9 @@
                             .setSessionCallback(mCallbackExecutor, new MediaSessionCallback())
                             .build();
                 }
+                if (mSpeed != mMediaSession.getPlaybackSpeed()) {
+                    mMediaSession.setPlaybackSpeed(mSpeed);
+                }
                 if (localPlaybackState == STATE_PLAYING) {
                     mMediaSession.play();
                 }
@@ -207,6 +211,7 @@
         mVideoWidth = 0;
         mVideoHeight = 0;
         mSelectedSubtitleTrackIndex = INVALID_TRACK_INDEX;
+        mSpeed = 1.0f;
 
         mAudioAttributes = new AudioAttributesCompat.Builder()
                 .setUsage(AudioAttributesCompat.USAGE_MEDIA)
@@ -335,13 +340,15 @@
     /**
      * Returns {@link SessionToken2} so that developers create their own
      * {@link androidx.media2.MediaController2} instance. This method should be called when
-     * VideoView2 is attached to window, or it throws IllegalStateException.
+     * VideoView2 is attached to window or after {@link #setMediaItem2} is called.
      *
      * @throws IllegalStateException if internal MediaSession is not created yet.
      */
     @Override
     public SessionToken2 getMediaSessionToken2() {
-        checkMediaSession();
+        if (mMediaSession == null) {
+            throw new IllegalStateException("MediaSession2 instance is not available.");
+        }
         return mMediaSession.getToken();
     }
 
@@ -384,8 +391,10 @@
             Log.e(TAG, "Unsupported speed (" + speed + ") is ignored.");
             return;
         }
-        checkMediaSession();
-        mMediaSession.setPlaybackSpeed(speed);
+        mSpeed = speed;
+        if (isMediaPrepared()) {
+            mMediaSession.setPlaybackSpeed(speed);
+        }
     }
 
     /**
@@ -396,8 +405,7 @@
      */
     @Override
     public float getSpeed() {
-        checkMediaSession();
-        return mMediaSession.getPlaybackSpeed();
+        return mSpeed;
     }
 
     /**
@@ -679,12 +687,6 @@
     ///////////////////////////////////////////////////
     // Protected or private methods
     ///////////////////////////////////////////////////
-    private void checkMediaSession() {
-        if (mMediaSession == null) {
-            throw new IllegalStateException("MediaSession instance is not available.");
-        }
-    }
-
     private void attachMediaControlView() {
         // Get MediaController from MediaSession and set it inside MediaControlView
         mMediaControlView.setMediaSessionToken2(mMediaSession.getToken());
@@ -694,7 +696,7 @@
         mInstance.addView(mMediaControlView, params);
     }
 
-    private boolean isInPlaybackState() {
+    private boolean isMediaPrepared() {
         return mMediaSession != null
                 && mMediaSession.getPlayerState() != BaseMediaPlayer.PLAYER_STATE_ERROR
                 && mMediaSession.getPlayerState() != BaseMediaPlayer.PLAYER_STATE_IDLE;
@@ -750,6 +752,7 @@
                 mMediaSession.updatePlayer(mMediaPlayer.getBaseMediaPlayer(),
                         mMediaSession.getPlaylistAgent());
             }
+            mMediaSession.setPlaylist(mPlayList, null);
 
             final Context context = mInstance.getContext();
             mSubtitleController = new SubtitleController(context);
@@ -757,7 +760,6 @@
             mSubtitleController.registerRenderer(new Cea708CaptionRenderer(context));
             mSubtitleController.setAnchor((SubtitleController.Anchor) mSubtitleAnchorView);
 
-            mMediaSession.setPlaylist(mPlayList, null);
 
             // we don't set the target state here either, but preserve the
             // target state that was there before.
@@ -794,7 +796,7 @@
     }
 
     private void selectOrDeselectSubtitle(boolean select) {
-        if (!isInPlaybackState()) {
+        if (!isMediaPrepared()) {
             return;
         }
         if (select) {
@@ -1082,6 +1084,9 @@
                         extractTracks();
                         extractMetadata();
                         sendMetadata();
+                        if (mSpeed != mMediaSession.getPlaybackSpeed()) {
+                            mMediaSession.setPlaybackSpeed(mSpeed);
+                        }
                     }
 
                     if (mMediaControlView != null) {
@@ -1235,7 +1240,18 @@
         @Override
         public void onMediaPrepared(@NonNull MediaSession2 session,
                 @NonNull BaseMediaPlayer player, @NonNull MediaItem2 item) {
-            Log.d(TAG, "onMediaPrepared() is called.");
+            if (DEBUG) {
+                Log.d(TAG, "onMediaPrepared() is called.");
+            }
+        }
+
+        @Override
+        public void onPlaybackSpeedChanged(@NonNull MediaSession2 session,
+                 @NonNull BaseMediaPlayer player, float speed) {
+            if (DEBUG) {
+                Log.d(TAG, "onPlaybackSpeedChanged is called. Speed: " + speed);
+            }
+            mSpeed = speed;
         }
     }
 }
diff --git a/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java b/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
index d457aa2..a81d3d4 100644
--- a/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -1033,13 +1033,14 @@
             if (keyEvent == null || keyEvent.getAction() != KeyEvent.ACTION_DOWN) {
                 return false;
             }
+            RemoteUserInfo remoteUserInfo = impl.getCurrentControllerInfo();
             int keyCode = keyEvent.getKeyCode();
             switch (keyCode) {
                 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
                 case KeyEvent.KEYCODE_HEADSETHOOK:
                     if (keyEvent.getRepeatCount() > 0) {
                         // Consider long-press as a single tap.
-                        handleMediaPlayPauseKeySingleTapIfPending();
+                        handleMediaPlayPauseKeySingleTapIfPending(remoteUserInfo);
                     } else if (mMediaPlayPauseKeyPending) {
                         mCallbackHandler.removeMessages(
                                 CallbackHandler.MSG_MEDIA_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
@@ -1052,21 +1053,22 @@
                         }
                     } else {
                         mMediaPlayPauseKeyPending = true;
-                        mCallbackHandler.sendEmptyMessageDelayed(
+                        mCallbackHandler.sendMessageDelayed(mCallbackHandler.obtainMessage(
                                 CallbackHandler.MSG_MEDIA_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT,
+                                remoteUserInfo),
                                 ViewConfiguration.getDoubleTapTimeout());
                     }
                     return true;
                 default:
                     // If another key is pressed within double tap timeout, consider the pending
                     // pending play/pause as a single tap to handle media keys in order.
-                    handleMediaPlayPauseKeySingleTapIfPending();
+                    handleMediaPlayPauseKeySingleTapIfPending(remoteUserInfo);
                     break;
             }
             return false;
         }
 
-        void handleMediaPlayPauseKeySingleTapIfPending() {
+        void handleMediaPlayPauseKeySingleTapIfPending(RemoteUserInfo remoteUserInfo) {
             if (!mMediaPlayPauseKeyPending) {
                 return;
             }
@@ -1085,11 +1087,13 @@
                         | PlaybackStateCompat.ACTION_PLAY)) != 0;
             boolean canPause = (validActions & (PlaybackStateCompat.ACTION_PLAY_PAUSE
                         | PlaybackStateCompat.ACTION_PAUSE)) != 0;
+            impl.setCurrentControllerInfo(remoteUserInfo);
             if (isPlaying && canPause) {
                 onPause();
             } else if (!isPlaying && canPlay) {
                 onPlay();
             }
+            impl.setCurrentControllerInfo(null);
         }
 
         /**
@@ -1330,7 +1334,7 @@
             @Override
             public void handleMessage(Message msg) {
                 if (msg.what == MSG_MEDIA_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT) {
-                    handleMediaPlayPauseKeySingleTapIfPending();
+                    handleMediaPlayPauseKeySingleTapIfPending((RemoteUserInfo) msg.obj);
                 }
             }
         }
@@ -1969,6 +1973,9 @@
 
         String getCallingPackage();
         RemoteUserInfo getCurrentControllerInfo();
+
+        // Internal only method
+        void setCurrentControllerInfo(RemoteUserInfo remoteUserInfo);
     }
 
     static class MediaSessionImplBase implements MediaSessionImpl {
@@ -1995,6 +2002,7 @@
         private boolean mIsMbrRegistered = false;
         private boolean mIsRccRegistered = false;
         volatile Callback mCallback;
+        private RemoteUserInfo mRemoteUserInfo;
 
         @SessionFlags int mFlags;
 
@@ -2422,11 +2430,15 @@
         @Override
         public RemoteUserInfo getCurrentControllerInfo() {
             synchronized (mLock) {
-                if (mHandler != null) {
-                    return mHandler.getRemoteUserInfo();
-                }
+                return mRemoteUserInfo;
             }
-            return null;
+        }
+
+        @Override
+        public void setCurrentControllerInfo(RemoteUserInfo remoteUserInfo) {
+            synchronized (mLock) {
+                mRemoteUserInfo = remoteUserInfo;
+            }
         }
 
         // Registers/unregisters components as needed.
@@ -2976,7 +2988,6 @@
         }
 
         class MessageHandler extends Handler {
-
             private static final int MSG_COMMAND = 1;
             private static final int MSG_ADJUST_VOLUME = 2;
             private static final int MSG_PREPARE = 3;
@@ -3012,8 +3023,6 @@
             private static final int KEYCODE_MEDIA_PAUSE = 127;
             private static final int KEYCODE_MEDIA_PLAY = 126;
 
-            private RemoteUserInfo mRemoteUserInfo;
-
             public MessageHandler(Looper looper) {
                 super(looper);
             }
@@ -3027,8 +3036,8 @@
 
                 Bundle data = msg.getData();
                 ensureClassLoader(data);
-                mRemoteUserInfo = new RemoteUserInfo(data.getString(DATA_CALLING_PACKAGE),
-                        data.getInt(DATA_CALLING_PID), data.getInt(DATA_CALLING_UID));
+                setCurrentControllerInfo(new RemoteUserInfo(data.getString(DATA_CALLING_PACKAGE),
+                        data.getInt(DATA_CALLING_PID), data.getInt(DATA_CALLING_UID)));
 
                 Bundle extras = data.getBundle(DATA_EXTRAS);
                 ensureClassLoader(extras);
@@ -3141,7 +3150,7 @@
                             break;
                     }
                 } finally {
-                    mRemoteUserInfo = null;
+                    setCurrentControllerInfo(null);
                 }
             }
 
@@ -3195,10 +3204,6 @@
                         break;
                 }
             }
-
-            RemoteUserInfo getRemoteUserInfo() {
-                return mRemoteUserInfo;
-            }
         }
     }
 
@@ -3570,10 +3575,17 @@
 
         @Override
         public Object getRemoteControlClient() {
+            // Note: When this returns somthing, {@link MediaSessionCompatCallbackTest} and
+            //       {@link #setCurrentUserInfoOverride} should be also updated.
             return null;
         }
 
         @Override
+        public void setCurrentControllerInfo(RemoteUserInfo remoteUserInfo) {
+            // No-op, until we return something from {@link #getRemoteControlClient}.
+        }
+
+        @Override
         public String getCallingPackage() {
             if (android.os.Build.VERSION.SDK_INT < 24) {
                 return null;
@@ -3584,6 +3596,7 @@
 
         @Override
         public RemoteUserInfo getCurrentControllerInfo() {
+            // Note: Update MediaSessionCompatCallbackTest when we return something.
             return null;
         }
 
@@ -3898,6 +3911,11 @@
         }
 
         @Override
+        public void setCurrentControllerInfo(RemoteUserInfo remoteUserInfo) {
+            // No-op. {@link MediaSession#getCurrentControllerInfo} would work.
+        }
+
+        @Override
         public final @NonNull RemoteUserInfo getCurrentControllerInfo() {
             android.media.session.MediaSessionManager.RemoteUserInfo info =
                     ((MediaSession) mSessionObj).getCurrentControllerInfo();
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java
index 3227482..35b762d 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java
@@ -20,6 +20,7 @@
 import static android.support.mediacompat.testlib.MediaControllerConstants
         .ADD_QUEUE_ITEM_WITH_INDEX;
 import static android.support.mediacompat.testlib.MediaControllerConstants.ADJUST_VOLUME;
+import static android.support.mediacompat.testlib.MediaControllerConstants.DISPATCH_MEDIA_BUTTON;
 import static android.support.mediacompat.testlib.MediaControllerConstants.FAST_FORWARD;
 import static android.support.mediacompat.testlib.MediaControllerConstants.PAUSE;
 import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY;
@@ -67,6 +68,7 @@
 import android.support.v4.media.session.MediaControllerCompat.TransportControls;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
+import android.view.KeyEvent;
 
 public class ClientBroadcastReceiver extends BroadcastReceiver {
 
@@ -113,6 +115,10 @@
                 case ADJUST_VOLUME:
                     controller.adjustVolume(extras.getInt(KEY_ARGUMENT), 0);
                     break;
+                case DISPATCH_MEDIA_BUTTON:
+                    controller.dispatchMediaButtonEvent(
+                            (KeyEvent) extras.getParcelable(KEY_ARGUMENT));
+                    break;
             }
         } else if (ACTION_CALL_TRANSPORT_CONTROLS_METHOD.equals(intent.getAction())
                 && extras != null) {
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java b/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
index fb9c183..204461f 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
@@ -19,6 +19,7 @@
 import static android.support.mediacompat.testlib.MediaControllerConstants
         .ADD_QUEUE_ITEM_WITH_INDEX;
 import static android.support.mediacompat.testlib.MediaControllerConstants.ADJUST_VOLUME;
+import static android.support.mediacompat.testlib.MediaControllerConstants.DISPATCH_MEDIA_BUTTON;
 import static android.support.mediacompat.testlib.MediaControllerConstants.FAST_FORWARD;
 import static android.support.mediacompat.testlib.MediaControllerConstants.PAUSE;
 import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY;
@@ -88,6 +89,7 @@
 import android.os.Process;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
+import android.support.mediacompat.testlib.util.IntentUtil;
 import android.support.mediacompat.testlib.util.PollingCheck;
 import android.support.v4.media.MediaBrowserCompat;
 import android.support.v4.media.MediaDescriptionCompat;
@@ -213,6 +215,7 @@
             return;
         }
         mCallback.reset(1);
+        mCallback.setExpectedCallerPackageName(IntentUtil.SERVICE_PACKAGE_NAME);
         mSession.setCallback(mCallback, new Handler(Looper.getMainLooper()));
         MediaSessionCompat session = MediaSessionCompat.fromMediaSession(
                 getContext(), mSession.getMediaSession());
@@ -227,7 +230,7 @@
     @SmallTest
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
     public void testCallers() throws Exception {
-        mCallback.reset(1);
+        mCallback.reset(1, getContext().getPackageName());
         mSession.setCallback(mCallback, new Handler(Looper.getMainLooper()));
         MediaSessionCompat session = MediaSessionCompat.fromMediaSession(
                 getContext(), mSession.getMediaSession());
@@ -245,7 +248,7 @@
         assertEquals(Process.myUid(), remoteUserInfo1.getUid());
         assertEquals(Process.myPid(), remoteUserInfo1.getPid());
 
-        mCallback.reset(1);
+        mCallback.reset(1, getContext().getPackageName());
         controller2.getTransportControls().stop();
         mCallback.await(TIME_OUT_MS);
         assertTrue(mCallback.mOnStopCalled);
@@ -654,7 +657,7 @@
         mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS);
         mSession.setActive(true);
 
-        final long waitTimeForNoResponse = 30L;
+        final long waitTimeForNoResponse = 100L;
 
         Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON)
                 .setComponent(new ComponentName(getContext(), getContext().getClass()));
@@ -665,37 +668,37 @@
         setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
 
         mCallback.reset(1);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_PLAY);
         assertTrue(mCallback.await(TIME_OUT_MS));
         assertEquals(1, mCallback.mOnPlayCalledCount);
 
         mCallback.reset(1);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PAUSE);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_PAUSE);
         assertTrue(mCallback.await(TIME_OUT_MS));
         assertTrue(mCallback.mOnPauseCalled);
 
         mCallback.reset(1);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_NEXT);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_NEXT);
         assertTrue(mCallback.await(TIME_OUT_MS));
         assertTrue(mCallback.mOnSkipToNextCalled);
 
         mCallback.reset(1);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
         assertTrue(mCallback.await(TIME_OUT_MS));
         assertTrue(mCallback.mOnSkipToPreviousCalled);
 
         mCallback.reset(1);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_STOP);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_STOP);
         assertTrue(mCallback.await(TIME_OUT_MS));
         assertTrue(mCallback.mOnStopCalled);
 
         mCallback.reset(1);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
         assertTrue(mCallback.await(TIME_OUT_MS));
         assertTrue(mCallback.mOnFastForwardCalled);
 
         mCallback.reset(1);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_REWIND);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_REWIND);
         assertTrue(mCallback.await(TIME_OUT_MS));
         assertTrue(mCallback.mOnRewindCalled);
 
@@ -703,22 +706,22 @@
         // First, send PLAY_PAUSE button event while in STATE_PAUSED.
         mCallback.reset(1);
         setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
         assertTrue(mCallback.await(TIME_OUT_MS));
         assertEquals(1, mCallback.mOnPlayCalledCount);
 
         // Next, send PLAY_PAUSE button event while in STATE_PLAYING.
         mCallback.reset(1);
         setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
         assertTrue(mCallback.await(TIME_OUT_MS));
         assertTrue(mCallback.mOnPauseCalled);
 
         // Double tap of PLAY_PAUSE is the next track.
         mCallback.reset(2);
         setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
         assertFalse(mCallback.await(waitTimeForNoResponse));
         assertTrue(mCallback.mOnSkipToNextCalled);
         assertEquals(0, mCallback.mOnPlayCalledCount);
@@ -728,7 +731,7 @@
         // It should be the same as the single short-press.
         mCallback.reset(1);
         setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
         assertTrue(mCallback.await(TIME_OUT_MS));
         assertEquals(1, mCallback.mOnPlayCalledCount);
 
@@ -736,8 +739,8 @@
         // Initial down event from the second press within double tap time-out will make
         // onSkipToNext() to be called, so further down events shouldn't be handled again.
         mCallback.reset(2);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
         assertFalse(mCallback.await(waitTimeForNoResponse));
         assertTrue(mCallback.mOnSkipToNextCalled);
         assertEquals(0, mCallback.mOnPlayCalledCount);
@@ -748,8 +751,8 @@
         // so it shouldn't be used as the first tap of the double tap.
         mCallback.reset(2);
         setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
         assertTrue(mCallback.await(TIME_OUT_MS));
         // onMediaButtonEvent() calls either onPlay() or onPause() depending on the playback state,
         // so onPlay() should be called once and onPause() also should be called once.
@@ -761,9 +764,9 @@
         // PLAY_PAUSE should be handled as normal.
         mCallback.reset(3);
         setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_STOP);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_STOP);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
         assertTrue(mCallback.await(TIME_OUT_MS));
         assertFalse(mCallback.mOnSkipToNextCalled);
         assertTrue(mCallback.mOnStopCalled);
@@ -772,8 +775,8 @@
         // Test if media keys are handled in order.
         mCallback.reset(2);
         setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_STOP);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyEventFromController(KeyEvent.KEYCODE_MEDIA_STOP);
         assertTrue(mCallback.await(TIME_OUT_MS));
         assertEquals(1, mCallback.mOnPlayCalledCount);
         assertTrue(mCallback.mOnStopCalled);
@@ -976,29 +979,32 @@
         }
     }
 
-    private void sendMediaKeyInputToController(int keyCode) {
-        sendMediaKeyInputToController(keyCode, false);
+    private void sendMediaKeyEventFromController(int keyCode) {
+        sendMediaKeyEventFromController(keyCode, false);
     }
 
-    private void sendMediaKeyInputToController(int keyCode, boolean isLongPress) {
-        MediaControllerCompat controller = mSession.getController();
+    private void sendMediaKeyEventFromController(int keyCode, boolean isLongPress) {
         long currentTimeMs = System.currentTimeMillis();
         KeyEvent down = new KeyEvent(
                 currentTimeMs, currentTimeMs, KeyEvent.ACTION_DOWN, keyCode, 0);
-        controller.dispatchMediaButtonEvent(down);
+        callMediaControllerMethod(
+                DISPATCH_MEDIA_BUTTON, down, getContext(), mSession.getSessionToken());
         if (isLongPress) {
             KeyEvent longPress = new KeyEvent(
                     currentTimeMs, System.currentTimeMillis(), KeyEvent.ACTION_DOWN, keyCode, 1);
-            controller.dispatchMediaButtonEvent(longPress);
+            callMediaControllerMethod(
+                    DISPATCH_MEDIA_BUTTON, longPress, getContext(), mSession.getSessionToken());
         }
         KeyEvent up = new KeyEvent(
                 currentTimeMs, System.currentTimeMillis(), KeyEvent.ACTION_UP, keyCode, 0);
-        controller.dispatchMediaButtonEvent(up);
+        callMediaControllerMethod(
+                DISPATCH_MEDIA_BUTTON, up, getContext(), mSession.getSessionToken());
     }
 
     private class MediaSessionCallback extends MediaSessionCompat.Callback {
         private CountDownLatch mLatch;
         private RemoteUserInfo mRemoteUserInfoForStop;
+        private String mExpectedCallerPackageName;
         private long mSeekPosition;
         private long mQueueItemId;
         private RatingCompat mRating;
@@ -1044,6 +1050,7 @@
 
         public void reset(int count) {
             mLatch = new CountDownLatch(count);
+            mExpectedCallerPackageName = IntentUtil.CLIENT_PACKAGE_NAME;
             mSeekPosition = -1;
             mQueueItemId = -1;
             mRating = null;
@@ -1088,6 +1095,15 @@
             mOnRemoveQueueItemCalled = false;
         }
 
+        public void reset(int count, String expectedCallerPackageName) {
+            reset(count);
+            setExpectedCallerPackageName(expectedCallerPackageName);
+        }
+
+        public void setExpectedCallerPackageName(String packageName) {
+            mExpectedCallerPackageName = packageName;
+        }
+
         public boolean await(long timeoutMs) {
             try {
                 return mLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
@@ -1098,6 +1114,10 @@
 
         @Override
         public void onPlay() {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnPlayCalledCount++;
             setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
             mLatch.countDown();
@@ -1105,6 +1125,10 @@
 
         @Override
         public void onPause() {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnPauseCalled = true;
             setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
             mLatch.countDown();
@@ -1112,6 +1136,10 @@
 
         @Override
         public void onStop() {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnStopCalled = true;
             setPlaybackState(PlaybackStateCompat.STATE_STOPPED);
             mRemoteUserInfoForStop = mSession.getCurrentControllerInfo();
@@ -1120,30 +1148,50 @@
 
         @Override
         public void onFastForward() {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnFastForwardCalled = true;
             mLatch.countDown();
         }
 
         @Override
         public void onRewind() {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnRewindCalled = true;
             mLatch.countDown();
         }
 
         @Override
         public void onSkipToPrevious() {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnSkipToPreviousCalled = true;
             mLatch.countDown();
         }
 
         @Override
         public void onSkipToNext() {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnSkipToNextCalled = true;
             mLatch.countDown();
         }
 
         @Override
         public void onSeekTo(long pos) {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnSeekToCalled = true;
             mSeekPosition = pos;
             mLatch.countDown();
@@ -1151,6 +1199,10 @@
 
         @Override
         public void onSetRating(RatingCompat rating) {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnSetRatingCalled = true;
             mRating = rating;
             mLatch.countDown();
@@ -1158,6 +1210,10 @@
 
         @Override
         public void onPlayFromMediaId(String mediaId, Bundle extras) {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnPlayFromMediaIdCalled = true;
             mMediaId = mediaId;
             mExtras = extras;
@@ -1166,6 +1222,10 @@
 
         @Override
         public void onPlayFromSearch(String query, Bundle extras) {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnPlayFromSearchCalled = true;
             mQuery = query;
             mExtras = extras;
@@ -1174,6 +1234,10 @@
 
         @Override
         public void onPlayFromUri(Uri uri, Bundle extras) {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnPlayFromUriCalled = true;
             mUri = uri;
             mExtras = extras;
@@ -1182,6 +1246,10 @@
 
         @Override
         public void onCustomAction(String action, Bundle extras) {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnCustomActionCalled = true;
             mAction = action;
             mExtras = extras;
@@ -1190,6 +1258,10 @@
 
         @Override
         public void onSkipToQueueItem(long id) {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnSkipToQueueItemCalled = true;
             mQueueItemId = id;
             mLatch.countDown();
@@ -1197,6 +1269,10 @@
 
         @Override
         public void onCommand(String command, Bundle extras, ResultReceiver cb) {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnCommandCalled = true;
             mCommand = command;
             mExtras = extras;
@@ -1206,12 +1282,20 @@
 
         @Override
         public void onPrepare() {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnPrepareCalled = true;
             mLatch.countDown();
         }
 
         @Override
         public void onPrepareFromMediaId(String mediaId, Bundle extras) {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnPrepareFromMediaIdCalled = true;
             mMediaId = mediaId;
             mExtras = extras;
@@ -1220,6 +1304,10 @@
 
         @Override
         public void onPrepareFromSearch(String query, Bundle extras) {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnPrepareFromSearchCalled = true;
             mQuery = query;
             mExtras = extras;
@@ -1228,6 +1316,10 @@
 
         @Override
         public void onPrepareFromUri(Uri uri, Bundle extras) {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnPrepareFromUriCalled = true;
             mUri = uri;
             mExtras = extras;
@@ -1236,6 +1328,10 @@
 
         @Override
         public void onSetRepeatMode(int repeatMode) {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnSetRepeatModeCalled = true;
             mRepeatMode = repeatMode;
             mLatch.countDown();
@@ -1243,6 +1339,10 @@
 
         @Override
         public void onAddQueueItem(MediaDescriptionCompat description) {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnAddQueueItemCalled = true;
             mQueueDescription = description;
             mQueue.add(new MediaSessionCompat.QueueItem(description, mQueue.size()));
@@ -1252,6 +1352,10 @@
 
         @Override
         public void onAddQueueItem(MediaDescriptionCompat description, int index) {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnAddQueueItemAtCalled = true;
             mQueueIndex = index;
             mQueueDescription = description;
@@ -1262,6 +1366,10 @@
 
         @Override
         public void onRemoveQueueItem(MediaDescriptionCompat description) {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnRemoveQueueItemCalled = true;
             String mediaId = description.getMediaId();
             for (int i = mQueue.size() - 1; i >= 0; --i) {
@@ -1276,6 +1384,10 @@
 
         @Override
         public void onSetCaptioningEnabled(boolean enabled) {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnSetCaptioningEnabledCalled = true;
             mCaptioningEnabled = enabled;
             mLatch.countDown();
@@ -1283,9 +1395,26 @@
 
         @Override
         public void onSetShuffleMode(int shuffleMode) {
+            if (!isCallerTestClient()) {
+                // Ignore
+                return;
+            }
             mOnSetShuffleModeCalled = true;
             mShuffleMode = shuffleMode;
             mLatch.countDown();
         }
+
+        private boolean isCallerTestClient() {
+            if (Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT < 28) {
+                // RemoteUserInfo isn't available.
+                return true;
+            }
+            RemoteUserInfo info = mSession.getCurrentControllerInfo();
+            assertNotNull(info);
+
+            // Don't stop test for an unexpected package name here, because any controller may
+            // connect to test session and send command while testing.
+            return mExpectedCallerPackageName.equals(info.getPackageName());
+        }
     }
 }
diff --git a/media/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/MediaControllerConstants.java b/media/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/MediaControllerConstants.java
index e81f7d9..3480664 100644
--- a/media/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/MediaControllerConstants.java
+++ b/media/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/MediaControllerConstants.java
@@ -28,6 +28,7 @@
     public static final int REMOVE_QUEUE_ITEM = 204;
     public static final int SET_VOLUME_TO = 205;
     public static final int ADJUST_VOLUME = 206;
+    public static final int DISPATCH_MEDIA_BUTTON = 207;
 
     // TransportControls methods.
     public static final int PLAY = 301;
diff --git a/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java b/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java
index 3227482..35b762d 100644
--- a/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java
+++ b/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java
@@ -20,6 +20,7 @@
 import static android.support.mediacompat.testlib.MediaControllerConstants
         .ADD_QUEUE_ITEM_WITH_INDEX;
 import static android.support.mediacompat.testlib.MediaControllerConstants.ADJUST_VOLUME;
+import static android.support.mediacompat.testlib.MediaControllerConstants.DISPATCH_MEDIA_BUTTON;
 import static android.support.mediacompat.testlib.MediaControllerConstants.FAST_FORWARD;
 import static android.support.mediacompat.testlib.MediaControllerConstants.PAUSE;
 import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY;
@@ -67,6 +68,7 @@
 import android.support.v4.media.session.MediaControllerCompat.TransportControls;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
+import android.view.KeyEvent;
 
 public class ClientBroadcastReceiver extends BroadcastReceiver {
 
@@ -113,6 +115,10 @@
                 case ADJUST_VOLUME:
                     controller.adjustVolume(extras.getInt(KEY_ARGUMENT), 0);
                     break;
+                case DISPATCH_MEDIA_BUTTON:
+                    controller.dispatchMediaButtonEvent(
+                            (KeyEvent) extras.getParcelable(KEY_ARGUMENT));
+                    break;
             }
         } else if (ACTION_CALL_TRANSPORT_CONTROLS_METHOD.equals(intent.getAction())
                 && extras != null) {