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 com.android.server.media; 18 19 import static android.media.MediaRoute2ProviderService.REASON_REJECTED; 20 import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE; 21 22 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.ServiceConnection; 30 import android.media.IMediaRoute2ProviderService; 31 import android.media.IMediaRoute2ProviderServiceCallback; 32 import android.media.MediaRoute2Info; 33 import android.media.MediaRoute2ProviderInfo; 34 import android.media.MediaRoute2ProviderService; 35 import android.media.MediaRoute2ProviderService.Reason; 36 import android.media.RouteDiscoveryPreference; 37 import android.media.RoutingSessionInfo; 38 import android.os.Bundle; 39 import android.os.Handler; 40 import android.os.IBinder; 41 import android.os.IBinder.DeathRecipient; 42 import android.os.Looper; 43 import android.os.RemoteException; 44 import android.os.UserHandle; 45 import android.text.TextUtils; 46 import android.util.ArrayMap; 47 import android.util.Log; 48 import android.util.LongSparseArray; 49 import android.util.Slog; 50 51 import com.android.internal.annotations.GuardedBy; 52 import com.android.media.flags.Flags; 53 54 import java.lang.ref.WeakReference; 55 import java.util.ArrayList; 56 import java.util.Collections; 57 import java.util.HashMap; 58 import java.util.List; 59 import java.util.Map; 60 import java.util.Objects; 61 import java.util.Set; 62 63 /** Maintains a connection to a particular {@link MediaRoute2ProviderService}. */ 64 final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { 65 private static final String TAG = "MR2ProviderSvcProxy"; 66 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 67 68 private final Context mContext; 69 private final int mUserId; 70 private final Handler mHandler; 71 private final boolean mIsSelfScanOnlyProvider; 72 private final boolean mSupportsSystemMediaRouting; 73 private final ServiceConnection mServiceConnection = new ServiceConnectionImpl(); 74 75 // Connection state 76 private boolean mRunning; 77 private boolean mBound; 78 private Connection mActiveConnection; 79 private boolean mConnectionReady; 80 81 private boolean mIsManagerScanning; 82 private RouteDiscoveryPreference mLastDiscoveryPreference = null; 83 private boolean mLastDiscoveryPreferenceIncludesThisPackage = false; 84 85 @GuardedBy("mLock") 86 private final List<RoutingSessionInfo> mReleasingSessions = new ArrayList<>(); 87 88 // We keep pending requests for transfers and sessions creation separately because transfers 89 // don't have an associated request id and session creations don't have a session id. 90 @GuardedBy("mLock") 91 private final LongSparseArray<SessionCreationOrTransferRequest> 92 mRequestIdToSessionCreationRequest; 93 94 @GuardedBy("mLock") 95 private final Map<String, SystemMediaSessionCallback> mSystemSessionCallbacks; 96 97 @GuardedBy("mLock") 98 private final LongSparseArray<SystemMediaSessionCallback> mRequestIdToSystemSessionRequest; 99 100 @GuardedBy("mLock") 101 private final Map<String, SessionCreationOrTransferRequest> mSessionOriginalIdToTransferRequest; 102 MediaRoute2ProviderServiceProxy( @onNull Context context, @NonNull Looper looper, @NonNull ComponentName componentName, boolean isSelfScanOnlyProvider, boolean supportsSystemMediaRouting, int userId)103 MediaRoute2ProviderServiceProxy( 104 @NonNull Context context, 105 @NonNull Looper looper, 106 @NonNull ComponentName componentName, 107 boolean isSelfScanOnlyProvider, 108 boolean supportsSystemMediaRouting, 109 int userId) { 110 super(componentName, /* isSystemRouteProvider= */ false); 111 mContext = Objects.requireNonNull(context, "Context must not be null."); 112 mRequestIdToSessionCreationRequest = new LongSparseArray<>(); 113 mSessionOriginalIdToTransferRequest = new HashMap<>(); 114 mRequestIdToSystemSessionRequest = new LongSparseArray<>(); 115 mSystemSessionCallbacks = new ArrayMap<>(); 116 mIsSelfScanOnlyProvider = isSelfScanOnlyProvider; 117 mSupportsSystemMediaRouting = supportsSystemMediaRouting; 118 mUserId = userId; 119 mHandler = new Handler(looper); 120 } 121 setManagerScanning(boolean managerScanning)122 public void setManagerScanning(boolean managerScanning) { 123 if (mIsManagerScanning != managerScanning) { 124 mIsManagerScanning = managerScanning; 125 updateBinding(); 126 } 127 } 128 129 @Override requestCreateSession( long requestId, String packageName, String routeOriginalId, Bundle sessionHints, @RoutingSessionInfo.TransferReason int transferReason, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName)130 public void requestCreateSession( 131 long requestId, 132 String packageName, 133 String routeOriginalId, 134 Bundle sessionHints, 135 @RoutingSessionInfo.TransferReason int transferReason, 136 @NonNull UserHandle transferInitiatorUserHandle, 137 @NonNull String transferInitiatorPackageName) { 138 if (mConnectionReady) { 139 if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { 140 synchronized (mLock) { 141 mRequestIdToSessionCreationRequest.put( 142 requestId, 143 new SessionCreationOrTransferRequest( 144 requestId, 145 routeOriginalId, 146 transferReason, 147 transferInitiatorUserHandle, 148 transferInitiatorPackageName)); 149 } 150 } 151 mActiveConnection.requestCreateSession( 152 requestId, packageName, routeOriginalId, sessionHints); 153 updateBinding(); 154 } 155 } 156 157 @Override releaseSession(long requestId, String sessionId)158 public void releaseSession(long requestId, String sessionId) { 159 if (mConnectionReady) { 160 if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { 161 synchronized (mLock) { 162 mSessionOriginalIdToTransferRequest.remove(sessionId); 163 } 164 } 165 mActiveConnection.releaseSession(requestId, sessionId); 166 updateBinding(); 167 } 168 } 169 170 @Override updateDiscoveryPreference( Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference)171 public void updateDiscoveryPreference( 172 Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference) { 173 mLastDiscoveryPreference = discoveryPreference; 174 mLastDiscoveryPreferenceIncludesThisPackage = 175 activelyScanningPackages.contains(mComponentName.getPackageName()); 176 if (mConnectionReady) { 177 mActiveConnection.updateDiscoveryPreference(discoveryPreference); 178 } 179 updateBinding(); 180 } 181 182 @Override selectRoute(long requestId, String sessionId, String routeId)183 public void selectRoute(long requestId, String sessionId, String routeId) { 184 if (mConnectionReady) { 185 mActiveConnection.selectRoute(requestId, sessionId, routeId); 186 } 187 } 188 189 @Override deselectRoute(long requestId, String sessionId, String routeId)190 public void deselectRoute(long requestId, String sessionId, String routeId) { 191 if (mConnectionReady) { 192 mActiveConnection.deselectRoute(requestId, sessionId, routeId); 193 } 194 } 195 196 @Override transferToRoute( long requestId, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName, String sessionOriginalId, String routeOriginalId, @RoutingSessionInfo.TransferReason int transferReason)197 public void transferToRoute( 198 long requestId, 199 @NonNull UserHandle transferInitiatorUserHandle, 200 @NonNull String transferInitiatorPackageName, 201 String sessionOriginalId, 202 String routeOriginalId, 203 @RoutingSessionInfo.TransferReason int transferReason) { 204 if (mConnectionReady) { 205 if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { 206 synchronized (mLock) { 207 mSessionOriginalIdToTransferRequest.put( 208 sessionOriginalId, 209 new SessionCreationOrTransferRequest( 210 requestId, 211 routeOriginalId, 212 transferReason, 213 transferInitiatorUserHandle, 214 transferInitiatorPackageName)); 215 } 216 } 217 mActiveConnection.transferToRoute(requestId, sessionOriginalId, routeOriginalId); 218 } 219 } 220 221 @Override setRouteVolume(long requestId, String routeOriginalId, int volume)222 public void setRouteVolume(long requestId, String routeOriginalId, int volume) { 223 if (mConnectionReady) { 224 mActiveConnection.setRouteVolume(requestId, routeOriginalId, volume); 225 updateBinding(); 226 } 227 } 228 229 @Override setSessionVolume(long requestId, String sessionOriginalId, int volume)230 public void setSessionVolume(long requestId, String sessionOriginalId, int volume) { 231 if (mConnectionReady) { 232 mActiveConnection.setSessionVolume(requestId, sessionOriginalId, volume); 233 updateBinding(); 234 } 235 } 236 237 @Override prepareReleaseSession(@onNull String sessionUniqueId)238 public void prepareReleaseSession(@NonNull String sessionUniqueId) { 239 synchronized (mLock) { 240 for (RoutingSessionInfo session : mSessionInfos) { 241 if (TextUtils.equals(session.getId(), sessionUniqueId)) { 242 mSessionInfos.remove(session); 243 mReleasingSessions.add(session); 244 break; 245 } 246 } 247 } 248 } 249 250 /** 251 * Requests the creation of a system media routing session. 252 * 253 * @param requestId The id of the request. 254 * @param uid The uid of the package whose media to route, or {@link 255 * android.os.Process#INVALID_UID} if not applicable (for example, if all the system's media 256 * must be routed). 257 * @param packageName The package name to populate {@link 258 * RoutingSessionInfo#getClientPackageName()}. 259 * @param routeId The id of the route to be initially {@link 260 * RoutingSessionInfo#getSelectedRoutes()}. 261 * @param sessionHints An optional bundle with paramets. 262 * @param callback A {@link SystemMediaSessionCallback} to notify of session events. 263 * @see MediaRoute2ProviderService#onCreateSystemRoutingSession 264 */ requestCreateSystemMediaSession( long requestId, int uid, String packageName, String routeId, @Nullable Bundle sessionHints, @NonNull SystemMediaSessionCallback callback)265 public void requestCreateSystemMediaSession( 266 long requestId, 267 int uid, 268 String packageName, 269 String routeId, 270 @Nullable Bundle sessionHints, 271 @NonNull SystemMediaSessionCallback callback) { 272 if (!Flags.enableMirroringInMediaRouter2()) { 273 throw new IllegalStateException( 274 "Unexpected call to requestCreateSystemMediaSession. Governing flag is" 275 + " disabled."); 276 } 277 if (mConnectionReady) { 278 boolean binderRequestSucceeded = 279 mActiveConnection.requestCreateSystemMediaSession( 280 requestId, uid, packageName, routeId, sessionHints); 281 if (!binderRequestSucceeded) { 282 // notify failure. 283 return; 284 } 285 updateBinding(); 286 synchronized (mLock) { 287 mRequestIdToSystemSessionRequest.put(requestId, callback); 288 } 289 } 290 } 291 hasComponentName(String packageName, String className)292 public boolean hasComponentName(String packageName, String className) { 293 return mComponentName.getPackageName().equals(packageName) 294 && mComponentName.getClassName().equals(className); 295 } 296 start(boolean rebindIfDisconnected)297 public void start(boolean rebindIfDisconnected) { 298 if (!mRunning) { 299 if (DEBUG) { 300 Slog.d(TAG, this + ": Starting"); 301 } 302 mRunning = true; 303 if (!Flags.enablePreventionOfKeepAliveRouteProviders()) { 304 updateBinding(); 305 } 306 } 307 if (rebindIfDisconnected && mActiveConnection == null && shouldBind()) { 308 unbind(); 309 bind(); 310 } 311 } 312 stop()313 public void stop() { 314 if (mRunning) { 315 if (DEBUG) { 316 Slog.d(TAG, this + ": Stopping"); 317 } 318 mRunning = false; 319 updateBinding(); 320 } 321 } 322 updateBinding()323 private void updateBinding() { 324 if (shouldBind()) { 325 bind(); 326 } else { 327 unbind(); 328 } 329 } 330 shouldBind()331 private boolean shouldBind() { 332 if (!mRunning) { 333 return false; 334 } 335 // We bind if any manager is scanning (regardless of whether an app is scanning) to give 336 // the opportunity for providers to publish routing sessions that were established 337 // directly between the app and the provider (typically via AndroidX MediaRouter). See 338 // b/176774510#comment20 for more information. 339 boolean bindDueToManagerScan = 340 mIsManagerScanning && !Flags.enablePreventionOfManagerScansWhenNoAppsScan(); 341 // We also bind if this provider supports system media routing, because even if an app 342 // doesn't have any registered discovery preference, we should still be able to route their 343 // system media. 344 boolean bindDueToSystemMediaRoutingSupport = 345 mLastDiscoveryPreference != null 346 && mLastDiscoveryPreference.shouldPerformActiveScan() 347 && mSupportsSystemMediaRouting; 348 boolean bindDueToOngoingSystemMediaRoutingSessions = false; 349 if (Flags.enableMirroringInMediaRouter2()) { 350 synchronized (mLock) { 351 bindDueToOngoingSystemMediaRoutingSessions = !mSystemSessionCallbacks.isEmpty(); 352 } 353 } 354 if (!getSessionInfos().isEmpty() 355 || bindDueToOngoingSystemMediaRoutingSessions 356 || bindDueToManagerScan 357 || bindDueToSystemMediaRoutingSupport) { 358 return true; 359 } 360 boolean anAppIsScanning = 361 mLastDiscoveryPreference != null 362 && !mLastDiscoveryPreference.getPreferredFeatures().isEmpty(); 363 return anAppIsScanning 364 && (mLastDiscoveryPreferenceIncludesThisPackage || !mIsSelfScanOnlyProvider); 365 } 366 bind()367 private void bind() { 368 if (!mBound) { 369 if (DEBUG) { 370 Slog.d(TAG, this + ": Binding"); 371 } 372 373 Intent service = new Intent(MediaRoute2ProviderService.SERVICE_INTERFACE); 374 service.setComponent(mComponentName); 375 try { 376 mBound = 377 mContext.bindServiceAsUser( 378 service, 379 mServiceConnection, 380 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, 381 new UserHandle(mUserId)); 382 if (!mBound && DEBUG) { 383 Slog.d(TAG, this + ": Bind failed"); 384 } 385 } catch (SecurityException ex) { 386 if (DEBUG) { 387 Slog.d(TAG, this + ": Bind failed", ex); 388 } 389 } 390 } 391 } 392 unbind()393 private void unbind() { 394 if (mBound) { 395 if (DEBUG) { 396 Slog.d(TAG, this + ": Unbinding"); 397 } 398 399 mBound = false; 400 disconnect(); 401 mContext.unbindService(mServiceConnection); 402 } 403 } 404 onServiceConnectedInternal(IBinder service)405 private void onServiceConnectedInternal(IBinder service) { 406 if (DEBUG) { 407 Slog.d(TAG, this + ": Connected"); 408 } 409 410 if (mBound) { 411 disconnect(); 412 IMediaRoute2ProviderService serviceBinder = 413 IMediaRoute2ProviderService.Stub.asInterface(service); 414 if (serviceBinder != null) { 415 Connection connection = new Connection(serviceBinder); 416 if (connection.register()) { 417 mActiveConnection = connection; 418 } else { 419 if (DEBUG) { 420 Slog.d(TAG, this + ": Registration failed"); 421 } 422 } 423 } else { 424 Slog.e(TAG, this + ": Service returned invalid binder"); 425 } 426 } 427 } 428 onServiceDisconnectedInternal()429 private void onServiceDisconnectedInternal() { 430 if (DEBUG) { 431 Slog.d(TAG, this + ": Service disconnected"); 432 } 433 disconnect(); 434 } 435 onBindingDiedInternal(ComponentName name)436 private void onBindingDiedInternal(ComponentName name) { 437 unbind(); 438 if (Flags.enablePreventionOfKeepAliveRouteProviders()) { 439 Slog.w( 440 TAG, 441 TextUtils.formatSimple( 442 "Route provider service (%s) binding died, but we did not rebind.", 443 name.toString())); 444 } else if (shouldBind()) { 445 Slog.w( 446 TAG, 447 TextUtils.formatSimple( 448 "Rebound to provider service (%s) after binding died.", 449 name.toString())); 450 bind(); 451 } 452 } 453 onConnectionReady(Connection connection)454 private void onConnectionReady(Connection connection) { 455 if (mActiveConnection == connection) { 456 mConnectionReady = true; 457 if (mLastDiscoveryPreference != null) { 458 updateDiscoveryPreference( 459 mLastDiscoveryPreferenceIncludesThisPackage 460 ? Set.of(mComponentName.getPackageName()) 461 : Set.of(), 462 mLastDiscoveryPreference); 463 } 464 } 465 } 466 onConnectionDied(Connection connection)467 private void onConnectionDied(Connection connection) { 468 if (mActiveConnection == connection) { 469 if (DEBUG) { 470 Slog.d(TAG, this + ": Service connection died"); 471 } 472 disconnect(); 473 } 474 } 475 onProviderUpdated(Connection connection, MediaRoute2ProviderInfo providerInfo)476 private void onProviderUpdated(Connection connection, MediaRoute2ProviderInfo providerInfo) { 477 if (mActiveConnection != connection) { 478 return; 479 } 480 if (DEBUG) { 481 Slog.d(TAG, this + ": updated"); 482 } 483 setAndNotifyProviderState(providerInfo); 484 } 485 onSessionCreated(Connection connection, long requestId, RoutingSessionInfo newSession)486 private void onSessionCreated(Connection connection, long requestId, 487 RoutingSessionInfo newSession) { 488 if (mActiveConnection != connection) { 489 return; 490 } 491 492 if (newSession == null) { 493 Slog.w(TAG, "onSessionCreated: Ignoring null session sent from " + mComponentName); 494 return; 495 } 496 497 newSession = assignProviderIdForSession(newSession); 498 String newSessionId = newSession.getId(); 499 500 synchronized (mLock) { 501 var systemMediaSessionCallback = mRequestIdToSystemSessionRequest.get(requestId); 502 if (systemMediaSessionCallback != null) { 503 mRequestIdToSystemSessionRequest.remove(requestId); 504 mSystemSessionCallbacks.put(newSession.getOriginalId(), systemMediaSessionCallback); 505 systemMediaSessionCallback.onSessionUpdate(newSession); 506 return; 507 } 508 509 if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { 510 newSession = 511 createSessionWithPopulatedTransferInitiationDataLocked( 512 requestId, /* oldSessionInfo= */ null, newSession); 513 } 514 if (mSessionInfos.stream() 515 .anyMatch(session -> TextUtils.equals(session.getId(), newSessionId)) 516 || mReleasingSessions.stream() 517 .anyMatch(session -> TextUtils.equals(session.getId(), newSessionId))) { 518 Slog.w(TAG, "onSessionCreated: Duplicate session already exists. Ignoring."); 519 return; 520 } 521 mSessionInfos.add(newSession); 522 } 523 524 mCallback.onSessionCreated(this, requestId, newSession); 525 } 526 527 @GuardedBy("mLock") findSessionByIdLocked(RoutingSessionInfo session)528 private int findSessionByIdLocked(RoutingSessionInfo session) { 529 for (int i = 0; i < mSessionInfos.size(); i++) { 530 if (TextUtils.equals(mSessionInfos.get(i).getId(), session.getId())) { 531 return i; 532 } 533 } 534 return -1; 535 } 536 537 onSessionsUpdated(Connection connection, List<RoutingSessionInfo> sessions)538 private void onSessionsUpdated(Connection connection, List<RoutingSessionInfo> sessions) { 539 if (mActiveConnection != connection) { 540 return; 541 } 542 543 int targetIndex = 0; 544 synchronized (mLock) { 545 for (RoutingSessionInfo session : sessions) { 546 if (session == null) continue; 547 session = assignProviderIdForSession(session); 548 549 if (Flags.enableMirroringInMediaRouter2()) { 550 var systemSessionCallback = 551 mSystemSessionCallbacks.get(session.getOriginalId()); 552 if (systemSessionCallback != null) { 553 systemSessionCallback.onSessionUpdate(session); 554 continue; 555 } 556 } 557 558 int sourceIndex = findSessionByIdLocked(session); 559 if (sourceIndex < 0) { 560 mSessionInfos.add(targetIndex++, session); 561 dispatchSessionCreated(REQUEST_ID_NONE, session); 562 } else if (sourceIndex < targetIndex) { 563 Slog.w(TAG, "Ignoring duplicate session ID: " + session.getId()); 564 } else { 565 if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { 566 RoutingSessionInfo oldSessionInfo = mSessionInfos.get(sourceIndex); 567 session = 568 createSessionWithPopulatedTransferInitiationDataLocked( 569 REQUEST_ID_NONE, oldSessionInfo, session); 570 } 571 mSessionInfos.set(sourceIndex, session); 572 Collections.swap(mSessionInfos, sourceIndex, targetIndex++); 573 dispatchSessionUpdated(session); 574 } 575 } 576 for (int i = mSessionInfos.size() - 1; i >= targetIndex; i--) { 577 RoutingSessionInfo releasedSession = mSessionInfos.remove(i); 578 mSessionOriginalIdToTransferRequest.remove(releasedSession.getId()); 579 dispatchSessionReleased(releasedSession); 580 } 581 } 582 } 583 584 /** 585 * Returns a {@link RoutingSessionInfo} with transfer initiation data from the given {@code 586 * oldSessionInfo}, and any pending transfer or session creation requests. 587 */ 588 @GuardedBy("mLock") createSessionWithPopulatedTransferInitiationDataLocked( long requestId, @Nullable RoutingSessionInfo oldSessionInfo, @NonNull RoutingSessionInfo newSessionInfo)589 private RoutingSessionInfo createSessionWithPopulatedTransferInitiationDataLocked( 590 long requestId, 591 @Nullable RoutingSessionInfo oldSessionInfo, 592 @NonNull RoutingSessionInfo newSessionInfo) { 593 SessionCreationOrTransferRequest pendingRequest = 594 oldSessionInfo != null 595 ? mSessionOriginalIdToTransferRequest.get(newSessionInfo.getOriginalId()) 596 : mRequestIdToSessionCreationRequest.get(requestId); 597 boolean pendingTargetRouteInSelectedRoutes = 598 pendingRequest != null 599 && pendingRequest.isTargetRouteIdInRouteUniqueIdList( 600 newSessionInfo.getSelectedRoutes()); 601 boolean pendingTargetRouteInTransferableRoutes = 602 pendingRequest != null 603 && pendingRequest.isTargetRouteIdInRouteUniqueIdList( 604 newSessionInfo.getTransferableRoutes()); 605 606 int transferReason; 607 UserHandle transferInitiatorUserHandle; 608 String transferInitiatorPackageName; 609 if (pendingTargetRouteInSelectedRoutes) { // The pending request has been satisfied. 610 transferReason = pendingRequest.mTransferReason; 611 transferInitiatorUserHandle = pendingRequest.mTransferInitiatorUserHandle; 612 transferInitiatorPackageName = pendingRequest.mTransferInitiatorPackageName; 613 } else if (oldSessionInfo != null) { 614 // No pending request, we copy the values from the old session object. 615 transferReason = oldSessionInfo.getTransferReason(); 616 transferInitiatorUserHandle = oldSessionInfo.getTransferInitiatorUserHandle(); 617 transferInitiatorPackageName = oldSessionInfo.getTransferInitiatorPackageName(); 618 } else { // There's a new session with no associated creation request, we use defaults. 619 transferReason = RoutingSessionInfo.TRANSFER_REASON_FALLBACK; 620 transferInitiatorUserHandle = UserHandle.of(mUserId); 621 transferInitiatorPackageName = newSessionInfo.getClientPackageName(); 622 } 623 if (pendingTargetRouteInSelectedRoutes || !pendingTargetRouteInTransferableRoutes) { 624 // The pending request has been satisfied, or the target route is no longer available. 625 if (oldSessionInfo != null) { 626 mSessionOriginalIdToTransferRequest.remove(newSessionInfo.getId()); 627 } else if (pendingRequest != null) { 628 mRequestIdToSessionCreationRequest.remove(pendingRequest.mRequestId); 629 } 630 } 631 return new RoutingSessionInfo.Builder(newSessionInfo) 632 .setTransferInitiator(transferInitiatorUserHandle, transferInitiatorPackageName) 633 .setTransferReason(transferReason) 634 .build(); 635 } 636 onSessionReleased(Connection connection, RoutingSessionInfo releasedSession)637 private void onSessionReleased(Connection connection, RoutingSessionInfo releasedSession) { 638 if (mActiveConnection != connection) { 639 return; 640 } 641 if (releasedSession == null) { 642 Slog.w(TAG, "onSessionReleased: Ignoring null session sent from " + mComponentName); 643 return; 644 } 645 646 releasedSession = assignProviderIdForSession(releasedSession); 647 648 boolean found = false; 649 synchronized (mLock) { 650 var sessionCallback = mSystemSessionCallbacks.get(releasedSession.getOriginalId()); 651 if (sessionCallback != null) { 652 sessionCallback.onSessionReleased(); 653 return; 654 } 655 656 mSessionOriginalIdToTransferRequest.remove(releasedSession.getId()); 657 for (RoutingSessionInfo session : mSessionInfos) { 658 if (TextUtils.equals(session.getId(), releasedSession.getId())) { 659 mSessionInfos.remove(session); 660 found = true; 661 break; 662 } 663 } 664 if (!found) { 665 for (RoutingSessionInfo session : mReleasingSessions) { 666 if (TextUtils.equals(session.getId(), releasedSession.getId())) { 667 mReleasingSessions.remove(session); 668 return; 669 } 670 } 671 } 672 } 673 674 if (!found) { 675 Slog.w(TAG, "onSessionReleased: Matching session info not found"); 676 return; 677 } 678 679 mCallback.onSessionReleased(this, releasedSession); 680 } 681 dispatchSessionCreated(long requestId, RoutingSessionInfo session)682 private void dispatchSessionCreated(long requestId, RoutingSessionInfo session) { 683 mHandler.sendMessage( 684 obtainMessage(mCallback::onSessionCreated, this, requestId, session)); 685 } 686 dispatchSessionUpdated(RoutingSessionInfo session)687 private void dispatchSessionUpdated(RoutingSessionInfo session) { 688 mHandler.sendMessage( 689 obtainMessage( 690 mCallback::onSessionUpdated, 691 this, 692 session, 693 /* packageNamesWithRoutingSessionOverrides= */ Set.of())); 694 } 695 dispatchSessionReleased(RoutingSessionInfo session)696 private void dispatchSessionReleased(RoutingSessionInfo session) { 697 mHandler.sendMessage( 698 obtainMessage(mCallback::onSessionReleased, this, session)); 699 } 700 assignProviderIdForSession(RoutingSessionInfo sessionInfo)701 private RoutingSessionInfo assignProviderIdForSession(RoutingSessionInfo sessionInfo) { 702 return new RoutingSessionInfo.Builder(sessionInfo) 703 .setOwnerPackageName(mComponentName.getPackageName()) 704 .setProviderId(getUniqueId()) 705 .build(); 706 } 707 onRequestFailed(Connection connection, long requestId, int reason)708 private void onRequestFailed(Connection connection, long requestId, int reason) { 709 if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { 710 synchronized (mLock) { 711 mRequestIdToSessionCreationRequest.remove(requestId); 712 } 713 } 714 if (mActiveConnection != connection) { 715 return; 716 } 717 718 if (requestId == REQUEST_ID_NONE) { 719 Slog.w(TAG, "onRequestFailed: Ignoring requestId REQUEST_ID_NONE"); 720 return; 721 } 722 723 mCallback.onRequestFailed(this, requestId, reason); 724 } 725 disconnect()726 private void disconnect() { 727 if (mActiveConnection != null) { 728 mConnectionReady = false; 729 mActiveConnection.dispose(); 730 mActiveConnection = null; 731 setAndNotifyProviderState(null); 732 synchronized (mLock) { 733 for (RoutingSessionInfo sessionInfo : mSessionInfos) { 734 mCallback.onSessionReleased(this, sessionInfo); 735 } 736 if (Flags.enableMirroringInMediaRouter2()) { 737 for (var callback : mSystemSessionCallbacks.values()) { 738 callback.onSessionReleased(); 739 } 740 mSystemSessionCallbacks.clear(); 741 int requestsSize = mRequestIdToSystemSessionRequest.size(); 742 for (int i = 0; i < requestsSize; i++) { 743 var callback = mRequestIdToSystemSessionRequest.valueAt(i); 744 var requestId = mRequestIdToSystemSessionRequest.keyAt(i); 745 callback.onRequestFailed(requestId, REASON_REJECTED); 746 } 747 mSystemSessionCallbacks.clear(); 748 } 749 mSessionInfos.clear(); 750 mReleasingSessions.clear(); 751 mRequestIdToSessionCreationRequest.clear(); 752 mSessionOriginalIdToTransferRequest.clear(); 753 } 754 } 755 } 756 757 @Override getDebugString()758 protected String getDebugString() { 759 int pendingSessionCreationCount; 760 int pendingTransferCount; 761 synchronized (mLock) { 762 pendingSessionCreationCount = mRequestIdToSessionCreationRequest.size(); 763 pendingTransferCount = mSessionOriginalIdToTransferRequest.size(); 764 } 765 return TextUtils.formatSimple( 766 "ProviderServiceProxy - package: %s, bound: %b, connection (active:%b, ready:%b), " 767 + "system media=%b, pending (session creations: %d, transfers: %d)", 768 mComponentName.getPackageName(), 769 mBound, 770 mActiveConnection != null, 771 mConnectionReady, 772 mSupportsSystemMediaRouting, 773 pendingSessionCreationCount, 774 pendingTransferCount); 775 } 776 777 /** 778 * Callback for events related to system media sessions. 779 * 780 * @see MediaRoute2ProviderService#onCreateSystemRoutingSession 781 */ 782 public interface SystemMediaSessionCallback { 783 784 /** 785 * Called when the corresponding session's {@link RoutingSessionInfo}, or upon the creation 786 * of the given session info. 787 */ onSessionUpdate(@onNull RoutingSessionInfo sessionInfo)788 void onSessionUpdate(@NonNull RoutingSessionInfo sessionInfo); 789 790 /** Called when the request with the given id fails for the given reason. */ onRequestFailed(long requestId, @Reason int reason)791 void onRequestFailed(long requestId, @Reason int reason); 792 793 /** Called when the corresponding session is released. */ onSessionReleased()794 void onSessionReleased(); 795 } 796 797 // All methods in this class are called on the main thread. 798 private final class ServiceConnectionImpl implements ServiceConnection { 799 800 @Override onServiceConnected(ComponentName name, IBinder service)801 public void onServiceConnected(ComponentName name, IBinder service) { 802 if (Flags.enableMr2ServiceNonMainBgThread()) { 803 mHandler.post(() -> onServiceConnectedInternal(service)); 804 } else { 805 onServiceConnectedInternal(service); 806 } 807 } 808 809 @Override onServiceDisconnected(ComponentName name)810 public void onServiceDisconnected(ComponentName name) { 811 if (Flags.enableMr2ServiceNonMainBgThread()) { 812 mHandler.post(() -> onServiceDisconnectedInternal()); 813 } else { 814 onServiceDisconnectedInternal(); 815 } 816 } 817 818 @Override onBindingDied(ComponentName name)819 public void onBindingDied(ComponentName name) { 820 if (Flags.enableMr2ServiceNonMainBgThread()) { 821 mHandler.post(() -> onBindingDiedInternal(name)); 822 } else { 823 onBindingDiedInternal(name); 824 } 825 } 826 } 827 828 private final class Connection implements DeathRecipient { 829 private final IMediaRoute2ProviderService mService; 830 private final ServiceCallbackStub mCallbackStub; 831 Connection(IMediaRoute2ProviderService serviceBinder)832 Connection(IMediaRoute2ProviderService serviceBinder) { 833 mService = serviceBinder; 834 mCallbackStub = new ServiceCallbackStub(this, mSupportsSystemMediaRouting); 835 } 836 register()837 public boolean register() { 838 try { 839 mService.asBinder().linkToDeath(this, 0); 840 mService.setCallback(mCallbackStub); 841 mHandler.post(() -> onConnectionReady(Connection.this)); 842 return true; 843 } catch (RemoteException ex) { 844 binderDied(); 845 } 846 return false; 847 } 848 dispose()849 public void dispose() { 850 mService.asBinder().unlinkToDeath(this, 0); 851 mCallbackStub.dispose(); 852 } 853 requestCreateSession(long requestId, String packageName, String routeId, Bundle sessionHints)854 public void requestCreateSession(long requestId, String packageName, String routeId, 855 Bundle sessionHints) { 856 try { 857 mService.requestCreateSession(requestId, packageName, routeId, sessionHints); 858 } catch (RemoteException ex) { 859 Slog.e(TAG, "requestCreateSession: Failed to deliver request."); 860 } 861 } 862 863 /** 864 * Sends a system media session creation request to the provider service, and returns 865 * whether the request transaction succeeded. 866 * 867 * <p>The transaction might fail, for example, if the recipient process has died. 868 */ requestCreateSystemMediaSession( long requestId, int uid, String packageName, String routeId, @Nullable Bundle sessionHints)869 public boolean requestCreateSystemMediaSession( 870 long requestId, 871 int uid, 872 String packageName, 873 String routeId, 874 @Nullable Bundle sessionHints) { 875 try { 876 mService.requestCreateSystemMediaSession( 877 requestId, uid, packageName, routeId, sessionHints); 878 return true; 879 } catch (RemoteException ex) { 880 Slog.e(TAG, "requestCreateSystemMediaSession: Failed to deliver request."); 881 } 882 return false; 883 } 884 releaseSession(long requestId, String sessionId)885 public void releaseSession(long requestId, String sessionId) { 886 try { 887 mService.releaseSession(requestId, sessionId); 888 } catch (RemoteException ex) { 889 Slog.e(TAG, "releaseSession: Failed to deliver request."); 890 } 891 } 892 updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference)893 public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) { 894 try { 895 mService.updateDiscoveryPreference(discoveryPreference); 896 } catch (RemoteException ex) { 897 Slog.e(TAG, "updateDiscoveryPreference: Failed to deliver request."); 898 } 899 } 900 selectRoute(long requestId, String sessionId, String routeId)901 public void selectRoute(long requestId, String sessionId, String routeId) { 902 try { 903 mService.selectRoute(requestId, sessionId, routeId); 904 } catch (RemoteException ex) { 905 Slog.e(TAG, "selectRoute: Failed to deliver request.", ex); 906 } 907 } 908 deselectRoute(long requestId, String sessionId, String routeId)909 public void deselectRoute(long requestId, String sessionId, String routeId) { 910 try { 911 mService.deselectRoute(requestId, sessionId, routeId); 912 } catch (RemoteException ex) { 913 Slog.e(TAG, "deselectRoute: Failed to deliver request.", ex); 914 } 915 } 916 transferToRoute(long requestId, String sessionId, String routeId)917 public void transferToRoute(long requestId, String sessionId, String routeId) { 918 try { 919 mService.transferToRoute(requestId, sessionId, routeId); 920 } catch (RemoteException ex) { 921 Slog.e(TAG, "transferToRoute: Failed to deliver request.", ex); 922 } 923 } 924 setRouteVolume(long requestId, String routeId, int volume)925 public void setRouteVolume(long requestId, String routeId, int volume) { 926 try { 927 mService.setRouteVolume(requestId, routeId, volume); 928 } catch (RemoteException ex) { 929 Slog.e(TAG, "setRouteVolume: Failed to deliver request.", ex); 930 } 931 } 932 setSessionVolume(long requestId, String sessionId, int volume)933 public void setSessionVolume(long requestId, String sessionId, int volume) { 934 try { 935 mService.setSessionVolume(requestId, sessionId, volume); 936 } catch (RemoteException ex) { 937 Slog.e(TAG, "setSessionVolume: Failed to deliver request.", ex); 938 } 939 } 940 941 @Override binderDied()942 public void binderDied() { 943 mHandler.post(() -> onConnectionDied(Connection.this)); 944 } 945 postProviderUpdated(MediaRoute2ProviderInfo providerInfo)946 void postProviderUpdated(MediaRoute2ProviderInfo providerInfo) { 947 mHandler.post(() -> onProviderUpdated(Connection.this, providerInfo)); 948 } 949 postSessionCreated(long requestId, RoutingSessionInfo sessionInfo)950 void postSessionCreated(long requestId, RoutingSessionInfo sessionInfo) { 951 mHandler.post(() -> onSessionCreated(Connection.this, requestId, sessionInfo)); 952 } 953 postSessionsUpdated(List<RoutingSessionInfo> sessionInfo)954 void postSessionsUpdated(List<RoutingSessionInfo> sessionInfo) { 955 mHandler.post(() -> onSessionsUpdated(Connection.this, sessionInfo)); 956 } 957 postSessionReleased(RoutingSessionInfo sessionInfo)958 void postSessionReleased(RoutingSessionInfo sessionInfo) { 959 mHandler.post(() -> onSessionReleased(Connection.this, sessionInfo)); 960 } 961 postRequestFailed(long requestId, int reason)962 void postRequestFailed(long requestId, int reason) { 963 mHandler.post(() -> onRequestFailed(Connection.this, requestId, reason)); 964 } 965 } 966 967 private static final class ServiceCallbackStub extends 968 IMediaRoute2ProviderServiceCallback.Stub { 969 private final WeakReference<Connection> mConnectionRef; 970 private final boolean mAllowSystemMediaRoutes; 971 ServiceCallbackStub(Connection connection, boolean allowSystemMediaRoutes)972 ServiceCallbackStub(Connection connection, boolean allowSystemMediaRoutes) { 973 mConnectionRef = new WeakReference<>(connection); 974 mAllowSystemMediaRoutes = allowSystemMediaRoutes; 975 } 976 dispose()977 public void dispose() { 978 mConnectionRef.clear(); 979 } 980 981 @Override notifyProviderUpdated(@onNull MediaRoute2ProviderInfo providerInfo)982 public void notifyProviderUpdated(@NonNull MediaRoute2ProviderInfo providerInfo) { 983 Objects.requireNonNull(providerInfo, "providerInfo must not be null"); 984 985 for (MediaRoute2Info route : providerInfo.getRoutes()) { 986 if (Flags.enableMirroringInMediaRouter2() 987 && route.supportsRemoteRouting() 988 && route.supportsSystemMediaRouting() 989 && route.getDeduplicationIds().isEmpty()) { 990 // This code is not accessible if the app is using the public API. 991 throw new SecurityException("Route is missing deduplication id: " + route); 992 } 993 994 if (route.isSystemRoute()) { 995 throw new SecurityException( 996 "Only the system is allowed to publish system routes. " 997 + "Disallowed route: " 998 + route); 999 } 1000 1001 if (route.getSuitabilityStatus() 1002 == MediaRoute2Info.SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER) { 1003 throw new SecurityException( 1004 "Only the system is allowed to set not suitable for transfer status. " 1005 + "Disallowed route: " 1006 + route); 1007 } 1008 1009 if (route.isSystemRouteType()) { 1010 throw new SecurityException( 1011 "Only the system is allowed to publish routes with system route types. " 1012 + "Disallowed route: " 1013 + route); 1014 } 1015 1016 if (route.supportsSystemMediaRouting() && !mAllowSystemMediaRoutes) { 1017 throw new SecurityException( 1018 "This provider is not allowed to publish routes that support system" 1019 + " media routing. Disallowed route: " 1020 + route); 1021 } 1022 } 1023 1024 Connection connection = mConnectionRef.get(); 1025 if (connection != null) { 1026 connection.postProviderUpdated(providerInfo); 1027 } 1028 } 1029 1030 @Override notifySessionCreated(long requestId, RoutingSessionInfo sessionInfo)1031 public void notifySessionCreated(long requestId, RoutingSessionInfo sessionInfo) { 1032 Connection connection = mConnectionRef.get(); 1033 if (connection != null) { 1034 connection.postSessionCreated(requestId, sessionInfo); 1035 } 1036 } 1037 1038 @Override notifySessionsUpdated(List<RoutingSessionInfo> sessionInfo)1039 public void notifySessionsUpdated(List<RoutingSessionInfo> sessionInfo) { 1040 Connection connection = mConnectionRef.get(); 1041 if (connection != null) { 1042 connection.postSessionsUpdated(sessionInfo); 1043 } 1044 } 1045 1046 @Override notifySessionReleased(RoutingSessionInfo sessionInfo)1047 public void notifySessionReleased(RoutingSessionInfo sessionInfo) { 1048 Connection connection = mConnectionRef.get(); 1049 if (connection != null) { 1050 connection.postSessionReleased(sessionInfo); 1051 } 1052 } 1053 1054 @Override notifyRequestFailed(long requestId, int reason)1055 public void notifyRequestFailed(long requestId, int reason) { 1056 Connection connection = mConnectionRef.get(); 1057 if (connection != null) { 1058 connection.postRequestFailed(requestId, reason); 1059 } 1060 } 1061 } 1062 } 1063