Loading core/api/current.txt +15 −0 Original line number Original line Diff line number Diff line Loading @@ -24406,6 +24406,7 @@ package android.media { } } public final class MediaRouter2 { public final class MediaRouter2 { method @FlaggedApi("com.android.media.flags.enable_screen_off_scanning") public void cancelScanRequest(@NonNull android.media.MediaRouter2.ScanToken); method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String); method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String); method @NonNull public java.util.List<android.media.MediaRouter2.RoutingController> getControllers(); method @NonNull public java.util.List<android.media.MediaRouter2.RoutingController> getControllers(); method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context); method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context); Loading @@ -24417,6 +24418,7 @@ package android.media { method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference); method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference); method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void registerRouteListingPreferenceUpdatedCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.media.RouteListingPreference>); method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void registerRouteListingPreferenceUpdatedCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.media.RouteListingPreference>); method public void registerTransferCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.TransferCallback); method public void registerTransferCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.TransferCallback); method @FlaggedApi("com.android.media.flags.enable_screen_off_scanning") @NonNull public android.media.MediaRouter2.ScanToken requestScan(@NonNull android.media.MediaRouter2.ScanRequest); method public void setOnGetControllerHintsListener(@Nullable android.media.MediaRouter2.OnGetControllerHintsListener); method public void setOnGetControllerHintsListener(@Nullable android.media.MediaRouter2.OnGetControllerHintsListener); method public void setRouteListingPreference(@Nullable android.media.RouteListingPreference); method public void setRouteListingPreference(@Nullable android.media.RouteListingPreference); method public boolean showSystemOutputSwitcher(); method public boolean showSystemOutputSwitcher(); Loading Loading @@ -24463,6 +24465,19 @@ package android.media { method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferInitiatedBySelf(); method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferInitiatedBySelf(); } } @FlaggedApi("com.android.media.flags.enable_screen_off_scanning") public static final class MediaRouter2.ScanRequest { method public boolean isScreenOffScan(); } public static final class MediaRouter2.ScanRequest.Builder { ctor public MediaRouter2.ScanRequest.Builder(); method @NonNull public android.media.MediaRouter2.ScanRequest build(); method @NonNull public android.media.MediaRouter2.ScanRequest.Builder setScreenOffScan(boolean); } @FlaggedApi("com.android.media.flags.enable_screen_off_scanning") public static final class MediaRouter2.ScanToken { } public abstract static class MediaRouter2.TransferCallback { public abstract static class MediaRouter2.TransferCallback { ctor public MediaRouter2.TransferCallback(); ctor public MediaRouter2.TransferCallback(); method public void onStop(@NonNull android.media.MediaRouter2.RoutingController); method public void onStop(@NonNull android.media.MediaRouter2.RoutingController); media/java/android/media/IMediaRouterService.aidl +2 −3 Original line number Original line Diff line number Diff line Loading @@ -27,7 +27,6 @@ import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; import android.media.RoutingSessionInfo; import android.os.Bundle; import android.os.Bundle; import android.os.UserHandle; import android.os.UserHandle; /** /** * {@hide} * {@hide} */ */ Loading Loading @@ -56,6 +55,7 @@ interface IMediaRouterService { void registerRouter2(IMediaRouter2 router, String packageName); void registerRouter2(IMediaRouter2 router, String packageName); void unregisterRouter2(IMediaRouter2 router); void unregisterRouter2(IMediaRouter2 router); void updateScanningStateWithRouter2(IMediaRouter2 router, @JavaPassthrough(annotation="@android.media.MediaRouter2.ScanningState") int scanningState); void setDiscoveryRequestWithRouter2(IMediaRouter2 router, void setDiscoveryRequestWithRouter2(IMediaRouter2 router, in RouteDiscoveryPreference preference); in RouteDiscoveryPreference preference); void setRouteListingPreference(IMediaRouter2 router, void setRouteListingPreference(IMediaRouter2 router, Loading @@ -81,8 +81,7 @@ interface IMediaRouterService { void unregisterManager(IMediaRouter2Manager manager); void unregisterManager(IMediaRouter2Manager manager); void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId, void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId, in MediaRoute2Info route, int volume); in MediaRoute2Info route, int volume); void startScan(IMediaRouter2Manager manager); void updateScanningState(IMediaRouter2Manager manager, @JavaPassthrough(annotation="@android.media.MediaRouter2.ScanningState") int scanningState); void stopScan(IMediaRouter2Manager manager); void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId, void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId, in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route, in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route, Loading media/java/android/media/MediaRouter2.java +250 −6 Original line number Original line Diff line number Diff line Loading @@ -20,10 +20,12 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES; import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES; import static com.android.media.flags.Flags.FLAG_ENABLE_CROSS_USER_ROUTING_IN_MEDIA_ROUTER2; import static com.android.media.flags.Flags.FLAG_ENABLE_CROSS_USER_ROUTING_IN_MEDIA_ROUTER2; import static com.android.media.flags.Flags.FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2; import static com.android.media.flags.Flags.FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2; import static com.android.media.flags.Flags.FLAG_ENABLE_SCREEN_OFF_SCANNING; import android.Manifest; import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.RequiresPermission; Loading @@ -42,9 +44,12 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArrayMap; import android.util.ArraySet; import android.util.ArraySet; import android.util.Log; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.ArrayList; import java.util.Collections; import java.util.Collections; import java.util.Comparator; import java.util.Comparator; Loading Loading @@ -73,6 +78,48 @@ import java.util.stream.Collectors; // Not only MediaRouter2, but also to service / manager / provider. // Not only MediaRouter2, but also to service / manager / provider. // TODO: ensure thread-safe and document it // TODO: ensure thread-safe and document it public final class MediaRouter2 { public final class MediaRouter2 { /** * The state of a router not requesting route scanning. * * @hide */ public static final int SCANNING_STATE_NOT_SCANNING = 0; /** * The state of a router requesting scanning only while the user interacts with its owner app. * * <p>The device's screen must be on and the app must be in the foreground to trigger scanning * under this state. * * @hide */ public static final int SCANNING_STATE_WHILE_INTERACTIVE = 1; /** * The state of a router requesting unrestricted scanning. * * <p>This state triggers scanning regardless of the restrictions required for {@link * #SCANNING_STATE_WHILE_INTERACTIVE}. * * <p>Routers requesting unrestricted scanning must hold {@link * Manifest.permission#MEDIA_ROUTING_CONTROL}. * * @hide */ public static final int SCANNING_STATE_SCANNING_FULL = 2; /** @hide */ @IntDef( prefix = "SCANNING_STATE", value = { SCANNING_STATE_NOT_SCANNING, SCANNING_STATE_WHILE_INTERACTIVE, SCANNING_STATE_SCANNING_FULL }) @Retention(RetentionPolicy.SOURCE) public @interface ScanningState {} private static final String TAG = "MR2"; private static final String TAG = "MR2"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final Object sSystemRouterLock = new Object(); private static final Object sSystemRouterLock = new Object(); Loading Loading @@ -123,6 +170,13 @@ public final class MediaRouter2 { @GuardedBy("mLock") @GuardedBy("mLock") private final Map<String, RoutingController> mNonSystemRoutingControllers = new ArrayMap<>(); private final Map<String, RoutingController> mNonSystemRoutingControllers = new ArrayMap<>(); @GuardedBy("mLock") private int mScreenOffScanRequestCount = 0; @GuardedBy("mLock") private int mScreenOnScanRequestCount = 0; private final SparseArray<ScanRequest> mScanRequestsMap = new SparseArray<>(); private final AtomicInteger mNextRequestId = new AtomicInteger(1); private final AtomicInteger mNextRequestId = new AtomicInteger(1); private final Handler mHandler; private final Handler mHandler; Loading Loading @@ -335,6 +389,100 @@ public final class MediaRouter2 { mImpl.stopScan(); mImpl.stopScan(); } } /** * Requests the system to actively scan for routes based on the router's {@link * RouteDiscoveryPreference route discovery preference}. * * <p>You must call {@link #cancelScanRequest(ScanToken)} promptly to preserve system resources * like battery. Avoid scanning unless there is clear intention from the user to start routing * their media. * * <p>{@code scanRequest} specifies relevant scanning options, like whether the system should * scan with the screen off. Screen off scanning requires {@link * Manifest.permission#MEDIA_ROUTING_CONTROL} * * <p>Proxy routers use the registered {@link RouteDiscoveryPreference} of their target routers. * * @return A unique {@link ScanToken} that identifies the scan request. */ @FlaggedApi(FLAG_ENABLE_SCREEN_OFF_SCANNING) @NonNull public ScanToken requestScan(@NonNull ScanRequest scanRequest) { Objects.requireNonNull(scanRequest, "scanRequest must not be null."); ScanToken token = new ScanToken(mNextRequestId.getAndIncrement()); synchronized (mLock) { boolean shouldUpdate = mScreenOffScanRequestCount == 0 && (scanRequest.isScreenOffScan() || mScreenOnScanRequestCount == 0); if (shouldUpdate) { try { mImpl.updateScanningState( scanRequest.isScreenOffScan() ? SCANNING_STATE_SCANNING_FULL : SCANNING_STATE_WHILE_INTERACTIVE); if (scanRequest.isScreenOffScan()) { mScreenOffScanRequestCount++; } else { mScreenOnScanRequestCount++; } } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } mScanRequestsMap.put(token.mId, scanRequest); return token; } } /** * Releases the active scan request linked to the provided {@link ScanToken}. * * @see #requestScan(ScanRequest) * @param token {@link ScanToken} of the {@link ScanRequest} to release. * @throws IllegalArgumentException if the token does not match any active scan request. */ @FlaggedApi(FLAG_ENABLE_SCREEN_OFF_SCANNING) public void cancelScanRequest(@NonNull ScanToken token) { Objects.requireNonNull(token, "token must not be null"); synchronized (mLock) { ScanRequest request = mScanRequestsMap.get(token.mId); if (request == null) { throw new IllegalArgumentException( "The token does not match any active scan request"); } boolean shouldUpdate = mScreenOffScanRequestCount == 1 && (request.isScreenOffScan() || mScreenOnScanRequestCount == 1); if (shouldUpdate) { try { if (request.isScreenOffScan() && mScreenOnScanRequestCount == 0) { mImpl.updateScanningState(SCANNING_STATE_NOT_SCANNING); } else { mImpl.updateScanningState(SCANNING_STATE_WHILE_INTERACTIVE); } if (request.isScreenOffScan()) { mScreenOffScanRequestCount--; } else { mScreenOnScanRequestCount--; } } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } } mScanRequestsMap.remove(token.mId); } } private MediaRouter2(Context appContext) { private MediaRouter2(Context appContext) { mContext = appContext; mContext = appContext; mMediaRouterService = mMediaRouterService = Loading Loading @@ -1428,6 +1576,78 @@ public final class MediaRouter2 { public void onControllerUpdated(@NonNull RoutingController controller) {} public void onControllerUpdated(@NonNull RoutingController controller) {} } } /** * Represents an active scan request registered in the system. * * <p>See {@link #requestScan(ScanRequest)} for more information. */ @FlaggedApi(FLAG_ENABLE_SCREEN_OFF_SCANNING) public static final class ScanToken { private final int mId; private ScanToken(int id) { mId = id; } } /** * Represents a set of parameters for scanning requests. * * <p>See {@link #requestScan(ScanRequest)} for more details. */ @FlaggedApi(FLAG_ENABLE_SCREEN_OFF_SCANNING) public static final class ScanRequest { private final boolean mIsScreenOffScan; private ScanRequest(boolean isScreenOffScan) { mIsScreenOffScan = isScreenOffScan; } /** * Returns whether the scan request corresponds to a screen-off scan. * * @see #requestScan(ScanRequest) */ public boolean isScreenOffScan() { return mIsScreenOffScan; } /** * Builder class for {@link ScanRequest}. * * @see #requestScan(ScanRequest) */ public static final class Builder { boolean mIsScreenOffScan; /** * Creates a builder for a {@link ScanRequest} instance. * * @see #requestScan(ScanRequest) */ public Builder() {} /** * Sets whether the app is requesting to scan even while the screen is off, bypassing * default scanning restrictions. Only companion apps holding {@link * Manifest.permission#MEDIA_ROUTING_CONTROL} should set this to {@code true}. * * @see #requestScan(ScanRequest) */ @NonNull public Builder setScreenOffScan(boolean isScreenOffScan) { mIsScreenOffScan = isScreenOffScan; return this; } /** Returns a new {@link ScanRequest} instance. */ @NonNull public ScanRequest build() { return new ScanRequest(mIsScreenOffScan); } } } /** /** * A class to control media routing session in media route provider. For example, * A class to control media routing session in media route provider. For example, * selecting/deselecting/transferring to routes of a session can be done through this. Instances * selecting/deselecting/transferring to routes of a session can be done through this. Instances Loading Loading @@ -2092,6 +2312,9 @@ public final class MediaRouter2 { * ProxyMediaRouter2Impl proxy} {@link MediaRouter2} instances. * ProxyMediaRouter2Impl proxy} {@link MediaRouter2} instances. */ */ private interface MediaRouter2Impl { private interface MediaRouter2Impl { void updateScanningState(@ScanningState int scanningState) throws RemoteException; void startScan(); void startScan(); void stopScan(); void stopScan(); Loading Loading @@ -2194,12 +2417,18 @@ public final class MediaRouter2 { } } } } @Override public void updateScanningState(int scanningState) throws RemoteException { mMediaRouterService.updateScanningState(mClient, scanningState); } @Override @Override public void startScan() { public void startScan() { if (!mIsScanning.getAndSet(true)) { if (!mIsScanning.getAndSet(true)) { if (mScanRequestCount.getAndIncrement() == 0) { if (mScanRequestCount.getAndIncrement() == 0) { try { try { mMediaRouterService.startScan(mClient); mMediaRouterService.updateScanningState( mClient, SCANNING_STATE_WHILE_INTERACTIVE); } catch (RemoteException ex) { } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); throw ex.rethrowFromSystemServer(); } } Loading @@ -2221,7 +2450,8 @@ public final class MediaRouter2 { }) }) == 0) { == 0) { try { try { mMediaRouterService.stopScan(mClient); mMediaRouterService.updateScanningState( mClient, SCANNING_STATE_NOT_SCANNING); } catch (RemoteException ex) { } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); throw ex.rethrowFromSystemServer(); } } Loading Loading @@ -3041,6 +3271,18 @@ public final class MediaRouter2 { // Do nothing. // Do nothing. } } @Override @GuardedBy("mLock") public void updateScanningState(int scanningState) throws RemoteException { if (scanningState != SCANNING_STATE_NOT_SCANNING) { registerRouterStubIfNeededLocked(); } mMediaRouterService.updateScanningStateWithRouter2(mStub, scanningState); if (scanningState == SCANNING_STATE_NOT_SCANNING) { unregisterRouterStubIfNeededLocked(/* isScanningStopping */ true); } } /** /** * Returns {@code null}. The client package name is only associated to proxy {@link * Returns {@code null}. The client package name is only associated to proxy {@link * MediaRouter2} instances. * MediaRouter2} instances. Loading Loading @@ -3103,7 +3345,7 @@ public final class MediaRouter2 { mStub, mDiscoveryPreference); mStub, mDiscoveryPreference); } } unregisterRouterStubIfNeededLocked(); unregisterRouterStubIfNeededLocked(/* isScanningStopping */ false); } catch (RemoteException ex) { } catch (RemoteException ex) { Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.", ex); Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.", ex); Loading Loading @@ -3313,7 +3555,7 @@ public final class MediaRouter2 { } } try { try { unregisterRouterStubIfNeededLocked(); unregisterRouterStubIfNeededLocked(/* isScanningStopping */ false); } catch (RemoteException ex) { } catch (RemoteException ex) { ex.rethrowFromSystemServer(); ex.rethrowFromSystemServer(); } } Loading @@ -3331,10 +3573,12 @@ public final class MediaRouter2 { } } @GuardedBy("mLock") @GuardedBy("mLock") private void unregisterRouterStubIfNeededLocked() throws RemoteException { private void unregisterRouterStubIfNeededLocked(boolean isScanningStopping) throws RemoteException { if (mStub != null if (mStub != null && mRouteCallbackRecords.isEmpty() && mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) { && mNonSystemRoutingControllers.isEmpty() && (mScanRequestsMap.size() == 0 || isScanningStopping)) { mMediaRouterService.unregisterRouter2(mStub); mMediaRouterService.unregisterRouter2(mStub); mStub = null; mStub = null; } } Loading media/java/android/media/MediaRouter2Manager.java +5 −2 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,9 @@ package android.media; package android.media; import static android.media.MediaRouter2.SCANNING_STATE_NOT_SCANNING; import static android.media.MediaRouter2.SCANNING_STATE_WHILE_INTERACTIVE; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.Manifest; import android.Manifest; Loading Loading @@ -174,7 +177,7 @@ public final class MediaRouter2Manager { public void registerScanRequest() { public void registerScanRequest() { if (mScanRequestCount.getAndIncrement() == 0) { if (mScanRequestCount.getAndIncrement() == 0) { try { try { mMediaRouterService.startScan(mClient); mMediaRouterService.updateScanningState(mClient, SCANNING_STATE_WHILE_INTERACTIVE); } catch (RemoteException ex) { } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); throw ex.rethrowFromSystemServer(); } } Loading @@ -201,7 +204,7 @@ public final class MediaRouter2Manager { }) }) == 0) { == 0) { try { try { mMediaRouterService.stopScan(mClient); mMediaRouterService.updateScanningState(mClient, SCANNING_STATE_NOT_SCANNING); } catch (RemoteException ex) { } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); throw ex.rethrowFromSystemServer(); } } Loading media/java/android/media/flags/media_better_together.aconfig +7 −0 Original line number Original line Diff line number Diff line Loading @@ -90,3 +90,10 @@ flag { description: "Enables mechanisms to prevent route providers from keeping malicious apps alive." description: "Enables mechanisms to prevent route providers from keeping malicious apps alive." bug: "263520343" bug: "263520343" } } flag { name: "enable_screen_off_scanning" namespace: "media_solutions" description: "Enable new MediaRouter2 API to enable watch companion apps to scan while the phone screen is off." bug: "281072508" } Loading
core/api/current.txt +15 −0 Original line number Original line Diff line number Diff line Loading @@ -24406,6 +24406,7 @@ package android.media { } } public final class MediaRouter2 { public final class MediaRouter2 { method @FlaggedApi("com.android.media.flags.enable_screen_off_scanning") public void cancelScanRequest(@NonNull android.media.MediaRouter2.ScanToken); method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String); method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String); method @NonNull public java.util.List<android.media.MediaRouter2.RoutingController> getControllers(); method @NonNull public java.util.List<android.media.MediaRouter2.RoutingController> getControllers(); method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context); method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context); Loading @@ -24417,6 +24418,7 @@ package android.media { method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference); method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference); method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void registerRouteListingPreferenceUpdatedCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.media.RouteListingPreference>); method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void registerRouteListingPreferenceUpdatedCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.media.RouteListingPreference>); method public void registerTransferCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.TransferCallback); method public void registerTransferCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.TransferCallback); method @FlaggedApi("com.android.media.flags.enable_screen_off_scanning") @NonNull public android.media.MediaRouter2.ScanToken requestScan(@NonNull android.media.MediaRouter2.ScanRequest); method public void setOnGetControllerHintsListener(@Nullable android.media.MediaRouter2.OnGetControllerHintsListener); method public void setOnGetControllerHintsListener(@Nullable android.media.MediaRouter2.OnGetControllerHintsListener); method public void setRouteListingPreference(@Nullable android.media.RouteListingPreference); method public void setRouteListingPreference(@Nullable android.media.RouteListingPreference); method public boolean showSystemOutputSwitcher(); method public boolean showSystemOutputSwitcher(); Loading Loading @@ -24463,6 +24465,19 @@ package android.media { method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferInitiatedBySelf(); method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferInitiatedBySelf(); } } @FlaggedApi("com.android.media.flags.enable_screen_off_scanning") public static final class MediaRouter2.ScanRequest { method public boolean isScreenOffScan(); } public static final class MediaRouter2.ScanRequest.Builder { ctor public MediaRouter2.ScanRequest.Builder(); method @NonNull public android.media.MediaRouter2.ScanRequest build(); method @NonNull public android.media.MediaRouter2.ScanRequest.Builder setScreenOffScan(boolean); } @FlaggedApi("com.android.media.flags.enable_screen_off_scanning") public static final class MediaRouter2.ScanToken { } public abstract static class MediaRouter2.TransferCallback { public abstract static class MediaRouter2.TransferCallback { ctor public MediaRouter2.TransferCallback(); ctor public MediaRouter2.TransferCallback(); method public void onStop(@NonNull android.media.MediaRouter2.RoutingController); method public void onStop(@NonNull android.media.MediaRouter2.RoutingController);
media/java/android/media/IMediaRouterService.aidl +2 −3 Original line number Original line Diff line number Diff line Loading @@ -27,7 +27,6 @@ import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; import android.media.RoutingSessionInfo; import android.os.Bundle; import android.os.Bundle; import android.os.UserHandle; import android.os.UserHandle; /** /** * {@hide} * {@hide} */ */ Loading Loading @@ -56,6 +55,7 @@ interface IMediaRouterService { void registerRouter2(IMediaRouter2 router, String packageName); void registerRouter2(IMediaRouter2 router, String packageName); void unregisterRouter2(IMediaRouter2 router); void unregisterRouter2(IMediaRouter2 router); void updateScanningStateWithRouter2(IMediaRouter2 router, @JavaPassthrough(annotation="@android.media.MediaRouter2.ScanningState") int scanningState); void setDiscoveryRequestWithRouter2(IMediaRouter2 router, void setDiscoveryRequestWithRouter2(IMediaRouter2 router, in RouteDiscoveryPreference preference); in RouteDiscoveryPreference preference); void setRouteListingPreference(IMediaRouter2 router, void setRouteListingPreference(IMediaRouter2 router, Loading @@ -81,8 +81,7 @@ interface IMediaRouterService { void unregisterManager(IMediaRouter2Manager manager); void unregisterManager(IMediaRouter2Manager manager); void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId, void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId, in MediaRoute2Info route, int volume); in MediaRoute2Info route, int volume); void startScan(IMediaRouter2Manager manager); void updateScanningState(IMediaRouter2Manager manager, @JavaPassthrough(annotation="@android.media.MediaRouter2.ScanningState") int scanningState); void stopScan(IMediaRouter2Manager manager); void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId, void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId, in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route, in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route, Loading
media/java/android/media/MediaRouter2.java +250 −6 Original line number Original line Diff line number Diff line Loading @@ -20,10 +20,12 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES; import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES; import static com.android.media.flags.Flags.FLAG_ENABLE_CROSS_USER_ROUTING_IN_MEDIA_ROUTER2; import static com.android.media.flags.Flags.FLAG_ENABLE_CROSS_USER_ROUTING_IN_MEDIA_ROUTER2; import static com.android.media.flags.Flags.FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2; import static com.android.media.flags.Flags.FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2; import static com.android.media.flags.Flags.FLAG_ENABLE_SCREEN_OFF_SCANNING; import android.Manifest; import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.RequiresPermission; Loading @@ -42,9 +44,12 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArrayMap; import android.util.ArraySet; import android.util.ArraySet; import android.util.Log; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.ArrayList; import java.util.Collections; import java.util.Collections; import java.util.Comparator; import java.util.Comparator; Loading Loading @@ -73,6 +78,48 @@ import java.util.stream.Collectors; // Not only MediaRouter2, but also to service / manager / provider. // Not only MediaRouter2, but also to service / manager / provider. // TODO: ensure thread-safe and document it // TODO: ensure thread-safe and document it public final class MediaRouter2 { public final class MediaRouter2 { /** * The state of a router not requesting route scanning. * * @hide */ public static final int SCANNING_STATE_NOT_SCANNING = 0; /** * The state of a router requesting scanning only while the user interacts with its owner app. * * <p>The device's screen must be on and the app must be in the foreground to trigger scanning * under this state. * * @hide */ public static final int SCANNING_STATE_WHILE_INTERACTIVE = 1; /** * The state of a router requesting unrestricted scanning. * * <p>This state triggers scanning regardless of the restrictions required for {@link * #SCANNING_STATE_WHILE_INTERACTIVE}. * * <p>Routers requesting unrestricted scanning must hold {@link * Manifest.permission#MEDIA_ROUTING_CONTROL}. * * @hide */ public static final int SCANNING_STATE_SCANNING_FULL = 2; /** @hide */ @IntDef( prefix = "SCANNING_STATE", value = { SCANNING_STATE_NOT_SCANNING, SCANNING_STATE_WHILE_INTERACTIVE, SCANNING_STATE_SCANNING_FULL }) @Retention(RetentionPolicy.SOURCE) public @interface ScanningState {} private static final String TAG = "MR2"; private static final String TAG = "MR2"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final Object sSystemRouterLock = new Object(); private static final Object sSystemRouterLock = new Object(); Loading Loading @@ -123,6 +170,13 @@ public final class MediaRouter2 { @GuardedBy("mLock") @GuardedBy("mLock") private final Map<String, RoutingController> mNonSystemRoutingControllers = new ArrayMap<>(); private final Map<String, RoutingController> mNonSystemRoutingControllers = new ArrayMap<>(); @GuardedBy("mLock") private int mScreenOffScanRequestCount = 0; @GuardedBy("mLock") private int mScreenOnScanRequestCount = 0; private final SparseArray<ScanRequest> mScanRequestsMap = new SparseArray<>(); private final AtomicInteger mNextRequestId = new AtomicInteger(1); private final AtomicInteger mNextRequestId = new AtomicInteger(1); private final Handler mHandler; private final Handler mHandler; Loading Loading @@ -335,6 +389,100 @@ public final class MediaRouter2 { mImpl.stopScan(); mImpl.stopScan(); } } /** * Requests the system to actively scan for routes based on the router's {@link * RouteDiscoveryPreference route discovery preference}. * * <p>You must call {@link #cancelScanRequest(ScanToken)} promptly to preserve system resources * like battery. Avoid scanning unless there is clear intention from the user to start routing * their media. * * <p>{@code scanRequest} specifies relevant scanning options, like whether the system should * scan with the screen off. Screen off scanning requires {@link * Manifest.permission#MEDIA_ROUTING_CONTROL} * * <p>Proxy routers use the registered {@link RouteDiscoveryPreference} of their target routers. * * @return A unique {@link ScanToken} that identifies the scan request. */ @FlaggedApi(FLAG_ENABLE_SCREEN_OFF_SCANNING) @NonNull public ScanToken requestScan(@NonNull ScanRequest scanRequest) { Objects.requireNonNull(scanRequest, "scanRequest must not be null."); ScanToken token = new ScanToken(mNextRequestId.getAndIncrement()); synchronized (mLock) { boolean shouldUpdate = mScreenOffScanRequestCount == 0 && (scanRequest.isScreenOffScan() || mScreenOnScanRequestCount == 0); if (shouldUpdate) { try { mImpl.updateScanningState( scanRequest.isScreenOffScan() ? SCANNING_STATE_SCANNING_FULL : SCANNING_STATE_WHILE_INTERACTIVE); if (scanRequest.isScreenOffScan()) { mScreenOffScanRequestCount++; } else { mScreenOnScanRequestCount++; } } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } mScanRequestsMap.put(token.mId, scanRequest); return token; } } /** * Releases the active scan request linked to the provided {@link ScanToken}. * * @see #requestScan(ScanRequest) * @param token {@link ScanToken} of the {@link ScanRequest} to release. * @throws IllegalArgumentException if the token does not match any active scan request. */ @FlaggedApi(FLAG_ENABLE_SCREEN_OFF_SCANNING) public void cancelScanRequest(@NonNull ScanToken token) { Objects.requireNonNull(token, "token must not be null"); synchronized (mLock) { ScanRequest request = mScanRequestsMap.get(token.mId); if (request == null) { throw new IllegalArgumentException( "The token does not match any active scan request"); } boolean shouldUpdate = mScreenOffScanRequestCount == 1 && (request.isScreenOffScan() || mScreenOnScanRequestCount == 1); if (shouldUpdate) { try { if (request.isScreenOffScan() && mScreenOnScanRequestCount == 0) { mImpl.updateScanningState(SCANNING_STATE_NOT_SCANNING); } else { mImpl.updateScanningState(SCANNING_STATE_WHILE_INTERACTIVE); } if (request.isScreenOffScan()) { mScreenOffScanRequestCount--; } else { mScreenOnScanRequestCount--; } } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } } mScanRequestsMap.remove(token.mId); } } private MediaRouter2(Context appContext) { private MediaRouter2(Context appContext) { mContext = appContext; mContext = appContext; mMediaRouterService = mMediaRouterService = Loading Loading @@ -1428,6 +1576,78 @@ public final class MediaRouter2 { public void onControllerUpdated(@NonNull RoutingController controller) {} public void onControllerUpdated(@NonNull RoutingController controller) {} } } /** * Represents an active scan request registered in the system. * * <p>See {@link #requestScan(ScanRequest)} for more information. */ @FlaggedApi(FLAG_ENABLE_SCREEN_OFF_SCANNING) public static final class ScanToken { private final int mId; private ScanToken(int id) { mId = id; } } /** * Represents a set of parameters for scanning requests. * * <p>See {@link #requestScan(ScanRequest)} for more details. */ @FlaggedApi(FLAG_ENABLE_SCREEN_OFF_SCANNING) public static final class ScanRequest { private final boolean mIsScreenOffScan; private ScanRequest(boolean isScreenOffScan) { mIsScreenOffScan = isScreenOffScan; } /** * Returns whether the scan request corresponds to a screen-off scan. * * @see #requestScan(ScanRequest) */ public boolean isScreenOffScan() { return mIsScreenOffScan; } /** * Builder class for {@link ScanRequest}. * * @see #requestScan(ScanRequest) */ public static final class Builder { boolean mIsScreenOffScan; /** * Creates a builder for a {@link ScanRequest} instance. * * @see #requestScan(ScanRequest) */ public Builder() {} /** * Sets whether the app is requesting to scan even while the screen is off, bypassing * default scanning restrictions. Only companion apps holding {@link * Manifest.permission#MEDIA_ROUTING_CONTROL} should set this to {@code true}. * * @see #requestScan(ScanRequest) */ @NonNull public Builder setScreenOffScan(boolean isScreenOffScan) { mIsScreenOffScan = isScreenOffScan; return this; } /** Returns a new {@link ScanRequest} instance. */ @NonNull public ScanRequest build() { return new ScanRequest(mIsScreenOffScan); } } } /** /** * A class to control media routing session in media route provider. For example, * A class to control media routing session in media route provider. For example, * selecting/deselecting/transferring to routes of a session can be done through this. Instances * selecting/deselecting/transferring to routes of a session can be done through this. Instances Loading Loading @@ -2092,6 +2312,9 @@ public final class MediaRouter2 { * ProxyMediaRouter2Impl proxy} {@link MediaRouter2} instances. * ProxyMediaRouter2Impl proxy} {@link MediaRouter2} instances. */ */ private interface MediaRouter2Impl { private interface MediaRouter2Impl { void updateScanningState(@ScanningState int scanningState) throws RemoteException; void startScan(); void startScan(); void stopScan(); void stopScan(); Loading Loading @@ -2194,12 +2417,18 @@ public final class MediaRouter2 { } } } } @Override public void updateScanningState(int scanningState) throws RemoteException { mMediaRouterService.updateScanningState(mClient, scanningState); } @Override @Override public void startScan() { public void startScan() { if (!mIsScanning.getAndSet(true)) { if (!mIsScanning.getAndSet(true)) { if (mScanRequestCount.getAndIncrement() == 0) { if (mScanRequestCount.getAndIncrement() == 0) { try { try { mMediaRouterService.startScan(mClient); mMediaRouterService.updateScanningState( mClient, SCANNING_STATE_WHILE_INTERACTIVE); } catch (RemoteException ex) { } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); throw ex.rethrowFromSystemServer(); } } Loading @@ -2221,7 +2450,8 @@ public final class MediaRouter2 { }) }) == 0) { == 0) { try { try { mMediaRouterService.stopScan(mClient); mMediaRouterService.updateScanningState( mClient, SCANNING_STATE_NOT_SCANNING); } catch (RemoteException ex) { } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); throw ex.rethrowFromSystemServer(); } } Loading Loading @@ -3041,6 +3271,18 @@ public final class MediaRouter2 { // Do nothing. // Do nothing. } } @Override @GuardedBy("mLock") public void updateScanningState(int scanningState) throws RemoteException { if (scanningState != SCANNING_STATE_NOT_SCANNING) { registerRouterStubIfNeededLocked(); } mMediaRouterService.updateScanningStateWithRouter2(mStub, scanningState); if (scanningState == SCANNING_STATE_NOT_SCANNING) { unregisterRouterStubIfNeededLocked(/* isScanningStopping */ true); } } /** /** * Returns {@code null}. The client package name is only associated to proxy {@link * Returns {@code null}. The client package name is only associated to proxy {@link * MediaRouter2} instances. * MediaRouter2} instances. Loading Loading @@ -3103,7 +3345,7 @@ public final class MediaRouter2 { mStub, mDiscoveryPreference); mStub, mDiscoveryPreference); } } unregisterRouterStubIfNeededLocked(); unregisterRouterStubIfNeededLocked(/* isScanningStopping */ false); } catch (RemoteException ex) { } catch (RemoteException ex) { Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.", ex); Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.", ex); Loading Loading @@ -3313,7 +3555,7 @@ public final class MediaRouter2 { } } try { try { unregisterRouterStubIfNeededLocked(); unregisterRouterStubIfNeededLocked(/* isScanningStopping */ false); } catch (RemoteException ex) { } catch (RemoteException ex) { ex.rethrowFromSystemServer(); ex.rethrowFromSystemServer(); } } Loading @@ -3331,10 +3573,12 @@ public final class MediaRouter2 { } } @GuardedBy("mLock") @GuardedBy("mLock") private void unregisterRouterStubIfNeededLocked() throws RemoteException { private void unregisterRouterStubIfNeededLocked(boolean isScanningStopping) throws RemoteException { if (mStub != null if (mStub != null && mRouteCallbackRecords.isEmpty() && mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) { && mNonSystemRoutingControllers.isEmpty() && (mScanRequestsMap.size() == 0 || isScanningStopping)) { mMediaRouterService.unregisterRouter2(mStub); mMediaRouterService.unregisterRouter2(mStub); mStub = null; mStub = null; } } Loading
media/java/android/media/MediaRouter2Manager.java +5 −2 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,9 @@ package android.media; package android.media; import static android.media.MediaRouter2.SCANNING_STATE_NOT_SCANNING; import static android.media.MediaRouter2.SCANNING_STATE_WHILE_INTERACTIVE; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.Manifest; import android.Manifest; Loading Loading @@ -174,7 +177,7 @@ public final class MediaRouter2Manager { public void registerScanRequest() { public void registerScanRequest() { if (mScanRequestCount.getAndIncrement() == 0) { if (mScanRequestCount.getAndIncrement() == 0) { try { try { mMediaRouterService.startScan(mClient); mMediaRouterService.updateScanningState(mClient, SCANNING_STATE_WHILE_INTERACTIVE); } catch (RemoteException ex) { } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); throw ex.rethrowFromSystemServer(); } } Loading @@ -201,7 +204,7 @@ public final class MediaRouter2Manager { }) }) == 0) { == 0) { try { try { mMediaRouterService.stopScan(mClient); mMediaRouterService.updateScanningState(mClient, SCANNING_STATE_NOT_SCANNING); } catch (RemoteException ex) { } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); throw ex.rethrowFromSystemServer(); } } Loading
media/java/android/media/flags/media_better_together.aconfig +7 −0 Original line number Original line Diff line number Diff line Loading @@ -90,3 +90,10 @@ flag { description: "Enables mechanisms to prevent route providers from keeping malicious apps alive." description: "Enables mechanisms to prevent route providers from keeping malicious apps alive." bug: "263520343" bug: "263520343" } } flag { name: "enable_screen_off_scanning" namespace: "media_solutions" description: "Enable new MediaRouter2 API to enable watch companion apps to scan while the phone screen is off." bug: "281072508" }