• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.mediaroutertest;
18 
19 import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
20 import static android.media.MediaRoute2Info.FEATURE_REMOTE_PLAYBACK;
21 import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
22 import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
23 import static android.media.MediaRoute2ProviderService.REASON_REJECTED;
24 import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE;
25 
26 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.FEATURE_SAMPLE;
27 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.FEATURE_SPECIAL;
28 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID1;
29 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID2;
30 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID4_TO_SELECT_AND_DESELECT;
31 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID5_TO_TRANSFER_TO;
32 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID6_TO_BE_IGNORED;
33 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_FIXED_VOLUME;
34 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_SPECIAL_FEATURE;
35 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_VARIABLE_VOLUME;
36 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_NAME2;
37 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.VOLUME_MAX;
38 
39 import static org.junit.Assert.assertEquals;
40 import static org.junit.Assert.assertFalse;
41 import static org.junit.Assert.assertNotNull;
42 import static org.junit.Assert.assertNull;
43 import static org.junit.Assert.assertTrue;
44 
45 import android.content.Context;
46 import android.media.MediaRoute2Info;
47 import android.media.MediaRouter2;
48 import android.media.MediaRouter2.RouteCallback;
49 import android.media.MediaRouter2.TransferCallback;
50 import android.media.MediaRouter2Manager;
51 import android.media.MediaRouter2Utils;
52 import android.media.RouteDiscoveryPreference;
53 import android.media.RoutingSessionInfo;
54 import android.os.Bundle;
55 import android.support.test.InstrumentationRegistry;
56 import android.support.test.filters.LargeTest;
57 import android.support.test.filters.SmallTest;
58 import android.support.test.runner.AndroidJUnit4;
59 import android.text.TextUtils;
60 
61 import org.junit.After;
62 import org.junit.Before;
63 import org.junit.Test;
64 import org.junit.runner.RunWith;
65 
66 import java.util.ArrayList;
67 import java.util.HashMap;
68 import java.util.List;
69 import java.util.Map;
70 import java.util.concurrent.CountDownLatch;
71 import java.util.concurrent.Executor;
72 import java.util.concurrent.Executors;
73 import java.util.concurrent.TimeUnit;
74 import java.util.function.Predicate;
75 import java.util.stream.Collectors;
76 
77 @RunWith(AndroidJUnit4.class)
78 @SmallTest
79 public class MediaRouter2ManagerTest {
80     private static final String TAG = "MediaRouter2ManagerTest";
81     private static final int WAIT_TIME_MS = 2000;
82     private static final int TIMEOUT_MS = 5000;
83     private static final String TEST_KEY = "test_key";
84     private static final String TEST_VALUE = "test_value";
85     private static final String TEST_ID_UNKNOWN = "id_unknown";
86     private static final String TEST_NAME_UNKNOWN = "unknown";
87 
88     private Context mContext;
89     private MediaRouter2Manager mManager;
90     private MediaRouter2 mRouter2;
91     private Executor mExecutor;
92     private String mPackageName;
93 
94     private final List<MediaRouter2Manager.Callback> mManagerCallbacks = new ArrayList<>();
95     private final List<RouteCallback> mRouteCallbacks = new ArrayList<>();
96     private final List<MediaRouter2.TransferCallback> mTransferCallbacks = new ArrayList<>();
97 
98     public static final List<String> FEATURES_ALL = new ArrayList();
99     public static final List<String> FEATURES_SPECIAL = new ArrayList();
100 
101     static {
102         FEATURES_ALL.add(FEATURE_SAMPLE);
103         FEATURES_ALL.add(FEATURE_SPECIAL);
104         FEATURES_ALL.add(FEATURE_LIVE_AUDIO);
105 
106         FEATURES_SPECIAL.add(FEATURE_SPECIAL);
107     }
108 
109     @Before
setUp()110     public void setUp() throws Exception {
111         mContext = InstrumentationRegistry.getTargetContext();
112         mManager = MediaRouter2Manager.getInstance(mContext);
113         mRouter2 = MediaRouter2.getInstance(mContext);
114         // If we need to support thread pool executors, change this to thread pool executor.
115         mExecutor = Executors.newSingleThreadExecutor();
116         mPackageName = mContext.getPackageName();
117     }
118 
119     @After
tearDown()120     public void tearDown() {
121         // order matters (callbacks should be cleared at the last)
122         releaseAllSessions();
123         // unregister callbacks
124         clearCallbacks();
125 
126         StubMediaRoute2ProviderService instance = StubMediaRoute2ProviderService.getInstance();
127         if (instance != null) {
128             instance.setProxy(null);
129             instance.setSpy(null);
130         }
131     }
132 
133     @Test
testOnRoutesRemovedAndAdded()134     public void testOnRoutesRemovedAndAdded() throws Exception {
135         RouteCallback routeCallback = new RouteCallback() {};
136         mRouteCallbacks.add(routeCallback);
137         mRouter2.registerRouteCallback(mExecutor, routeCallback,
138                 new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
139 
140         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
141 
142         CountDownLatch removedLatch = new CountDownLatch(1);
143         CountDownLatch addedLatch = new CountDownLatch(1);
144 
145         addManagerCallback(new MediaRouter2Manager.Callback() {
146             @Override
147             public void onRoutesRemoved(List<MediaRoute2Info> routes) {
148                 assertTrue(routes.size() > 0);
149                 for (MediaRoute2Info route : routes) {
150                     if (route.getOriginalId().equals(ROUTE_ID2)
151                             && route.getName().equals(ROUTE_NAME2)) {
152                         removedLatch.countDown();
153                     }
154                 }
155             }
156             @Override
157             public void onRoutesAdded(List<MediaRoute2Info> routes) {
158                 assertTrue(routes.size() > 0);
159                 if (removedLatch.getCount() > 0) {
160                     return;
161                 }
162                 for (MediaRoute2Info route : routes) {
163                     if (route.getOriginalId().equals(ROUTE_ID2)
164                             && route.getName().equals(ROUTE_NAME2)) {
165                         addedLatch.countDown();
166                     }
167                 }
168             }
169         });
170 
171         MediaRoute2Info routeToRemove = routes.get(ROUTE_ID2);
172         assertNotNull(routeToRemove);
173 
174         StubMediaRoute2ProviderService sInstance =
175                 StubMediaRoute2ProviderService.getInstance();
176         assertNotNull(sInstance);
177         sInstance.removeRoute(ROUTE_ID2);
178         assertTrue(removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
179 
180         sInstance.addRoute(routeToRemove);
181         assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
182     }
183 
184     @Test
testGetRoutes_removedRoute_returnsCorrectRoutes()185     public void testGetRoutes_removedRoute_returnsCorrectRoutes() throws Exception {
186         CountDownLatch addedLatch = new CountDownLatch(1);
187         CountDownLatch removedLatch = new CountDownLatch(1);
188 
189         RouteCallback routeCallback = new RouteCallback() {
190             // Used to ensure the removed route is added.
191             @Override
192             public void onRoutesAdded(List<MediaRoute2Info> routes) {
193                 if (removedLatch.getCount() > 0) {
194                     return;
195                 }
196                 addedLatch.countDown();
197             }
198 
199             @Override
200             public void onRoutesRemoved(List<MediaRoute2Info> routes) {
201                 removedLatch.countDown();
202             }
203         };
204 
205         mRouter2.registerRouteCallback(mExecutor, routeCallback,
206                 new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
207         mRouteCallbacks.add(routeCallback);
208 
209         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
210         MediaRoute2Info routeToRemove = routes.get(ROUTE_ID2);
211         assertNotNull(routeToRemove);
212 
213         StubMediaRoute2ProviderService sInstance =
214                 StubMediaRoute2ProviderService.getInstance();
215         assertNotNull(sInstance);
216         sInstance.removeRoute(ROUTE_ID2);
217 
218         // Wait until the route is removed.
219         assertTrue(removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
220 
221         Map<String, MediaRoute2Info> newRoutes = waitAndGetRoutesWithManager(FEATURES_ALL);
222         assertNull(newRoutes.get(ROUTE_ID2));
223 
224         // Revert the removal.
225         sInstance.addRoute(routeToRemove);
226         assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
227         mRouter2.unregisterRouteCallback(routeCallback);
228     }
229 
230     /**
231      * Tests if we get proper routes for application that has special route feature.
232      */
233     @Test
testRouteFeatures()234     public void testRouteFeatures() throws Exception {
235         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_SPECIAL);
236 
237         int routeCount = 0;
238         for (MediaRoute2Info route : routes.values()) {
239             if (!route.isSystemRoute()) {
240                 routeCount++;
241             }
242         }
243 
244         assertEquals(1, routeCount);
245         assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE));
246     }
247 
248     /**
249      * Tests if MR2.SessionCallback.onSessionCreated is called
250      * when a route is selected from MR2Manager.
251      */
252     @Test
testRouterOnSessionCreated()253     public void testRouterOnSessionCreated() throws Exception {
254         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
255 
256         CountDownLatch latch = new CountDownLatch(1);
257 
258         addManagerCallback(new MediaRouter2Manager.Callback());
259         addRouterCallback(new MediaRouter2.RouteCallback() {});
260         addTransferCallback(new MediaRouter2.TransferCallback() {
261             @Override
262             public void onTransfer(MediaRouter2.RoutingController oldController,
263                     MediaRouter2.RoutingController newController) {
264                 if (newController == null) {
265                     return;
266                 }
267                 if (createRouteMap(newController.getSelectedRoutes()).containsKey(ROUTE_ID1)) {
268                     latch.countDown();
269                 }
270             }
271         });
272 
273         MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
274         assertNotNull(routeToSelect);
275 
276         mManager.selectRoute(mPackageName, routeToSelect);
277         assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
278         assertEquals(2, mManager.getActiveSessions().size());
279     }
280 
281     @Test
testGetRoutingSessions()282     public void testGetRoutingSessions() throws Exception {
283         CountDownLatch latch = new CountDownLatch(1);
284 
285         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
286         MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
287 
288         addRouterCallback(new RouteCallback() {});
289         addManagerCallback(new MediaRouter2Manager.Callback() {
290             @Override
291             public void onTransferred(RoutingSessionInfo oldSessionInfo,
292                     RoutingSessionInfo newSessionInfo) {
293                 if (TextUtils.equals(mPackageName, newSessionInfo.getClientPackageName())
294                         && newSessionInfo.getSelectedRoutes().contains(routeToSelect.getId())) {
295                     latch.countDown();
296                 }
297             }
298         });
299 
300         assertEquals(1, mManager.getRoutingSessions(mPackageName).size());
301 
302         mManager.selectRoute(mPackageName, routeToSelect);
303         assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
304 
305         List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
306         assertEquals(2, sessions.size());
307 
308         RoutingSessionInfo sessionInfo = sessions.get(1);
309         awaitOnRouteChangedManager(
310                 () -> mManager.releaseSession(sessionInfo),
311                 ROUTE_ID1,
312                 route -> TextUtils.equals(route.getClientPackageName(), null));
313         assertEquals(1, mManager.getRoutingSessions(mPackageName).size());
314     }
315 
316     @Test
testTransfer_unknownRoute_fail()317     public void testTransfer_unknownRoute_fail() throws Exception {
318         addRouterCallback(new RouteCallback() {});
319 
320         CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
321         CountDownLatch onTransferFailedLatch = new CountDownLatch(1);
322 
323         addManagerCallback(new MediaRouter2Manager.Callback() {
324             @Override
325             public void onTransferred(RoutingSessionInfo oldSessionInfo,
326                     RoutingSessionInfo newSessionInfo) {
327                 assertNotNull(newSessionInfo);
328                 onSessionCreatedLatch.countDown();
329             }
330             @Override
331             public void onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route) {
332                 onTransferFailedLatch.countDown();
333             }
334         });
335 
336         MediaRoute2Info unknownRoute =
337                 new MediaRoute2Info.Builder(TEST_ID_UNKNOWN, TEST_NAME_UNKNOWN)
338                 .addFeature(FEATURE_REMOTE_PLAYBACK)
339                 .build();
340 
341         mManager.transfer(mManager.getSystemRoutingSession(), unknownRoute);
342         assertFalse(onSessionCreatedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
343         assertTrue(onTransferFailedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
344     }
345 
346     @Test
testRouterRelease_managerGetRoutingSessions()347     public void testRouterRelease_managerGetRoutingSessions() throws Exception {
348         CountDownLatch transferLatch = new CountDownLatch(1);
349         CountDownLatch releaseLatch = new CountDownLatch(1);
350 
351         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
352         MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
353         assertNotNull(routeToSelect);
354 
355         addRouterCallback(new RouteCallback() {});
356         addManagerCallback(new MediaRouter2Manager.Callback() {
357             @Override
358             public void onTransferred(RoutingSessionInfo oldSessionInfo,
359                     RoutingSessionInfo newSessionInfo) {
360                 if (TextUtils.equals(mPackageName, newSessionInfo.getClientPackageName())
361                         && newSessionInfo.getSelectedRoutes().contains(routeToSelect.getId())) {
362                     transferLatch.countDown();
363                 }
364             }
365             @Override
366             public void onSessionReleased(RoutingSessionInfo session) {
367                 releaseLatch.countDown();
368             }
369         });
370 
371         assertEquals(1, mManager.getRoutingSessions(mPackageName).size());
372         assertEquals(1, mRouter2.getControllers().size());
373 
374         mManager.transfer(mManager.getRoutingSessions(mPackageName).get(0), routeToSelect);
375         assertTrue(transferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
376 
377         assertEquals(2, mManager.getRoutingSessions(mPackageName).size());
378         assertEquals(2, mRouter2.getControllers().size());
379         mRouter2.getControllers().get(1).release();
380 
381         assertTrue(releaseLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
382 
383         assertEquals(1, mRouter2.getControllers().size());
384         assertEquals(1, mManager.getRoutingSessions(mPackageName).size());
385     }
386 
387     /**
388      * Tests select, transfer, release of routes of a provider
389      */
390     @Test
testSelectAndTransferAndRelease()391     public void testSelectAndTransferAndRelease() throws Exception {
392         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
393         addRouterCallback(new RouteCallback() {});
394 
395         CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
396 
397         addManagerCallback(new MediaRouter2Manager.Callback() {
398             @Override
399             public void onTransferred(RoutingSessionInfo oldSessionInfo,
400                     RoutingSessionInfo newSessionInfo) {
401                 assertNotNull(newSessionInfo);
402                 onSessionCreatedLatch.countDown();
403             }
404         });
405         awaitOnRouteChangedManager(
406                 () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)),
407                 ROUTE_ID1,
408                 route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
409         assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
410 
411         List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
412 
413         assertEquals(2, sessions.size());
414         RoutingSessionInfo sessionInfo = sessions.get(1);
415 
416         awaitOnRouteChangedManager(
417                 () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)),
418                 ROUTE_ID5_TO_TRANSFER_TO,
419                 route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
420 
421         awaitOnRouteChangedManager(
422                 () -> mManager.releaseSession(sessionInfo),
423                 ROUTE_ID5_TO_TRANSFER_TO,
424                 route -> TextUtils.equals(route.getClientPackageName(), null));
425     }
426 
427     @Test
428     @LargeTest
testTransferTwice()429     public void testTransferTwice() throws Exception {
430         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
431         addRouterCallback(new RouteCallback() { });
432 
433         CountDownLatch successLatch1 = new CountDownLatch(1);
434         CountDownLatch successLatch2 = new CountDownLatch(1);
435         CountDownLatch failureLatch = new CountDownLatch(1);
436         CountDownLatch managerOnSessionReleasedLatch = new CountDownLatch(1);
437         CountDownLatch serviceOnReleaseSessionLatch = new CountDownLatch(1);
438         List<RoutingSessionInfo> sessions = new ArrayList<>();
439 
440         StubMediaRoute2ProviderService instance = StubMediaRoute2ProviderService.getInstance();
441         assertNotNull(instance);
442         instance.setSpy(new StubMediaRoute2ProviderService.Spy() {
443             @Override
444             public void onReleaseSession(long requestId, String sessionId) {
445                 serviceOnReleaseSessionLatch.countDown();
446             }
447         });
448 
449         addManagerCallback(new MediaRouter2Manager.Callback() {
450             @Override
451             public void onTransferred(RoutingSessionInfo oldSession,
452                     RoutingSessionInfo newSession) {
453                 sessions.add(newSession);
454                 if (successLatch1.getCount() > 0) {
455                     successLatch1.countDown();
456                 } else {
457                     successLatch2.countDown();
458                 }
459             }
460 
461             @Override
462             public void onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route) {
463                 failureLatch.countDown();
464             }
465 
466             @Override
467             public void onSessionReleased(RoutingSessionInfo session) {
468                 managerOnSessionReleasedLatch.countDown();
469             }
470         });
471 
472         MediaRoute2Info route1 = routes.get(ROUTE_ID1);
473         MediaRoute2Info route2 = routes.get(ROUTE_ID2);
474         assertNotNull(route1);
475         assertNotNull(route2);
476 
477         mManager.selectRoute(mPackageName, route1);
478         assertTrue(successLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
479         mManager.selectRoute(mPackageName, route2);
480         assertTrue(successLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
481 
482         // onTransferFailed/onSessionReleased should not be called.
483         assertFalse(failureLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
484         assertFalse(managerOnSessionReleasedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
485 
486         assertEquals(2, sessions.size());
487         List<String> activeSessionIds = mManager.getActiveSessions().stream()
488                 .map(RoutingSessionInfo::getId)
489                 .collect(Collectors.toList());
490         // The old session shouldn't appear on the active session list.
491         assertFalse(activeSessionIds.contains(sessions.get(0).getId()));
492         assertTrue(activeSessionIds.contains(sessions.get(1).getId()));
493 
494         assertFalse(serviceOnReleaseSessionLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
495         mManager.releaseSession(sessions.get(0));
496         assertTrue(serviceOnReleaseSessionLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
497         assertFalse(managerOnSessionReleasedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
498     }
499 
500     @Test
501     @LargeTest
testTransfer_ignored_fails()502     public void testTransfer_ignored_fails() throws Exception {
503         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
504         addRouterCallback(new RouteCallback() {});
505 
506         CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
507         CountDownLatch onFailedLatch = new CountDownLatch(1);
508 
509         addManagerCallback(new MediaRouter2Manager.Callback() {
510             @Override
511             public void onTransferred(RoutingSessionInfo oldSessionInfo,
512                     RoutingSessionInfo newSessionInfo) {
513                 onSessionCreatedLatch.countDown();
514             }
515             @Override
516             public void onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route) {
517                 onFailedLatch.countDown();
518             }
519         });
520 
521         List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
522         RoutingSessionInfo targetSession = sessions.get(sessions.size() - 1);
523         mManager.transfer(targetSession, routes.get(ROUTE_ID6_TO_BE_IGNORED));
524 
525         assertFalse(onSessionCreatedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
526         assertTrue(onFailedLatch.await(MediaRouter2Manager.TRANSFER_TIMEOUT_MS,
527                 TimeUnit.MILLISECONDS));
528     }
529     @Test
testSetSystemRouteVolume()530     public void testSetSystemRouteVolume() throws Exception {
531         // ensure client
532         addManagerCallback(new MediaRouter2Manager.Callback());
533         String selectedSystemRouteId =
534                 MediaRouter2Utils.getOriginalId(
535                 mManager.getActiveSessions().get(0).getSelectedRoutes().get(0));
536         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
537         MediaRoute2Info volRoute = routes.get(selectedSystemRouteId);
538         assertNotNull(volRoute);
539 
540         int originalVolume = volRoute.getVolume();
541         int targetVolume = originalVolume == volRoute.getVolumeMax()
542                 ? originalVolume - 1 : originalVolume + 1;
543 
544         awaitOnRouteChangedManager(
545                 () -> mManager.setRouteVolume(volRoute, targetVolume),
546                 selectedSystemRouteId,
547                 (route -> route.getVolume() == targetVolume));
548 
549         awaitOnRouteChangedManager(
550                 () -> mManager.setRouteVolume(volRoute, originalVolume),
551                 selectedSystemRouteId,
552                 (route -> route.getVolume() == originalVolume));
553     }
554 
555     @Test
testSetRouteVolume()556     public void testSetRouteVolume() throws Exception {
557         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
558         MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
559 
560         int originalVolume = volRoute.getVolume();
561         int targetVolume = originalVolume == volRoute.getVolumeMax()
562                 ? originalVolume - 1 : originalVolume + 1;
563 
564         awaitOnRouteChangedManager(
565                 () -> mManager.setRouteVolume(volRoute, targetVolume),
566                 ROUTE_ID_VARIABLE_VOLUME,
567                 (route -> route.getVolume() == targetVolume));
568 
569         awaitOnRouteChangedManager(
570                 () -> mManager.setRouteVolume(volRoute, originalVolume),
571                 ROUTE_ID_VARIABLE_VOLUME,
572                 (route -> route.getVolume() == originalVolume));
573     }
574 
575     @Test
testSetSessionVolume()576     public void testSetSessionVolume() throws Exception {
577         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
578         addRouterCallback(new RouteCallback() {});
579 
580         CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
581         CountDownLatch volumeChangedLatch = new CountDownLatch(2);
582 
583         // create a controller
584         addManagerCallback(new MediaRouter2Manager.Callback() {
585             @Override
586             public void onTransferred(RoutingSessionInfo oldSessionInfo,
587                     RoutingSessionInfo newSessionInfo) {
588                 assertNotNull(newSessionInfo);
589                 onSessionCreatedLatch.countDown();
590             }
591         });
592 
593         mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1));
594         assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
595 
596         List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
597         assertEquals(2, sessions.size());
598 
599         // test setSessionVolume
600         RoutingSessionInfo sessionInfo = sessions.get(1);
601         int currentVolume = sessionInfo.getVolume();
602         int targetVolume = (currentVolume == 0) ? 1 : (currentVolume - 1);
603 
604         MediaRouter2.ControllerCallback controllerCallback = new MediaRouter2.ControllerCallback() {
605             @Override
606             public void onControllerUpdated(MediaRouter2.RoutingController controller) {
607                 if (!TextUtils.equals(sessionInfo.getId(), controller.getId())) {
608                     return;
609                 }
610                 if (controller.getVolume() == targetVolume) {
611                     volumeChangedLatch.countDown();
612                 }
613             }
614         };
615 
616         addManagerCallback(new MediaRouter2Manager.Callback() {
617             @Override
618             public void onSessionUpdated(RoutingSessionInfo updatedSessionInfo) {
619                 if (!TextUtils.equals(sessionInfo.getId(), updatedSessionInfo.getId())) {
620                     return;
621                 }
622 
623                 if (updatedSessionInfo.getVolume() == targetVolume) {
624                     volumeChangedLatch.countDown();
625                 }
626             }
627         });
628 
629         try {
630             mRouter2.registerControllerCallback(mExecutor, controllerCallback);
631             mManager.setSessionVolume(sessionInfo, targetVolume);
632             assertTrue(volumeChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
633         } finally {
634             mRouter2.unregisterControllerCallback(controllerCallback);
635         }
636     }
637 
638     /**
639      * Tests that {@link android.media.MediaRoute2ProviderService#notifyRequestFailed(long, int)}
640      * should invoke the callback only when the right requestId is used.
641      */
642     @Test
testOnRequestFailedCalledForProperRequestId()643     public void testOnRequestFailedCalledForProperRequestId() throws Exception {
644         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
645         MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
646 
647         StubMediaRoute2ProviderService instance = StubMediaRoute2ProviderService.getInstance();
648         assertNotNull(instance);
649 
650         final List<Long> requestIds = new ArrayList<>();
651         final CountDownLatch onSetRouteVolumeLatch = new CountDownLatch(1);
652         instance.setProxy(new StubMediaRoute2ProviderService.Proxy() {
653             @Override
654             public void onSetRouteVolume(String routeId, int volume, long requestId) {
655                 requestIds.add(requestId);
656                 onSetRouteVolumeLatch.countDown();
657             }
658         });
659 
660         addManagerCallback(new MediaRouter2Manager.Callback() {});
661         mManager.setRouteVolume(volRoute, 0);
662         assertTrue(onSetRouteVolumeLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
663         assertFalse(requestIds.isEmpty());
664 
665         final int failureReason = REASON_REJECTED;
666         final CountDownLatch onRequestFailedLatch = new CountDownLatch(1);
667         final CountDownLatch onRequestFailedSecondCallLatch = new CountDownLatch(1);
668         addManagerCallback(new MediaRouter2Manager.Callback() {
669             @Override
670             public void onRequestFailed(int reason) {
671                 if (reason == failureReason) {
672                     if (onRequestFailedLatch.getCount() > 0) {
673                         onRequestFailedLatch.countDown();
674                     } else {
675                         onRequestFailedSecondCallLatch.countDown();
676                     }
677                 }
678             }
679         });
680 
681         final long invalidRequestId = REQUEST_ID_NONE;
682         instance.notifyRequestFailed(invalidRequestId, failureReason);
683         assertFalse(onRequestFailedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
684 
685         final long validRequestId = requestIds.get(0);
686         instance.notifyRequestFailed(validRequestId, failureReason);
687         assertTrue(onRequestFailedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
688 
689         // Test calling notifyRequestFailed() multiple times with the same valid requestId.
690         // onRequestFailed() shouldn't be called since the requestId has been already handled.
691         instance.notifyRequestFailed(validRequestId, failureReason);
692         assertFalse(onRequestFailedSecondCallLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
693     }
694 
695     @Test
testVolumeHandling()696     public void testVolumeHandling() throws Exception {
697         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
698 
699         MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME);
700         MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
701 
702         assertEquals(PLAYBACK_VOLUME_FIXED, fixedVolumeRoute.getVolumeHandling());
703         assertEquals(PLAYBACK_VOLUME_VARIABLE, variableVolumeRoute.getVolumeHandling());
704         assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax());
705     }
706 
707     @Test
testRouter2SetOnGetControllerHintsListener()708     public void testRouter2SetOnGetControllerHintsListener() throws Exception {
709         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
710         addRouterCallback(new RouteCallback() {});
711 
712         MediaRoute2Info route = routes.get(ROUTE_ID1);
713         assertNotNull(route);
714 
715         final Bundle controllerHints = new Bundle();
716         controllerHints.putString(TEST_KEY, TEST_VALUE);
717         final CountDownLatch hintLatch = new CountDownLatch(1);
718         final MediaRouter2.OnGetControllerHintsListener listener =
719                 route1 -> {
720                     hintLatch.countDown();
721                     return controllerHints;
722                 };
723 
724         final CountDownLatch successLatch = new CountDownLatch(1);
725         final CountDownLatch failureLatch = new CountDownLatch(1);
726 
727         addManagerCallback(new MediaRouter2Manager.Callback() {
728             @Override
729             public void onTransferred(RoutingSessionInfo oldSession,
730                     RoutingSessionInfo newSession) {
731                 assertTrue(newSession.getSelectedRoutes().contains(route.getId()));
732                 // The StubMediaRoute2ProviderService is supposed to set control hints
733                 // with the given controllerHints.
734                 Bundle controlHints = newSession.getControlHints();
735                 assertNotNull(controlHints);
736                 assertTrue(controlHints.containsKey(TEST_KEY));
737                 assertEquals(TEST_VALUE, controlHints.getString(TEST_KEY));
738 
739                 successLatch.countDown();
740             }
741 
742             @Override
743             public void onTransferFailed(RoutingSessionInfo session,
744                     MediaRoute2Info requestedRoute) {
745                 failureLatch.countDown();
746             }
747         });
748 
749         mRouter2.setOnGetControllerHintsListener(listener);
750         mManager.selectRoute(mPackageName, route);
751         assertTrue(hintLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
752         assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
753 
754         assertFalse(failureLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
755     }
756 
757     /**
758      * Tests if getSelectableRoutes and getDeselectableRoutes filter routes based on
759      * selected routes
760      */
761     @Test
testGetSelectableRoutes_notReturnsSelectedRoutes()762     public void testGetSelectableRoutes_notReturnsSelectedRoutes() throws Exception {
763         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
764         addRouterCallback(new RouteCallback() {});
765 
766         CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
767 
768         addManagerCallback(new MediaRouter2Manager.Callback() {
769             @Override
770             public void onTransferred(RoutingSessionInfo oldSessionInfo,
771                     RoutingSessionInfo newSessionInfo) {
772                 assertNotNull(newSessionInfo);
773                 List<String> selectedRoutes = mManager.getSelectedRoutes(newSessionInfo).stream()
774                         .map(MediaRoute2Info::getId)
775                         .collect(Collectors.toList());
776                 for (MediaRoute2Info selectableRoute :
777                         mManager.getSelectableRoutes(newSessionInfo)) {
778                     assertFalse(selectedRoutes.contains(selectableRoute.getId()));
779                 }
780                 for (MediaRoute2Info deselectableRoute :
781                         mManager.getDeselectableRoutes(newSessionInfo)) {
782                     assertTrue(selectedRoutes.contains(deselectableRoute.getId()));
783                 }
784                 onSessionCreatedLatch.countDown();
785             }
786         });
787 
788         mManager.selectRoute(mPackageName, routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT));
789         assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
790     }
791 
792     @Test
testGetActiveSessions_returnsNonEmptyList()793     public void testGetActiveSessions_returnsNonEmptyList() {
794         assertFalse(mManager.getActiveSessions().isEmpty());
795     }
796 
waitAndGetRoutesWithManager(List<String> routeFeatures)797     Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeFeatures)
798             throws Exception {
799         CountDownLatch addedLatch = new CountDownLatch(1);
800         CountDownLatch featuresLatch = new CountDownLatch(1);
801 
802         // A dummy callback is required to send route feature info.
803         RouteCallback routeCallback = new RouteCallback() {};
804         MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
805             @Override
806             public void onRoutesAdded(List<MediaRoute2Info> routes) {
807                 for (MediaRoute2Info route : routes) {
808                     if (!route.isSystemRoute()) {
809                         addedLatch.countDown();
810                         break;
811                     }
812                 }
813             }
814 
815             @Override
816             public void onPreferredFeaturesChanged(String packageName,
817                     List<String> preferredFeatures) {
818                 if (TextUtils.equals(mPackageName, packageName)
819                         && preferredFeatures.size() == routeFeatures.size()
820                         && preferredFeatures.containsAll(routeFeatures)) {
821                     featuresLatch.countDown();
822                 }
823             }
824         };
825         mManager.registerCallback(mExecutor, managerCallback);
826         mRouter2.registerRouteCallback(mExecutor, routeCallback,
827                 new RouteDiscoveryPreference.Builder(routeFeatures, true).build());
828         try {
829             if (mManager.getAllRoutes().isEmpty()) {
830                 addedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
831             }
832             featuresLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
833             return createRouteMap(mManager.getAvailableRoutes(mPackageName));
834         } finally {
835             mRouter2.unregisterRouteCallback(routeCallback);
836             mManager.unregisterCallback(managerCallback);
837         }
838     }
839 
awaitOnRouteChangedManager(Runnable task, String routeId, Predicate<MediaRoute2Info> predicate)840     void awaitOnRouteChangedManager(Runnable task, String routeId,
841             Predicate<MediaRoute2Info> predicate) throws Exception {
842         CountDownLatch latch = new CountDownLatch(1);
843         MediaRouter2Manager.Callback callback = new MediaRouter2Manager.Callback() {
844             @Override
845             public void onRoutesChanged(List<MediaRoute2Info> changed) {
846                 MediaRoute2Info route = createRouteMap(changed).get(routeId);
847                 if (route != null && predicate.test(route)) {
848                     latch.countDown();
849                 }
850             }
851         };
852         mManager.registerCallback(mExecutor, callback);
853         try {
854             task.run();
855             assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
856         } finally {
857             mManager.unregisterCallback(callback);
858         }
859     }
860 
861     // Helper for getting routes easily
createRouteMap(List<MediaRoute2Info> routes)862     static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
863         Map<String, MediaRoute2Info> routeMap = new HashMap<>();
864         for (MediaRoute2Info route : routes) {
865             routeMap.put(route.getOriginalId(), route);
866         }
867         return routeMap;
868     }
869 
addManagerCallback(MediaRouter2Manager.Callback callback)870     private void addManagerCallback(MediaRouter2Manager.Callback callback) {
871         mManagerCallbacks.add(callback);
872         mManager.registerCallback(mExecutor, callback);
873     }
874 
addRouterCallback(RouteCallback routeCallback)875     private void addRouterCallback(RouteCallback routeCallback) {
876         mRouteCallbacks.add(routeCallback);
877         mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
878     }
879 
addTransferCallback(TransferCallback transferCallback)880     private void addTransferCallback(TransferCallback transferCallback) {
881         mTransferCallbacks.add(transferCallback);
882         mRouter2.registerTransferCallback(mExecutor, transferCallback);
883     }
884 
clearCallbacks()885     private void clearCallbacks() {
886         for (MediaRouter2Manager.Callback callback : mManagerCallbacks) {
887             mManager.unregisterCallback(callback);
888         }
889         mManagerCallbacks.clear();
890 
891         for (RouteCallback routeCallback : mRouteCallbacks) {
892             mRouter2.unregisterRouteCallback(routeCallback);
893         }
894         mRouteCallbacks.clear();
895 
896         for (MediaRouter2.TransferCallback transferCallback : mTransferCallbacks) {
897             mRouter2.unregisterTransferCallback(transferCallback);
898         }
899         mTransferCallbacks.clear();
900     }
901 
releaseAllSessions()902     private void releaseAllSessions() {
903         // ensure ManagerRecord in MediaRouter2ServiceImpl
904         addManagerCallback(new MediaRouter2Manager.Callback());
905 
906         for (RoutingSessionInfo session : mManager.getActiveSessions()) {
907             mManager.releaseSession(session);
908         }
909     }
910 }
911