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