• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.media.session.cts;
17 
18 import static android.media.AudioAttributes.USAGE_GAME;
19 import static android.media.AudioAttributes.USAGE_MEDIA;
20 import static android.media.cts.Utils.compareRemoteUserInfo;
21 import static android.media.session.cts.MediaSessionTestService.KEY_EXPECTED_QUEUE_SIZE;
22 import static android.media.session.cts.MediaSessionTestService.KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS;
23 import static android.media.session.cts.MediaSessionTestService.KEY_SESSION_TOKEN;
24 import static android.media.session.cts.MediaSessionTestService.STEP_CHECK;
25 import static android.media.session.cts.MediaSessionTestService.STEP_CLEAN_UP;
26 import static android.media.session.cts.MediaSessionTestService.STEP_SET_UP;
27 import static android.media.session.cts.MediaSessionTestService.TEST_SERIES_OF_SET_QUEUE;
28 import static android.media.session.cts.MediaSessionTestService.TEST_SET_QUEUE;
29 import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
30 
31 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
32 
33 import static com.google.common.truth.Truth.assertThat;
34 import static com.google.common.truth.Truth.assertWithMessage;
35 
36 import android.app.PendingIntent;
37 import android.content.ComponentName;
38 import android.content.Context;
39 import android.content.Intent;
40 import android.media.AudioAttributes;
41 import android.media.AudioManager;
42 import android.media.MediaDescription;
43 import android.media.MediaMetadata;
44 import android.media.MediaSession2;
45 import android.media.Rating;
46 import android.media.VolumeProvider;
47 import android.media.cts.Utils;
48 import android.media.session.MediaController;
49 import android.media.session.MediaSession;
50 import android.media.session.MediaSession.QueueItem;
51 import android.media.session.MediaSessionManager;
52 import android.media.session.MediaSessionManager.RemoteUserInfo;
53 import android.media.session.PlaybackState;
54 import android.os.Bundle;
55 import android.os.Handler;
56 import android.os.Looper;
57 import android.os.Parcel;
58 import android.os.Process;
59 import android.os.UserManager;
60 import android.platform.test.annotations.AppModeFull;
61 import android.text.TextUtils;
62 import android.view.KeyEvent;
63 
64 import androidx.test.platform.app.InstrumentationRegistry;
65 
66 import com.android.bedstead.harrier.BedsteadJUnit4;
67 import com.android.bedstead.harrier.UserType;
68 import com.android.bedstead.harrier.annotations.UserTest;
69 import com.android.compatibility.common.util.FrameworkSpecificTest;
70 
71 import org.junit.After;
72 import org.junit.Assume;
73 import org.junit.Before;
74 import org.junit.Ignore;
75 import org.junit.Test;
76 import org.junit.runner.RunWith;
77 
78 import java.util.ArrayList;
79 import java.util.Collections;
80 import java.util.List;
81 import java.util.Optional;
82 import java.util.concurrent.CountDownLatch;
83 import java.util.concurrent.TimeUnit;
84 
85 @FrameworkSpecificTest
86 @AppModeFull(reason = "TODO: evaluate and port to instant")
87 @RunWith(BedsteadJUnit4.class)
88 public class MediaSessionTest {
89     // The maximum time to wait for an operation that is expected to succeed.
90     private static final long TIME_OUT_MS = 3000L;
91     // The maximum time to wait for an operation that is expected to fail.
92     private static final long WAIT_MS = 100L;
93     private static final int MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT = 10;
94     private static final String TEST_SESSION_TAG = "test-session-tag";
95     private static final String TEST_KEY = "test-key";
96     private static final String TEST_VALUE = "test-val";
97     private static final String TEST_SESSION_EVENT = "test-session-event";
98     private static final String TEST_VOLUME_CONTROL_ID = "test-volume-control-id";
99     private static final int TEST_CURRENT_VOLUME = 10;
100     private static final int TEST_MAX_VOLUME = 11;
101     private static final long TEST_QUEUE_ID = 12L;
102     private static final long TEST_ACTION = 55L;
103     private static final int TEST_TOO_MANY_SESSION_COUNT = 1000;
104     private static final boolean SUPPORTS_MULTIPLE_USERS = UserManager.supportsMultipleUsers();
105 
106     private AudioManager mAudioManager;
107     private Handler mHandler = new Handler(Looper.getMainLooper());
108     private Object mWaitLock = new Object();
109     private MediaControllerCallback mCallback = new MediaControllerCallback();
110     private MediaSession mSession;
111     private RemoteUserInfo mKeyDispatcherInfo;
112     private Context mContext;
113     private Optional<Integer> mCloneProfileId = Optional.empty();
114 
getContext()115     private Context getContext() {
116         return InstrumentationRegistry.getInstrumentation().getContext();
117     }
118 
119     @Before
setUp()120     public void setUp() {
121         mContext = getContext();
122         mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
123         mSession = new MediaSession(getContext(), TEST_SESSION_TAG);
124         mKeyDispatcherInfo = new MediaSessionManager.RemoteUserInfo(
125                 getContext().getPackageName(), Process.myPid(), Process.myUid());
126     }
127 
128     @After
tearDown()129     public void tearDown() throws Exception {
130         // It is OK to call release() twice.
131         if (mSession != null) {
132             mSession.release();
133             mSession = null;
134         }
135         removeCloneProfile();
136     }
137 
createCloneProfile()138     private void createCloneProfile() {
139         Assume.assumeTrue(SUPPORTS_MULTIPLE_USERS);
140 
141         InstrumentationRegistry
142             .getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
143         UserManager userManager = mContext.getSystemService(UserManager.class);
144         boolean isCloneProfileEnabled = userManager.isUserTypeEnabled(USER_TYPE_PROFILE_CLONE);
145         InstrumentationRegistry
146             .getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
147         Assume.assumeTrue(isCloneProfileEnabled);
148 
149         final Context context = InstrumentationRegistry.getInstrumentation().getContext();
150         final String output = runShellCommand(
151                 "pm create-user --user-type android.os.usertype.profile.CLONE --profileOf "
152                 + context.getUserId() + " user2");
153 
154         // On a successful run the output will be like
155         // Success: created user id 11
156         // Hence we use the last index of " " to fetch the cloned profile id.
157         int userIdIndex = output.lastIndexOf(" ");
158         if (userIdIndex != -1) {
159             mCloneProfileId = Optional.of(
160                 Integer.parseInt(output.substring(userIdIndex).trim()));
161         }
162     }
163 
removeCloneProfile()164     private void removeCloneProfile() {
165         mCloneProfileId.ifPresent(cloneProfileId -> {
166             runShellCommand("am stop-user -w -f " + cloneProfileId);
167             runShellCommand("pm remove-user " + cloneProfileId);
168             mCloneProfileId = Optional.empty();
169         });
170     }
171 
172     /** Tests that a session can be created and that all the fields are initialized correctly. */
173     @Test
174     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testCreateSession()175     public void testCreateSession() throws Exception {
176         assertThat(mSession.getSessionToken()).isNotNull();
177         assertWithMessage("New session should not be active").that(mSession.isActive()).isFalse();
178 
179         // Verify by getting the controller and checking all its fields
180         MediaController controller = mSession.getController();
181         assertThat(controller).isNotNull();
182         verifyNewSession(controller);
183     }
184 
185     @Test
186     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
187     // Needed for assertThat(sessionToken.equals(mSession)).isFalse().
188     @SuppressWarnings("EqualsIncompatibleType")
testSessionTokenEquals()189     public void testSessionTokenEquals() {
190         MediaSession anotherSession = null;
191         try {
192             anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
193             MediaSession.Token sessionToken = mSession.getSessionToken();
194             MediaSession.Token anotherSessionToken = anotherSession.getSessionToken();
195 
196             // Explicitly checks equals as Guava's EqualsTester is not yet supported (b/236153976).
197             assertThat(sessionToken.equals(sessionToken)).isTrue();
198             assertThat(sessionToken.equals(null)).isFalse();
199             assertThat(sessionToken.equals(mSession)).isFalse();
200             assertThat(sessionToken.equals(anotherSessionToken)).isFalse();
201         } finally {
202             if (anotherSession != null) {
203                 anotherSession.release();
204             }
205         }
206     }
207 
208     /** Tests MediaSession.Token created in the constructor of MediaSession. */
209     @Test
210     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSessionToken()211     public void testSessionToken() throws Exception {
212         MediaSession.Token sessionToken = mSession.getSessionToken();
213 
214         assertThat(sessionToken).isNotNull();
215         assertThat(sessionToken.describeContents()).isEqualTo(0);
216 
217         // Test writeToParcel
218         Parcel p = Parcel.obtain();
219         sessionToken.writeToParcel(p, 0);
220         p.setDataPosition(0);
221         MediaSession.Token tokenFromParcel = MediaSession.Token.CREATOR.createFromParcel(p);
222         assertThat(tokenFromParcel).isEqualTo(sessionToken);
223         p.recycle();
224 
225         final int arraySize = 5;
226         MediaSession.Token[] tokenArray = MediaSession.Token.CREATOR.newArray(arraySize);
227         assertThat(tokenArray).isNotNull();
228         assertThat(tokenArray.length).isEqualTo(arraySize);
229         for (MediaSession.Token tokenElement : tokenArray) {
230             assertThat(tokenElement).isNull();
231         }
232     }
233 
234     /** Tests that the various configuration bits on a session get passed to the controller. */
235     @Test
236     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testConfigureSession()237     public void testConfigureSession() throws Exception {
238         MediaController controller = mSession.getController();
239         controller.registerCallback(mCallback, mHandler);
240         final MediaController.Callback callback = (MediaController.Callback) mCallback;
241 
242         synchronized (mWaitLock) {
243             // test setExtras
244             mCallback.resetLocked();
245             final Bundle extras = new Bundle();
246             extras.putString(TEST_KEY, TEST_VALUE);
247             mSession.setExtras(extras);
248             mWaitLock.wait(TIME_OUT_MS);
249             assertThat(mCallback.mOnExtraChangedCalled).isTrue();
250             // just call the callback once directly so it's marked as tested
251             callback.onExtrasChanged(mCallback.mExtras);
252 
253             Bundle extrasOut = mCallback.mExtras;
254             assertThat(extrasOut).isNotNull();
255             assertThat(extrasOut.get(TEST_KEY)).isEqualTo(TEST_VALUE);
256 
257             extrasOut = controller.getExtras();
258             assertThat(extrasOut).isNotNull();
259             assertThat(extrasOut.get(TEST_KEY)).isEqualTo(TEST_VALUE);
260 
261             // test setFlags
262             mSession.setFlags(5);
263             assertThat(controller.getFlags()).isEqualTo(5);
264 
265             // test setMetadata
266             mCallback.resetLocked();
267             MediaMetadata metadata =
268                     new MediaMetadata.Builder().putString(TEST_KEY, TEST_VALUE).build();
269             mSession.setMetadata(metadata);
270             mWaitLock.wait(TIME_OUT_MS);
271             assertThat(mCallback.mOnMetadataChangedCalled).isTrue();
272             // just call the callback once directly so it's marked as tested
273             callback.onMetadataChanged(mCallback.mMediaMetadata);
274 
275             MediaMetadata metadataOut = mCallback.mMediaMetadata;
276             assertThat(metadataOut).isNotNull();
277             assertThat(metadataOut.getString(TEST_KEY)).isEqualTo(TEST_VALUE);
278 
279             metadataOut = controller.getMetadata();
280             assertThat(metadataOut).isNotNull();
281             assertThat(metadataOut.getString(TEST_KEY)).isEqualTo(TEST_VALUE);
282 
283             // test setPlaybackState
284             mCallback.resetLocked();
285             PlaybackState state = new PlaybackState.Builder().setActions(TEST_ACTION).build();
286             mSession.setPlaybackState(state);
287             mWaitLock.wait(TIME_OUT_MS);
288             assertThat(mCallback.mOnPlaybackStateChangedCalled).isTrue();
289             // just call the callback once directly so it's marked as tested
290             callback.onPlaybackStateChanged(mCallback.mPlaybackState);
291 
292             PlaybackState stateOut = mCallback.mPlaybackState;
293             assertThat(stateOut).isNotNull();
294             assertThat(stateOut.getActions()).isEqualTo(TEST_ACTION);
295 
296             stateOut = controller.getPlaybackState();
297             assertThat(stateOut).isNotNull();
298             assertThat(stateOut.getActions()).isEqualTo(TEST_ACTION);
299 
300             // test setQueue and setQueueTitle
301             mCallback.resetLocked();
302             List<QueueItem> queue = new ArrayList<>();
303             QueueItem item = new QueueItem(new MediaDescription.Builder()
304                     .setMediaId(TEST_VALUE).setTitle("title").build(), TEST_QUEUE_ID);
305             queue.add(item);
306             mSession.setQueue(queue);
307             mWaitLock.wait(TIME_OUT_MS);
308             assertThat(mCallback.mOnQueueChangedCalled).isTrue();
309             // just call the callback once directly so it's marked as tested
310             callback.onQueueChanged(mCallback.mQueue);
311 
312             mSession.setQueueTitle(TEST_VALUE);
313             mWaitLock.wait(TIME_OUT_MS);
314             assertThat(mCallback.mOnQueueTitleChangedCalled).isTrue();
315 
316             assertThat(mCallback.mTitle).isEqualTo(TEST_VALUE);
317             assertThat(mCallback.mQueue.size()).isEqualTo(queue.size());
318             assertThat(mCallback.mQueue.get(0).getQueueId()).isEqualTo(TEST_QUEUE_ID);
319             assertThat(mCallback.mQueue.get(0).getDescription().getMediaId()).isEqualTo(TEST_VALUE);
320 
321             assertThat(controller.getQueueTitle()).isEqualTo(TEST_VALUE);
322             assertThat(controller.getQueue().size()).isEqualTo(queue.size());
323             assertThat(controller.getQueue().get(0).getQueueId()).isEqualTo(TEST_QUEUE_ID);
324             assertThat(controller.getQueue().get(0).getDescription().getMediaId())
325                     .isEqualTo(TEST_VALUE);
326 
327             mCallback.resetLocked();
328             mSession.setQueue(null);
329             mWaitLock.wait(TIME_OUT_MS);
330             assertThat(mCallback.mOnQueueChangedCalled).isTrue();
331             // just call the callback once directly so it's marked as tested
332             callback.onQueueChanged(mCallback.mQueue);
333 
334             mSession.setQueueTitle(null);
335             mWaitLock.wait(TIME_OUT_MS);
336             assertThat(mCallback.mOnQueueTitleChangedCalled).isTrue();
337             // just call the callback once directly so it's marked as tested
338             callback.onQueueTitleChanged(mCallback.mTitle);
339 
340             assertThat(mCallback.mTitle).isNull();
341             assertThat(mCallback.mQueue).isNull();
342             assertThat(controller.getQueueTitle()).isNull();
343             assertThat(controller.getQueue()).isNull();
344 
345             // test setSessionActivity
346             Intent intent = new Intent("cts.MEDIA_SESSION_ACTION");
347             PendingIntent pi = PendingIntent.getActivity(getContext(), 555, intent,
348                     PendingIntent.FLAG_MUTABLE_UNAUDITED);
349             mSession.setSessionActivity(pi);
350             assertThat(controller.getSessionActivity()).isEqualTo(pi);
351 
352             // test setActivity
353             mSession.setActive(true);
354             assertThat(mSession.isActive()).isTrue();
355 
356             // test sendSessionEvent
357             mCallback.resetLocked();
358             mSession.sendSessionEvent(TEST_SESSION_EVENT, extras);
359             mWaitLock.wait(TIME_OUT_MS);
360 
361             assertThat(mCallback.mOnSessionEventCalled).isTrue();
362             assertThat(mCallback.mEvent).isEqualTo(TEST_SESSION_EVENT);
363             assertThat(mCallback.mExtras.getString(TEST_KEY)).isEqualTo(TEST_VALUE);
364             // just call the callback once directly so it's marked as tested
365             callback.onSessionEvent(mCallback.mEvent, mCallback.mExtras);
366 
367             // test release
368             mCallback.resetLocked();
369             mSession.release();
370             mWaitLock.wait(TIME_OUT_MS);
371             assertThat(mCallback.mOnSessionDestroyedCalled).isTrue();
372             // just call the callback once directly so it's marked as tested
373             callback.onSessionDestroyed();
374         }
375     }
376 
377     @Test
378     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
setMediaSession_withInaccessibleUri_uriCleared()379     public void setMediaSession_withInaccessibleUri_uriCleared() throws Exception {
380         createCloneProfile();
381         Assume.assumeTrue(mCloneProfileId.isPresent());
382         String testMediaUri =
383                 "content://" + mCloneProfileId.get() + "@media/external/images/media/";
384         // Save a screenshot in second user files.
385         runShellCommand("screencap -p " + testMediaUri);
386 
387         MediaController controller = mSession.getController();
388         controller.registerCallback(mCallback, mHandler);
389         final MediaController.Callback callback = mCallback;
390 
391         synchronized (mWaitLock) {
392             // test setMetadata
393             mCallback.resetLocked();
394             MediaMetadata metadata =
395                     new MediaMetadata.Builder().putString(MediaMetadata.METADATA_KEY_ART_URI,
396                             testMediaUri).build();
397             mSession.setMetadata(metadata);
398             mWaitLock.wait(TIME_OUT_MS);
399 
400             assertThat(mCallback.mOnMetadataChangedCalled).isTrue();
401             // just call the callback once directly so it's marked as tested
402             callback.onMetadataChanged(mCallback.mMediaMetadata);
403 
404             MediaMetadata metadataOut = mCallback.mMediaMetadata;
405             assertThat(metadataOut).isNotNull();
406             assertThat(TextUtils.isEmpty(metadataOut.getString(MediaMetadata.METADATA_KEY_ART_URI)))
407                     .isTrue();
408         }
409     }
410 
411     @Test
412     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
setMediaSession_withUri_uriExists()413     public void setMediaSession_withUri_uriExists() throws Exception {
414         String testMediaUri = "content://media/external/images/media/";
415         MediaController controller = mSession.getController();
416         controller.registerCallback(mCallback, mHandler);
417         final MediaController.Callback callback = mCallback;
418 
419         synchronized (mWaitLock) {
420             // test setMetadata
421             mCallback.resetLocked();
422             MediaMetadata metadata =
423                     new MediaMetadata.Builder().putString(MediaMetadata.METADATA_KEY_ART_URI,
424                             testMediaUri).build();
425             mSession.setMetadata(metadata);
426             mWaitLock.wait(TIME_OUT_MS);
427 
428             assertThat(mCallback.mOnMetadataChangedCalled).isTrue();
429             // just call the callback once directly so it's marked as tested
430             callback.onMetadataChanged(mCallback.mMediaMetadata);
431 
432             MediaMetadata metadataOut = mCallback.mMediaMetadata;
433             assertThat(metadataOut).isNotNull();
434             assertThat(metadataOut.getString(MediaMetadata.METADATA_KEY_ART_URI))
435                     .isEqualTo(testMediaUri);
436         }
437     }
438 
439     /**
440      * Test whether media button receiver can be a explicit broadcast receiver via
441      * MediaSession.setMediaButtonReceiver(PendingIntent).
442      */
443     @Ignore // TODO(b/291800179): Diagnose flakiness and re-enable.
444     @Test
445     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSetMediaButtonReceiver_broadcastReceiver()446     public void testSetMediaButtonReceiver_broadcastReceiver() throws Exception {
447         Intent intent = new Intent(mContext.getApplicationContext(),
448                 MediaButtonBroadcastReceiver.class);
449         PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent,
450                 PendingIntent.FLAG_MUTABLE_UNAUDITED);
451 
452         // Play a sound so this session can get the priority.
453         Utils.assertMediaPlaybackStarted(getContext());
454 
455         // There is a different MediaSession that's created from StubMediaBrowserService class.
456         // This session takes over all the callbacks. we need to change the state of our session
457         // to STATE_PLAYING so it has higher priority.
458         setPlaybackState(PlaybackState.STATE_PLAYING);
459 
460         // Sets the media button receiver. Framework will keep the broadcast receiver component name
461         // from the pending intent in persistent storage.
462         mSession.setMediaButtonReceiver(pi);
463 
464         // Call explicit release, so change in the media key event session can be notified with the
465         // pending intent.
466         mSession.release();
467 
468         int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
469         try {
470             CountDownLatch latch = new CountDownLatch(2);
471             MediaButtonBroadcastReceiver.setCallback((keyEvent) -> {
472                 assertThat(keyEvent.getKeyCode()).isEqualTo(keyCode);
473                 switch ((int) latch.getCount()) {
474                     case 2:
475                         assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
476                         break;
477                     case 1:
478                         assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_UP);
479                         break;
480                 }
481                 latch.countDown();
482             });
483             // Also try to dispatch media key event.
484             // System would try to dispatch event.
485             simulateMediaKeyInput(keyCode);
486 
487             assertThat(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)).isTrue();
488         } finally {
489             MediaButtonBroadcastReceiver.setCallback(null);
490         }
491     }
492 
493     /** Test whether media button receiver can be a explicit service. */
494     @Test
495     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSetMediaButtonReceiver_service()496     public void testSetMediaButtonReceiver_service() throws Exception {
497         Intent intent = new Intent(mContext.getApplicationContext(),
498                 MediaButtonReceiverService.class);
499         PendingIntent pi = PendingIntent.getService(mContext, 0, intent,
500                 PendingIntent.FLAG_MUTABLE_UNAUDITED);
501 
502         // Play a sound so this session can get the priority.
503         Utils.assertMediaPlaybackStarted(getContext());
504 
505         // There is a different MediaSession that's created from StubMediaBrowserService class.
506         // This session takes over all the callbacks. we need to change the state of our session
507         // to STATE_PLAYING so it has higher priority.
508         setPlaybackState(PlaybackState.STATE_PLAYING);
509 
510         // Sets the media button receiver. Framework would try to keep the pending intent in the
511         // persistent store.
512         mSession.setMediaButtonReceiver(pi);
513 
514         // Call explicit release, so change in the media key event session can be notified with the
515         // pending intent.
516         mSession.release();
517 
518         int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
519         try {
520             CountDownLatch latch = new CountDownLatch(2);
521             MediaButtonReceiverService.setCallback((keyEvent) -> {
522                 assertThat(keyEvent.getKeyCode()).isEqualTo(keyCode);
523                 switch ((int) latch.getCount()) {
524                     case 2:
525                         assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
526                         break;
527                     case 1:
528                         assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_UP);
529                         break;
530                 }
531                 latch.countDown();
532             });
533             // Also try to dispatch media key event.
534             // System would try to dispatch event.
535             simulateMediaKeyInput(keyCode);
536 
537             assertThat(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)).isTrue();
538         } finally {
539             MediaButtonReceiverService.setCallback(null);
540         }
541     }
542 
543     /**
544      * Test whether system doesn't crash by {@link
545      * MediaSession#setMediaButtonReceiver(PendingIntent)} with implicit intent.
546      */
547     @Test
548     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSetMediaButtonReceiver_implicitIntent()549     public void testSetMediaButtonReceiver_implicitIntent() throws Exception {
550         // Note: No such broadcast receiver exists.
551         Intent intent = new Intent("android.media.session.cts.ACTION_MEDIA_TEST");
552         PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent,
553                 PendingIntent.FLAG_MUTABLE_UNAUDITED);
554 
555         // Play a sound so this session can get the priority.
556         Utils.assertMediaPlaybackStarted(getContext());
557 
558         // Sets the media button receiver. Framework would try to keep the pending intent in the
559         // persistent store.
560         mSession.setMediaButtonReceiver(pi);
561 
562         // Call explicit release, so change in the media key event session can be notified with the
563         // pending intent.
564         mSession.release();
565 
566         // Also try to dispatch media key event. System would try to send key event via pending
567         // intent, but it would no-op because there's no receiver.
568         simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
569     }
570 
571     @Test
572     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSetMediaButtonReceiver_withNull_doesNotThrow()573     public void testSetMediaButtonReceiver_withNull_doesNotThrow() {
574         try {
575             mSession.setMediaButtonReceiver(null);
576         } finally {
577             mSession.release();
578         }
579     }
580 
581     /**
582      * Test whether media button receiver can be a explicit broadcast receiver via
583      * MediaSession.setMediaButtonBroadcastReceiver(ComponentName)
584      */
585     @Ignore // TODO(b/291800179): Diagnose flakiness and re-enable.
586     @Test
587     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSetMediaButtonBroadcastReceiver_broadcastReceiver()588     public void testSetMediaButtonBroadcastReceiver_broadcastReceiver() throws Exception {
589         // Play a sound so this session can get the priority.
590         Utils.assertMediaPlaybackStarted(getContext());
591 
592         // Sets the broadcast receiver's component name. Framework will keep the component name in
593         // persistent storage.
594         mSession.setMediaButtonBroadcastReceiver(new ComponentName(mContext,
595                 MediaButtonBroadcastReceiver.class));
596 
597         // There is a different MediaSession that's created from StubMediaBrowserService class.
598         // This session takes over all the callbacks. we need to change the state of our session
599         // to STATE_PLAYING so it has higher priority.
600         setPlaybackState(PlaybackState.STATE_PLAYING);
601 
602         // Call explicit release, so change in the media key event session can be notified using the
603         // component name.
604         mSession.release();
605 
606         int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
607         try {
608             CountDownLatch latch = new CountDownLatch(2);
609             MediaButtonBroadcastReceiver.setCallback((keyEvent) -> {
610                 assertThat(keyEvent.getKeyCode()).isEqualTo(keyCode);
611                 switch ((int) latch.getCount()) {
612                     case 2:
613                         assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
614                         break;
615                     case 1:
616                         assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_UP);
617                         break;
618                 }
619                 latch.countDown();
620             });
621             // Also try to dispatch media key event.
622             // System would try to dispatch event.
623             simulateMediaKeyInput(keyCode);
624 
625             assertThat(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)).isTrue();
626         } finally {
627             MediaButtonBroadcastReceiver.setCallback(null);
628         }
629     }
630 
631     /** Test public APIs of {@link VolumeProvider}. */
632     @Test
633     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testVolumeProvider()634     public void testVolumeProvider() {
635         VolumeProvider vp = new VolumeProvider(VolumeProvider.VOLUME_CONTROL_RELATIVE,
636                 TEST_MAX_VOLUME, TEST_CURRENT_VOLUME, TEST_VOLUME_CONTROL_ID) {};
637         assertThat(vp.getVolumeControl()).isEqualTo(VolumeProvider.VOLUME_CONTROL_RELATIVE);
638         assertThat(vp.getMaxVolume()).isEqualTo(TEST_MAX_VOLUME);
639         assertThat(vp.getCurrentVolume()).isEqualTo(TEST_CURRENT_VOLUME);
640         assertThat(vp.getVolumeControlId()).isEqualTo(TEST_VOLUME_CONTROL_ID);
641     }
642 
643     /**
644      * Test {@link MediaSession#setPlaybackToLocal} and {@link MediaSession#setPlaybackToRemote}.
645      */
646     @Test
647     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testPlaybackToLocalAndRemote()648     public void testPlaybackToLocalAndRemote() throws Exception {
649         MediaController controller = mSession.getController();
650         controller.registerCallback(mCallback, mHandler);
651 
652         synchronized (mWaitLock) {
653             // test setPlaybackToRemote, do this before testing setPlaybackToLocal
654             // to ensure it switches correctly.
655             mCallback.resetLocked();
656             try {
657                 mSession.setPlaybackToRemote(null);
658                 assertWithMessage("Expected IAE for setPlaybackToRemote(null)").fail();
659             } catch (IllegalArgumentException e) {
660                 // expected
661             }
662             VolumeProvider vp = new VolumeProvider(VolumeProvider.VOLUME_CONTROL_FIXED,
663                     TEST_MAX_VOLUME, TEST_CURRENT_VOLUME, TEST_VOLUME_CONTROL_ID) {};
664             mSession.setPlaybackToRemote(vp);
665 
666             MediaController.PlaybackInfo info = null;
667             for (int i = 0; i < MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT; ++i) {
668                 mCallback.mOnAudioInfoChangedCalled = false;
669                 mWaitLock.wait(TIME_OUT_MS);
670                 assertThat(mCallback.mOnAudioInfoChangedCalled).isTrue();
671                 info = mCallback.mPlaybackInfo;
672                 if (info != null && info.getCurrentVolume() == TEST_CURRENT_VOLUME
673                         && info.getMaxVolume() == TEST_MAX_VOLUME
674                         && info.getVolumeControl() == VolumeProvider.VOLUME_CONTROL_FIXED
675                         && info.getPlaybackType()
676                                 == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE
677                         && TextUtils.equals(info.getVolumeControlId(), TEST_VOLUME_CONTROL_ID)) {
678                     break;
679                 }
680             }
681             assertThat(info).isNotNull();
682             assertThat(info.getPlaybackType())
683                     .isEqualTo(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE);
684             assertThat(info.getMaxVolume()).isEqualTo(TEST_MAX_VOLUME);
685             assertThat(info.getCurrentVolume()).isEqualTo(TEST_CURRENT_VOLUME);
686             assertThat(info.getVolumeControl()).isEqualTo(VolumeProvider.VOLUME_CONTROL_FIXED);
687             assertThat(info.getVolumeControlId()).isEqualTo(TEST_VOLUME_CONTROL_ID);
688 
689             info = controller.getPlaybackInfo();
690             assertThat(info).isNotNull();
691             assertThat(info.getPlaybackType())
692                     .isEqualTo(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE);
693             assertThat(info.getMaxVolume()).isEqualTo(TEST_MAX_VOLUME);
694             assertThat(info.getCurrentVolume()).isEqualTo(TEST_CURRENT_VOLUME);
695             assertThat(info.getVolumeControl()).isEqualTo(VolumeProvider.VOLUME_CONTROL_FIXED);
696             assertThat(info.getVolumeControlId()).isEqualTo(TEST_VOLUME_CONTROL_ID);
697 
698             // test setPlaybackToLocal
699             AudioAttributes attrs = new AudioAttributes.Builder().setUsage(USAGE_GAME).build();
700             mSession.setPlaybackToLocal(attrs);
701 
702             info = controller.getPlaybackInfo();
703             assertThat(info).isNotNull();
704             assertThat(info.getPlaybackType())
705                     .isEqualTo(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL);
706             assertThat(info.getAudioAttributes()).isEqualTo(attrs);
707             assertThat(info.getVolumeControlId()).isNull();
708         }
709     }
710 
711     /** Test {@link MediaSession.Callback#onMediaButtonEvent}. */
712     @Test
713     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testCallbackOnMediaButtonEvent()714     public void testCallbackOnMediaButtonEvent() throws Exception {
715         MediaSessionCallback sessionCallback = new MediaSessionCallback();
716         mSession.setCallback(sessionCallback, new Handler(Looper.getMainLooper()));
717         mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
718         mSession.setActive(true);
719 
720         // Set state to STATE_PLAYING to get higher priority.
721         setPlaybackState(PlaybackState.STATE_PLAYING);
722 
723         // A media playback is also needed to receive media key events.
724         Utils.assertMediaPlaybackStarted(getContext());
725 
726         sessionCallback.reset(1);
727         simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY);
728         assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue();
729         assertThat(sessionCallback.mOnPlayCalledCount).isEqualTo(1);
730         assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue();
731 
732         sessionCallback.reset(1);
733         simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PAUSE);
734         assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue();
735         assertThat(sessionCallback.mOnPauseCalled).isTrue();
736         assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue();
737 
738         sessionCallback.reset(1);
739         simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_NEXT);
740         assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue();
741         assertThat(sessionCallback.mOnSkipToNextCalled).isTrue();
742         assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue();
743 
744         sessionCallback.reset(1);
745         simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
746         assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue();
747         assertThat(sessionCallback.mOnSkipToPreviousCalled).isTrue();
748         assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue();
749 
750         sessionCallback.reset(1);
751         simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_STOP);
752         assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue();
753         assertThat(sessionCallback.mOnStopCalled).isTrue();
754         assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue();
755 
756         sessionCallback.reset(1);
757         simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
758         assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue();
759         assertThat(sessionCallback.mOnFastForwardCalled).isTrue();
760         assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue();
761 
762         sessionCallback.reset(1);
763         simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_REWIND);
764         assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue();
765         assertThat(sessionCallback.mOnRewindCalled).isTrue();
766         assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue();
767 
768         // Test PLAY_PAUSE button twice.
769         // First, simulate PLAY_PAUSE button while in STATE_PAUSED.
770         sessionCallback.reset(1);
771         setPlaybackState(PlaybackState.STATE_PAUSED);
772         simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
773         assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue();
774         assertThat(sessionCallback.mOnPlayCalledCount).isEqualTo(1);
775         assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue();
776 
777         // Next, simulate PLAY_PAUSE button while in STATE_PLAYING.
778         sessionCallback.reset(1);
779         setPlaybackState(PlaybackState.STATE_PLAYING);
780         simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
781         assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue();
782         assertThat(sessionCallback.mOnPauseCalled).isTrue();
783         assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue();
784 
785         // Double tap of PLAY_PAUSE is the next track instead of changing PLAY/PAUSE.
786         sessionCallback.reset(2);
787         setPlaybackState(PlaybackState.STATE_PLAYING);
788         simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
789         simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
790         assertThat(sessionCallback.await(WAIT_MS)).isFalse();
791         assertThat(sessionCallback.mOnSkipToNextCalled).isTrue();
792         assertThat(sessionCallback.mOnPlayCalledCount).isEqualTo(0);
793         assertThat(sessionCallback.mOnPauseCalled).isFalse();
794         assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue();
795 
796         // Test if PLAY_PAUSE double tap is considered as two single taps when another media
797         // key is pressed.
798         sessionCallback.reset(3);
799         setPlaybackState(PlaybackState.STATE_PAUSED);
800         simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
801         simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_STOP);
802         simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
803         assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue();
804         assertThat(sessionCallback.mOnPlayCalledCount).isEqualTo(2);
805         assertThat(sessionCallback.mOnStopCalled).isTrue();
806         assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue();
807 
808         // Test if media keys are handled in order.
809         sessionCallback.reset(2);
810         setPlaybackState(PlaybackState.STATE_PAUSED);
811         simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
812         simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_STOP);
813         assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue();
814         assertThat(sessionCallback.mOnPlayCalledCount).isEqualTo(1);
815         assertThat(sessionCallback.mOnStopCalled).isTrue();
816         assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue();
817         synchronized (mWaitLock) {
818             assertThat(mSession.getController().getPlaybackState().getState())
819                     .isEqualTo(PlaybackState.STATE_STOPPED);
820         }
821     }
822 
823     /**
824      * Tests {@link MediaSession#setCallback} with {@code null}. No callbacks will be called once
825      * {@code setCallback(null)} is done.
826      */
827     @Test
828     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSetCallbackWithNull()829     public void testSetCallbackWithNull() throws Exception {
830         MediaSessionCallback sessionCallback = new MediaSessionCallback();
831         mSession.setCallback(sessionCallback, mHandler);
832         mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
833         mSession.setActive(true);
834 
835         MediaController controller = mSession.getController();
836         setPlaybackState(PlaybackState.STATE_PLAYING);
837 
838         sessionCallback.reset(1);
839         mSession.setCallback(null, mHandler);
840 
841         controller.getTransportControls().pause();
842         assertThat(sessionCallback.await(WAIT_MS)).isFalse();
843         assertWithMessage("Callback shouldn't be called.")
844                 .that(sessionCallback.mOnPauseCalled).isFalse();
845     }
846 
setPlaybackState(int state)847     private void setPlaybackState(int state) {
848         final long allActions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PAUSE
849                 | PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_STOP
850                 | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS
851                 | PlaybackState.ACTION_FAST_FORWARD | PlaybackState.ACTION_REWIND;
852         PlaybackState playbackState = new PlaybackState.Builder().setActions(allActions)
853                 .setState(state, 0L, 0.0f).build();
854         synchronized (mWaitLock) {
855             mSession.setPlaybackState(playbackState);
856         }
857     }
858 
859     /**
860      * Test {@link MediaSession#release} doesn't crash when multiple media sessions are in the app
861      * which receives the media key events. See: b/36669550
862      */
863     @Test
864     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testReleaseNoCrashWithMultipleSessions()865     public void testReleaseNoCrashWithMultipleSessions() throws Exception {
866         // Start a media playback for this app to receive media key events.
867         Utils.assertMediaPlaybackStarted(getContext());
868 
869         MediaSession anotherSession = null;
870         try {
871             anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
872             mSession.release();
873             anotherSession.release();
874 
875             // Try release with the different order.
876             mSession = new MediaSession(getContext(), TEST_SESSION_TAG);
877             anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
878             anotherSession.release();
879             mSession.release();
880         } finally {
881             if (anotherSession != null) {
882                 anotherSession.release();
883                 anotherSession = null;
884             }
885         }
886     }
887 
888     // This uses public APIs to dispatch key events, so sessions would consider this as
889     // 'media key event from this application'.
simulateMediaKeyInput(int keyCode)890     private void simulateMediaKeyInput(int keyCode) {
891         long downTime = System.currentTimeMillis();
892         mAudioManager.dispatchMediaKeyEvent(
893                 new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0));
894         mAudioManager.dispatchMediaKeyEvent(
895                 new KeyEvent(downTime, System.currentTimeMillis(), KeyEvent.ACTION_UP, keyCode, 0));
896     }
897 
898     /** Tests {@link MediaSession.QueueItem}. */
899     @Test
900     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testQueueItem()901     public void testQueueItem() {
902         MediaDescription.Builder descriptionBuilder = new MediaDescription.Builder()
903                 .setMediaId("media-id")
904                 .setTitle("title");
905 
906         try {
907             new QueueItem(/*description=*/null, TEST_QUEUE_ID);
908             assertWithMessage("Unreachable statement.").fail();
909         } catch (IllegalArgumentException e) {
910             // Expected
911         }
912         try {
913             new QueueItem(descriptionBuilder.build(), QueueItem.UNKNOWN_ID);
914             assertWithMessage("Unreachable statement.").fail();
915         } catch (IllegalArgumentException e) {
916             // Expected
917         }
918 
919         QueueItem item = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID);
920 
921         Parcel p = Parcel.obtain();
922         item.writeToParcel(p, 0);
923         p.setDataPosition(0);
924         QueueItem other = QueueItem.CREATOR.createFromParcel(p);
925         assertThat(other.toString()).isEqualTo(item.toString());
926         p.recycle();
927 
928         final int arraySize = 5;
929         QueueItem[] queueItemArray = QueueItem.CREATOR.newArray(arraySize);
930         assertThat(queueItemArray).isNotNull();
931         assertThat(queueItemArray.length).isEqualTo(arraySize);
932         for (QueueItem elem : queueItemArray) {
933             assertThat(elem).isNull();
934         }
935     }
936 
937     @Test
938     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testQueueItemEquals()939     public void testQueueItemEquals() {
940         MediaDescription.Builder descriptionBuilder = new MediaDescription.Builder()
941                 .setMediaId("media-id")
942                 .setTitle("title");
943 
944         QueueItem item = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID);
945         assertThat(item.getQueueId()).isEqualTo(TEST_QUEUE_ID);
946         assertThat(item.getDescription().getMediaId()).isEqualTo("media-id");
947         assertThat(item.getDescription().getTitle()).isEqualTo("title");
948         assertThat(item.describeContents()).isEqualTo(0);
949 
950         assertThat(item.equals(null)).isFalse();
951         assertThat(item).isNotEqualTo(descriptionBuilder.build());
952 
953         QueueItem sameItem = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID);
954         assertThat(item.equals(sameItem)).isTrue();
955 
956         QueueItem differentQueueId = new QueueItem(
957                 descriptionBuilder.build(), TEST_QUEUE_ID + 1);
958         assertThat(item.equals(differentQueueId)).isFalse();
959 
960         QueueItem differentDescription = new QueueItem(
961                 descriptionBuilder.setTitle("title2").build(), TEST_QUEUE_ID);
962         assertThat(item.equals(differentDescription)).isFalse();
963     }
964 
965     @Test
966     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSessionInfoWithFrameworkParcelable()967     public void testSessionInfoWithFrameworkParcelable() {
968         final String testKey = "test_key";
969         final AudioAttributes frameworkParcelable = new AudioAttributes.Builder().build();
970 
971         Bundle sessionInfo = new Bundle();
972         sessionInfo.putParcelable(testKey, frameworkParcelable);
973 
974         MediaSession session = null;
975         try {
976             session = new MediaSession(
977                     mContext, "testSessionInfoWithFrameworkParcelable", sessionInfo);
978             Bundle sessionInfoOut = session.getController().getSessionInfo();
979             assertThat(sessionInfoOut.containsKey(testKey)).isTrue();
980             assertThat((AudioAttributes) sessionInfoOut.getParcelable(testKey))
981                     .isEqualTo(frameworkParcelable);
982         } finally {
983             if (session != null) {
984                 session.release();
985             }
986         }
987 
988     }
989 
990     @Test
991     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSessionInfoWithCustomParcelable()992     public void testSessionInfoWithCustomParcelable() {
993         final String testKey = "test_key";
994         final MediaSession2Test.CustomParcelable customParcelable =
995                 new MediaSession2Test.CustomParcelable(1);
996 
997         Bundle sessionInfo = new Bundle();
998         sessionInfo.putParcelable(testKey, customParcelable);
999 
1000         MediaSession session = null;
1001         try {
1002             session = new MediaSession(
1003                     mContext, "testSessionInfoWithCustomParcelable", sessionInfo);
1004             assertWithMessage("Custom Parcelable shouldn't be accepted!").fail();
1005         } catch (IllegalArgumentException e) {
1006             // Expected
1007         } finally {
1008             if (session != null) {
1009                 session.release();
1010             }
1011         }
1012     }
1013 
1014     /**
1015      * An app should not be able to create too many sessions. See
1016      * MediaSessionService#SESSION_CREATION_LIMIT_PER_UID
1017      */
1018     @Test
1019     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSessionCreationLimit()1020     public void testSessionCreationLimit() {
1021         List<MediaSession> sessions = new ArrayList<>();
1022         try {
1023             for (int i = 0; i < TEST_TOO_MANY_SESSION_COUNT; i++) {
1024                 sessions.add(new MediaSession(mContext, "testSessionCreationLimit"));
1025             }
1026             assertWithMessage("The number of session should be limited!").fail();
1027         } catch (RuntimeException e) {
1028             // Expected
1029         } finally {
1030             for (MediaSession session : sessions) {
1031                 session.release();
1032             }
1033         }
1034     }
1035 
1036     /**
1037      * Check that calling {@link MediaSession#release()} multiple times for the same session does
1038      * not decrement current session count multiple times.
1039      */
1040     @Test
1041     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSessionCreationLimitWithMediaSessionRelease()1042     public void testSessionCreationLimitWithMediaSessionRelease() {
1043         List<MediaSession> sessions = new ArrayList<>();
1044         MediaSession sessionToReleaseMultipleTimes = null;
1045         try {
1046             sessionToReleaseMultipleTimes = new MediaSession(
1047                     mContext, "testSessionCreationLimitWithMediaSessionRelease");
1048             for (int i = 0; i < TEST_TOO_MANY_SESSION_COUNT; i++) {
1049                 sessions.add(new MediaSession(
1050                         mContext, "testSessionCreationLimitWithMediaSessionRelease"));
1051                 // Call release() many times with the same session.
1052                 sessionToReleaseMultipleTimes.release();
1053             }
1054             assertWithMessage("The number of session should be limited!").fail();
1055         } catch (RuntimeException e) {
1056             // Expected
1057         } finally {
1058             for (MediaSession session : sessions) {
1059                 session.release();
1060             }
1061             if (sessionToReleaseMultipleTimes != null) {
1062                 sessionToReleaseMultipleTimes.release();
1063             }
1064         }
1065     }
1066 
1067     /**
1068      * Check that calling {@link MediaSession2#close()} does not decrement current session count.
1069      */
1070     @Test
1071     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSessionCreationLimitWithMediaSession2Release()1072     public void testSessionCreationLimitWithMediaSession2Release() {
1073         List<MediaSession> sessions = new ArrayList<>();
1074         try {
1075             for (int i = 0; i < 1000; i++) {
1076                 sessions.add(new MediaSession(
1077                         mContext, "testSessionCreationLimitWithMediaSession2Release"));
1078 
1079                 try (MediaSession2 session2 = new MediaSession2.Builder(mContext).build()) {
1080                     // Do nothing
1081                 }
1082             }
1083             assertWithMessage("The number of session should be limited!").fail();
1084         } catch (RuntimeException e) {
1085             // Expected
1086         } finally {
1087             for (MediaSession session : sessions) {
1088                 session.release();
1089             }
1090         }
1091     }
1092 
1093     /**
1094      * Check that a series of {@link MediaSession#setQueue} does not break {@link MediaController}
1095      * on the remote process due to binder buffer overflow.
1096      */
1097     @Test
1098     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSeriesOfSetQueue()1099     public void testSeriesOfSetQueue() throws Exception {
1100         int numberOfCalls = 100;
1101         int queueSize = 1_000;
1102         List<QueueItem> queue = new ArrayList<>();
1103         for (int id = 0; id < queueSize; id++) {
1104             MediaDescription description = new MediaDescription.Builder()
1105                     .setMediaId(Integer.toString(id)).build();
1106             queue.add(new QueueItem(description, id));
1107         }
1108 
1109         try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext,
1110                 MediaSessionTestService.class, TEST_SERIES_OF_SET_QUEUE)) {
1111             Bundle args = new Bundle();
1112             args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken());
1113             args.putInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS, numberOfCalls * queueSize);
1114             invoker.run(STEP_SET_UP, args);
1115             for (int i = 0; i < numberOfCalls; i++) {
1116                 mSession.setQueue(queue);
1117             }
1118             invoker.run(STEP_CHECK);
1119             invoker.run(STEP_CLEAN_UP);
1120         }
1121     }
1122 
1123     @Test
1124     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSetQueueWithLargeNumberOfItems()1125     public void testSetQueueWithLargeNumberOfItems() throws Exception {
1126         int queueSize = 500_000;
1127         List<QueueItem> queue = new ArrayList<>();
1128         for (int id = 0; id < queueSize; id++) {
1129             MediaDescription description = new MediaDescription.Builder()
1130                     .setMediaId(Integer.toString(id)).build();
1131             queue.add(new QueueItem(description, id));
1132         }
1133 
1134         try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext,
1135                 MediaSessionTestService.class, TEST_SET_QUEUE)) {
1136             Bundle args = new Bundle();
1137             args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken());
1138             args.putInt(KEY_EXPECTED_QUEUE_SIZE, queueSize);
1139             invoker.run(STEP_SET_UP, args);
1140             mSession.setQueue(queue);
1141             invoker.run(STEP_CHECK);
1142             invoker.run(STEP_CLEAN_UP);
1143         }
1144     }
1145 
1146     @Test
1147     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSetQueueWithEmptyQueue()1148     public void testSetQueueWithEmptyQueue() throws Exception {
1149         try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext,
1150                 MediaSessionTestService.class, TEST_SET_QUEUE)) {
1151             Bundle args = new Bundle();
1152             args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken());
1153             args.putInt(KEY_EXPECTED_QUEUE_SIZE, 0);
1154             invoker.run(STEP_SET_UP, args);
1155             mSession.setQueue(Collections.emptyList());
1156             invoker.run(STEP_CHECK);
1157             invoker.run(STEP_CLEAN_UP);
1158         }
1159     }
1160 
1161     /**
1162      * Verifies that a new session hasn't had any configuration bits set yet.
1163      *
1164      * @param controller The controller for the session
1165      */
1166     @SuppressWarnings("ReturnValueIgnored")
verifyNewSession(MediaController controller)1167     private void verifyNewSession(MediaController controller) {
1168         assertWithMessage("New session has unexpected configuration")
1169                 .that(controller.getFlags()).isEqualTo(0);
1170         assertWithMessage("New session has unexpected configuration")
1171                 .that(controller.getExtras()).isNull();
1172         assertWithMessage("New session has unexpected configuration")
1173                 .that(controller.getMetadata()).isNull();
1174         assertWithMessage("New session has unexpected configuration")
1175                 .that(controller.getPackageName()).isEqualTo(getContext().getPackageName());
1176         assertWithMessage("New session has unexpected configuration")
1177                 .that(controller.getPlaybackState()).isNull();
1178         assertWithMessage("New session has unexpected configuration")
1179                 .that(controller.getQueue()).isNull();
1180         assertWithMessage("New session has unexpected configuration")
1181                 .that(controller.getQueueTitle()).isNull();
1182         assertWithMessage("New session has unexpected configuration")
1183                 .that(controller.getRatingType()).isEqualTo(Rating.RATING_NONE);
1184         assertWithMessage("New session has unexpected configuration")
1185                 .that(controller.getSessionActivity()).isNull();
1186 
1187         assertThat(controller.getSessionToken()).isNotNull();
1188         assertThat(controller.getTransportControls()).isNotNull();
1189 
1190         MediaController.PlaybackInfo info = controller.getPlaybackInfo();
1191         assertThat(info).isNotNull();
1192         info.toString(); // Test that calling PlaybackInfo.toString() does not crash.
1193         assertThat(info.getPlaybackType())
1194                 .isEqualTo(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL);
1195         AudioAttributes attrs = info.getAudioAttributes();
1196         assertThat(attrs).isNotNull();
1197         assertThat(attrs.getUsage()).isEqualTo(USAGE_MEDIA);
1198         assertThat(info.getCurrentVolume())
1199                 .isEqualTo(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
1200     }
1201 
1202     private class MediaControllerCallback extends MediaController.Callback {
1203         private volatile boolean mOnPlaybackStateChangedCalled;
1204         private volatile boolean mOnMetadataChangedCalled;
1205         private volatile boolean mOnQueueChangedCalled;
1206         private volatile boolean mOnQueueTitleChangedCalled;
1207         private volatile boolean mOnExtraChangedCalled;
1208         private volatile boolean mOnAudioInfoChangedCalled;
1209         private volatile boolean mOnSessionDestroyedCalled;
1210         private volatile boolean mOnSessionEventCalled;
1211 
1212         private volatile PlaybackState mPlaybackState;
1213         private volatile MediaMetadata mMediaMetadata;
1214         private volatile List<QueueItem> mQueue;
1215         private volatile CharSequence mTitle;
1216         private volatile String mEvent;
1217         private volatile Bundle mExtras;
1218         private volatile MediaController.PlaybackInfo mPlaybackInfo;
1219 
resetLocked()1220         public void resetLocked() {
1221             mOnPlaybackStateChangedCalled = false;
1222             mOnMetadataChangedCalled = false;
1223             mOnQueueChangedCalled = false;
1224             mOnQueueTitleChangedCalled = false;
1225             mOnExtraChangedCalled = false;
1226             mOnAudioInfoChangedCalled = false;
1227             mOnSessionDestroyedCalled = false;
1228             mOnSessionEventCalled = false;
1229 
1230             mPlaybackState = null;
1231             mMediaMetadata = null;
1232             mQueue = null;
1233             mTitle = null;
1234             mExtras = null;
1235             mPlaybackInfo = null;
1236         }
1237 
1238         @Override
onPlaybackStateChanged(PlaybackState state)1239         public void onPlaybackStateChanged(PlaybackState state) {
1240             synchronized (mWaitLock) {
1241                 mOnPlaybackStateChangedCalled = true;
1242                 mPlaybackState = state;
1243                 mWaitLock.notify();
1244             }
1245         }
1246 
1247         @Override
onMetadataChanged(MediaMetadata metadata)1248         public void onMetadataChanged(MediaMetadata metadata) {
1249             synchronized (mWaitLock) {
1250                 mOnMetadataChangedCalled = true;
1251                 mMediaMetadata = metadata;
1252                 mWaitLock.notify();
1253             }
1254         }
1255 
1256         @Override
onQueueChanged(List<QueueItem> queue)1257         public void onQueueChanged(List<QueueItem> queue) {
1258             synchronized (mWaitLock) {
1259                 mOnQueueChangedCalled = true;
1260                 mQueue = queue;
1261                 mWaitLock.notify();
1262             }
1263         }
1264 
1265         @Override
onQueueTitleChanged(CharSequence title)1266         public void onQueueTitleChanged(CharSequence title) {
1267             synchronized (mWaitLock) {
1268                 mOnQueueTitleChangedCalled = true;
1269                 mTitle = title;
1270                 mWaitLock.notify();
1271             }
1272         }
1273 
1274         @Override
onExtrasChanged(Bundle extras)1275         public void onExtrasChanged(Bundle extras) {
1276             synchronized (mWaitLock) {
1277                 mOnExtraChangedCalled = true;
1278                 mExtras = extras;
1279                 mWaitLock.notify();
1280             }
1281         }
1282 
1283         @Override
onAudioInfoChanged(MediaController.PlaybackInfo info)1284         public void onAudioInfoChanged(MediaController.PlaybackInfo info) {
1285             synchronized (mWaitLock) {
1286                 mOnAudioInfoChangedCalled = true;
1287                 mPlaybackInfo = info;
1288                 mWaitLock.notify();
1289             }
1290         }
1291 
1292         @Override
onSessionDestroyed()1293         public void onSessionDestroyed() {
1294             synchronized (mWaitLock) {
1295                 mOnSessionDestroyedCalled = true;
1296                 mWaitLock.notify();
1297             }
1298         }
1299 
1300         @Override
onSessionEvent(String event, Bundle extras)1301         public void onSessionEvent(String event, Bundle extras) {
1302             synchronized (mWaitLock) {
1303                 mOnSessionEventCalled = true;
1304                 mEvent = event;
1305                 mExtras = (Bundle) extras.clone();
1306                 mWaitLock.notify();
1307             }
1308         }
1309     }
1310 
1311     private class MediaSessionCallback extends MediaSession.Callback {
1312         private CountDownLatch mLatch;
1313         private int mOnPlayCalledCount;
1314         private boolean mOnPauseCalled;
1315         private boolean mOnStopCalled;
1316         private boolean mOnFastForwardCalled;
1317         private boolean mOnRewindCalled;
1318         private boolean mOnSkipToPreviousCalled;
1319         private boolean mOnSkipToNextCalled;
1320         private RemoteUserInfo mCallerInfo;
1321 
reset(int count)1322         public void reset(int count) {
1323             mLatch = new CountDownLatch(count);
1324             mOnPlayCalledCount = 0;
1325             mOnPauseCalled = false;
1326             mOnStopCalled = false;
1327             mOnFastForwardCalled = false;
1328             mOnRewindCalled = false;
1329             mOnSkipToPreviousCalled = false;
1330             mOnSkipToNextCalled = false;
1331         }
1332 
await(long waitMs)1333         public boolean await(long waitMs) {
1334             try {
1335                 return mLatch.await(waitMs, TimeUnit.MILLISECONDS);
1336             } catch (InterruptedException e) {
1337                 return false;
1338             }
1339         }
1340 
1341         @Override
onPlay()1342         public void onPlay() {
1343             mOnPlayCalledCount++;
1344             mCallerInfo = mSession.getCurrentControllerInfo();
1345             setPlaybackState(PlaybackState.STATE_PLAYING);
1346             mLatch.countDown();
1347         }
1348 
1349         @Override
onPause()1350         public void onPause() {
1351             mOnPauseCalled = true;
1352             mCallerInfo = mSession.getCurrentControllerInfo();
1353             setPlaybackState(PlaybackState.STATE_PAUSED);
1354             mLatch.countDown();
1355         }
1356 
1357         @Override
onStop()1358         public void onStop() {
1359             mOnStopCalled = true;
1360             mCallerInfo = mSession.getCurrentControllerInfo();
1361             setPlaybackState(PlaybackState.STATE_STOPPED);
1362             mLatch.countDown();
1363         }
1364 
1365         @Override
onFastForward()1366         public void onFastForward() {
1367             mOnFastForwardCalled = true;
1368             mCallerInfo = mSession.getCurrentControllerInfo();
1369             mLatch.countDown();
1370         }
1371 
1372         @Override
onRewind()1373         public void onRewind() {
1374             mOnRewindCalled = true;
1375             mCallerInfo = mSession.getCurrentControllerInfo();
1376             mLatch.countDown();
1377         }
1378 
1379         @Override
onSkipToPrevious()1380         public void onSkipToPrevious() {
1381             mOnSkipToPreviousCalled = true;
1382             mCallerInfo = mSession.getCurrentControllerInfo();
1383             mLatch.countDown();
1384         }
1385 
1386         @Override
onSkipToNext()1387         public void onSkipToNext() {
1388             mOnSkipToNextCalled = true;
1389             mCallerInfo = mSession.getCurrentControllerInfo();
1390             mLatch.countDown();
1391         }
1392     }
1393 }
1394