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 mProviderInfo = new MediaRoute2ProviderInfo.Builder() 479 .addRoutes(routes) 480 .build(); 481 schedulePublishState(); 482 } 483 setCallback(IMediaRoute2ProviderServiceCallback callback)484 void setCallback(IMediaRoute2ProviderServiceCallback callback) { 485 mRemoteCallback = callback; 486 schedulePublishState(); 487 scheduleUpdateSessions(); 488 } 489 schedulePublishState()490 void schedulePublishState() { 491 if (mStatePublishScheduled.compareAndSet(false, true)) { 492 mHandler.post(this::publishState); 493 } 494 } 495 publishState()496 private void publishState() { 497 if (!mStatePublishScheduled.compareAndSet(true, false)) { 498 return; 499 } 500 501 if (mRemoteCallback == null) { 502 return; 503 } 504 505 try { 506 mRemoteCallback.notifyProviderUpdated(mProviderInfo); 507 } catch (RemoteException ex) { 508 Log.w(TAG, "Failed to publish provider state.", ex); 509 } 510 } 511 scheduleUpdateSessions()512 void scheduleUpdateSessions() { 513 if (mSessionUpdateScheduled.compareAndSet(false, true)) { 514 mHandler.post(this::updateSessions); 515 } 516 } 517 updateSessions()518 private void updateSessions() { 519 if (!mSessionUpdateScheduled.compareAndSet(true, false)) { 520 return; 521 } 522 523 if (mRemoteCallback == null) { 524 return; 525 } 526 527 List<RoutingSessionInfo> sessions; 528 synchronized (mSessionLock) { 529 sessions = new ArrayList<>(mSessionInfos.values()); 530 } 531 532 try { 533 mRemoteCallback.notifySessionsUpdated(sessions); 534 } catch (RemoteException ex) { 535 Log.w(TAG, "Failed to notify session info changed."); 536 } 537 538 } 539 540 /** 541 * Adds a requestId in the request ID list whose max size is {@link #MAX_REQUEST_IDS_SIZE}. 542 * When the max size is reached, the first element is removed (FIFO). 543 */ addRequestId(long requestId)544 private void addRequestId(long requestId) { 545 synchronized (mRequestIdsLock) { 546 if (mRequestIds.size() >= MAX_REQUEST_IDS_SIZE) { 547 mRequestIds.removeFirst(); 548 } 549 mRequestIds.addLast(requestId); 550 } 551 } 552 553 /** 554 * Removes the given {@code requestId} from received request ID list. 555 * <p> 556 * Returns whether the list contains the {@code requestId}. These are the cases when the list 557 * doesn't contain the given {@code requestId}: 558 * <ul> 559 * <li>This service has never received a request with the requestId. </li> 560 * <li>{@link #notifyRequestFailed} or {@link #notifySessionCreated} already has been called 561 * for the requestId. </li> 562 * </ul> 563 */ removeRequestId(long requestId)564 private boolean removeRequestId(long requestId) { 565 synchronized (mRequestIdsLock) { 566 return mRequestIds.removeFirstOccurrence(requestId); 567 } 568 } 569 570 final class MediaRoute2ProviderServiceStub extends IMediaRoute2ProviderService.Stub { MediaRoute2ProviderServiceStub()571 MediaRoute2ProviderServiceStub() { } 572 checkCallerIsSystem()573 private boolean checkCallerIsSystem() { 574 return Binder.getCallingUid() == Process.SYSTEM_UID; 575 } 576 checkSessionIdIsValid(String sessionId, String description)577 private boolean checkSessionIdIsValid(String sessionId, String description) { 578 if (TextUtils.isEmpty(sessionId)) { 579 Log.w(TAG, description + ": Ignoring empty sessionId from system service."); 580 return false; 581 } 582 if (getSessionInfo(sessionId) == null) { 583 Log.w(TAG, description + ": Ignoring unknown session from system service. " 584 + "sessionId=" + sessionId); 585 return false; 586 } 587 return true; 588 } 589 checkRouteIdIsValid(String routeId, String description)590 private boolean checkRouteIdIsValid(String routeId, String description) { 591 if (TextUtils.isEmpty(routeId)) { 592 Log.w(TAG, description + ": Ignoring empty routeId from system service."); 593 return false; 594 } 595 if (mProviderInfo == null || mProviderInfo.getRoute(routeId) == null) { 596 Log.w(TAG, description + ": Ignoring unknown route from system service. " 597 + "routeId=" + routeId); 598 return false; 599 } 600 return true; 601 } 602 603 @Override setCallback(IMediaRoute2ProviderServiceCallback callback)604 public void setCallback(IMediaRoute2ProviderServiceCallback callback) { 605 if (!checkCallerIsSystem()) { 606 return; 607 } 608 mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::setCallback, 609 MediaRoute2ProviderService.this, callback)); 610 } 611 612 @Override updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference)613 public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) { 614 if (!checkCallerIsSystem()) { 615 return; 616 } 617 mHandler.sendMessage(obtainMessage( 618 MediaRoute2ProviderService::onDiscoveryPreferenceChanged, 619 MediaRoute2ProviderService.this, discoveryPreference)); 620 } 621 622 @Override setRouteVolume(long requestId, String routeId, int volume)623 public void setRouteVolume(long requestId, String routeId, int volume) { 624 if (!checkCallerIsSystem()) { 625 return; 626 } 627 if (!checkRouteIdIsValid(routeId, "setRouteVolume")) { 628 return; 629 } 630 addRequestId(requestId); 631 mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetRouteVolume, 632 MediaRoute2ProviderService.this, requestId, routeId, volume)); 633 } 634 635 @Override requestCreateSession(long requestId, String packageName, String routeId, @Nullable Bundle requestCreateSession)636 public void requestCreateSession(long requestId, String packageName, String routeId, 637 @Nullable Bundle requestCreateSession) { 638 if (!checkCallerIsSystem()) { 639 return; 640 } 641 if (!checkRouteIdIsValid(routeId, "requestCreateSession")) { 642 return; 643 } 644 addRequestId(requestId); 645 mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession, 646 MediaRoute2ProviderService.this, requestId, packageName, routeId, 647 requestCreateSession)); 648 } 649 650 @Override selectRoute(long requestId, String sessionId, String routeId)651 public void selectRoute(long requestId, String sessionId, String routeId) { 652 if (!checkCallerIsSystem()) { 653 return; 654 } 655 if (!checkSessionIdIsValid(sessionId, "selectRoute") 656 || !checkRouteIdIsValid(routeId, "selectRoute")) { 657 return; 658 } 659 addRequestId(requestId); 660 mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute, 661 MediaRoute2ProviderService.this, requestId, sessionId, routeId)); 662 } 663 664 @Override deselectRoute(long requestId, String sessionId, String routeId)665 public void deselectRoute(long requestId, String sessionId, String routeId) { 666 if (!checkCallerIsSystem()) { 667 return; 668 } 669 if (!checkSessionIdIsValid(sessionId, "deselectRoute") 670 || !checkRouteIdIsValid(routeId, "deselectRoute")) { 671 return; 672 } 673 addRequestId(requestId); 674 mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onDeselectRoute, 675 MediaRoute2ProviderService.this, requestId, sessionId, routeId)); 676 } 677 678 @Override transferToRoute(long requestId, String sessionId, String routeId)679 public void transferToRoute(long requestId, String sessionId, String routeId) { 680 if (!checkCallerIsSystem()) { 681 return; 682 } 683 if (!checkSessionIdIsValid(sessionId, "transferToRoute") 684 || !checkRouteIdIsValid(routeId, "transferToRoute")) { 685 return; 686 } 687 addRequestId(requestId); 688 mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferToRoute, 689 MediaRoute2ProviderService.this, requestId, sessionId, routeId)); 690 } 691 692 @Override setSessionVolume(long requestId, String sessionId, int volume)693 public void setSessionVolume(long requestId, String sessionId, int volume) { 694 if (!checkCallerIsSystem()) { 695 return; 696 } 697 if (!checkSessionIdIsValid(sessionId, "setSessionVolume")) { 698 return; 699 } 700 addRequestId(requestId); 701 mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetSessionVolume, 702 MediaRoute2ProviderService.this, requestId, sessionId, volume)); 703 } 704 705 @Override releaseSession(long requestId, String sessionId)706 public void releaseSession(long requestId, String sessionId) { 707 if (!checkCallerIsSystem()) { 708 return; 709 } 710 if (!checkSessionIdIsValid(sessionId, "releaseSession")) { 711 return; 712 } 713 addRequestId(requestId); 714 mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession, 715 MediaRoute2ProviderService.this, requestId, sessionId)); 716 } 717 } 718 } 719