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