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