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 android.media; 18 19 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; 20 21 import android.annotation.CallSuper; 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.SdkConstant; 26 import android.app.Service; 27 import android.content.Intent; 28 import android.os.Binder; 29 import android.os.Bundle; 30 import android.os.Handler; 31 import android.os.IBinder; 32 import android.os.Looper; 33 import android.os.Process; 34 import android.os.RemoteException; 35 import android.text.TextUtils; 36 import android.util.ArrayMap; 37 import android.util.Log; 38 39 import com.android.internal.annotations.GuardedBy; 40 41 import java.lang.annotation.Retention; 42 import java.lang.annotation.RetentionPolicy; 43 import java.util.ArrayDeque; 44 import java.util.ArrayList; 45 import java.util.Collection; 46 import java.util.Deque; 47 import java.util.List; 48 import java.util.Objects; 49 import java.util.concurrent.atomic.AtomicBoolean; 50 51 /** 52 * Base class for media route provider services. 53 * <p> 54 * Media route provider services are used to publish {@link MediaRoute2Info media routes} such as 55 * speakers, TVs, etc. The routes are published by calling {@link #notifyRoutes(Collection)}. 56 * Media apps which use {@link MediaRouter2} can request to play their media on the routes. 57 * </p><p> 58 * When {@link MediaRouter2 media router} wants to play media on a route, 59 * {@link #onCreateSession(long, String, String, Bundle)} will be called to handle the request. 60 * A session can be considered as a group of currently selected routes for each connection. 61 * Create and manage the sessions by yourself, and notify the {@link RoutingSessionInfo 62 * session infos} when there are any changes. 63 * </p><p> 64 * The system media router service will bind to media route provider services when a 65 * {@link RouteDiscoveryPreference discovery preference} is registered via 66 * a {@link MediaRouter2 media router} by an application. See 67 * {@link #onDiscoveryPreferenceChanged(RouteDiscoveryPreference)} for the details. 68 * </p> 69 * Use {@link #notifyRequestFailed(long, int)} to notify the failure with previously received 70 * request ID. 71 */ 72 public abstract class MediaRoute2ProviderService extends Service { 73 private static final String TAG = "MR2ProviderService"; 74 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 75 76 /** 77 * The {@link Intent} action that must be declared as handled by the service. 78 * Put this in your manifest to provide media routes. 79 */ 80 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 81 public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService"; 82 83 /** 84 * A category indicating that the associated provider is only intended for use within the app 85 * that hosts the provider. 86 * 87 * <p>Declaring this category helps the system save resources by avoiding the launch of services 88 * whose routes are known to be private to the app that provides them. 89 * 90 * @hide 91 */ 92 public static final String CATEGORY_SELF_SCAN_ONLY = 93 "android.media.MediaRoute2ProviderService.SELF_SCAN_ONLY"; 94 95 /** 96 * The request ID to pass {@link #notifySessionCreated(long, RoutingSessionInfo)} 97 * when {@link MediaRoute2ProviderService} created a session although there was no creation 98 * request. 99 * 100 * @see #notifySessionCreated(long, RoutingSessionInfo) 101 */ 102 public static final long REQUEST_ID_NONE = 0; 103 104 /** 105 * The request has failed due to unknown reason. 106 * 107 * @see #notifyRequestFailed(long, int) 108 */ 109 public static final int REASON_UNKNOWN_ERROR = 0; 110 111 /** 112 * The request has failed since this service rejected the request. 113 * 114 * @see #notifyRequestFailed(long, int) 115 */ 116 public static final int REASON_REJECTED = 1; 117 118 /** 119 * The request has failed due to a network error. 120 * 121 * @see #notifyRequestFailed(long, int) 122 */ 123 public static final int REASON_NETWORK_ERROR = 2; 124 125 /** 126 * The request has failed since the requested route is no longer available. 127 * 128 * @see #notifyRequestFailed(long, int) 129 */ 130 public static final int REASON_ROUTE_NOT_AVAILABLE = 3; 131 132 /** 133 * The request has failed since the request is not valid. For example, selecting a route 134 * which is not selectable. 135 * 136 * @see #notifyRequestFailed(long, int) 137 */ 138 public static final int REASON_INVALID_COMMAND = 4; 139 140 /** 141 * @hide 142 */ 143 @IntDef(prefix = "REASON_", value = { 144 REASON_UNKNOWN_ERROR, REASON_REJECTED, REASON_NETWORK_ERROR, REASON_ROUTE_NOT_AVAILABLE, 145 REASON_INVALID_COMMAND 146 }) 147 @Retention(RetentionPolicy.SOURCE) 148 public @interface Reason {} 149 150 private static final int MAX_REQUEST_IDS_SIZE = 500; 151 152 private final Handler mHandler; 153 private final Object mSessionLock = new Object(); 154 private final Object mRequestIdsLock = new Object(); 155 private final AtomicBoolean mStatePublishScheduled = new AtomicBoolean(false); 156 private final AtomicBoolean mSessionUpdateScheduled = new AtomicBoolean(false); 157 private MediaRoute2ProviderServiceStub mStub; 158 private IMediaRoute2ProviderServiceCallback mRemoteCallback; 159 private volatile MediaRoute2ProviderInfo mProviderInfo; 160 161 @GuardedBy("mRequestIdsLock") 162 private final Deque<Long> mRequestIds = new ArrayDeque<>(MAX_REQUEST_IDS_SIZE); 163 164 @GuardedBy("mSessionLock") 165 private final ArrayMap<String, RoutingSessionInfo> mSessionInfos = new ArrayMap<>(); 166 MediaRoute2ProviderService()167 public MediaRoute2ProviderService() { 168 mHandler = new Handler(Looper.getMainLooper()); 169 } 170 171 /** 172 * If overriding this method, call through to the super method for any unknown actions. 173 * <p> 174 * {@inheritDoc} 175 */ 176 @CallSuper 177 @Override 178 @Nullable onBind(@onNull Intent intent)179 public IBinder onBind(@NonNull Intent intent) { 180 if (SERVICE_INTERFACE.equals(intent.getAction())) { 181 if (mStub == null) { 182 mStub = new MediaRoute2ProviderServiceStub(); 183 } 184 return mStub; 185 } 186 return null; 187 } 188 189 /** 190 * Called when a volume setting is requested on a route of the provider 191 * 192 * @param requestId the ID of this request 193 * @param routeId the ID of the route 194 * @param volume the target volume 195 * @see MediaRoute2Info.Builder#setVolume(int) 196 */ onSetRouteVolume(long requestId, @NonNull String routeId, int volume)197 public abstract void onSetRouteVolume(long requestId, @NonNull String routeId, int volume); 198 199 /** 200 * Called when {@link MediaRouter2.RoutingController#setVolume(int)} is called on 201 * a routing session of the provider 202 * 203 * @param requestId the ID of this request 204 * @param sessionId the ID of the routing session 205 * @param volume the target volume 206 * @see RoutingSessionInfo.Builder#setVolume(int) 207 */ onSetSessionVolume(long requestId, @NonNull String sessionId, int volume)208 public abstract void onSetSessionVolume(long requestId, @NonNull String sessionId, int volume); 209 210 /** 211 * Gets information of the session with the given id. 212 * 213 * @param sessionId the ID of the session 214 * @return information of the session with the given id. 215 * null if the session is released or ID is not valid. 216 */ 217 @Nullable getSessionInfo(@onNull String sessionId)218 public final RoutingSessionInfo getSessionInfo(@NonNull String sessionId) { 219 if (TextUtils.isEmpty(sessionId)) { 220 throw new IllegalArgumentException("sessionId must not be empty"); 221 } 222 synchronized (mSessionLock) { 223 return mSessionInfos.get(sessionId); 224 } 225 } 226 227 /** 228 * Gets the list of {@link RoutingSessionInfo session info} that the provider service maintains. 229 */ 230 @NonNull getAllSessionInfo()231 public final List<RoutingSessionInfo> getAllSessionInfo() { 232 synchronized (mSessionLock) { 233 return new ArrayList<>(mSessionInfos.values()); 234 } 235 } 236 237 /** 238 * Notifies clients of that the session is created and ready for use. 239 * <p> 240 * If this session is created without any creation request, use {@link #REQUEST_ID_NONE} 241 * as the request ID. 242 * 243 * @param requestId the ID of the previous request to create this session provided in 244 * {@link #onCreateSession(long, String, String, Bundle)}. Can be 245 * {@link #REQUEST_ID_NONE} if this session is created without any request. 246 * @param sessionInfo information of the new session. 247 * The {@link RoutingSessionInfo#getId() id} of the session must be unique. 248 * @see #onCreateSession(long, String, String, Bundle) 249 * @see #getSessionInfo(String) 250 */ notifySessionCreated(long requestId, @NonNull RoutingSessionInfo sessionInfo)251 public final void notifySessionCreated(long requestId, 252 @NonNull RoutingSessionInfo sessionInfo) { 253 Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); 254 255 if (DEBUG) { 256 Log.d(TAG, "notifySessionCreated: Creating a session. requestId=" + requestId 257 + ", sessionInfo=" + sessionInfo); 258 } 259 260 if (requestId != REQUEST_ID_NONE && !removeRequestId(requestId)) { 261 Log.w(TAG, "notifySessionCreated: The requestId doesn't exist. requestId=" + requestId); 262 return; 263 } 264 265 String sessionId = sessionInfo.getId(); 266 synchronized (mSessionLock) { 267 if (mSessionInfos.containsKey(sessionId)) { 268 Log.w(TAG, "notifySessionCreated: Ignoring duplicate session id."); 269 return; 270 } 271 mSessionInfos.put(sessionInfo.getId(), sessionInfo); 272 273 if (mRemoteCallback == null) { 274 return; 275 } 276 try { 277 mRemoteCallback.notifySessionCreated(requestId, sessionInfo); 278 } catch (RemoteException ex) { 279 Log.w(TAG, "Failed to notify session created."); 280 } 281 } 282 } 283 284 /** 285 * Notifies the existing session is updated. For example, when 286 * {@link RoutingSessionInfo#getSelectedRoutes() selected routes} are changed. 287 */ notifySessionUpdated(@onNull RoutingSessionInfo sessionInfo)288 public final void notifySessionUpdated(@NonNull RoutingSessionInfo sessionInfo) { 289 Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); 290 291 if (DEBUG) { 292 Log.d(TAG, "notifySessionUpdated: Updating session id=" + sessionInfo); 293 } 294 295 String sessionId = sessionInfo.getId(); 296 synchronized (mSessionLock) { 297 if (mSessionInfos.containsKey(sessionId)) { 298 mSessionInfos.put(sessionId, sessionInfo); 299 } else { 300 Log.w(TAG, "notifySessionUpdated: Ignoring unknown session info."); 301 return; 302 } 303 } 304 scheduleUpdateSessions(); 305 } 306 307 /** 308 * Notifies that the session is released. 309 * 310 * @param sessionId the ID of the released session. 311 * @see #onReleaseSession(long, String) 312 */ notifySessionReleased(@onNull String sessionId)313 public final void notifySessionReleased(@NonNull String sessionId) { 314 if (TextUtils.isEmpty(sessionId)) { 315 throw new IllegalArgumentException("sessionId must not be empty"); 316 } 317 if (DEBUG) { 318 Log.d(TAG, "notifySessionReleased: Releasing session id=" + sessionId); 319 } 320 321 RoutingSessionInfo sessionInfo; 322 synchronized (mSessionLock) { 323 sessionInfo = mSessionInfos.remove(sessionId); 324 325 if (sessionInfo == null) { 326 Log.w(TAG, "notifySessionReleased: Ignoring unknown session info."); 327 return; 328 } 329 330 if (mRemoteCallback == null) { 331 return; 332 } 333 try { 334 mRemoteCallback.notifySessionReleased(sessionInfo); 335 } catch (RemoteException ex) { 336 Log.w(TAG, "Failed to notify session released.", ex); 337 } 338 } 339 } 340 341 /** 342 * Notifies to the client that the request has failed. 343 * 344 * @param requestId the ID of the previous request 345 * @param reason the reason why the request has failed 346 * 347 * @see #REASON_UNKNOWN_ERROR 348 * @see #REASON_REJECTED 349 * @see #REASON_NETWORK_ERROR 350 * @see #REASON_ROUTE_NOT_AVAILABLE 351 * @see #REASON_INVALID_COMMAND 352 */ notifyRequestFailed(long requestId, @Reason int reason)353 public final void notifyRequestFailed(long requestId, @Reason int reason) { 354 if (mRemoteCallback == null) { 355 return; 356 } 357 358 if (!removeRequestId(requestId)) { 359 Log.w(TAG, "notifyRequestFailed: The requestId doesn't exist. requestId=" 360 + requestId); 361 return; 362 } 363 364 try { 365 mRemoteCallback.notifyRequestFailed(requestId, reason); 366 } catch (RemoteException ex) { 367 Log.w(TAG, "Failed to notify that the request has failed."); 368 } 369 } 370 371 /** 372 * Called when the service receives a request to create a session. 373 * <p> 374 * You should create and maintain your own session and notifies the client of 375 * session info. Call {@link #notifySessionCreated(long, RoutingSessionInfo)} 376 * with the given {@code requestId} to notify the information of a new session. 377 * The created session must have the same route feature and must include the given route 378 * specified by {@code routeId}. 379 * <p> 380 * If the session can be controlled, you can optionally pass the control hints to 381 * {@link RoutingSessionInfo.Builder#setControlHints(Bundle)}. Control hints is a 382 * {@link Bundle} which contains how to control the session. 383 * <p> 384 * If you can't create the session or want to reject the request, call 385 * {@link #notifyRequestFailed(long, int)} with the given {@code requestId}. 386 * 387 * @param requestId the ID of this request 388 * @param packageName the package name of the application that selected the route 389 * @param routeId the ID of the route initially being connected 390 * @param sessionHints an optional bundle of app-specific arguments sent by 391 * {@link MediaRouter2}, or null if none. The contents of this bundle 392 * may affect the result of session creation. 393 * 394 * @see RoutingSessionInfo.Builder#Builder(String, String) 395 * @see RoutingSessionInfo.Builder#addSelectedRoute(String) 396 * @see RoutingSessionInfo.Builder#setControlHints(Bundle) 397 */ onCreateSession(long requestId, @NonNull String packageName, @NonNull String routeId, @Nullable Bundle sessionHints)398 public abstract void onCreateSession(long requestId, @NonNull String packageName, 399 @NonNull String routeId, @Nullable Bundle sessionHints); 400 401 /** 402 * Called when the session should be released. A client of the session or system can request 403 * a session to be released. 404 * <p> 405 * After releasing the session, call {@link #notifySessionReleased(String)} 406 * with the ID of the released session. 407 * 408 * Note: Calling {@link #notifySessionReleased(String)} will <em>NOT</em> trigger 409 * this method to be called. 410 * 411 * @param requestId the ID of this request 412 * @param sessionId the ID of the session being released. 413 * @see #notifySessionReleased(String) 414 * @see #getSessionInfo(String) 415 */ onReleaseSession(long requestId, @NonNull String sessionId)416 public abstract void onReleaseSession(long requestId, @NonNull String sessionId); 417 418 /** 419 * Called when a client requests selecting a route for the session. 420 * After the route is selected, call {@link #notifySessionUpdated(RoutingSessionInfo)} 421 * to update session info. 422 * 423 * @param requestId the ID of this request 424 * @param sessionId the ID of the session 425 * @param routeId the ID of the route 426 */ onSelectRoute(long requestId, @NonNull String sessionId, @NonNull String routeId)427 public abstract void onSelectRoute(long requestId, @NonNull String sessionId, 428 @NonNull String routeId); 429 430 /** 431 * Called when a client requests deselecting a route from the session. 432 * After the route is deselected, call {@link #notifySessionUpdated(RoutingSessionInfo)} 433 * to update session info. 434 * 435 * @param requestId the ID of this request 436 * @param sessionId the ID of the session 437 * @param routeId the ID of the route 438 */ onDeselectRoute(long requestId, @NonNull String sessionId, @NonNull String routeId)439 public abstract void onDeselectRoute(long requestId, @NonNull String sessionId, 440 @NonNull String routeId); 441 442 /** 443 * Called when a client requests transferring a session to a route. 444 * After the transfer is finished, call {@link #notifySessionUpdated(RoutingSessionInfo)} 445 * to update session info. 446 * 447 * @param requestId the ID of this request 448 * @param sessionId the ID of the session 449 * @param routeId the ID of the route 450 */ onTransferToRoute(long requestId, @NonNull String sessionId, @NonNull String routeId)451 public abstract void onTransferToRoute(long requestId, @NonNull String sessionId, 452 @NonNull String routeId); 453 454 /** 455 * Called when the {@link RouteDiscoveryPreference discovery preference} has changed. 456 * <p> 457 * Whenever an application registers a {@link MediaRouter2.RouteCallback callback}, 458 * it also provides a discovery preference to specify features of routes that it is interested 459 * in. The media router combines all of these discovery request into a single discovery 460 * preference and notifies each provider. 461 * </p><p> 462 * The provider should examine {@link RouteDiscoveryPreference#getPreferredFeatures() 463 * preferred features} in the discovery preference to determine what kind of routes it should 464 * try to discover and whether it should perform active or passive scans. In many cases, 465 * the provider may be able to save power by not performing any scans when the request doesn't 466 * have any matching route features. 467 * </p> 468 * 469 * @param preference the new discovery preference 470 */ onDiscoveryPreferenceChanged(@onNull RouteDiscoveryPreference preference)471 public void onDiscoveryPreferenceChanged(@NonNull RouteDiscoveryPreference preference) {} 472 473 /** 474 * Updates routes of the provider and notifies the system media router service. 475 */ notifyRoutes(@onNull Collection<MediaRoute2Info> routes)476 public final void notifyRoutes(@NonNull Collection<MediaRoute2Info> routes) { 477 Objects.requireNonNull(routes, "routes must not be null"); 478 List<MediaRoute2Info> sanitizedRoutes = new ArrayList<>(routes.size()); 479 480 for (MediaRoute2Info route : routes) { 481 if (route.isSystemRouteType()) { 482 Log.w( 483 TAG, 484 "Attempting to add a system route type from a non-system route " 485 + "provider. Overriding type to TYPE_UNKNOWN. Route: " 486 + route); 487 sanitizedRoutes.add( 488 new MediaRoute2Info.Builder(route) 489 .setType(MediaRoute2Info.TYPE_UNKNOWN) 490 .build()); 491 } else { 492 sanitizedRoutes.add(route); 493 } 494 } 495 496 mProviderInfo = new MediaRoute2ProviderInfo.Builder().addRoutes(sanitizedRoutes).build(); 497 schedulePublishState(); 498 } 499 setCallback(IMediaRoute2ProviderServiceCallback callback)500 void setCallback(IMediaRoute2ProviderServiceCallback callback) { 501 mRemoteCallback = callback; 502 schedulePublishState(); 503 scheduleUpdateSessions(); 504 } 505 schedulePublishState()506 void schedulePublishState() { 507 if (mStatePublishScheduled.compareAndSet(false, true)) { 508 mHandler.post(this::publishState); 509 } 510 } 511 publishState()512 private void publishState() { 513 if (!mStatePublishScheduled.compareAndSet(true, false)) { 514 return; 515 } 516 517 if (mRemoteCallback == null) { 518 return; 519 } 520 521 if (mProviderInfo == null) { 522 return; 523 } 524 525 try { 526 mRemoteCallback.notifyProviderUpdated(mProviderInfo); 527 } catch (RemoteException ex) { 528 Log.w(TAG, "Failed to publish provider state.", ex); 529 } 530 } 531 scheduleUpdateSessions()532 void scheduleUpdateSessions() { 533 if (mSessionUpdateScheduled.compareAndSet(false, true)) { 534 mHandler.post(this::updateSessions); 535 } 536 } 537 updateSessions()538 private void updateSessions() { 539 if (!mSessionUpdateScheduled.compareAndSet(true, false)) { 540 return; 541 } 542 543 if (mRemoteCallback == null) { 544 return; 545 } 546 547 List<RoutingSessionInfo> sessions; 548 synchronized (mSessionLock) { 549 sessions = new ArrayList<>(mSessionInfos.values()); 550 } 551 552 try { 553 mRemoteCallback.notifySessionsUpdated(sessions); 554 } catch (RemoteException ex) { 555 Log.w(TAG, "Failed to notify session info changed."); 556 } 557 558 } 559 560 /** 561 * Adds a requestId in the request ID list whose max size is {@link #MAX_REQUEST_IDS_SIZE}. 562 * When the max size is reached, the first element is removed (FIFO). 563 */ addRequestId(long requestId)564 private void addRequestId(long requestId) { 565 synchronized (mRequestIdsLock) { 566 if (mRequestIds.size() >= MAX_REQUEST_IDS_SIZE) { 567 mRequestIds.removeFirst(); 568 } 569 mRequestIds.addLast(requestId); 570 } 571 } 572 573 /** 574 * Removes the given {@code requestId} from received request ID list. 575 * <p> 576 * Returns whether the list contains the {@code requestId}. These are the cases when the list 577 * doesn't contain the given {@code requestId}: 578 * <ul> 579 * <li>This service has never received a request with the requestId. </li> 580 * <li>{@link #notifyRequestFailed} or {@link #notifySessionCreated} already has been called 581 * for the requestId. </li> 582 * </ul> 583 */ removeRequestId(long requestId)584 private boolean removeRequestId(long requestId) { 585 synchronized (mRequestIdsLock) { 586 return mRequestIds.removeFirstOccurrence(requestId); 587 } 588 } 589 590 final class MediaRoute2ProviderServiceStub extends IMediaRoute2ProviderService.Stub { MediaRoute2ProviderServiceStub()591 MediaRoute2ProviderServiceStub() { } 592 checkCallerIsSystem()593 private boolean checkCallerIsSystem() { 594 return Binder.getCallingUid() == Process.SYSTEM_UID; 595 } 596 checkSessionIdIsValid(String sessionId, String description)597 private boolean checkSessionIdIsValid(String sessionId, String description) { 598 if (TextUtils.isEmpty(sessionId)) { 599 Log.w(TAG, description + ": Ignoring empty sessionId from system service."); 600 return false; 601 } 602 if (getSessionInfo(sessionId) == null) { 603 Log.w(TAG, description + ": Ignoring unknown session from system service. " 604 + "sessionId=" + sessionId); 605 return false; 606 } 607 return true; 608 } 609 checkRouteIdIsValid(String routeId, String description)610 private boolean checkRouteIdIsValid(String routeId, String description) { 611 if (TextUtils.isEmpty(routeId)) { 612 Log.w(TAG, description + ": Ignoring empty routeId from system service."); 613 return false; 614 } 615 if (mProviderInfo == null || mProviderInfo.getRoute(routeId) == null) { 616 Log.w(TAG, description + ": Ignoring unknown route from system service. " 617 + "routeId=" + routeId); 618 return false; 619 } 620 return true; 621 } 622 623 @Override setCallback(IMediaRoute2ProviderServiceCallback callback)624 public void setCallback(IMediaRoute2ProviderServiceCallback callback) { 625 if (!checkCallerIsSystem()) { 626 return; 627 } 628 mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::setCallback, 629 MediaRoute2ProviderService.this, callback)); 630 } 631 632 @Override updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference)633 public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) { 634 if (!checkCallerIsSystem()) { 635 return; 636 } 637 mHandler.sendMessage(obtainMessage( 638 MediaRoute2ProviderService::onDiscoveryPreferenceChanged, 639 MediaRoute2ProviderService.this, discoveryPreference)); 640 } 641 642 @Override setRouteVolume(long requestId, String routeId, int volume)643 public void setRouteVolume(long requestId, String routeId, int volume) { 644 if (!checkCallerIsSystem()) { 645 return; 646 } 647 if (!checkRouteIdIsValid(routeId, "setRouteVolume")) { 648 return; 649 } 650 addRequestId(requestId); 651 mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetRouteVolume, 652 MediaRoute2ProviderService.this, requestId, routeId, volume)); 653 } 654 655 @Override requestCreateSession(long requestId, String packageName, String routeId, @Nullable Bundle requestCreateSession)656 public void requestCreateSession(long requestId, String packageName, String routeId, 657 @Nullable Bundle requestCreateSession) { 658 if (!checkCallerIsSystem()) { 659 return; 660 } 661 if (!checkRouteIdIsValid(routeId, "requestCreateSession")) { 662 return; 663 } 664 addRequestId(requestId); 665 mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession, 666 MediaRoute2ProviderService.this, requestId, packageName, routeId, 667 requestCreateSession)); 668 } 669 670 @Override selectRoute(long requestId, String sessionId, String routeId)671 public void selectRoute(long requestId, String sessionId, String routeId) { 672 if (!checkCallerIsSystem()) { 673 return; 674 } 675 if (!checkSessionIdIsValid(sessionId, "selectRoute") 676 || !checkRouteIdIsValid(routeId, "selectRoute")) { 677 return; 678 } 679 addRequestId(requestId); 680 mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute, 681 MediaRoute2ProviderService.this, requestId, sessionId, routeId)); 682 } 683 684 @Override deselectRoute(long requestId, String sessionId, String routeId)685 public void deselectRoute(long requestId, String sessionId, String routeId) { 686 if (!checkCallerIsSystem()) { 687 return; 688 } 689 if (!checkSessionIdIsValid(sessionId, "deselectRoute") 690 || !checkRouteIdIsValid(routeId, "deselectRoute")) { 691 return; 692 } 693 addRequestId(requestId); 694 mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onDeselectRoute, 695 MediaRoute2ProviderService.this, requestId, sessionId, routeId)); 696 } 697 698 @Override transferToRoute(long requestId, String sessionId, String routeId)699 public void transferToRoute(long requestId, String sessionId, String routeId) { 700 if (!checkCallerIsSystem()) { 701 return; 702 } 703 if (!checkSessionIdIsValid(sessionId, "transferToRoute") 704 || !checkRouteIdIsValid(routeId, "transferToRoute")) { 705 return; 706 } 707 addRequestId(requestId); 708 mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferToRoute, 709 MediaRoute2ProviderService.this, requestId, sessionId, routeId)); 710 } 711 712 @Override setSessionVolume(long requestId, String sessionId, int volume)713 public void setSessionVolume(long requestId, String sessionId, int volume) { 714 if (!checkCallerIsSystem()) { 715 return; 716 } 717 if (!checkSessionIdIsValid(sessionId, "setSessionVolume")) { 718 return; 719 } 720 addRequestId(requestId); 721 mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetSessionVolume, 722 MediaRoute2ProviderService.this, requestId, sessionId, volume)); 723 } 724 725 @Override releaseSession(long requestId, String sessionId)726 public void releaseSession(long requestId, String sessionId) { 727 if (!checkCallerIsSystem()) { 728 return; 729 } 730 if (!checkSessionIdIsValid(sessionId, "releaseSession")) { 731 return; 732 } 733 addRequestId(requestId); 734 mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession, 735 MediaRoute2ProviderService.this, requestId, sessionId)); 736 } 737 } 738 } 739