1 /* 2 * Copyright (C) 2022 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 android.media.router.cts; 18 19 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; 20 import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO; 21 import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED; 22 import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE; 23 24 import static com.google.common.truth.Truth.assertThat; 25 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.app.ActivityManager; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.media.MediaRoute2Info; 32 import android.media.MediaRoute2ProviderService; 33 import android.media.MediaRouter2; 34 import android.media.RouteDiscoveryPreference; 35 import android.media.RoutingSessionInfo; 36 import android.os.Bundle; 37 import android.os.IBinder; 38 import android.text.TextUtils; 39 40 import com.android.compatibility.common.util.PollingCheck; 41 42 import org.junit.rules.ExternalResource; 43 44 import java.lang.reflect.Method; 45 import java.util.ArrayList; 46 import java.util.Collection; 47 import java.util.HashMap; 48 import java.util.List; 49 import java.util.Map; 50 import java.util.Objects; 51 import java.util.concurrent.Executors; 52 53 import javax.annotation.concurrent.GuardedBy; 54 55 public class StubMediaRoute2ProviderService extends MediaRoute2ProviderService { 56 private static final String TAG = "SampleMR2ProviderSvc"; 57 private static final Object sLock = new Object(); 58 59 public static final String ROUTE_ID1 = "route_id1"; 60 public static final String ROUTE_NAME1 = "Sample Route 1"; 61 public static final String ROUTE_ID2 = "route_id2"; 62 public static final String ROUTE_NAME2 = "Sample Route 2"; 63 public static final String ROUTE_ID3_SESSION_CREATION_FAILED = 64 "route_id3_session_creation_failed"; 65 public static final String ROUTE_NAME3 = "Sample Route 3 - Session creation failed"; 66 public static final String ROUTE_ID4_TO_SELECT_AND_DESELECT = 67 "route_id4_to_select_and_deselect"; 68 public static final String ROUTE_NAME4 = "Sample Route 4 - Route to select and deselect"; 69 public static final String ROUTE_ID5_TO_TRANSFER_TO = "route_id5_to_transfer_to"; 70 public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to"; 71 72 public static final String ROUTE_ID_SPECIAL_FEATURE = "route_special_feature"; 73 public static final String ROUTE_NAME_SPECIAL_FEATURE = "Special Feature Route"; 74 75 public static final String ROUTE_ID6_REJECT_SET_VOLUME = "route_id6_reject_set_volume"; 76 public static final String ROUTE_NAME_6 = "Sample Route 6 - Reject Set Route Volume"; 77 78 public static final String ROUTE_ID7_STATIC_GROUP = "route_id7_static_group"; 79 public static final String ROUTE_NAME7 = "Sample Route 7 - Static Group"; 80 81 public static final String ROUTE_ID8_SYSTEM_TYPE = "route_id8_system_type"; 82 public static final String ROUTE_NAME8 = "Sample Route 8 - System Type"; 83 84 public static final int INITIAL_VOLUME = 30; 85 public static final int VOLUME_MAX = 100; 86 public static final int SESSION_VOLUME_MAX = 50; 87 public static final int SESSION_VOLUME_INITIAL = 20; 88 89 public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume"; 90 public static final String ROUTE_NAME_FIXED_VOLUME = "Fixed Volume Route"; 91 public static final String ROUTE_ID_VARIABLE_VOLUME = "route_variable_volume"; 92 public static final String ROUTE_NAME_VARIABLE_VOLUME = "Variable Volume Route"; 93 94 public static final String FEATURE_SAMPLE = "android.media.router.cts.FEATURE_SAMPLE"; 95 public static final String FEATURE_SPECIAL = "android.media.router.cts.FEATURE_SPECIAL"; 96 97 public static final List<String> FEATURES_ALL = new ArrayList(); 98 public static final List<String> FEATURES_SPECIAL = new ArrayList(); 99 public static final List<String> STATIC_GROUP_SELECTED_ROUTES_IDS = new ArrayList<>(); 100 public static final List<String> FEATURE_SPECIAL_ROUTE_IDS = new ArrayList<>(); 101 102 static { 103 FEATURES_ALL.add(FEATURE_SAMPLE); 104 FEATURES_ALL.add(FEATURE_SPECIAL); 105 FEATURES_ALL.add(FEATURE_LIVE_AUDIO); 106 107 FEATURES_SPECIAL.add(FEATURE_SPECIAL); 108 109 STATIC_GROUP_SELECTED_ROUTES_IDS.add(ROUTE_ID7_STATIC_GROUP); 110 STATIC_GROUP_SELECTED_ROUTES_IDS.add(ROUTE_ID1); 111 112 FEATURE_SPECIAL_ROUTE_IDS.add(ROUTE_ID_SPECIAL_FEATURE); 113 FEATURE_SPECIAL_ROUTE_IDS.add(ROUTE_ID7_STATIC_GROUP); 114 } 115 116 Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); 117 Map<String, String> mRouteIdToSessionId = new HashMap<>(); 118 private int mNextSessionId = 1000; 119 120 @GuardedBy("sLock") 121 private static StubMediaRoute2ProviderService sInstance; 122 private Proxy mProxy; 123 initializeRoutes()124 public void initializeRoutes() { 125 MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1) 126 .addFeature(FEATURE_SAMPLE) 127 .build(); 128 MediaRoute2Info route2 = new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2) 129 .addFeature(FEATURE_SAMPLE) 130 .build(); 131 MediaRoute2Info route3 = new MediaRoute2Info.Builder( 132 ROUTE_ID3_SESSION_CREATION_FAILED, ROUTE_NAME3) 133 .addFeature(FEATURE_SAMPLE) 134 .build(); 135 MediaRoute2Info route4 = new MediaRoute2Info.Builder( 136 ROUTE_ID4_TO_SELECT_AND_DESELECT, ROUTE_NAME4) 137 .addFeature(FEATURE_SAMPLE) 138 .build(); 139 MediaRoute2Info route5 = new MediaRoute2Info.Builder( 140 ROUTE_ID5_TO_TRANSFER_TO, ROUTE_NAME5) 141 .addFeature(FEATURE_SAMPLE) 142 .build(); 143 MediaRoute2Info route6 = new MediaRoute2Info.Builder( 144 ROUTE_ID6_REJECT_SET_VOLUME, ROUTE_NAME_6) 145 .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE) 146 .setVolume(INITIAL_VOLUME) 147 .setVolumeMax(VOLUME_MAX) 148 .addFeature(FEATURE_SAMPLE) 149 .build(); 150 MediaRoute2Info route7 = 151 new MediaRoute2Info.Builder(ROUTE_ID7_STATIC_GROUP, ROUTE_NAME7) 152 .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE) 153 .setVolume(INITIAL_VOLUME) 154 .setVolumeMax(VOLUME_MAX) 155 .addFeature(FEATURE_SPECIAL) 156 .build(); 157 MediaRoute2Info route8 = 158 new MediaRoute2Info.Builder(ROUTE_ID8_SYSTEM_TYPE, ROUTE_NAME8) 159 .setVolumeHandling(PLAYBACK_VOLUME_FIXED) 160 .setType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP) 161 .addFeature(FEATURE_SAMPLE) 162 .build(); 163 MediaRoute2Info routeSpecial = 164 new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_FEATURE, ROUTE_NAME_SPECIAL_FEATURE) 165 .addFeature(FEATURE_SAMPLE) 166 .addFeature(FEATURE_SPECIAL) 167 .build(); 168 MediaRoute2Info fixedVolumeRoute = 169 new MediaRoute2Info.Builder(ROUTE_ID_FIXED_VOLUME, ROUTE_NAME_FIXED_VOLUME) 170 .addFeature(FEATURE_SAMPLE) 171 .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_FIXED) 172 .build(); 173 MediaRoute2Info variableVolumeRoute = 174 new MediaRoute2Info.Builder(ROUTE_ID_VARIABLE_VOLUME, ROUTE_NAME_VARIABLE_VOLUME) 175 .addFeature(FEATURE_SAMPLE) 176 .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE) 177 .setVolume(INITIAL_VOLUME) 178 .setVolumeMax(VOLUME_MAX) 179 .build(); 180 181 mRoutes.put(route1.getId(), route1); 182 mRoutes.put(route2.getId(), route2); 183 mRoutes.put(route3.getId(), route3); 184 mRoutes.put(route4.getId(), route4); 185 mRoutes.put(route5.getId(), route5); 186 mRoutes.put(route6.getId(), route6); 187 mRoutes.put(route7.getId(), route7); 188 mRoutes.put(route8.getId(), route8); 189 mRoutes.put(routeSpecial.getId(), routeSpecial); 190 mRoutes.put(fixedVolumeRoute.getId(), fixedVolumeRoute); 191 mRoutes.put(variableVolumeRoute.getId(), variableVolumeRoute); 192 } 193 getInstance()194 public static StubMediaRoute2ProviderService getInstance() { 195 synchronized (sLock) { 196 return sInstance; 197 } 198 } 199 clear()200 public void clear() { 201 mProxy = null; 202 mRoutes.clear(); 203 mRouteIdToSessionId.clear(); 204 for (RoutingSessionInfo sessionInfo : getAllSessionInfo()) { 205 notifySessionReleased(sessionInfo.getId()); 206 } 207 } 208 setProxy(@ullable Proxy proxy)209 public void setProxy(@Nullable Proxy proxy) { 210 mProxy = proxy; 211 } 212 213 @Override onCreate()214 public void onCreate() { 215 super.onCreate(); 216 synchronized (sLock) { 217 sInstance = this; 218 } 219 } 220 221 @Override onDestroy()222 public void onDestroy() { 223 super.onDestroy(); 224 synchronized (sLock) { 225 if (sInstance == this) { 226 sInstance = null; 227 } 228 } 229 } 230 231 @Override onBind(Intent intent)232 public IBinder onBind(Intent intent) { 233 return super.onBind(intent); 234 } 235 236 @Override onSetRouteVolume(long requestId, String routeId, int volume)237 public void onSetRouteVolume(long requestId, String routeId, int volume) { 238 MediaRoute2Info route = mRoutes.get(routeId); 239 if (route == null) { 240 return; 241 } 242 243 if (TextUtils.equals(route.getOriginalId(), ROUTE_ID6_REJECT_SET_VOLUME)) { 244 notifyRequestFailed(requestId, REASON_REJECTED); 245 return; 246 } 247 248 volume = Math.max(0, Math.min(volume, route.getVolumeMax())); 249 mRoutes.put(routeId, new MediaRoute2Info.Builder(route) 250 .setVolume(volume) 251 .build()); 252 publishRoutes(); 253 } 254 255 @Override onSetSessionVolume(long requestId, String sessionId, int volume)256 public void onSetSessionVolume(long requestId, String sessionId, int volume) { 257 RoutingSessionInfo sessionInfo = getSessionInfo(sessionId); 258 if (sessionInfo == null) { 259 return; 260 } 261 volume = Math.max(0, Math.min(volume, sessionInfo.getVolumeMax())); 262 RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo) 263 .setVolume(volume) 264 .build(); 265 notifySessionUpdated(newSessionInfo); 266 } 267 268 @Override onCreateSession(long requestId, String packageName, String routeId, @Nullable Bundle sessionHints)269 public void onCreateSession(long requestId, String packageName, String routeId, 270 @Nullable Bundle sessionHints) { 271 Proxy proxy = mProxy; 272 if (doesProxyOverridesMethod(proxy, "onCreateSession")) { 273 proxy.onCreateSession(requestId, packageName, routeId, sessionHints); 274 return; 275 } 276 277 MediaRoute2Info route = mRoutes.get(routeId); 278 if (route == null || TextUtils.equals(ROUTE_ID3_SESSION_CREATION_FAILED, routeId)) { 279 notifyRequestFailed(requestId, REASON_UNKNOWN_ERROR); 280 return; 281 } 282 maybeDeselectRoute(routeId, requestId); 283 284 final String sessionId = String.valueOf(mNextSessionId); 285 mNextSessionId++; 286 287 mRoutes.put(routeId, new MediaRoute2Info.Builder(route) 288 .setClientPackageName(packageName) 289 .build()); 290 mRouteIdToSessionId.put(routeId, sessionId); 291 292 RoutingSessionInfo.Builder sessionInfoBuilder = 293 new RoutingSessionInfo.Builder(sessionId, packageName) 294 .addSelectedRoute(routeId) 295 .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT) 296 .addTransferableRoute(ROUTE_ID5_TO_TRANSFER_TO) 297 .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE) 298 .setVolumeMax(SESSION_VOLUME_MAX) 299 .setVolume(SESSION_VOLUME_INITIAL) 300 // Set control hints with given sessionHints 301 .setControlHints(sessionHints); 302 303 if (TextUtils.equals(routeId, ROUTE_ID7_STATIC_GROUP)) { 304 // Add group member routes. 305 sessionInfoBuilder.addSelectedRoute(ROUTE_ID1); 306 sessionInfoBuilder.addDeselectableRoute(ROUTE_ID1); 307 308 // Set client package name for group member routes. 309 mRoutes.put( 310 ROUTE_ID1, 311 new MediaRoute2Info.Builder(mRoutes.get(ROUTE_ID1)) 312 .setClientPackageName(packageName) 313 .build()); 314 315 mRouteIdToSessionId.put(ROUTE_ID1, sessionId); 316 } 317 318 notifySessionCreated(requestId, sessionInfoBuilder.build()); 319 publishRoutes(); 320 } 321 322 @Override onReleaseSession(long requestId, String sessionId)323 public void onReleaseSession(long requestId, String sessionId) { 324 Proxy proxy = mProxy; 325 if (doesProxyOverridesMethod(proxy, "onReleaseSession")) { 326 proxy.onReleaseSession(requestId, sessionId); 327 return; 328 } 329 330 RoutingSessionInfo sessionInfo = getSessionInfo(sessionId); 331 if (sessionInfo == null) { 332 return; 333 } 334 335 for (String routeId : sessionInfo.getSelectedRoutes()) { 336 mRouteIdToSessionId.remove(routeId); 337 MediaRoute2Info route = mRoutes.get(routeId); 338 if (route != null) { 339 mRoutes.put(routeId, new MediaRoute2Info.Builder(route) 340 .setClientPackageName(null) 341 .build()); 342 } 343 } 344 notifySessionReleased(sessionId); 345 publishRoutes(); 346 } 347 348 @Override onDiscoveryPreferenceChanged(RouteDiscoveryPreference preference)349 public void onDiscoveryPreferenceChanged(RouteDiscoveryPreference preference) { 350 Proxy proxy = mProxy; 351 if (doesProxyOverridesMethod(proxy, "onDiscoveryPreferenceChanged")) { 352 proxy.onDiscoveryPreferenceChanged(preference); 353 return; 354 } 355 356 // Just call the empty super method in order to mark the callback as tested. 357 super.onDiscoveryPreferenceChanged(preference); 358 } 359 360 @Override onSelectRoute(long requestId, String sessionId, String routeId)361 public void onSelectRoute(long requestId, String sessionId, String routeId) { 362 Proxy proxy = mProxy; 363 if (doesProxyOverridesMethod(proxy, "onSelectRoute")) { 364 proxy.onSelectRoute(requestId, sessionId, routeId); 365 return; 366 } 367 368 RoutingSessionInfo sessionInfo = getSessionInfo(sessionId); 369 MediaRoute2Info route = mRoutes.get(routeId); 370 if (route == null || sessionInfo == null) { 371 return; 372 } 373 maybeDeselectRoute(routeId, requestId); 374 375 mRoutes.put(routeId, new MediaRoute2Info.Builder(route) 376 .setClientPackageName(sessionInfo.getClientPackageName()) 377 .build()); 378 mRouteIdToSessionId.put(routeId, sessionId); 379 publishRoutes(); 380 381 RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo) 382 .addSelectedRoute(routeId) 383 .removeSelectableRoute(routeId) 384 .addDeselectableRoute(routeId) 385 .build(); 386 notifySessionUpdated(newSessionInfo); 387 } 388 389 @Override onDeselectRoute(long requestId, String sessionId, String routeId)390 public void onDeselectRoute(long requestId, String sessionId, String routeId) { 391 Proxy proxy = mProxy; 392 if (doesProxyOverridesMethod(proxy, "onDeselectRoute")) { 393 proxy.onDeselectRoute(requestId, sessionId, routeId); 394 return; 395 } 396 397 RoutingSessionInfo sessionInfo = getSessionInfo(sessionId); 398 MediaRoute2Info route = mRoutes.get(routeId); 399 400 if (sessionInfo == null || route == null 401 || !sessionInfo.getSelectedRoutes().contains(routeId)) { 402 return; 403 } 404 405 mRouteIdToSessionId.remove(routeId); 406 mRoutes.put(routeId, new MediaRoute2Info.Builder(route) 407 .setClientPackageName(null) 408 .build()); 409 publishRoutes(); 410 411 if (sessionInfo.getSelectedRoutes().size() == 1) { 412 notifySessionReleased(sessionId); 413 return; 414 } 415 416 RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo) 417 .removeSelectedRoute(routeId) 418 .addSelectableRoute(routeId) 419 .removeDeselectableRoute(routeId) 420 .build(); 421 notifySessionUpdated(newSessionInfo); 422 } 423 424 @Override onTransferToRoute(long requestId, String sessionId, String routeId)425 public void onTransferToRoute(long requestId, String sessionId, String routeId) { 426 Proxy proxy = mProxy; 427 if (doesProxyOverridesMethod(proxy, "onTransferToRoute")) { 428 proxy.onTransferToRoute(requestId, sessionId, routeId); 429 return; 430 } 431 432 RoutingSessionInfo sessionInfo = getSessionInfo(sessionId); 433 MediaRoute2Info route = mRoutes.get(routeId); 434 435 if (sessionInfo == null || route == null) { 436 return; 437 } 438 439 for (String selectedRouteId : sessionInfo.getSelectedRoutes()) { 440 mRouteIdToSessionId.remove(selectedRouteId); 441 MediaRoute2Info selectedRoute = mRoutes.get(selectedRouteId); 442 if (selectedRoute != null) { 443 mRoutes.put(selectedRouteId, new MediaRoute2Info.Builder(selectedRoute) 444 .setClientPackageName(null) 445 .build()); 446 } 447 } 448 449 mRoutes.put(routeId, new MediaRoute2Info.Builder(route) 450 .setClientPackageName(sessionInfo.getClientPackageName()) 451 .build()); 452 mRouteIdToSessionId.put(routeId, sessionId); 453 454 RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo) 455 .clearSelectedRoutes() 456 .addSelectedRoute(routeId) 457 .removeDeselectableRoute(routeId) 458 .removeTransferableRoute(routeId) 459 .build(); 460 notifySessionUpdated(newSessionInfo); 461 publishRoutes(); 462 } 463 464 /** 465 * Adds a route and publishes it. It could replace a route in the provider if 466 * they have the same route id. 467 */ addRoute(@onNull MediaRoute2Info route)468 public void addRoute(@NonNull MediaRoute2Info route) { 469 Objects.requireNonNull(route, "route must not be null"); 470 mRoutes.put(route.getOriginalId(), route); 471 publishRoutes(); 472 } 473 474 /** 475 * Removes a route and publishes it. 476 */ removeRoute(@onNull String routeId)477 public void removeRoute(@NonNull String routeId) { 478 Objects.requireNonNull(routeId, "routeId must not be null"); 479 MediaRoute2Info route = mRoutes.get(routeId); 480 if (route != null) { 481 mRoutes.remove(routeId); 482 publishRoutes(); 483 } 484 } 485 486 /** Removes routes that don't match a set of ids, and publishes the remaining routes. */ removeAllRoutesExcept(Collection<String> routeIdsToKeep)487 public void removeAllRoutesExcept(Collection<String> routeIdsToKeep) { 488 List<String> routeIds = List.copyOf(mRoutes.keySet()); 489 for (String id : routeIds) { 490 if (!routeIdsToKeep.contains(id)) { 491 mRoutes.remove(id); 492 } 493 } 494 publishRoutes(); 495 } 496 maybeDeselectRoute(String routeId, long requestId)497 void maybeDeselectRoute(String routeId, long requestId) { 498 if (!mRouteIdToSessionId.containsKey(routeId)) { 499 return; 500 } 501 502 String sessionId = mRouteIdToSessionId.get(routeId); 503 onDeselectRoute(requestId, sessionId, routeId); 504 } 505 publishRoutes()506 void publishRoutes() { 507 notifyRoutes(new ArrayList<>(mRoutes.values())); 508 } 509 510 public static class Proxy { onCreateSession(long requestId, @NonNull String packageName, @NonNull String routeId, @Nullable Bundle sessionHints)511 public void onCreateSession(long requestId, @NonNull String packageName, 512 @NonNull String routeId, @Nullable Bundle sessionHints) {} onReleaseSession(long requestId, @NonNull String sessionId)513 public void onReleaseSession(long requestId, @NonNull String sessionId) {} onSelectRoute(long requestId, @NonNull String sessionId, @NonNull String routeId)514 public void onSelectRoute(long requestId, @NonNull String sessionId, 515 @NonNull String routeId) {} onDeselectRoute(long requestId, @NonNull String sessionId, @NonNull String routeId)516 public void onDeselectRoute(long requestId, @NonNull String sessionId, 517 @NonNull String routeId) {} onTransferToRoute(long requestId, @NonNull String sessionId, @NonNull String routeId)518 public void onTransferToRoute(long requestId, @NonNull String sessionId, 519 @NonNull String routeId) {} onDiscoveryPreferenceChanged(RouteDiscoveryPreference preference)520 public void onDiscoveryPreferenceChanged(RouteDiscoveryPreference preference) {} 521 // TODO: Handle onSetRouteVolume() && onSetSessionVolume() 522 } 523 doesProxyOverridesMethod(Proxy proxy, String methodName)524 private static boolean doesProxyOverridesMethod(Proxy proxy, String methodName) { 525 if (proxy == null) { 526 return false; 527 } 528 Method[] methods = proxy.getClass().getMethods(); 529 if (methods == null) { 530 return false; 531 } 532 for (int i = 0; i < methods.length; i++) { 533 if (methods[i].getName().equals(methodName)) { 534 // Found method. Check if it overrides 535 return methods[i].getDeclaringClass() != Proxy.class; 536 } 537 } 538 return false; 539 } 540 541 // This class can be used as a JUnit @Rule to initialize and get a reference to a running 542 // StubMediaRoute2ProviderService instance that will remain valid until the end of the test, and 543 // be cleaned up afterwards. 544 public static class Setup extends ExternalResource { 545 private static final long TIMEOUT_MS = 5000; 546 private MediaRouter2 mRouter2; 547 private MediaRouter2.RouteCallback mEmptyCallback; 548 private StubMediaRoute2ProviderService mService; 549 550 /** 551 * This should be called once per test invocation, to setup and get a reference to a 552 * StubMediaRoute2ProviderService. 553 */ setupAndGetService(Context context)554 public StubMediaRoute2ProviderService setupAndGetService(Context context) { 555 assertThat(mService).isNull(); 556 557 // In order to make the system bind to the test service, we need to set a non-empty 558 // discovery preference while the app is in the foreground. 559 ActivityManager.RunningAppProcessInfo appInfo = 560 new ActivityManager.RunningAppProcessInfo(); 561 ActivityManager.getMyMemoryState(appInfo); 562 assertThat(appInfo.importance).isAtMost(IMPORTANCE_VISIBLE); 563 564 RouteDiscoveryPreference preference = 565 new RouteDiscoveryPreference.Builder(List.of("unimportant_value"), false) 566 .build(); 567 568 mRouter2 = MediaRouter2.getInstance(context); 569 570 // This callback needs to stay registered until the end of the test, to prevent the 571 // provider service from being torn down prematurely. 572 mEmptyCallback = new MediaRouter2.RouteCallback() {}; 573 mRouter2.registerRouteCallback( 574 Executors.newSingleThreadExecutor(), mEmptyCallback, preference); 575 576 new PollingCheck(TIMEOUT_MS) { 577 @Override 578 protected boolean check() { 579 mService = StubMediaRoute2ProviderService.getInstance(); 580 if (mService != null) { 581 return true; 582 } 583 return false; 584 } 585 }.run(); 586 assertThat(mService).isNotNull(); 587 mService.initializeRoutes(); 588 mService.publishRoutes(); 589 return mService; 590 } 591 592 @Override after()593 protected void after() { 594 if (mEmptyCallback != null) { 595 mRouter2.unregisterRouteCallback(mEmptyCallback); 596 } 597 if (mService != null) { 598 mService.clear(); 599 } 600 } 601 } 602 } 603