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.bettertogether.cts;
17 
18 import static android.media.AudioAttributes.USAGE_GAME;
19 import static android.media.AudioAttributes.USAGE_MEDIA;
20 import static android.media.bettertogether.cts.MediaSessionTestService.KEY_EXPECTED_QUEUE_SIZE;
21 import static android.media.bettertogether.cts.MediaSessionTestService.KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS;
22 import static android.media.bettertogether.cts.MediaSessionTestService.KEY_SESSION_TOKEN;
23 import static android.media.bettertogether.cts.MediaSessionTestService.STEP_CHECK;
24 import static android.media.bettertogether.cts.MediaSessionTestService.STEP_CLEAN_UP;
25 import static android.media.bettertogether.cts.MediaSessionTestService.STEP_SET_UP;
26 import static android.media.bettertogether.cts.MediaSessionTestService.TEST_SERIES_OF_SET_QUEUE;
27 import static android.media.bettertogether.cts.MediaSessionTestService.TEST_SET_QUEUE;
28 import static android.media.cts.Utils.compareRemoteUserInfo;
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.ext.junit.runners.AndroidJUnit4;
65 import androidx.test.platform.app.InstrumentationRegistry;
66 
67 import com.android.compatibility.common.util.FrameworkSpecificTest;
68 import com.android.compatibility.common.util.NonMainlineTest;
69 
70 import org.junit.After;
71 import org.junit.Assume;
72 import org.junit.Before;
73 import org.junit.Ignore;
74 import org.junit.Test;
75 import org.junit.runner.RunWith;
76 
77 import java.util.ArrayList;
78 import java.util.Collections;
79 import java.util.List;
80 import java.util.Optional;
81 import java.util.concurrent.CountDownLatch;
82 import java.util.concurrent.TimeUnit;
83 
84 @FrameworkSpecificTest
85 @NonMainlineTest
86 @AppModeFull(reason = "TODO: evaluate and port to instant")
87 @RunWith(AndroidJUnit4.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     /**
173      * Tests that a session can be created and that all the fields are
174      * initialized correctly.
175      */
176     @Test
testCreateSession()177     public void testCreateSession() throws Exception {
178         assertThat(mSession.getSessionToken()).isNotNull();
179         assertWithMessage("New session should not be active").that(mSession.isActive()).isFalse();
180 
181         // Verify by getting the controller and checking all its fields
182         MediaController controller = mSession.getController();
183         assertThat(controller).isNotNull();
184         verifyNewSession(controller);
185     }
186 
187     @Test
188     // Needed for assertThat(sessionToken.equals(mSession)).isFalse().
189     @SuppressWarnings("EqualsIncompatibleType")
testSessionTokenEquals()190     public void testSessionTokenEquals() {
191         MediaSession anotherSession = null;
192         try {
193             anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
194             MediaSession.Token sessionToken = mSession.getSessionToken();
195             MediaSession.Token anotherSessionToken = anotherSession.getSessionToken();
196 
197             // Explicitly checks equals as Guava's EqualsTester is not yet supported (b/236153976).
198             assertThat(sessionToken.equals(sessionToken)).isTrue();
199             assertThat(sessionToken.equals(null)).isFalse();
200             assertThat(sessionToken.equals(mSession)).isFalse();
201             assertThat(sessionToken.equals(anotherSessionToken)).isFalse();
202         } finally {
203             if (anotherSession != null) {
204                 anotherSession.release();
205             }
206         }
207     }
208 
209     /**
210      * Tests MediaSession.Token created in the constructor of MediaSession.
211      */
212     @Test
testSessionToken()213     public void testSessionToken() throws Exception {
214         MediaSession.Token sessionToken = mSession.getSessionToken();
215 
216         assertThat(sessionToken).isNotNull();
217         assertThat(sessionToken.describeContents()).isEqualTo(0);
218 
219         // Test writeToParcel
220         Parcel p = Parcel.obtain();
221         sessionToken.writeToParcel(p, 0);
222         p.setDataPosition(0);
223         MediaSession.Token tokenFromParcel = MediaSession.Token.CREATOR.createFromParcel(p);
224         assertThat(tokenFromParcel).isEqualTo(sessionToken);
225         p.recycle();
226 
227         final int arraySize = 5;
228         MediaSession.Token[] tokenArray = MediaSession.Token.CREATOR.newArray(arraySize);
229         assertThat(tokenArray).isNotNull();
230         assertThat(tokenArray.length).isEqualTo(arraySize);
231         for (MediaSession.Token tokenElement : tokenArray) {
232             assertThat(tokenElement).isNull();
233         }
234     }
235 
236     /**
237      * Tests that the various configuration bits on a session get passed to the
238      * controller.
239      */
240     @Test
testConfigureSession()241     public void testConfigureSession() throws Exception {
242         MediaController controller = mSession.getController();
243         controller.registerCallback(mCallback, mHandler);
244         final MediaController.Callback callback = (MediaController.Callback) mCallback;
245 
246         synchronized (mWaitLock) {
247             // test setExtras
248             mCallback.resetLocked();
249             final Bundle extras = new Bundle();
250             extras.putString(TEST_KEY, TEST_VALUE);
251             mSession.setExtras(extras);
252             mWaitLock.wait(TIME_OUT_MS);
253             assertThat(mCallback.mOnExtraChangedCalled).isTrue();
254             // just call the callback once directly so it's marked as tested
255             callback.onExtrasChanged(mCallback.mExtras);
256 
257             Bundle extrasOut = mCallback.mExtras;
258             assertThat(extrasOut).isNotNull();
259             assertThat(extrasOut.get(TEST_KEY)).isEqualTo(TEST_VALUE);
260 
261             extrasOut = controller.getExtras();
262             assertThat(extrasOut).isNotNull();
263             assertThat(extrasOut.get(TEST_KEY)).isEqualTo(TEST_VALUE);
264 
265             // test setFlags
266             mSession.setFlags(5);
267             assertThat(controller.getFlags()).isEqualTo(5);
268 
269             // test setMetadata
270             mCallback.resetLocked();
271             MediaMetadata metadata =
272                     new MediaMetadata.Builder().putString(TEST_KEY, TEST_VALUE).build();
273             mSession.setMetadata(metadata);
274             mWaitLock.wait(TIME_OUT_MS);
275             assertThat(mCallback.mOnMetadataChangedCalled).isTrue();
276             // just call the callback once directly so it's marked as tested
277             callback.onMetadataChanged(mCallback.mMediaMetadata);
278 
279             MediaMetadata metadataOut = mCallback.mMediaMetadata;
280             assertThat(metadataOut).isNotNull();
281             assertThat(metadataOut.getString(TEST_KEY)).isEqualTo(TEST_VALUE);
282 
283             metadataOut = controller.getMetadata();
284             assertThat(metadataOut).isNotNull();
285             assertThat(metadataOut.getString(TEST_KEY)).isEqualTo(TEST_VALUE);
286 
287             // test setPlaybackState
288             mCallback.resetLocked();
289             PlaybackState state = new PlaybackState.Builder().setActions(TEST_ACTION).build();
290             mSession.setPlaybackState(state);
291             mWaitLock.wait(TIME_OUT_MS);
292             assertThat(mCallback.mOnPlaybackStateChangedCalled).isTrue();
293             // just call the callback once directly so it's marked as tested
294             callback.onPlaybackStateChanged(mCallback.mPlaybackState);
295 
296             PlaybackState stateOut = mCallback.mPlaybackState;
297             assertThat(stateOut).isNotNull();
298             assertThat(stateOut.getActions()).isEqualTo(TEST_ACTION);
299 
300             stateOut = controller.getPlaybackState();
301             assertThat(stateOut).isNotNull();
302             assertThat(stateOut.getActions()).isEqualTo(TEST_ACTION);
303 
304             // test setQueue and setQueueTitle
305             mCallback.resetLocked();
306             List<QueueItem> queue = new ArrayList<>();
307             QueueItem item = new QueueItem(new MediaDescription.Builder()
308                     .setMediaId(TEST_VALUE).setTitle("title").build(), TEST_QUEUE_ID);
309             queue.add(item);
310             mSession.setQueue(queue);
311             mWaitLock.wait(TIME_OUT_MS);
312             assertThat(mCallback.mOnQueueChangedCalled).isTrue();
313             // just call the callback once directly so it's marked as tested
314             callback.onQueueChanged(mCallback.mQueue);
315 
316             mSession.setQueueTitle(TEST_VALUE);
317             mWaitLock.wait(TIME_OUT_MS);
318             assertThat(mCallback.mOnQueueTitleChangedCalled).isTrue();
319 
320             assertThat(mCallback.mTitle).isEqualTo(TEST_VALUE);
321             assertThat(mCallback.mQueue.size()).isEqualTo(queue.size());
322             assertThat(mCallback.mQueue.get(0).getQueueId()).isEqualTo(TEST_QUEUE_ID);
323             assertThat(mCallback.mQueue.get(0).getDescription().getMediaId()).isEqualTo(TEST_VALUE);
324 
325             assertThat(controller.getQueueTitle()).isEqualTo(TEST_VALUE);
326             assertThat(controller.getQueue().size()).isEqualTo(queue.size());
327             assertThat(controller.getQueue().get(0).getQueueId()).isEqualTo(TEST_QUEUE_ID);
328             assertThat(controller.getQueue().get(0).getDescription().getMediaId())
329                     .isEqualTo(TEST_VALUE);
330 
331             mCallback.resetLocked();
332             mSession.setQueue(null);
333             mWaitLock.wait(TIME_OUT_MS);
334             assertThat(mCallback.mOnQueueChangedCalled).isTrue();
335             // just call the callback once directly so it's marked as tested
336             callback.onQueueChanged(mCallback.mQueue);
337 
338             mSession.setQueueTitle(null);
339             mWaitLock.wait(TIME_OUT_MS);
340             assertThat(mCallback.mOnQueueTitleChangedCalled).isTrue();
341             // just call the callback once directly so it's marked as tested
342             callback.onQueueTitleChanged(mCallback.mTitle);
343 
344             assertThat(mCallback.mTitle).isNull();
345             assertThat(mCallback.mQueue).isNull();
346             assertThat(controller.getQueueTitle()).isNull();
347             assertThat(controller.getQueue()).isNull();
348 
349             // test setSessionActivity
350             Intent intent = new Intent("cts.MEDIA_SESSION_ACTION");
351             PendingIntent pi = PendingIntent.getActivity(getContext(), 555, intent,
352                     PendingIntent.FLAG_MUTABLE_UNAUDITED);
353             mSession.setSessionActivity(pi);
354             assertThat(controller.getSessionActivity()).isEqualTo(pi);
355 
356             // test setActivity
357             mSession.setActive(true);
358             assertThat(mSession.isActive()).isTrue();
359 
360             // test sendSessionEvent
361             mCallback.resetLocked();
362             mSession.sendSessionEvent(TEST_SESSION_EVENT, extras);
363             mWaitLock.wait(TIME_OUT_MS);
364 
365             assertThat(mCallback.mOnSessionEventCalled).isTrue();
366             assertThat(mCallback.mEvent).isEqualTo(TEST_SESSION_EVENT);
367             assertThat(mCallback.mExtras.getString(TEST_KEY)).isEqualTo(TEST_VALUE);
368             // just call the callback once directly so it's marked as tested
369             callback.onSessionEvent(mCallback.mEvent, mCallback.mExtras);
370 
371             // test release
372             mCallback.resetLocked();
373             mSession.release();
374             mWaitLock.wait(TIME_OUT_MS);
375             assertThat(mCallback.mOnSessionDestroyedCalled).isTrue();
376             // just call the callback once directly so it's marked as tested
377             callback.onSessionDestroyed();
378         }
379     }
380 
381     @Test
setMediaSession_withInaccessibleUri_uriCleared()382     public void setMediaSession_withInaccessibleUri_uriCleared() throws Exception {
383         createCloneProfile();
384         Assume.assumeTrue(mCloneProfileId.isPresent());
385         String testMediaUri =
386                 "content://" + mCloneProfileId.get() + "@media/external/images/media/";
387         // Save a screenshot in second user files.
388         runShellCommand("screencap -p " + testMediaUri);
389 
390         MediaController controller = mSession.getController();
391         controller.registerCallback(mCallback, mHandler);
392         final MediaController.Callback callback = mCallback;
393 
394         synchronized (mWaitLock) {
395             // test setMetadata
396             mCallback.resetLocked();
397             MediaMetadata metadata =
398                     new MediaMetadata.Builder().putString(MediaMetadata.METADATA_KEY_ART_URI,
399                             testMediaUri).build();
400             mSession.setMetadata(metadata);
401             mWaitLock.wait(TIME_OUT_MS);
402 
403             assertThat(mCallback.mOnMetadataChangedCalled).isTrue();
404             // just call the callback once directly so it's marked as tested
405             callback.onMetadataChanged(mCallback.mMediaMetadata);
406 
407             MediaMetadata metadataOut = mCallback.mMediaMetadata;
408             assertThat(metadataOut).isNotNull();
409             assertThat(TextUtils.isEmpty(metadataOut.getString(MediaMetadata.METADATA_KEY_ART_URI)))
410                     .isTrue();
411         }
412     }
413 
414     @Test
setMediaSession_withUri_uriExists()415     public void setMediaSession_withUri_uriExists() throws Exception {
416         String testMediaUri = "content://media/external/images/media/";
417         MediaController controller = mSession.getController();
418         controller.registerCallback(mCallback, mHandler);
419         final MediaController.Callback callback = mCallback;
420 
421         synchronized (mWaitLock) {
422             // test setMetadata
423             mCallback.resetLocked();
424             MediaMetadata metadata =
425                     new MediaMetadata.Builder().putString(MediaMetadata.METADATA_KEY_ART_URI,
426                             testMediaUri).build();
427             mSession.setMetadata(metadata);
428             mWaitLock.wait(TIME_OUT_MS);
429 
430             assertThat(mCallback.mOnMetadataChangedCalled).isTrue();
431             // just call the callback once directly so it's marked as tested
432             callback.onMetadataChanged(mCallback.mMediaMetadata);
433 
434             MediaMetadata metadataOut = mCallback.mMediaMetadata;
435             assertThat(metadataOut).isNotNull();
436             assertThat(metadataOut.getString(MediaMetadata.METADATA_KEY_ART_URI))
437                     .isEqualTo(testMediaUri);
438         }
439     }
440 
441     /**
442      * Test whether media button receiver can be a explicit broadcast receiver via
443      * MediaSession.setMediaButtonReceiver(PendingIntent).
444      */
445     @Ignore // TODO(b/291800179): Diagnose flakiness and re-enable.
446     @Test
testSetMediaButtonReceiver_broadcastReceiver()447     public void testSetMediaButtonReceiver_broadcastReceiver() throws Exception {
448         Intent intent = new Intent(mContext.getApplicationContext(),
449                 MediaButtonBroadcastReceiver.class);
450         PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent,
451                 PendingIntent.FLAG_MUTABLE_UNAUDITED);
452 
453         // Play a sound so this session can get the priority.
454         Utils.assertMediaPlaybackStarted(getContext());
455 
456         // There is a different MediaSession that's created from StubMediaBrowserService class.
457         // This session takes over all the callbacks. we need to change the state of our session
458         // to STATE_PLAYING so it has higher priority.
459         setPlaybackState(PlaybackState.STATE_PLAYING);
460 
461         // Sets the media button receiver. Framework will keep the broadcast receiver component name
462         // from the pending intent in persistent storage.
463         mSession.setMediaButtonReceiver(pi);
464 
465         // Call explicit release, so change in the media key event session can be notified with the
466         // pending intent.
467         mSession.release();
468 
469         int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
470         try {
471             CountDownLatch latch = new CountDownLatch(2);
472             MediaButtonBroadcastReceiver.setCallback((keyEvent) -> {
473                 assertThat(keyEvent.getKeyCode()).isEqualTo(keyCode);
474                 switch ((int) latch.getCount()) {
475                     case 2:
476                         assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
477                         break;
478                     case 1:
479                         assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_UP);
480                         break;
481                 }
482                 latch.countDown();
483             });
484             // Also try to dispatch media key event.
485             // System would try to dispatch event.
486             simulateMediaKeyInput(keyCode);
487 
488             assertThat(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)).isTrue();
489         } finally {
490             MediaButtonBroadcastReceiver.setCallback(null);
491         }
492     }
493 
494     /**
495      * Test whether media button receiver can be a explicit service.
496      */
497     @Test
testSetMediaButtonReceiver_service()498     public void testSetMediaButtonReceiver_service() throws Exception {
499         Intent intent = new Intent(mContext.getApplicationContext(),
500                 MediaButtonReceiverService.class);
501         PendingIntent pi = PendingIntent.getService(mContext, 0, intent,
502                 PendingIntent.FLAG_MUTABLE_UNAUDITED);
503 
504         // Play a sound so this session can get the priority.
505         Utils.assertMediaPlaybackStarted(getContext());
506 
507         // There is a different MediaSession that's created from StubMediaBrowserService class.
508         // This session takes over all the callbacks. we need to change the state of our session
509         // to STATE_PLAYING so it has higher priority.
510         setPlaybackState(PlaybackState.STATE_PLAYING);
511 
512         // Sets the media button receiver. Framework would try to keep the pending intent in the
513         // persistent store.
514         mSession.setMediaButtonReceiver(pi);
515 
516         // Call explicit release, so change in the media key event session can be notified with the
517         // pending intent.
518         mSession.release();
519 
520         int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
521         try {
522             CountDownLatch latch = new CountDownLatch(2);
523             MediaButtonReceiverService.setCallback((keyEvent) -> {
524                 assertThat(keyEvent.getKeyCode()).isEqualTo(keyCode);
525                 switch ((int) latch.getCount()) {
526                     case 2:
527                         assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
528                         break;
529                     case 1:
530                         assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_UP);
531                         break;
532                 }
533                 latch.countDown();
534             });
535             // Also try to dispatch media key event.
536             // System would try to dispatch event.
537             simulateMediaKeyInput(keyCode);
538 
539             assertThat(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)).isTrue();
540         } finally {
541             MediaButtonReceiverService.setCallback(null);
542         }
543     }
544 
545     /**
546      * Test whether system doesn't crash by
547      * {@link MediaSession#setMediaButtonReceiver(PendingIntent)} with implicit intent.
548      */
549     @Test
testSetMediaButtonReceiver_implicitIntent()550     public void testSetMediaButtonReceiver_implicitIntent() throws Exception {
551         // Note: No such broadcast receiver exists.
552         Intent intent = new Intent("android.media.bettertogether.cts.ACTION_MEDIA_TEST");
553         PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent,
554                 PendingIntent.FLAG_MUTABLE_UNAUDITED);
555 
556         // Play a sound so this session can get the priority.
557         Utils.assertMediaPlaybackStarted(getContext());
558 
559         // Sets the media button receiver. Framework would try to keep the pending intent in the
560         // persistent store.
561         mSession.setMediaButtonReceiver(pi);
562 
563         // Call explicit release, so change in the media key event session can be notified with the
564         // pending intent.
565         mSession.release();
566 
567         // Also try to dispatch media key event. System would try to send key event via pending
568         // intent, but it would no-op because there's no receiver.
569         simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
570     }
571 
572     @Test
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
testSetMediaButtonBroadcastReceiver_broadcastReceiver()587     public void testSetMediaButtonBroadcastReceiver_broadcastReceiver() throws Exception {
588         // Play a sound so this session can get the priority.
589         Utils.assertMediaPlaybackStarted(getContext());
590 
591         // Sets the broadcast receiver's component name. Framework will keep the component name in
592         // persistent storage.
593         mSession.setMediaButtonBroadcastReceiver(new ComponentName(mContext,
594                 MediaButtonBroadcastReceiver.class));
595 
596         // There is a different MediaSession that's created from StubMediaBrowserService class.
597         // This session takes over all the callbacks. we need to change the state of our session
598         // to STATE_PLAYING so it has higher priority.
599         setPlaybackState(PlaybackState.STATE_PLAYING);
600 
601         // Call explicit release, so change in the media key event session can be notified using the
602         // component name.
603         mSession.release();
604 
605         int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
606         try {
607             CountDownLatch latch = new CountDownLatch(2);
608             MediaButtonBroadcastReceiver.setCallback((keyEvent) -> {
609                 assertThat(keyEvent.getKeyCode()).isEqualTo(keyCode);
610                 switch ((int) latch.getCount()) {
611                     case 2:
612                         assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
613                         break;
614                     case 1:
615                         assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_UP);
616                         break;
617                 }
618                 latch.countDown();
619             });
620             // Also try to dispatch media key event.
621             // System would try to dispatch event.
622             simulateMediaKeyInput(keyCode);
623 
624             assertThat(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)).isTrue();
625         } finally {
626             MediaButtonBroadcastReceiver.setCallback(null);
627         }
628     }
629 
630     /**
631      * Test public APIs of {@link VolumeProvider}.
632      */
633     @Test
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
testPlaybackToLocalAndRemote()647     public void testPlaybackToLocalAndRemote() throws Exception {
648         MediaController controller = mSession.getController();
649         controller.registerCallback(mCallback, mHandler);
650 
651         synchronized (mWaitLock) {
652             // test setPlaybackToRemote, do this before testing setPlaybackToLocal
653             // to ensure it switches correctly.
654             mCallback.resetLocked();
655             try {
656                 mSession.setPlaybackToRemote(null);
657                 assertWithMessage("Expected IAE for setPlaybackToRemote(null)").fail();
658             } catch (IllegalArgumentException e) {
659                 // expected
660             }
661             VolumeProvider vp = new VolumeProvider(VolumeProvider.VOLUME_CONTROL_FIXED,
662                     TEST_MAX_VOLUME, TEST_CURRENT_VOLUME, TEST_VOLUME_CONTROL_ID) {};
663             mSession.setPlaybackToRemote(vp);
664 
665             MediaController.PlaybackInfo info = null;
666             for (int i = 0; i < MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT; ++i) {
667                 mCallback.mOnAudioInfoChangedCalled = false;
668                 mWaitLock.wait(TIME_OUT_MS);
669                 assertThat(mCallback.mOnAudioInfoChangedCalled).isTrue();
670                 info = mCallback.mPlaybackInfo;
671                 if (info != null && info.getCurrentVolume() == TEST_CURRENT_VOLUME
672                         && info.getMaxVolume() == TEST_MAX_VOLUME
673                         && info.getVolumeControl() == VolumeProvider.VOLUME_CONTROL_FIXED
674                         && info.getPlaybackType()
675                                 == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE
676                         && TextUtils.equals(info.getVolumeControlId(), TEST_VOLUME_CONTROL_ID)) {
677                     break;
678                 }
679             }
680             assertThat(info).isNotNull();
681             assertThat(info.getPlaybackType())
682                     .isEqualTo(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE);
683             assertThat(info.getMaxVolume()).isEqualTo(TEST_MAX_VOLUME);
684             assertThat(info.getCurrentVolume()).isEqualTo(TEST_CURRENT_VOLUME);
685             assertThat(info.getVolumeControl()).isEqualTo(VolumeProvider.VOLUME_CONTROL_FIXED);
686             assertThat(info.getVolumeControlId()).isEqualTo(TEST_VOLUME_CONTROL_ID);
687 
688             info = controller.getPlaybackInfo();
689             assertThat(info).isNotNull();
690             assertThat(info.getPlaybackType())
691                     .isEqualTo(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE);
692             assertThat(info.getMaxVolume()).isEqualTo(TEST_MAX_VOLUME);
693             assertThat(info.getCurrentVolume()).isEqualTo(TEST_CURRENT_VOLUME);
694             assertThat(info.getVolumeControl()).isEqualTo(VolumeProvider.VOLUME_CONTROL_FIXED);
695             assertThat(info.getVolumeControlId()).isEqualTo(TEST_VOLUME_CONTROL_ID);
696 
697             // test setPlaybackToLocal
698             AudioAttributes attrs = new AudioAttributes.Builder().setUsage(USAGE_GAME).build();
699             mSession.setPlaybackToLocal(attrs);
700 
701             info = controller.getPlaybackInfo();
702             assertThat(info).isNotNull();
703             assertThat(info.getPlaybackType())
704                     .isEqualTo(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL);
705             assertThat(info.getAudioAttributes()).isEqualTo(attrs);
706             assertThat(info.getVolumeControlId()).isNull();
707         }
708     }
709 
710     /**
711      * Test {@link MediaSession.Callback#onMediaButtonEvent}.
712      */
713     @Test
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
825      * once {@code setCallback(null)} is done.
826      */
827     @Test
testSetCallbackWithNull()828     public void testSetCallbackWithNull() throws Exception {
829         MediaSessionCallback sessionCallback = new MediaSessionCallback();
830         mSession.setCallback(sessionCallback, mHandler);
831         mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
832         mSession.setActive(true);
833 
834         MediaController controller = mSession.getController();
835         setPlaybackState(PlaybackState.STATE_PLAYING);
836 
837         sessionCallback.reset(1);
838         mSession.setCallback(null, mHandler);
839 
840         controller.getTransportControls().pause();
841         assertThat(sessionCallback.await(WAIT_MS)).isFalse();
842         assertWithMessage("Callback shouldn't be called.")
843                 .that(sessionCallback.mOnPauseCalled).isFalse();
844     }
845 
setPlaybackState(int state)846     private void setPlaybackState(int state) {
847         final long allActions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PAUSE
848                 | PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_STOP
849                 | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS
850                 | PlaybackState.ACTION_FAST_FORWARD | PlaybackState.ACTION_REWIND;
851         PlaybackState playbackState = new PlaybackState.Builder().setActions(allActions)
852                 .setState(state, 0L, 0.0f).build();
853         synchronized (mWaitLock) {
854             mSession.setPlaybackState(playbackState);
855         }
856     }
857 
858     /**
859      * Test {@link MediaSession#release} doesn't crash when multiple media sessions are in the app
860      * which receives the media key events.
861      * See: b/36669550
862      */
863     @Test
testReleaseNoCrashWithMultipleSessions()864     public void testReleaseNoCrashWithMultipleSessions() throws Exception {
865         // Start a media playback for this app to receive media key events.
866         Utils.assertMediaPlaybackStarted(getContext());
867 
868         MediaSession anotherSession = null;
869         try {
870             anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
871             mSession.release();
872             anotherSession.release();
873 
874             // Try release with the different order.
875             mSession = new MediaSession(getContext(), TEST_SESSION_TAG);
876             anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
877             anotherSession.release();
878             mSession.release();
879         } finally {
880             if (anotherSession != null) {
881                 anotherSession.release();
882                 anotherSession = null;
883             }
884         }
885     }
886 
887     // This uses public APIs to dispatch key events, so sessions would consider this as
888     // 'media key event from this application'.
simulateMediaKeyInput(int keyCode)889     private void simulateMediaKeyInput(int keyCode) {
890         long downTime = System.currentTimeMillis();
891         mAudioManager.dispatchMediaKeyEvent(
892                 new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0));
893         mAudioManager.dispatchMediaKeyEvent(
894                 new KeyEvent(downTime, System.currentTimeMillis(), KeyEvent.ACTION_UP, keyCode, 0));
895     }
896 
897     /**
898      * Tests {@link MediaSession.QueueItem}.
899      */
900     @Test
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
testQueueItemEquals()938     public void testQueueItemEquals() {
939         MediaDescription.Builder descriptionBuilder = new MediaDescription.Builder()
940                 .setMediaId("media-id")
941                 .setTitle("title");
942 
943         QueueItem item = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID);
944         assertThat(item.getQueueId()).isEqualTo(TEST_QUEUE_ID);
945         assertThat(item.getDescription().getMediaId()).isEqualTo("media-id");
946         assertThat(item.getDescription().getTitle()).isEqualTo("title");
947         assertThat(item.describeContents()).isEqualTo(0);
948 
949         assertThat(item.equals(null)).isFalse();
950         assertThat(item).isNotEqualTo(descriptionBuilder.build());
951 
952         QueueItem sameItem = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID);
953         assertThat(item.equals(sameItem)).isTrue();
954 
955         QueueItem differentQueueId = new QueueItem(
956                 descriptionBuilder.build(), TEST_QUEUE_ID + 1);
957         assertThat(item.equals(differentQueueId)).isFalse();
958 
959         QueueItem differentDescription = new QueueItem(
960                 descriptionBuilder.setTitle("title2").build(), TEST_QUEUE_ID);
961         assertThat(item.equals(differentDescription)).isFalse();
962     }
963 
964     @Test
testSessionInfoWithFrameworkParcelable()965     public void testSessionInfoWithFrameworkParcelable() {
966         final String testKey = "test_key";
967         final AudioAttributes frameworkParcelable = new AudioAttributes.Builder().build();
968 
969         Bundle sessionInfo = new Bundle();
970         sessionInfo.putParcelable(testKey, frameworkParcelable);
971 
972         MediaSession session = null;
973         try {
974             session = new MediaSession(
975                     mContext, "testSessionInfoWithFrameworkParcelable", sessionInfo);
976             Bundle sessionInfoOut = session.getController().getSessionInfo();
977             assertThat(sessionInfoOut.containsKey(testKey)).isTrue();
978             assertThat((AudioAttributes) sessionInfoOut.getParcelable(testKey))
979                     .isEqualTo(frameworkParcelable);
980         } finally {
981             if (session != null) {
982                 session.release();
983             }
984         }
985 
986     }
987 
988     @Test
testSessionInfoWithCustomParcelable()989     public void testSessionInfoWithCustomParcelable() {
990         final String testKey = "test_key";
991         final MediaSession2Test.CustomParcelable customParcelable =
992                 new MediaSession2Test.CustomParcelable(1);
993 
994         Bundle sessionInfo = new Bundle();
995         sessionInfo.putParcelable(testKey, customParcelable);
996 
997         MediaSession session = null;
998         try {
999             session = new MediaSession(
1000                     mContext, "testSessionInfoWithCustomParcelable", sessionInfo);
1001             assertWithMessage("Custom Parcelable shouldn't be accepted!").fail();
1002         } catch (IllegalArgumentException e) {
1003             // Expected
1004         } finally {
1005             if (session != null) {
1006                 session.release();
1007             }
1008         }
1009     }
1010 
1011     /**
1012      * An app should not be able to create too many sessions.
1013      * See MediaSessionService#SESSION_CREATION_LIMIT_PER_UID
1014      */
1015     @Test
testSessionCreationLimit()1016     public void testSessionCreationLimit() {
1017         List<MediaSession> sessions = new ArrayList<>();
1018         try {
1019             for (int i = 0; i < TEST_TOO_MANY_SESSION_COUNT; i++) {
1020                 sessions.add(new MediaSession(mContext, "testSessionCreationLimit"));
1021             }
1022             assertWithMessage("The number of session should be limited!").fail();
1023         } catch (RuntimeException e) {
1024             // Expected
1025         } finally {
1026             for (MediaSession session : sessions) {
1027                 session.release();
1028             }
1029         }
1030     }
1031 
1032     /**
1033      * Check that calling {@link MediaSession#release()} multiple times for the same session
1034      * does not decrement current session count multiple times.
1035      */
1036     @Test
testSessionCreationLimitWithMediaSessionRelease()1037     public void testSessionCreationLimitWithMediaSessionRelease() {
1038         List<MediaSession> sessions = new ArrayList<>();
1039         MediaSession sessionToReleaseMultipleTimes = null;
1040         try {
1041             sessionToReleaseMultipleTimes = new MediaSession(
1042                     mContext, "testSessionCreationLimitWithMediaSessionRelease");
1043             for (int i = 0; i < TEST_TOO_MANY_SESSION_COUNT; i++) {
1044                 sessions.add(new MediaSession(
1045                         mContext, "testSessionCreationLimitWithMediaSessionRelease"));
1046                 // Call release() many times with the same session.
1047                 sessionToReleaseMultipleTimes.release();
1048             }
1049             assertWithMessage("The number of session should be limited!").fail();
1050         } catch (RuntimeException e) {
1051             // Expected
1052         } finally {
1053             for (MediaSession session : sessions) {
1054                 session.release();
1055             }
1056             if (sessionToReleaseMultipleTimes != null) {
1057                 sessionToReleaseMultipleTimes.release();
1058             }
1059         }
1060     }
1061 
1062     /**
1063      * Check that calling {@link MediaSession2#close()} does not decrement current session count.
1064      */
1065     @Test
testSessionCreationLimitWithMediaSession2Release()1066     public void testSessionCreationLimitWithMediaSession2Release() {
1067         List<MediaSession> sessions = new ArrayList<>();
1068         try {
1069             for (int i = 0; i < 1000; i++) {
1070                 sessions.add(new MediaSession(
1071                         mContext, "testSessionCreationLimitWithMediaSession2Release"));
1072 
1073                 try (MediaSession2 session2 = new MediaSession2.Builder(mContext).build()) {
1074                     // Do nothing
1075                 }
1076             }
1077             assertWithMessage("The number of session should be limited!").fail();
1078         } catch (RuntimeException e) {
1079             // Expected
1080         } finally {
1081             for (MediaSession session : sessions) {
1082                 session.release();
1083             }
1084         }
1085     }
1086 
1087     /**
1088      * Check that a series of {@link MediaSession#setQueue} does not break {@link MediaController}
1089      * on the remote process due to binder buffer overflow.
1090      */
1091     @Test
testSeriesOfSetQueue()1092     public void testSeriesOfSetQueue() throws Exception {
1093         int numberOfCalls = 100;
1094         int queueSize = 1_000;
1095         List<QueueItem> queue = new ArrayList<>();
1096         for (int id = 0; id < queueSize; id++) {
1097             MediaDescription description = new MediaDescription.Builder()
1098                     .setMediaId(Integer.toString(id)).build();
1099             queue.add(new QueueItem(description, id));
1100         }
1101 
1102         try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext,
1103                 MediaSessionTestService.class, TEST_SERIES_OF_SET_QUEUE)) {
1104             Bundle args = new Bundle();
1105             args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken());
1106             args.putInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS, numberOfCalls * queueSize);
1107             invoker.run(STEP_SET_UP, args);
1108             for (int i = 0; i < numberOfCalls; i++) {
1109                 mSession.setQueue(queue);
1110             }
1111             invoker.run(STEP_CHECK);
1112             invoker.run(STEP_CLEAN_UP);
1113         }
1114     }
1115 
1116     @Test
testSetQueueWithLargeNumberOfItems()1117     public void testSetQueueWithLargeNumberOfItems() throws Exception {
1118         int queueSize = 500_000;
1119         List<QueueItem> queue = new ArrayList<>();
1120         for (int id = 0; id < queueSize; id++) {
1121             MediaDescription description = new MediaDescription.Builder()
1122                     .setMediaId(Integer.toString(id)).build();
1123             queue.add(new QueueItem(description, id));
1124         }
1125 
1126         try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext,
1127                 MediaSessionTestService.class, TEST_SET_QUEUE)) {
1128             Bundle args = new Bundle();
1129             args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken());
1130             args.putInt(KEY_EXPECTED_QUEUE_SIZE, queueSize);
1131             invoker.run(STEP_SET_UP, args);
1132             mSession.setQueue(queue);
1133             invoker.run(STEP_CHECK);
1134             invoker.run(STEP_CLEAN_UP);
1135         }
1136     }
1137 
1138     @Test
testSetQueueWithEmptyQueue()1139     public void testSetQueueWithEmptyQueue() throws Exception {
1140         try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext,
1141                 MediaSessionTestService.class, TEST_SET_QUEUE)) {
1142             Bundle args = new Bundle();
1143             args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken());
1144             args.putInt(KEY_EXPECTED_QUEUE_SIZE, 0);
1145             invoker.run(STEP_SET_UP, args);
1146             mSession.setQueue(Collections.emptyList());
1147             invoker.run(STEP_CHECK);
1148             invoker.run(STEP_CLEAN_UP);
1149         }
1150     }
1151 
1152     /**
1153      * Verifies that a new session hasn't had any configuration bits set yet.
1154      *
1155      * @param controller The controller for the session
1156      */
1157     @SuppressWarnings("ReturnValueIgnored")
verifyNewSession(MediaController controller)1158     private void verifyNewSession(MediaController controller) {
1159         assertWithMessage("New session has unexpected configuration")
1160                 .that(controller.getFlags()).isEqualTo(0);
1161         assertWithMessage("New session has unexpected configuration")
1162                 .that(controller.getExtras()).isNull();
1163         assertWithMessage("New session has unexpected configuration")
1164                 .that(controller.getMetadata()).isNull();
1165         assertWithMessage("New session has unexpected configuration")
1166                 .that(controller.getPackageName()).isEqualTo(getContext().getPackageName());
1167         assertWithMessage("New session has unexpected configuration")
1168                 .that(controller.getPlaybackState()).isNull();
1169         assertWithMessage("New session has unexpected configuration")
1170                 .that(controller.getQueue()).isNull();
1171         assertWithMessage("New session has unexpected configuration")
1172                 .that(controller.getQueueTitle()).isNull();
1173         assertWithMessage("New session has unexpected configuration")
1174                 .that(controller.getRatingType()).isEqualTo(Rating.RATING_NONE);
1175         assertWithMessage("New session has unexpected configuration")
1176                 .that(controller.getSessionActivity()).isNull();
1177 
1178         assertThat(controller.getSessionToken()).isNotNull();
1179         assertThat(controller.getTransportControls()).isNotNull();
1180 
1181         MediaController.PlaybackInfo info = controller.getPlaybackInfo();
1182         assertThat(info).isNotNull();
1183         info.toString(); // Test that calling PlaybackInfo.toString() does not crash.
1184         assertThat(info.getPlaybackType())
1185                 .isEqualTo(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL);
1186         AudioAttributes attrs = info.getAudioAttributes();
1187         assertThat(attrs).isNotNull();
1188         assertThat(attrs.getUsage()).isEqualTo(USAGE_MEDIA);
1189         assertThat(info.getCurrentVolume())
1190                 .isEqualTo(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
1191     }
1192 
1193     private class MediaControllerCallback extends MediaController.Callback {
1194         private volatile boolean mOnPlaybackStateChangedCalled;
1195         private volatile boolean mOnMetadataChangedCalled;
1196         private volatile boolean mOnQueueChangedCalled;
1197         private volatile boolean mOnQueueTitleChangedCalled;
1198         private volatile boolean mOnExtraChangedCalled;
1199         private volatile boolean mOnAudioInfoChangedCalled;
1200         private volatile boolean mOnSessionDestroyedCalled;
1201         private volatile boolean mOnSessionEventCalled;
1202 
1203         private volatile PlaybackState mPlaybackState;
1204         private volatile MediaMetadata mMediaMetadata;
1205         private volatile List<QueueItem> mQueue;
1206         private volatile CharSequence mTitle;
1207         private volatile String mEvent;
1208         private volatile Bundle mExtras;
1209         private volatile MediaController.PlaybackInfo mPlaybackInfo;
1210 
resetLocked()1211         public void resetLocked() {
1212             mOnPlaybackStateChangedCalled = false;
1213             mOnMetadataChangedCalled = false;
1214             mOnQueueChangedCalled = false;
1215             mOnQueueTitleChangedCalled = false;
1216             mOnExtraChangedCalled = false;
1217             mOnAudioInfoChangedCalled = false;
1218             mOnSessionDestroyedCalled = false;
1219             mOnSessionEventCalled = false;
1220 
1221             mPlaybackState = null;
1222             mMediaMetadata = null;
1223             mQueue = null;
1224             mTitle = null;
1225             mExtras = null;
1226             mPlaybackInfo = null;
1227         }
1228 
1229         @Override
onPlaybackStateChanged(PlaybackState state)1230         public void onPlaybackStateChanged(PlaybackState state) {
1231             synchronized (mWaitLock) {
1232                 mOnPlaybackStateChangedCalled = true;
1233                 mPlaybackState = state;
1234                 mWaitLock.notify();
1235             }
1236         }
1237 
1238         @Override
onMetadataChanged(MediaMetadata metadata)1239         public void onMetadataChanged(MediaMetadata metadata) {
1240             synchronized (mWaitLock) {
1241                 mOnMetadataChangedCalled = true;
1242                 mMediaMetadata = metadata;
1243                 mWaitLock.notify();
1244             }
1245         }
1246 
1247         @Override
onQueueChanged(List<QueueItem> queue)1248         public void onQueueChanged(List<QueueItem> queue) {
1249             synchronized (mWaitLock) {
1250                 mOnQueueChangedCalled = true;
1251                 mQueue = queue;
1252                 mWaitLock.notify();
1253             }
1254         }
1255 
1256         @Override
onQueueTitleChanged(CharSequence title)1257         public void onQueueTitleChanged(CharSequence title) {
1258             synchronized (mWaitLock) {
1259                 mOnQueueTitleChangedCalled = true;
1260                 mTitle = title;
1261                 mWaitLock.notify();
1262             }
1263         }
1264 
1265         @Override
onExtrasChanged(Bundle extras)1266         public void onExtrasChanged(Bundle extras) {
1267             synchronized (mWaitLock) {
1268                 mOnExtraChangedCalled = true;
1269                 mExtras = extras;
1270                 mWaitLock.notify();
1271             }
1272         }
1273 
1274         @Override
onAudioInfoChanged(MediaController.PlaybackInfo info)1275         public void onAudioInfoChanged(MediaController.PlaybackInfo info) {
1276             synchronized (mWaitLock) {
1277                 mOnAudioInfoChangedCalled = true;
1278                 mPlaybackInfo = info;
1279                 mWaitLock.notify();
1280             }
1281         }
1282 
1283         @Override
onSessionDestroyed()1284         public void onSessionDestroyed() {
1285             synchronized (mWaitLock) {
1286                 mOnSessionDestroyedCalled = true;
1287                 mWaitLock.notify();
1288             }
1289         }
1290 
1291         @Override
onSessionEvent(String event, Bundle extras)1292         public void onSessionEvent(String event, Bundle extras) {
1293             synchronized (mWaitLock) {
1294                 mOnSessionEventCalled = true;
1295                 mEvent = event;
1296                 mExtras = (Bundle) extras.clone();
1297                 mWaitLock.notify();
1298             }
1299         }
1300     }
1301 
1302     private class MediaSessionCallback extends MediaSession.Callback {
1303         private CountDownLatch mLatch;
1304         private int mOnPlayCalledCount;
1305         private boolean mOnPauseCalled;
1306         private boolean mOnStopCalled;
1307         private boolean mOnFastForwardCalled;
1308         private boolean mOnRewindCalled;
1309         private boolean mOnSkipToPreviousCalled;
1310         private boolean mOnSkipToNextCalled;
1311         private RemoteUserInfo mCallerInfo;
1312 
reset(int count)1313         public void reset(int count) {
1314             mLatch = new CountDownLatch(count);
1315             mOnPlayCalledCount = 0;
1316             mOnPauseCalled = false;
1317             mOnStopCalled = false;
1318             mOnFastForwardCalled = false;
1319             mOnRewindCalled = false;
1320             mOnSkipToPreviousCalled = false;
1321             mOnSkipToNextCalled = false;
1322         }
1323 
await(long waitMs)1324         public boolean await(long waitMs) {
1325             try {
1326                 return mLatch.await(waitMs, TimeUnit.MILLISECONDS);
1327             } catch (InterruptedException e) {
1328                 return false;
1329             }
1330         }
1331 
1332         @Override
onPlay()1333         public void onPlay() {
1334             mOnPlayCalledCount++;
1335             mCallerInfo = mSession.getCurrentControllerInfo();
1336             setPlaybackState(PlaybackState.STATE_PLAYING);
1337             mLatch.countDown();
1338         }
1339 
1340         @Override
onPause()1341         public void onPause() {
1342             mOnPauseCalled = true;
1343             mCallerInfo = mSession.getCurrentControllerInfo();
1344             setPlaybackState(PlaybackState.STATE_PAUSED);
1345             mLatch.countDown();
1346         }
1347 
1348         @Override
onStop()1349         public void onStop() {
1350             mOnStopCalled = true;
1351             mCallerInfo = mSession.getCurrentControllerInfo();
1352             setPlaybackState(PlaybackState.STATE_STOPPED);
1353             mLatch.countDown();
1354         }
1355 
1356         @Override
onFastForward()1357         public void onFastForward() {
1358             mOnFastForwardCalled = true;
1359             mCallerInfo = mSession.getCurrentControllerInfo();
1360             mLatch.countDown();
1361         }
1362 
1363         @Override
onRewind()1364         public void onRewind() {
1365             mOnRewindCalled = true;
1366             mCallerInfo = mSession.getCurrentControllerInfo();
1367             mLatch.countDown();
1368         }
1369 
1370         @Override
onSkipToPrevious()1371         public void onSkipToPrevious() {
1372             mOnSkipToPreviousCalled = true;
1373             mCallerInfo = mSession.getCurrentControllerInfo();
1374             mLatch.countDown();
1375         }
1376 
1377         @Override
onSkipToNext()1378         public void onSkipToNext() {
1379             mOnSkipToNextCalled = true;
1380             mCallerInfo = mSession.getCurrentControllerInfo();
1381             mLatch.countDown();
1382         }
1383     }
1384 }
1385