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