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