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