/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.v4.media.session; import static android.support.test.InstrumentationRegistry.getContext; import static android.support.test.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.media.AudioManager; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Parcel; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.support.v4.media.MediaDescriptionCompat; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.RatingCompat; import android.support.v4.media.VolumeProviderCompat; import android.view.KeyEvent; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Test {@link MediaSessionCompat}. */ @RunWith(AndroidJUnit4.class) public class MediaSessionCompatTest { // The maximum time to wait for an operation, that is expected to happen. private static final long TIME_OUT_MS = 3000L; // The maximum time to wait for an operation, that is expected not to happen. private static final long WAIT_TIME_MS = 30L; private static final int MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT = 10; private static final String TEST_SESSION_TAG = "test-session-tag"; private static final String TEST_KEY = "test-key"; private static final String TEST_VALUE = "test-val"; private static final Bundle TEST_BUNDLE = createTestBundle(); private static final String TEST_SESSION_EVENT = "test-session-event"; private static final int TEST_CURRENT_VOLUME = 10; private static final int TEST_MAX_VOLUME = 11; private static final long TEST_QUEUE_ID = 12L; private static final long TEST_ACTION = 55L; private static final int TEST_ERROR_CODE = PlaybackStateCompat.ERROR_CODE_AUTHENTICATION_EXPIRED; private static final String TEST_ERROR_MSG = "test-error-msg"; private static Bundle createTestBundle() { Bundle bundle = new Bundle(); bundle.putString(TEST_KEY, TEST_VALUE); return bundle; } private AudioManager mAudioManager; private Handler mHandler = new Handler(Looper.getMainLooper()); private Object mWaitLock = new Object(); private MediaControllerCallback mCallback = new MediaControllerCallback(); private MediaSessionCompat mSession; @Before public void setUp() throws Exception { getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); mSession = new MediaSessionCompat(getContext(), TEST_SESSION_TAG); } }); } @After public void tearDown() throws Exception { // It is OK to call release() twice. mSession.release(); mSession = null; } /** * Tests that a session can be created and that all the fields are * initialized correctly. */ @Test @SmallTest public void testCreateSession() throws Exception { assertNotNull(mSession.getSessionToken()); assertFalse("New session should not be active", mSession.isActive()); // Verify by getting the controller and checking all its fields MediaControllerCompat controller = mSession.getController(); assertNotNull(controller); verifyNewSession(controller, TEST_SESSION_TAG); } /** * Tests that a session can be created from the framework session object and the callback * set on the framework session object before fromSession() is called works properly. */ @Test @SmallTest public void testFromSession() throws Exception { if (android.os.Build.VERSION.SDK_INT < 21) { // MediaSession was introduced from API level 21. return; } MediaSessionCallback callback = new MediaSessionCallback(); callback.reset(1); mSession.setCallback(callback, new Handler(Looper.getMainLooper())); MediaSessionCompat session = MediaSessionCompat.fromMediaSession( getContext(), mSession.getMediaSession()); assertEquals(session.getSessionToken(), mSession.getSessionToken()); synchronized (mWaitLock) { session.getController().getTransportControls().play(); mWaitLock.wait(TIME_OUT_MS); assertEquals(1, callback.mOnPlayCalledCount); } } /** * Tests MediaSessionCompat.Token created in the constructor of MediaSessionCompat. */ @Test @SmallTest public void testSessionToken() throws Exception { MediaSessionCompat.Token sessionToken = mSession.getSessionToken(); assertNotNull(sessionToken); assertEquals(0, sessionToken.describeContents()); // Test writeToParcel Parcel p = Parcel.obtain(); sessionToken.writeToParcel(p, 0); p.setDataPosition(0); MediaSessionCompat.Token token = MediaSessionCompat.Token.CREATOR.createFromParcel(p); assertEquals(token, sessionToken); p.recycle(); } /** * Tests {@link MediaSessionCompat#setExtras}. */ @Test @SmallTest public void testSetExtras() throws Exception { final Bundle extras = new Bundle(); MediaControllerCompat controller = mSession.getController(); controller.registerCallback(mCallback, mHandler); synchronized (mWaitLock) { mCallback.resetLocked(); mSession.setExtras(TEST_BUNDLE); mWaitLock.wait(TIME_OUT_MS); assertTrue(mCallback.mOnExtraChangedCalled); Bundle extrasOut = mCallback.mExtras; assertNotNull(extrasOut); assertEquals(TEST_VALUE, extrasOut.get(TEST_KEY)); extrasOut = controller.getExtras(); assertNotNull(extrasOut); assertEquals(TEST_VALUE, extrasOut.get(TEST_KEY)); } } /** * Tests {@link MediaSessionCompat#setFlags}. */ @Test @SmallTest public void testSetFlags() throws Exception { MediaControllerCompat controller = mSession.getController(); controller.registerCallback(mCallback, mHandler); synchronized (mWaitLock) { mCallback.resetLocked(); mSession.setFlags(5); assertEquals(5, controller.getFlags()); } } /** * Tests {@link MediaSessionCompat#setMetadata}. */ @Test @SmallTest public void testSetMetadata() throws Exception { MediaControllerCompat controller = mSession.getController(); controller.registerCallback(mCallback, mHandler); synchronized (mWaitLock) { mCallback.resetLocked(); MediaMetadataCompat metadata = new MediaMetadataCompat.Builder().putString(TEST_KEY, TEST_VALUE).build(); mSession.setMetadata(metadata); mWaitLock.wait(TIME_OUT_MS); assertTrue(mCallback.mOnMetadataChangedCalled); MediaMetadataCompat metadataOut = mCallback.mMediaMetadata; assertNotNull(metadataOut); assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY)); metadataOut = controller.getMetadata(); assertNotNull(metadataOut); assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY)); } } /** * Tests {@link MediaSessionCompat#setMetadata} with artwork bitmaps. */ @Test @SmallTest public void testSetMetadataWithArtworks() throws Exception { MediaControllerCompat controller = mSession.getController(); final Bitmap bitmapSmall = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); final Bitmap bitmapLarge = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ALPHA_8); controller.registerCallback(mCallback, mHandler); mSession.setActive(true); synchronized (mWaitLock) { mCallback.resetLocked(); MediaMetadataCompat metadata = new MediaMetadataCompat.Builder() .putString(TEST_KEY, TEST_VALUE) .putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmapSmall) .build(); mSession.setMetadata(metadata); mWaitLock.wait(TIME_OUT_MS); assertTrue(mCallback.mOnMetadataChangedCalled); MediaMetadataCompat metadataOut = mCallback.mMediaMetadata; assertNotNull(metadataOut); assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY)); Bitmap bitmapSmallOut = metadataOut.getBitmap(MediaMetadataCompat.METADATA_KEY_ART); assertNotNull(bitmapSmallOut); assertEquals(bitmapSmall.getHeight(), bitmapSmallOut.getHeight()); assertEquals(bitmapSmall.getWidth(), bitmapSmallOut.getWidth()); assertEquals(bitmapSmall.getConfig(), bitmapSmallOut.getConfig()); metadata = new MediaMetadataCompat.Builder() .putString(TEST_KEY, TEST_VALUE) .putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmapLarge) .build(); mSession.setMetadata(metadata); mWaitLock.wait(TIME_OUT_MS); assertTrue(mCallback.mOnMetadataChangedCalled); metadataOut = mCallback.mMediaMetadata; assertNotNull(metadataOut); assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY)); Bitmap bitmapLargeOut = metadataOut.getBitmap(MediaMetadataCompat.METADATA_KEY_ART); assertNotNull(bitmapLargeOut); // Don't check size here because large bitmaps can be scaled down. assertEquals(bitmapLarge.getConfig(), bitmapLargeOut.getConfig()); assertFalse(bitmapSmall.isRecycled()); assertFalse(bitmapLarge.isRecycled()); assertFalse(bitmapSmallOut.isRecycled()); assertFalse(bitmapLargeOut.isRecycled()); bitmapSmallOut.recycle(); bitmapLargeOut.recycle(); } bitmapSmall.recycle(); bitmapLarge.recycle(); } /** * Tests {@link MediaSessionCompat#setPlaybackState}. */ @Test @SmallTest public void testSetPlaybackState() throws Exception { MediaControllerCompat controller = mSession.getController(); controller.registerCallback(mCallback, mHandler); synchronized (mWaitLock) { mCallback.resetLocked(); PlaybackStateCompat state = new PlaybackStateCompat.Builder() .setActions(TEST_ACTION) .setErrorMessage(TEST_ERROR_CODE, TEST_ERROR_MSG) .build(); mSession.setPlaybackState(state); mWaitLock.wait(TIME_OUT_MS); assertTrue(mCallback.mOnPlaybackStateChangedCalled); PlaybackStateCompat stateOut = mCallback.mPlaybackState; assertNotNull(stateOut); assertEquals(TEST_ACTION, stateOut.getActions()); assertEquals(TEST_ERROR_CODE, stateOut.getErrorCode()); assertEquals(TEST_ERROR_MSG, stateOut.getErrorMessage().toString()); stateOut = controller.getPlaybackState(); assertNotNull(stateOut); assertEquals(TEST_ACTION, stateOut.getActions()); assertEquals(TEST_ERROR_CODE, stateOut.getErrorCode()); assertEquals(TEST_ERROR_MSG, stateOut.getErrorMessage().toString()); } } /** * Tests {@link MediaSessionCompat#setQueue} and {@link MediaSessionCompat#setQueueTitle}. */ @Test @SmallTest public void testSetQueueAndSetQueueTitle() throws Exception { MediaControllerCompat controller = mSession.getController(); controller.registerCallback(mCallback, mHandler); synchronized (mWaitLock) { mCallback.resetLocked(); List queue = new ArrayList<>(); MediaSessionCompat.QueueItem item = new MediaSessionCompat.QueueItem( new MediaDescriptionCompat.Builder() .setMediaId(TEST_VALUE) .setTitle("title") .build(), TEST_QUEUE_ID); queue.add(item); mSession.setQueue(queue); mWaitLock.wait(TIME_OUT_MS); assertTrue(mCallback.mOnQueueChangedCalled); mSession.setQueueTitle(TEST_VALUE); mWaitLock.wait(TIME_OUT_MS); assertTrue(mCallback.mOnQueueTitleChangedCalled); assertEquals(TEST_VALUE, mCallback.mTitle); assertEquals(queue.size(), mCallback.mQueue.size()); assertEquals(TEST_QUEUE_ID, mCallback.mQueue.get(0).getQueueId()); assertEquals(TEST_VALUE, mCallback.mQueue.get(0).getDescription().getMediaId()); assertEquals(TEST_VALUE, controller.getQueueTitle()); assertEquals(queue.size(), controller.getQueue().size()); assertEquals(TEST_QUEUE_ID, controller.getQueue().get(0).getQueueId()); assertEquals(TEST_VALUE, controller.getQueue().get(0).getDescription().getMediaId()); mCallback.resetLocked(); mSession.setQueue(null); mWaitLock.wait(TIME_OUT_MS); assertTrue(mCallback.mOnQueueChangedCalled); mSession.setQueueTitle(null); mWaitLock.wait(TIME_OUT_MS); assertTrue(mCallback.mOnQueueTitleChangedCalled); assertNull(mCallback.mTitle); assertNull(mCallback.mQueue); assertNull(controller.getQueueTitle()); assertNull(controller.getQueue()); } } /** * Tests {@link MediaSessionCompat#setSessionActivity}. */ @Test @SmallTest public void testSessionActivity() throws Exception { MediaControllerCompat controller = mSession.getController(); synchronized (mWaitLock) { Intent intent = new Intent("cts.MEDIA_SESSION_ACTION"); PendingIntent pi = PendingIntent.getActivity(getContext(), 555, intent, 0); mSession.setSessionActivity(pi); assertEquals(pi, controller.getSessionActivity()); } } /** * Tests {@link MediaSessionCompat#setCaptioningEnabled}. */ @Test @SmallTest public void testSetCaptioningEnabled() throws Exception { MediaControllerCompat controller = mSession.getController(); controller.registerCallback(mCallback, mHandler); synchronized (mWaitLock) { mCallback.resetLocked(); mSession.setCaptioningEnabled(true); mWaitLock.wait(TIME_OUT_MS); assertTrue(mCallback.mOnCaptioningEnabledChangedCalled); assertEquals(true, mCallback.mCaptioningEnabled); assertEquals(true, controller.isCaptioningEnabled()); mCallback.resetLocked(); mSession.setCaptioningEnabled(false); mWaitLock.wait(TIME_OUT_MS); assertTrue(mCallback.mOnCaptioningEnabledChangedCalled); assertEquals(false, mCallback.mCaptioningEnabled); assertEquals(false, controller.isCaptioningEnabled()); } } /** * Tests {@link MediaSessionCompat#setRepeatMode}. */ @Test @SmallTest public void testSetRepeatMode() throws Exception { MediaControllerCompat controller = mSession.getController(); controller.registerCallback(mCallback, mHandler); synchronized (mWaitLock) { mCallback.resetLocked(); final int repeatMode = PlaybackStateCompat.REPEAT_MODE_ALL; mSession.setRepeatMode(repeatMode); mWaitLock.wait(TIME_OUT_MS); assertTrue(mCallback.mOnRepeatModeChangedCalled); assertEquals(repeatMode, mCallback.mRepeatMode); assertEquals(repeatMode, controller.getRepeatMode()); } } /** * Tests {@link MediaSessionCompat#setShuffleModeEnabled}. */ @Test @SmallTest public void testSetShuffleModeEnabled() throws Exception { final boolean shuffleModeEnabled = true; MediaControllerCompat controller = mSession.getController(); controller.registerCallback(mCallback, mHandler); synchronized (mWaitLock) { mCallback.resetLocked(); mSession.setShuffleModeEnabled(shuffleModeEnabled); mWaitLock.wait(TIME_OUT_MS); assertTrue(mCallback.mOnShuffleModeChangedDeprecatedCalled); assertEquals(shuffleModeEnabled, mCallback.mShuffleModeEnabled); assertEquals(shuffleModeEnabled, controller.isShuffleModeEnabled()); } } /** * Tests {@link MediaSessionCompat#setShuffleMode}. */ @Test @SmallTest public void testSetShuffleMode() throws Exception { final int shuffleMode = PlaybackStateCompat.SHUFFLE_MODE_ALL; MediaControllerCompat controller = mSession.getController(); controller.registerCallback(mCallback, mHandler); synchronized (mWaitLock) { mCallback.resetLocked(); mSession.setShuffleMode(shuffleMode); mWaitLock.wait(TIME_OUT_MS); assertTrue(mCallback.mOnShuffleModeChangedCalled); assertEquals(shuffleMode, mCallback.mShuffleMode); assertEquals(shuffleMode, controller.getShuffleMode()); } } /** * Tests {@link MediaSessionCompat#sendSessionEvent}. */ @Test @SmallTest public void testSendSessionEvent() throws Exception { MediaControllerCompat controller = mSession.getController(); controller.registerCallback(mCallback, mHandler); synchronized (mWaitLock) { mCallback.resetLocked(); mSession.sendSessionEvent(TEST_SESSION_EVENT, TEST_BUNDLE); mWaitLock.wait(TIME_OUT_MS); assertTrue(mCallback.mOnSessionEventCalled); assertEquals(TEST_SESSION_EVENT, mCallback.mEvent); assertEquals(TEST_VALUE, mCallback.mExtras.getString(TEST_KEY)); } } /** * Tests {@link MediaSessionCompat#setActive} and {@link MediaSessionCompat#release}. */ @Test @SmallTest public void testSetActiveAndRelease() throws Exception { MediaControllerCompat controller = mSession.getController(); controller.registerCallback(mCallback, mHandler); synchronized (mWaitLock) { mSession.setActive(true); assertTrue(mSession.isActive()); mCallback.resetLocked(); mSession.release(); mWaitLock.wait(TIME_OUT_MS); assertTrue(mCallback.mOnSessionDestroyedCalled); } } /** * Tests {@link MediaSessionCompat#setPlaybackToLocal} and * {@link MediaSessionCompat#setPlaybackToRemote}. */ @Test @SmallTest public void testPlaybackToLocalAndRemote() throws Exception { MediaControllerCompat controller = mSession.getController(); controller.registerCallback(mCallback, mHandler); synchronized (mWaitLock) { // test setPlaybackToRemote, do this before testing setPlaybackToLocal // to ensure it switches correctly. mCallback.resetLocked(); try { mSession.setPlaybackToRemote(null); fail("Expected IAE for setPlaybackToRemote(null)"); } catch (IllegalArgumentException e) { // expected } VolumeProviderCompat vp = new VolumeProviderCompat( VolumeProviderCompat.VOLUME_CONTROL_FIXED, TEST_MAX_VOLUME, TEST_CURRENT_VOLUME) {}; mSession.setPlaybackToRemote(vp); MediaControllerCompat.PlaybackInfo info = null; for (int i = 0; i < MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT; ++i) { mCallback.mOnAudioInfoChangedCalled = false; mWaitLock.wait(TIME_OUT_MS); assertTrue(mCallback.mOnAudioInfoChangedCalled); info = mCallback.mPlaybackInfo; if (info != null && info.getCurrentVolume() == TEST_CURRENT_VOLUME && info.getMaxVolume() == TEST_MAX_VOLUME && info.getVolumeControl() == VolumeProviderCompat.VOLUME_CONTROL_FIXED && info.getPlaybackType() == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) { break; } } assertNotNull(info); assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType()); assertEquals(TEST_MAX_VOLUME, info.getMaxVolume()); assertEquals(TEST_CURRENT_VOLUME, info.getCurrentVolume()); assertEquals(VolumeProviderCompat.VOLUME_CONTROL_FIXED, info.getVolumeControl()); info = controller.getPlaybackInfo(); assertNotNull(info); assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType()); assertEquals(TEST_MAX_VOLUME, info.getMaxVolume()); assertEquals(TEST_CURRENT_VOLUME, info.getCurrentVolume()); assertEquals(VolumeProviderCompat.VOLUME_CONTROL_FIXED, info.getVolumeControl()); // test setPlaybackToLocal mSession.setPlaybackToLocal(AudioManager.STREAM_RING); info = controller.getPlaybackInfo(); assertNotNull(info); assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL, info.getPlaybackType()); } } /** * Tests {@link MediaSessionCompat.Callback#onMediaButtonEvent}. */ @Test @SmallTest public void testCallbackOnMediaButtonEvent() throws Exception { MediaSessionCallback sessionCallback = new MediaSessionCallback(); mSession.setCallback(sessionCallback, new Handler(Looper.getMainLooper())); mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS); mSession.setActive(true); Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON).setComponent( new ComponentName(getContext(), getContext().getClass())); PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, mediaButtonIntent, 0); mSession.setMediaButtonReceiver(pi); long supportedActions = PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS | PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND; // Set state to STATE_PLAYING to get higher priority. PlaybackStateCompat defaultState = new PlaybackStateCompat.Builder() .setActions(supportedActions) .setState(PlaybackStateCompat.STATE_PLAYING, 0L, 0.0f) .build(); mSession.setPlaybackState(defaultState); sessionCallback.reset(1); sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY); assertTrue(sessionCallback.await(TIME_OUT_MS)); assertEquals(1, sessionCallback.mOnPlayCalledCount); sessionCallback.reset(1); sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PAUSE); assertTrue(sessionCallback.await(TIME_OUT_MS)); assertTrue(sessionCallback.mOnPauseCalled); sessionCallback.reset(1); sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_NEXT); assertTrue(sessionCallback.await(TIME_OUT_MS)); assertTrue(sessionCallback.mOnSkipToNextCalled); sessionCallback.reset(1); sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PREVIOUS); assertTrue(sessionCallback.await(TIME_OUT_MS)); assertTrue(sessionCallback.mOnSkipToPreviousCalled); sessionCallback.reset(1); sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_STOP); assertTrue(sessionCallback.await(TIME_OUT_MS)); assertTrue(sessionCallback.mOnStopCalled); sessionCallback.reset(1); sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD); assertTrue(sessionCallback.await(TIME_OUT_MS)); assertTrue(sessionCallback.mOnFastForwardCalled); sessionCallback.reset(1); sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_REWIND); assertTrue(sessionCallback.await(TIME_OUT_MS)); assertTrue(sessionCallback.mOnRewindCalled); // Test PLAY_PAUSE button twice. // First, send PLAY_PAUSE button event while in STATE_PAUSED. sessionCallback.reset(1); mSession.setPlaybackState(new PlaybackStateCompat.Builder().setActions(supportedActions) .setState(PlaybackStateCompat.STATE_PAUSED, 0L, 0.0f).build()); sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); assertTrue(sessionCallback.await(TIME_OUT_MS)); assertEquals(1, sessionCallback.mOnPlayCalledCount); // Next, send PLAY_PAUSE button event while in STATE_PLAYING. sessionCallback.reset(1); mSession.setPlaybackState(new PlaybackStateCompat.Builder().setActions(supportedActions) .setState(PlaybackStateCompat.STATE_PLAYING, 0L, 0.0f).build()); sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); assertTrue(sessionCallback.await(TIME_OUT_MS)); assertTrue(sessionCallback.mOnPauseCalled); // Double tap of PLAY_PAUSE is the next track. sessionCallback.reset(2); mSession.setPlaybackState(new PlaybackStateCompat.Builder().setActions(supportedActions) .setState(PlaybackStateCompat.STATE_PAUSED, 0L, 0.0f).build()); sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); assertFalse(sessionCallback.await(WAIT_TIME_MS)); assertTrue(sessionCallback.mOnSkipToNextCalled); assertEquals(0, sessionCallback.mOnPlayCalledCount); assertFalse(sessionCallback.mOnPauseCalled); // Test PLAY_PAUSE button long-press. // It should be the same as the single short-press. sessionCallback.reset(1); mSession.setPlaybackState(new PlaybackStateCompat.Builder().setActions(supportedActions) .setState(PlaybackStateCompat.STATE_PAUSED, 0L, 0.0f).build()); sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true); assertTrue(sessionCallback.await(TIME_OUT_MS)); assertEquals(1, sessionCallback.mOnPlayCalledCount); // Double tap of PLAY_PAUSE should be handled once. // 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. sessionCallback.reset(2); sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true); assertFalse(sessionCallback.await(WAIT_TIME_MS)); assertTrue(sessionCallback.mOnSkipToNextCalled); assertEquals(0, sessionCallback.mOnPlayCalledCount); assertFalse(sessionCallback.mOnPauseCalled); // Test PLAY_PAUSE button short-press followed by the long-press. // Initial long-press of the PLAY_PAUSE is considered as the single short-press already, // so it shouldn't be used as the first tap of the double tap. sessionCallback.reset(2); mSession.setPlaybackState(new PlaybackStateCompat.Builder().setActions(supportedActions) .setState(PlaybackStateCompat.STATE_PAUSED, 0L, 0.0f).build()); sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true); sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); assertTrue(sessionCallback.await(TIME_OUT_MS)); // onMediaButtonEvent() calls either onPlay() or onPause() depending on the playback state, // so onPlay() should be called twice while onPause() isn't called. assertEquals(2, sessionCallback.mOnPlayCalledCount); assertFalse(sessionCallback.mOnPauseCalled); assertFalse(sessionCallback.mOnSkipToNextCalled); } @Test @SmallTest public void testSetNullCallback() throws Throwable { getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { try { MediaSessionCompat session = new MediaSessionCompat(getContext(), "TEST"); session.setCallback(null); } catch (Exception e) { fail("Fail with an exception: " + e); } } }); } /** * Tests {@link MediaSessionCompat.QueueItem}. */ @Test @SmallTest public void testQueueItem() { MediaSessionCompat.QueueItem item = new MediaSessionCompat.QueueItem( new MediaDescriptionCompat.Builder() .setMediaId("media-id") .setTitle("title") .build(), TEST_QUEUE_ID); assertEquals(TEST_QUEUE_ID, item.getQueueId()); assertEquals("media-id", item.getDescription().getMediaId()); assertEquals("title", item.getDescription().getTitle()); assertEquals(0, item.describeContents()); Parcel p = Parcel.obtain(); item.writeToParcel(p, 0); p.setDataPosition(0); MediaSessionCompat.QueueItem other = MediaSessionCompat.QueueItem.CREATOR.createFromParcel(p); assertEquals(item.toString(), other.toString()); p.recycle(); } /** * Verifies that a new session hasn't had any configuration bits set yet. * * @param controller The controller for the session */ private void verifyNewSession(MediaControllerCompat controller, String tag) { assertEquals("New session has unexpected configuration", 0L, controller.getFlags()); assertNull("New session has unexpected configuration", controller.getExtras()); assertNull("New session has unexpected configuration", controller.getMetadata()); assertEquals("New session has unexpected configuration", getContext().getPackageName(), controller.getPackageName()); assertNull("New session has unexpected configuration", controller.getPlaybackState()); assertNull("New session has unexpected configuration", controller.getQueue()); assertNull("New session has unexpected configuration", controller.getQueueTitle()); assertEquals("New session has unexpected configuration", RatingCompat.RATING_NONE, controller.getRatingType()); assertNull("New session has unexpected configuration", controller.getSessionActivity()); assertNotNull(controller.getSessionToken()); assertNotNull(controller.getTransportControls()); MediaControllerCompat.PlaybackInfo info = controller.getPlaybackInfo(); assertNotNull(info); assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL, info.getPlaybackType()); assertEquals(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC), info.getCurrentVolume()); } private void sendMediaKeyInputToController(int keyCode) { sendMediaKeyInputToController(keyCode, false); } private void sendMediaKeyInputToController(int keyCode, boolean isLongPress) { MediaControllerCompat controller = mSession.getController(); long currentTimeMs = System.currentTimeMillis(); KeyEvent down = new KeyEvent( currentTimeMs, currentTimeMs, KeyEvent.ACTION_DOWN, keyCode, 0); controller.dispatchMediaButtonEvent(down); if (isLongPress) { KeyEvent longPress = new KeyEvent( currentTimeMs, System.currentTimeMillis(), KeyEvent.ACTION_DOWN, keyCode, 1); controller.dispatchMediaButtonEvent(longPress); } KeyEvent up = new KeyEvent( currentTimeMs, System.currentTimeMillis(), KeyEvent.ACTION_UP, keyCode, 0); controller.dispatchMediaButtonEvent(up); } private class MediaControllerCallback extends MediaControllerCompat.Callback { private volatile boolean mOnPlaybackStateChangedCalled; private volatile boolean mOnMetadataChangedCalled; private volatile boolean mOnQueueChangedCalled; private volatile boolean mOnQueueTitleChangedCalled; private volatile boolean mOnExtraChangedCalled; private volatile boolean mOnAudioInfoChangedCalled; private volatile boolean mOnSessionDestroyedCalled; private volatile boolean mOnSessionEventCalled; private volatile boolean mOnCaptioningEnabledChangedCalled; private volatile boolean mOnRepeatModeChangedCalled; private volatile boolean mOnShuffleModeChangedDeprecatedCalled; private volatile boolean mOnShuffleModeChangedCalled; private volatile PlaybackStateCompat mPlaybackState; private volatile MediaMetadataCompat mMediaMetadata; private volatile List mQueue; private volatile CharSequence mTitle; private volatile String mEvent; private volatile Bundle mExtras; private volatile MediaControllerCompat.PlaybackInfo mPlaybackInfo; private volatile boolean mCaptioningEnabled; private volatile int mRepeatMode; private volatile boolean mShuffleModeEnabled; private volatile int mShuffleMode; public void resetLocked() { mOnPlaybackStateChangedCalled = false; mOnMetadataChangedCalled = false; mOnQueueChangedCalled = false; mOnQueueTitleChangedCalled = false; mOnExtraChangedCalled = false; mOnAudioInfoChangedCalled = false; mOnSessionDestroyedCalled = false; mOnSessionEventCalled = false; mOnRepeatModeChangedCalled = false; mOnShuffleModeChangedDeprecatedCalled = false; mOnShuffleModeChangedCalled = false; mPlaybackState = null; mMediaMetadata = null; mQueue = null; mTitle = null; mExtras = null; mPlaybackInfo = null; mCaptioningEnabled = false; mRepeatMode = PlaybackStateCompat.REPEAT_MODE_NONE; mShuffleModeEnabled = false; mShuffleMode = PlaybackStateCompat.SHUFFLE_MODE_NONE; } @Override public void onPlaybackStateChanged(PlaybackStateCompat state) { synchronized (mWaitLock) { mOnPlaybackStateChangedCalled = true; mPlaybackState = state; mWaitLock.notify(); } } @Override public void onMetadataChanged(MediaMetadataCompat metadata) { synchronized (mWaitLock) { mOnMetadataChangedCalled = true; mMediaMetadata = metadata; mWaitLock.notify(); } } @Override public void onQueueChanged(List queue) { synchronized (mWaitLock) { mOnQueueChangedCalled = true; mQueue = queue; mWaitLock.notify(); } } @Override public void onQueueTitleChanged(CharSequence title) { synchronized (mWaitLock) { mOnQueueTitleChangedCalled = true; mTitle = title; mWaitLock.notify(); } } @Override public void onExtrasChanged(Bundle extras) { synchronized (mWaitLock) { mOnExtraChangedCalled = true; mExtras = extras; mWaitLock.notify(); } } @Override public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) { synchronized (mWaitLock) { mOnAudioInfoChangedCalled = true; mPlaybackInfo = info; mWaitLock.notify(); } } @Override public void onSessionDestroyed() { synchronized (mWaitLock) { mOnSessionDestroyedCalled = true; mWaitLock.notify(); } } @Override public void onSessionEvent(String event, Bundle extras) { synchronized (mWaitLock) { mOnSessionEventCalled = true; mEvent = event; mExtras = (Bundle) extras.clone(); mWaitLock.notify(); } } @Override public void onCaptioningEnabledChanged(boolean enabled) { synchronized (mWaitLock) { mOnCaptioningEnabledChangedCalled = true; mCaptioningEnabled = enabled; mWaitLock.notify(); } } @Override public void onRepeatModeChanged(int repeatMode) { synchronized (mWaitLock) { mOnRepeatModeChangedCalled = true; mRepeatMode = repeatMode; mWaitLock.notify(); } } @Override public void onShuffleModeChanged(boolean enabled) { synchronized (mWaitLock) { mOnShuffleModeChangedDeprecatedCalled = true; mShuffleModeEnabled = enabled; mWaitLock.notify(); } } @Override public void onShuffleModeChanged(int shuffleMode) { synchronized (mWaitLock) { mOnShuffleModeChangedCalled = true; mShuffleMode = shuffleMode; mWaitLock.notify(); } } } private class MediaSessionCallback extends MediaSessionCompat.Callback { private CountDownLatch mLatch; private int mOnPlayCalledCount; private boolean mOnPauseCalled; private boolean mOnStopCalled; private boolean mOnFastForwardCalled; private boolean mOnRewindCalled; private boolean mOnSkipToPreviousCalled; private boolean mOnSkipToNextCalled; public void reset(int count) { mLatch = new CountDownLatch(count); mOnPlayCalledCount = 0; mOnPauseCalled = false; mOnStopCalled = false; mOnFastForwardCalled = false; mOnRewindCalled = false; mOnSkipToPreviousCalled = false; mOnSkipToNextCalled = false; } public boolean await(long timeoutMs) { try { return mLatch.await(timeoutMs, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { return false; } } @Override public void onPlay() { mOnPlayCalledCount++; mLatch.countDown(); } @Override public void onPause() { mOnPauseCalled = true; mLatch.countDown(); } @Override public void onStop() { mOnStopCalled = true; mLatch.countDown(); } @Override public void onFastForward() { mOnFastForwardCalled = true; mLatch.countDown(); } @Override public void onRewind() { mOnRewindCalled = true; mLatch.countDown(); } @Override public void onSkipToPrevious() { mOnSkipToPreviousCalled = true; mLatch.countDown(); } @Override public void onSkipToNext() { mOnSkipToNextCalled = true; mLatch.countDown(); } } }