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