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