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