• 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 
17 package android.media.session.cts;
18 
19 import static androidx.test.ext.truth.os.BundleSubject.assertThat;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.junit.Assert.assertThrows;
24 
25 import android.Manifest;
26 import android.app.PendingIntent;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.media.MediaController2;
31 import android.media.MediaSession2;
32 import android.media.Session2Command;
33 import android.media.Session2CommandGroup;
34 import android.media.Session2Token;
35 import android.media.cts.TestUtils;
36 import android.media.session.MediaSessionManager;
37 import android.os.Bundle;
38 import android.os.Handler;
39 import android.os.HandlerThread;
40 import android.os.Parcel;
41 import android.os.Parcelable;
42 import android.os.Process;
43 
44 import androidx.test.filters.SmallTest;
45 import androidx.test.platform.app.InstrumentationRegistry;
46 
47 import com.android.bedstead.harrier.BedsteadJUnit4;
48 import com.android.bedstead.harrier.DeviceState;
49 import com.android.bedstead.harrier.UserType;
50 import com.android.bedstead.harrier.annotations.AfterClass;
51 import com.android.bedstead.harrier.annotations.BeforeClass;
52 import com.android.bedstead.harrier.annotations.UserTest;
53 
54 import org.junit.Before;
55 import org.junit.ClassRule;
56 import org.junit.Rule;
57 import org.junit.Test;
58 import org.junit.runner.RunWith;
59 
60 import java.util.List;
61 import java.util.Objects;
62 import java.util.concurrent.CountDownLatch;
63 import java.util.concurrent.Executor;
64 import java.util.concurrent.TimeUnit;
65 
66 /** Tests {@link android.media.MediaSession2}. */
67 @RunWith(BedsteadJUnit4.class)
68 @SmallTest
69 public class MediaSession2Test {
70 
71     @ClassRule @Rule public static final DeviceState sDeviceState = new DeviceState();
72 
73     private static final long WAIT_TIME_MS = 300L;
74 
75     private static final String TEST_KEY = "test_key";
76     private static final String TEST_VALUE = "test_value";
77 
78     static Handler sHandler;
79     static Executor sHandlerExecutor;
80     static final Object sTestLock = new Object();
81 
82     private Context mContext;
83 
84     @BeforeClass
setUpThread()85     public static void setUpThread() {
86         synchronized (MediaSession2Test.class) {
87             if (sHandler != null) {
88                 return;
89             }
90             HandlerThread handlerThread = new HandlerThread("MediaSessionTestBase");
91             handlerThread.start();
92             sHandler = new Handler(handlerThread.getLooper());
93             sHandlerExecutor = (runnable) -> {
94                 Handler handler;
95                 synchronized (MediaSession2Test.class) {
96                     handler = sHandler;
97                 }
98                 if (handler != null) {
99                     handler.post(() -> {
100                         synchronized (sTestLock) {
101                             runnable.run();
102                         }
103                     });
104                 }
105             };
106         }
107     }
108 
109     @AfterClass
cleanUpThread()110     public static void cleanUpThread() {
111         synchronized (MediaSession2Test.class) {
112             if (sHandler == null) {
113                 return;
114             }
115             sHandler.getLooper().quitSafely();
116             sHandler = null;
117             sHandlerExecutor = null;
118         }
119     }
120 
121     @Before
setUp()122     public void setUp() throws Exception {
123         mContext = InstrumentationRegistry.getInstrumentation().getContext();
124     }
125 
126     @Test
127     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testBuilder_setIllegalArguments()128     public void testBuilder_setIllegalArguments() {
129         assertThrows("null context shouldn't be allowed",
130                 IllegalArgumentException.class,
131                 () -> new MediaSession2.Builder(null));
132 
133         assertThrows("null id shouldn't be allowed",
134                 IllegalArgumentException.class, () -> {
135                     final MediaSession2.Builder builder = new MediaSession2.Builder(mContext);
136                     builder.setId(null);
137                 });
138     }
139 
140     @Test
141     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testBuilder_setSessionActivity()142     public void testBuilder_setSessionActivity() {
143         Intent intent = new Intent(Intent.ACTION_MAIN);
144         PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0 /* requestCode */,
145                 intent, PendingIntent.FLAG_MUTABLE_UNAUDITED /* flags */);
146         try (MediaSession2 session = new MediaSession2.Builder(mContext)
147                 .setSessionActivity(pendingIntent)
148                 .build()) {
149             // Note: The pendingIntent is set but is never used inside of MediaSession2.
150             // TODO: If getter is created, put assertEquals() here.
151         }
152     }
153 
154     @Test
155     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testBuilder_createSessionWithoutId()156     public void testBuilder_createSessionWithoutId() {
157         try (MediaSession2 session = new MediaSession2.Builder(mContext).build()) {
158             assertThat(session.getId()).isEqualTo("");
159         }
160     }
161 
162     @Test
163     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testBuilder_createSessionWithDupId()164     public void testBuilder_createSessionWithDupId() {
165         final String dupSessionId = "TEST_SESSION_DUP_ID";
166         MediaSession2.Builder builder = new MediaSession2.Builder(mContext).setId(dupSessionId);
167         assertThrows("Duplicated id shouldn't be allowed",
168                 IllegalStateException.class, () -> {
169                     MediaSession2 session1 = builder.build();
170                     MediaSession2 session2 = builder.build();
171                 });
172     }
173 
174     @Test
175     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testBuilder_setExtras_withFrameworkParcelable()176     public void testBuilder_setExtras_withFrameworkParcelable() {
177         final String testKey = "test_key";
178         final Session2Token frameworkParcelable = new Session2Token(mContext,
179                 new ComponentName(mContext, this.getClass()));
180 
181         Bundle extras = new Bundle();
182         extras.putParcelable(testKey, frameworkParcelable);
183 
184         try (MediaSession2 session = new MediaSession2.Builder(mContext)
185                 .setExtras(extras)
186                 .build()) {
187             Bundle extrasOut = session.getToken().getExtras();
188             assertThat(extrasOut).isNotNull();
189             assertThat(extrasOut).containsKey(testKey);
190             assertThat(extrasOut).parcelable(testKey).isEqualTo(frameworkParcelable);
191         }
192     }
193 
194     @Test
195     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testBuilder_setExtras_withCustomParcelable()196     public void testBuilder_setExtras_withCustomParcelable() {
197         final String testKey = "test_key";
198         final CustomParcelable customParcelable = new CustomParcelable(1);
199 
200         Bundle extras = new Bundle();
201         extras.putParcelable(testKey, customParcelable);
202 
203         assertThrows("Custom Parcelables shouldn't be accepted!",
204                 IllegalArgumentException.class,
205                 () -> new MediaSession2.Builder(mContext)
206                         .setExtras(extras)
207                         .build());
208     }
209 
210     @Test
211     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSession2Token()212     public void testSession2Token() {
213         final Bundle extras = new Bundle();
214         try (MediaSession2 session = new MediaSession2.Builder(mContext)
215                 .setExtras(extras)
216                 .build()) {
217             Session2Token token = session.getToken();
218             assertThat(token.getUid()).isEqualTo(Process.myUid());
219             assertThat(token.getPackageName()).isEqualTo(mContext.getPackageName());
220             assertThat(token.getServiceName()).isNull();
221             assertThat(token.getType()).isEqualTo(Session2Token.TYPE_SESSION);
222             assertThat(token.describeContents()).isEqualTo(0);
223             assertThat(token.getExtras().isEmpty()).isTrue();
224         }
225     }
226 
227     @Test
228     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSession2Token_extrasNotSet()229     public void testSession2Token_extrasNotSet() {
230         try (MediaSession2 session = new MediaSession2.Builder(mContext)
231                 .build()) {
232             Session2Token token = session.getToken();
233             assertThat(token.getExtras().isEmpty()).isTrue();
234         }
235     }
236 
237     @Test
238     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testGetConnectedControllers_newController()239     public void testGetConnectedControllers_newController() throws Exception {
240         Session2Callback sessionCallback = new Session2Callback();
241         try (MediaSession2 session = new MediaSession2.Builder(mContext)
242                 .setSessionCallback(sHandlerExecutor, sessionCallback)
243                 .build()) {
244             Controller2Callback callback = new Controller2Callback();
245             MediaController2 controller =
246                     new MediaController2.Builder(mContext, session.getToken())
247                             .setControllerCallback(sHandlerExecutor, callback)
248                             .build();
249             assertThat(callback.awaitOnConnected(WAIT_TIME_MS)).isTrue();
250 
251             List<MediaSession2.ControllerInfo> controllers = session.getConnectedControllers();
252             boolean found = false;
253             for (MediaSession2.ControllerInfo controllerInfo : controllers) {
254                 if (Objects.equals(sessionCallback.mController, controllerInfo)) {
255                     assertThat(controllerInfo.getUid()).isEqualTo(Process.myUid());
256                     found = true;
257                     break;
258                 }
259             }
260             assertThat(found).isTrue();
261         }
262     }
263 
264     @Test
265     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testGetConnectedControllers_closedController()266     public void testGetConnectedControllers_closedController() throws Exception {
267         Session2Callback sessionCallback = new Session2Callback();
268         try (MediaSession2 session = new MediaSession2.Builder(mContext)
269                 .setSessionCallback(sHandlerExecutor, sessionCallback)
270                 .build()) {
271             Controller2Callback callback = new Controller2Callback();
272             MediaController2 controller =
273                     new MediaController2.Builder(mContext, session.getToken())
274                             .setControllerCallback(sHandlerExecutor, callback)
275                             .build();
276             assertThat(callback.awaitOnConnected(WAIT_TIME_MS)).isTrue();
277             controller.close();
278             assertThat(sessionCallback.awaitOnDisconnect(WAIT_TIME_MS)).isTrue();
279 
280             List<MediaSession2.ControllerInfo> controllers = session.getConnectedControllers();
281             assertThat(controllers).doesNotContain(sessionCallback.mController);
282         }
283     }
284 
285     @Test
286     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSession2Token_writeToParcel()287     public void testSession2Token_writeToParcel() {
288         final Bundle extras = new Bundle();
289         extras.putString(TEST_KEY, TEST_VALUE);
290 
291         try (MediaSession2 session = new MediaSession2.Builder(mContext)
292                 .setExtras(extras)
293                 .build()) {
294             Session2Token token = session.getToken();
295 
296             Parcel parcel = Parcel.obtain();
297             token.writeToParcel(parcel, 0 /* flags */);
298             parcel.setDataPosition(0);
299             Session2Token tokenOut = Session2Token.CREATOR.createFromParcel(parcel);
300             parcel.recycle();
301 
302             assertThat(tokenOut.getUid()).isEqualTo(Process.myUid());
303             assertThat(tokenOut.getPackageName()).isEqualTo(mContext.getPackageName());
304             assertThat(tokenOut.getServiceName()).isNull();
305             assertThat(tokenOut.getType()).isEqualTo(Session2Token.TYPE_SESSION);
306 
307             Bundle extrasOut = tokenOut.getExtras();
308             assertThat(extrasOut).isNotNull();
309             assertThat(extrasOut.getString(TEST_KEY)).isEqualTo(TEST_VALUE);
310         }
311     }
312 
313     @Test
314     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testBroadcastSessionCommand()315     public void testBroadcastSessionCommand() throws Exception {
316         Session2Callback sessionCallback = new Session2Callback();
317 
318         String commandStr = "test_command";
319         Session2Command command = new Session2Command(commandStr, null);
320 
321         int resultCode = 100;
322         Session2Command.Result commandResult = new Session2Command.Result(resultCode, null);
323 
324         try (MediaSession2 session = new MediaSession2.Builder(mContext)
325                 .setSessionCallback(sHandlerExecutor, sessionCallback)
326                 .build()) {
327 
328             // 1. Create two controllers with each latch.
329             final CountDownLatch latch1 = new CountDownLatch(1);
330             Controller2Callback callback1 = new Controller2Callback() {
331                 @Override
332                 public Session2Command.Result onSessionCommand(MediaController2 controller,
333                         Session2Command command, Bundle args) {
334                     if (commandStr.equals(command.getCustomAction())
335                             && command.getCustomExtras() == null) {
336                         latch1.countDown();
337                     }
338                     return commandResult;
339                 }
340             };
341 
342             MediaController2 controller1 =
343                     new MediaController2.Builder(mContext, session.getToken())
344                             .setControllerCallback(sHandlerExecutor, callback1)
345                             .build();
346 
347             final CountDownLatch latch2 = new CountDownLatch(1);
348             Controller2Callback callback2 = new Controller2Callback() {
349                 @Override
350                 public Session2Command.Result onSessionCommand(MediaController2 controller,
351                         Session2Command command, Bundle args) {
352                     if (commandStr.equals(command.getCustomAction())
353                             && command.getCustomExtras() == null) {
354                         latch2.countDown();
355                     }
356                     return commandResult;
357                 }
358             };
359             MediaController2 controller2 =
360                     new MediaController2.Builder(mContext, session.getToken())
361                             .setControllerCallback(sHandlerExecutor, callback2)
362                             .build();
363 
364             // 2. Wait until all the controllers are connected.
365             assertThat(callback1.awaitOnConnected(WAIT_TIME_MS)).isTrue();
366             assertThat(callback2.awaitOnConnected(WAIT_TIME_MS)).isTrue();
367 
368             // 3. Call MediaSession2#broadcastSessionCommand() and check both controller's
369             // onSessionCommand is called.
370             session.broadcastSessionCommand(command, null);
371             assertThat(latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)).isTrue();
372             assertThat(latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)).isTrue();
373         }
374     }
375 
376     @Test
377     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testCallback_onConnect_onDisconnect()378     public void testCallback_onConnect_onDisconnect() throws Exception {
379         Session2Callback sessionCallback = new Session2Callback();
380         try (MediaSession2 session = new MediaSession2.Builder(mContext)
381                 .setSessionCallback(sHandlerExecutor, sessionCallback)
382                 .build()) {
383             // Test onConnect
384             Controller2Callback controllerCallback = new Controller2Callback();
385             Bundle testConnectionHints = new Bundle();
386             testConnectionHints.putString("test_key", "test_value");
387 
388             MediaController2 controller =
389                     new MediaController2.Builder(mContext, session.getToken())
390                             .setConnectionHints(testConnectionHints)
391                             .setControllerCallback(sHandlerExecutor, controllerCallback)
392                             .build();
393             assertThat(controllerCallback.awaitOnConnected(WAIT_TIME_MS)).isTrue();
394             assertThat(sessionCallback.awaitOnConnect(WAIT_TIME_MS)).isTrue();
395             assertThat(sessionCallback.mSession).isEqualTo(session);
396             MediaSession2.ControllerInfo controllerInfo = sessionCallback.mController;
397 
398             // Check whether the controllerInfo is the right one.
399             assertThat(controllerInfo.getPackageName()).isEqualTo(mContext.getPackageName());
400             MediaSessionManager.RemoteUserInfo remoteUserInfo = controllerInfo.getRemoteUserInfo();
401             assertThat(remoteUserInfo.getPid()).isEqualTo(Process.myPid());
402             assertThat(remoteUserInfo.getUid()).isEqualTo(Process.myUid());
403             assertThat(remoteUserInfo.getPackageName()).isEqualTo(mContext.getPackageName());
404             assertThat(TestUtils.equals(testConnectionHints, controllerInfo.getConnectionHints()))
405                     .isTrue();
406 
407             // Test onDisconnect
408             controller.close();
409             assertThat(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS)).isTrue();
410             assertThat(sessionCallback.awaitOnDisconnect(WAIT_TIME_MS)).isTrue();
411             assertThat(sessionCallback.mSession).isEqualTo(session);
412             assertThat(sessionCallback.mController).isEqualTo(controllerInfo);
413         }
414     }
415 
416     @Test
417     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testCallback_onPostConnect_connected()418     public void testCallback_onPostConnect_connected() throws Exception {
419         Session2Callback sessionCallback = new Session2Callback();
420         try (MediaSession2 session = new MediaSession2.Builder(mContext)
421                 .setSessionCallback(sHandlerExecutor, sessionCallback)
422                 .build()) {
423             Controller2Callback controllerCallback = new Controller2Callback();
424             MediaController2 controller =
425                     new MediaController2.Builder(mContext, session.getToken())
426                             .setControllerCallback(sHandlerExecutor, controllerCallback)
427                             .build();
428             assertThat(controllerCallback.awaitOnConnected(WAIT_TIME_MS)).isTrue();
429             assertThat(sessionCallback.awaitOnPostConnect(WAIT_TIME_MS)).isTrue();
430             assertThat(sessionCallback.mController.getUid()).isEqualTo(Process.myUid());
431         }
432     }
433 
434     @Test
435     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testCallback_onPostConnect_rejected()436     public void testCallback_onPostConnect_rejected() throws Exception {
437         androidx.test.InstrumentationRegistry.getInstrumentation()
438                 .getUiAutomation()
439                 .adoptShellPermissionIdentity(Manifest.permission.MANAGE_EXTERNAL_STORAGE);
440         Session2Callback sessionCallback = new Session2Callback() {
441             @Override
442             public Session2CommandGroup onConnect(MediaSession2 session,
443                     MediaSession2.ControllerInfo controller) {
444                 // Reject all
445                 return null;
446             }
447         };
448         try (MediaSession2 session = new MediaSession2.Builder(mContext)
449                 .setSessionCallback(sHandlerExecutor, sessionCallback)
450                 .build()) {
451             Controller2Callback callback = new Controller2Callback();
452 
453             MediaController2 controller =
454                     new MediaController2.Builder(mContext, session.getToken())
455                             .setControllerCallback(sHandlerExecutor, callback)
456                             .build();
457             assertThat(sessionCallback.awaitOnPostConnect(WAIT_TIME_MS)).isFalse();
458         }
459     }
460 
461     @Test
462     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testCallback_onSessionCommand()463     public void testCallback_onSessionCommand() {
464         Session2Callback sessionCallback = new Session2Callback();
465 
466         try (MediaSession2 session = new MediaSession2.Builder(mContext)
467                 .setSessionCallback(sHandlerExecutor, sessionCallback)
468                 .build()) {
469             Controller2Callback controllerCallback = new Controller2Callback();
470             MediaController2 controller =
471                     new MediaController2.Builder(mContext, session.getToken())
472                             .setControllerCallback(sHandlerExecutor, controllerCallback)
473                             .build();
474             // Wait for connection
475             assertThat(controllerCallback.awaitOnConnected(WAIT_TIME_MS)).isTrue();
476             assertThat(sessionCallback.awaitOnConnect(WAIT_TIME_MS)).isTrue();
477             MediaSession2.ControllerInfo controllerInfo = sessionCallback.mController;
478 
479             // Test onSessionCommand
480             String commandStr = "test_command";
481             String commandExtraKey = "test_extra_key";
482             String commandExtraValue = "test_extra_value";
483             Bundle commandExtra = new Bundle();
484             commandExtra.putString(commandExtraKey, commandExtraValue);
485             Session2Command command = new Session2Command(commandStr, commandExtra);
486 
487             String commandArgKey = "test_arg_key";
488             String commandArgValue = "test_arg_value";
489             Bundle commandArg = new Bundle();
490             commandArg.putString(commandArgKey, commandArgValue);
491             controller.sendSessionCommand(command, commandArg);
492 
493             assertThat(sessionCallback.awaitOnSessionCommand(WAIT_TIME_MS)).isTrue();
494             assertThat(sessionCallback.mSession).isEqualTo(session);
495             assertThat(sessionCallback.mController).isEqualTo(controllerInfo);
496             assertThat(sessionCallback.mCommand.getCustomAction()).isEqualTo(commandStr);
497             assertThat(sessionCallback.mCommand.getCustomExtras().getString(commandExtraKey))
498                     .isEqualTo(commandExtraValue);
499             assertThat(sessionCallback.mCommandArgs.getString(commandArgKey))
500                     .isEqualTo(commandArgValue);
501 
502             controller.close();
503             assertThat(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS)).isTrue();
504         }
505     }
506 
507     @Test
508     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testCallback_onCommandResult()509     public void testCallback_onCommandResult() {
510         Session2Callback sessionCallback = new Session2Callback();
511 
512         int resultCode = 100;
513         String commandResultKey = "test_result_key";
514         String commandResultValue = "test_result_value";
515         Bundle resultData = new Bundle();
516         resultData.putString(commandResultKey, commandResultValue);
517         Session2Command.Result commandResult = new Session2Command.Result(resultCode, resultData);
518 
519         try (MediaSession2 session = new MediaSession2.Builder(mContext)
520                 .setSessionCallback(sHandlerExecutor, sessionCallback)
521                 .build()) {
522             Controller2Callback controllerCallback = new Controller2Callback() {
523                 @Override
524                 public Session2Command.Result onSessionCommand(MediaController2 controller,
525                         Session2Command command, Bundle args) {
526                     return commandResult;
527                 }
528             };
529             MediaController2 controller =
530                     new MediaController2.Builder(mContext, session.getToken())
531                             .setControllerCallback(sHandlerExecutor, controllerCallback)
532                             .build();
533             // Wait for connection
534             assertThat(sessionCallback.awaitOnConnect(WAIT_TIME_MS)).isTrue();
535             MediaSession2.ControllerInfo controllerInfo = sessionCallback.mController;
536 
537             // Test onCommandResult
538             String commandStr = "test_command";
539             String commandExtraKey = "test_extra_key";
540             String commandExtraValue = "test_extra_value";
541             Bundle commandExtra = new Bundle();
542             commandExtra.putString(commandExtraKey, commandExtraValue);
543             Session2Command command = new Session2Command(commandStr, commandExtra);
544 
545             String commandArgKey = "test_arg_key";
546             String commandArgValue = "test_arg_value";
547             Bundle commandArg = new Bundle();
548             commandArg.putString(commandArgKey, commandArgValue);
549             session.sendSessionCommand(controllerInfo, command, commandArg);
550 
551             assertThat(sessionCallback.awaitOnCommandResult(WAIT_TIME_MS)).isTrue();
552             assertThat(sessionCallback.mSession).isEqualTo(session);
553             assertThat(sessionCallback.mController).isEqualTo(controllerInfo);
554             assertThat(sessionCallback.mCommandResult.getResultCode()).isEqualTo(resultCode);
555             assertThat(sessionCallback.mCommandResult.getResultData().getString(commandResultKey))
556                     .isEqualTo(commandResultValue);
557 
558             controller.close();
559             assertThat(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS)).isTrue();
560         }
561     }
562 
563     @Test
564     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testSetPlaybackActive()565     public void testSetPlaybackActive() {
566         final boolean testInitialPlaybackActive = true;
567         final boolean testPlaybackActive = false;
568         Session2Callback sessionCallback = new Session2Callback();
569         try (MediaSession2 session = new MediaSession2.Builder(mContext)
570                 .setSessionCallback(sHandlerExecutor, sessionCallback)
571                 .build()) {
572             session.setPlaybackActive(testInitialPlaybackActive);
573             assertThat(session.isPlaybackActive()).isEqualTo(testInitialPlaybackActive);
574 
575             Controller2Callback controllerCallback = new Controller2Callback();
576             MediaController2 controller =
577                     new MediaController2.Builder(mContext, session.getToken())
578                             .setControllerCallback(sHandlerExecutor, controllerCallback)
579                             .build();
580             // Wait for connection
581             assertThat(controllerCallback.awaitOnConnected(WAIT_TIME_MS)).isTrue();
582 
583             // Check initial value
584             assertThat(controller.isPlaybackActive()).isEqualTo(testInitialPlaybackActive);
585 
586             // Change playback active change and wait for changes
587             session.setPlaybackActive(testPlaybackActive);
588             assertThat(session.isPlaybackActive()).isEqualTo(testPlaybackActive);
589             assertThat(controllerCallback.awaitOnPlaybackActiveChanged(WAIT_TIME_MS)).isTrue();
590 
591             assertThat(controllerCallback.getNotifiedPlaybackActive())
592                     .isEqualTo(testPlaybackActive);
593             assertThat(controller.isPlaybackActive()).isEqualTo(testPlaybackActive);
594 
595             controller.close();
596             assertThat(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS)).isTrue();
597         }
598     }
599 
600     @Test
601     @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER})
testCancelSessionCommand()602     public void testCancelSessionCommand() {
603         Session2Callback sessionCallback = new Session2Callback();
604         try (MediaSession2 session = new MediaSession2.Builder(mContext)
605                 .setSessionCallback(sHandlerExecutor, sessionCallback)
606                 .build()) {
607             Controller2Callback controllerCallback = new Controller2Callback();
608             MediaController2 controller =
609                     new MediaController2.Builder(mContext, session.getToken())
610                             .setControllerCallback(sHandlerExecutor, controllerCallback)
611                             .build();
612             // Wait for connection
613             assertThat(sessionCallback.awaitOnConnect(WAIT_TIME_MS)).isTrue();
614             MediaSession2.ControllerInfo controllerInfo = sessionCallback.mController;
615 
616             String commandStr = "test_command_";
617             String commandExtraKey = "test_extra_key_";
618             String commandExtraValue = "test_extra_value_";
619             Bundle commandExtra = new Bundle();
620             commandExtra.putString(commandExtraKey, commandExtraValue);
621             Session2Command command = new Session2Command(commandStr, commandExtra);
622 
623             String commandArgKey = "test_arg_key_";
624             String commandArgValue = "test_arg_value_";
625             Bundle commandArg = new Bundle();
626             commandArg.putString(commandArgKey, commandArgValue);
627             synchronized (sTestLock) {
628                 Object token = session.sendSessionCommand(controllerInfo, command, commandArg);
629                 session.cancelSessionCommand(controllerInfo, token);
630             }
631             assertThat(sessionCallback.awaitOnCommandResult(WAIT_TIME_MS)).isTrue();
632             assertThat(sessionCallback.mCommandResult.getResultCode())
633                     .isEqualTo(Session2Command.Result.RESULT_INFO_SKIPPED);
634 
635             controller.close();
636             assertThat(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS)).isTrue();
637         }
638     }
639 
640     class Controller2Callback extends MediaController2.ControllerCallback {
641         private final CountDownLatch mOnConnectedLatch = new CountDownLatch(1);
642         private final CountDownLatch mOnDisconnectedLatch = new CountDownLatch(1);
643         private final CountDownLatch mOnPlaybackActiveChangedLatch = new CountDownLatch(1);
644 
645         private boolean mPlaybackActive;
646 
647         @Override
onConnected(MediaController2 controller, Session2CommandGroup allowedCommands)648         public void onConnected(MediaController2 controller,
649                 Session2CommandGroup allowedCommands) {
650             mOnConnectedLatch.countDown();
651         }
652 
653         @Override
onDisconnected(MediaController2 controller)654         public void onDisconnected(MediaController2 controller) {
655             mOnDisconnectedLatch.countDown();
656         }
657 
658         @Override
onPlaybackActiveChanged(MediaController2 controller, boolean playbackActive)659         public void onPlaybackActiveChanged(MediaController2 controller, boolean playbackActive) {
660             mPlaybackActive = playbackActive;
661             mOnPlaybackActiveChangedLatch.countDown();
662         }
663 
awaitOnConnected(long waitTimeMs)664         public boolean awaitOnConnected(long waitTimeMs) {
665             try {
666                 return mOnConnectedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
667             } catch (InterruptedException e) {
668                 return false;
669             }
670         }
671 
awaitOnDisconnected(long waitTimeMs)672         public boolean awaitOnDisconnected(long waitTimeMs) {
673             try {
674                 return mOnDisconnectedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
675             } catch (InterruptedException e) {
676                 return false;
677             }
678         }
679 
awaitOnPlaybackActiveChanged(long waitTimeMs)680         public boolean awaitOnPlaybackActiveChanged(long waitTimeMs) {
681             try {
682                 return mOnPlaybackActiveChangedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
683             } catch (InterruptedException e) {
684                 return false;
685             }
686         }
687 
getNotifiedPlaybackActive()688         public boolean getNotifiedPlaybackActive() {
689             return mPlaybackActive;
690         }
691     }
692 
693     class Session2Callback extends MediaSession2.SessionCallback {
694         private final CountDownLatch mOnConnectLatch = new CountDownLatch(1);
695         private final CountDownLatch mOnPostConnectLatch = new CountDownLatch(1);
696         private final CountDownLatch mOnDisconnectLatch = new CountDownLatch(1);
697         private final CountDownLatch mOnSessionCommandLatch = new CountDownLatch(1);
698         private final CountDownLatch mOnCommandResultLatch = new CountDownLatch(1);
699 
700         MediaSession2 mSession;
701         MediaSession2.ControllerInfo mController;
702         Session2Command mCommand;
703         Bundle mCommandArgs;
704         Session2Command.Result mCommandResult;
705 
706         @Override
onConnect(MediaSession2 session, MediaSession2.ControllerInfo controller)707         public Session2CommandGroup onConnect(MediaSession2 session,
708                 MediaSession2.ControllerInfo controller) {
709             super.onConnect(session, controller);
710             if (controller.getUid() != Process.myUid()) {
711                 return null;
712             }
713             mSession = session;
714             mController = controller;
715             mOnConnectLatch.countDown();
716             return new Session2CommandGroup.Builder().build();
717         }
718 
719         @Override
onPostConnect(MediaSession2 session, MediaSession2.ControllerInfo controller)720         public void onPostConnect(MediaSession2 session, MediaSession2.ControllerInfo controller) {
721             super.onPostConnect(session, controller);
722             if (controller.getUid() != Process.myUid()) {
723                 return;
724             }
725             mSession = session;
726             mController = controller;
727             mOnPostConnectLatch.countDown();
728         }
729 
730         @Override
onDisconnected(MediaSession2 session, MediaSession2.ControllerInfo controller)731         public void onDisconnected(MediaSession2 session, MediaSession2.ControllerInfo controller) {
732             super.onDisconnected(session, controller);
733             if (controller.getUid() != Process.myUid()) {
734                 return;
735             }
736             mSession = session;
737             mController = controller;
738             mOnDisconnectLatch.countDown();
739         }
740 
741         @Override
onSessionCommand(MediaSession2 session, MediaSession2.ControllerInfo controller, Session2Command command, Bundle args)742         public Session2Command.Result onSessionCommand(MediaSession2 session,
743                 MediaSession2.ControllerInfo controller, Session2Command command, Bundle args) {
744             super.onSessionCommand(session, controller, command, args);
745             if (controller.getUid() != Process.myUid()) {
746                 return null;
747             }
748             mSession = session;
749             mController = controller;
750             mCommand = command;
751             mCommandArgs = args;
752             mOnSessionCommandLatch.countDown();
753 
754             int resultCode = 100;
755             String commandResultKey = "test_result_key";
756             String commandResultValue = "test_result_value";
757             Bundle resultData = new Bundle();
758             resultData.putString(commandResultKey, commandResultValue);
759             Session2Command.Result commandResult =
760                     new Session2Command.Result(resultCode, resultData);
761             return commandResult;
762         }
763 
764         @Override
onCommandResult(MediaSession2 session, MediaSession2.ControllerInfo controller, Object token, Session2Command command, Session2Command.Result result)765         public void onCommandResult(MediaSession2 session, MediaSession2.ControllerInfo controller,
766                 Object token, Session2Command command, Session2Command.Result result) {
767             super.onCommandResult(session, controller, token, command, result);
768             if (controller.getUid() != Process.myUid()) {
769                 return;
770             }
771             mSession = session;
772             mController = controller;
773             mCommand = command;
774             mCommandResult = result;
775             mOnCommandResultLatch.countDown();
776         }
777 
awaitOnConnect(long waitTimeMs)778         public boolean awaitOnConnect(long waitTimeMs) {
779             try {
780                 return mOnConnectLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
781             } catch (InterruptedException e) {
782                 return false;
783             }
784         }
785 
awaitOnPostConnect(long waitTimeMs)786         public boolean awaitOnPostConnect(long waitTimeMs) {
787             try {
788                 return mOnPostConnectLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
789             } catch (InterruptedException e) {
790                 return false;
791             }
792         }
793 
awaitOnDisconnect(long waitTimeMs)794         public boolean awaitOnDisconnect(long waitTimeMs) {
795             try {
796                 return mOnDisconnectLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
797             } catch (InterruptedException e) {
798                 return false;
799             }
800         }
801 
awaitOnSessionCommand(long waitTimeMs)802         public boolean awaitOnSessionCommand(long waitTimeMs) {
803             try {
804                 return mOnSessionCommandLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
805             } catch (InterruptedException e) {
806                 return false;
807             }
808         }
809 
awaitOnCommandResult(long waitTimeMs)810         public boolean awaitOnCommandResult(long waitTimeMs) {
811             try {
812                 return mOnCommandResultLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
813             } catch (InterruptedException e) {
814                 return false;
815             }
816         }
817     }
818 
819     /* package */ static class CustomParcelable implements Parcelable {
820         public int mValue;
821 
CustomParcelable(int value)822         /* package */ CustomParcelable(int value) {
823             mValue = value;
824         }
825 
826         @Override
describeContents()827         public int describeContents() {
828             return 0;
829         }
830 
831         @Override
writeToParcel(Parcel dest, int flags)832         public void writeToParcel(Parcel dest, int flags) {
833             dest.writeInt(mValue);
834         }
835 
836         public static final Parcelable.Creator<CustomParcelable> CREATOR =
837                 new Parcelable.Creator<CustomParcelable>() {
838             @Override
839             public CustomParcelable createFromParcel(Parcel in) {
840                 int value = in.readInt();
841                 return new CustomParcelable(value);
842             }
843 
844             @Override
845             public CustomParcelable[] newArray(int size) {
846                 return new CustomParcelable[size];
847             }
848         };
849     }
850 }
851