Loading core/api/current.txt +15 −0 Original line number Diff line number Diff line Loading @@ -24406,6 +24406,7 @@ package android.media { } 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 @NonNull public java.util.List<android.media.MediaRouter2.RoutingController> getControllers(); 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 @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 @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 setRouteListingPreference(@Nullable android.media.RouteListingPreference); 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(); } @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 { ctor public MediaRouter2.TransferCallback(); method public void onStop(@NonNull android.media.MediaRouter2.RoutingController); media/java/android/media/IMediaRouterService.aidl +2 −3 Original line number Diff line number Diff line Loading @@ -27,7 +27,6 @@ import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; import android.os.Bundle; import android.os.UserHandle; /** * {@hide} */ Loading Loading @@ -56,6 +55,7 @@ interface IMediaRouterService { void registerRouter2(IMediaRouter2 router, String packageName); void unregisterRouter2(IMediaRouter2 router); void updateScanningStateWithRouter2(IMediaRouter2 router, @JavaPassthrough(annotation="@android.media.MediaRouter2.ScanningState") int scanningState); void setDiscoveryRequestWithRouter2(IMediaRouter2 router, in RouteDiscoveryPreference preference); void setRouteListingPreference(IMediaRouter2 router, Loading @@ -81,8 +81,7 @@ interface IMediaRouterService { void unregisterManager(IMediaRouter2Manager manager); void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId, in MediaRoute2Info route, int volume); void startScan(IMediaRouter2Manager manager); void stopScan(IMediaRouter2Manager manager); void updateScanningState(IMediaRouter2Manager manager, @JavaPassthrough(annotation="@android.media.MediaRouter2.ScanningState") int scanningState); void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId, in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route, Loading media/java/android/media/MediaRouter2.java +250 −6 Original line number 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_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_SCREEN_OFF_SCANNING; import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; Loading @@ -42,9 +44,12 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; Loading Loading @@ -73,6 +78,48 @@ import java.util.stream.Collectors; // Not only MediaRouter2, but also to service / manager / provider. // TODO: ensure thread-safe and document it 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 boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final Object sSystemRouterLock = new Object(); Loading Loading @@ -123,6 +170,13 @@ public final class MediaRouter2 { @GuardedBy("mLock") 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 Handler mHandler; Loading Loading @@ -335,6 +389,100 @@ public final class MediaRouter2 { 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) { mContext = appContext; mMediaRouterService = Loading Loading @@ -1428,6 +1576,78 @@ public final class MediaRouter2 { 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, * 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. */ private interface MediaRouter2Impl { void updateScanningState(@ScanningState int scanningState) throws RemoteException; void startScan(); 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 public void startScan() { if (!mIsScanning.getAndSet(true)) { if (mScanRequestCount.getAndIncrement() == 0) { try { mMediaRouterService.startScan(mClient); mMediaRouterService.updateScanningState( mClient, SCANNING_STATE_WHILE_INTERACTIVE); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } Loading @@ -2221,7 +2450,8 @@ public final class MediaRouter2 { }) == 0) { try { mMediaRouterService.stopScan(mClient); mMediaRouterService.updateScanningState( mClient, SCANNING_STATE_NOT_SCANNING); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } Loading Loading @@ -3041,6 +3271,18 @@ public final class MediaRouter2 { // 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 * MediaRouter2} instances. Loading Loading @@ -3103,7 +3345,7 @@ public final class MediaRouter2 { mStub, mDiscoveryPreference); } unregisterRouterStubIfNeededLocked(); unregisterRouterStubIfNeededLocked(/* isScanningStopping */ false); } catch (RemoteException ex) { Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.", ex); Loading Loading @@ -3313,7 +3555,7 @@ public final class MediaRouter2 { } try { unregisterRouterStubIfNeededLocked(); unregisterRouterStubIfNeededLocked(/* isScanningStopping */ false); } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } Loading @@ -3331,10 +3573,12 @@ public final class MediaRouter2 { } @GuardedBy("mLock") private void unregisterRouterStubIfNeededLocked() throws RemoteException { private void unregisterRouterStubIfNeededLocked(boolean isScanningStopping) throws RemoteException { if (mStub != null && mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) { && mNonSystemRoutingControllers.isEmpty() && (mScanRequestsMap.size() == 0 || isScanningStopping)) { mMediaRouterService.unregisterRouter2(mStub); mStub = null; } Loading media/java/android/media/MediaRouter2Manager.java +5 −2 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ 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 android.Manifest; Loading Loading @@ -174,7 +177,7 @@ public final class MediaRouter2Manager { public void registerScanRequest() { if (mScanRequestCount.getAndIncrement() == 0) { try { mMediaRouterService.startScan(mClient); mMediaRouterService.updateScanningState(mClient, SCANNING_STATE_WHILE_INTERACTIVE); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } Loading @@ -201,7 +204,7 @@ public final class MediaRouter2Manager { }) == 0) { try { mMediaRouterService.stopScan(mClient); mMediaRouterService.updateScanningState(mClient, SCANNING_STATE_NOT_SCANNING); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } Loading media/java/android/media/flags/media_better_together.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -90,3 +90,10 @@ flag { description: "Enables mechanisms to prevent route providers from keeping malicious apps alive." 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 Diff line number Diff line Loading @@ -24406,6 +24406,7 @@ package android.media { } 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 @NonNull public java.util.List<android.media.MediaRouter2.RoutingController> getControllers(); 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 @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 @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 setRouteListingPreference(@Nullable android.media.RouteListingPreference); 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(); } @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 { ctor public MediaRouter2.TransferCallback(); method public void onStop(@NonNull android.media.MediaRouter2.RoutingController);
media/java/android/media/IMediaRouterService.aidl +2 −3 Original line number Diff line number Diff line Loading @@ -27,7 +27,6 @@ import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; import android.os.Bundle; import android.os.UserHandle; /** * {@hide} */ Loading Loading @@ -56,6 +55,7 @@ interface IMediaRouterService { void registerRouter2(IMediaRouter2 router, String packageName); void unregisterRouter2(IMediaRouter2 router); void updateScanningStateWithRouter2(IMediaRouter2 router, @JavaPassthrough(annotation="@android.media.MediaRouter2.ScanningState") int scanningState); void setDiscoveryRequestWithRouter2(IMediaRouter2 router, in RouteDiscoveryPreference preference); void setRouteListingPreference(IMediaRouter2 router, Loading @@ -81,8 +81,7 @@ interface IMediaRouterService { void unregisterManager(IMediaRouter2Manager manager); void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId, in MediaRoute2Info route, int volume); void startScan(IMediaRouter2Manager manager); void stopScan(IMediaRouter2Manager manager); void updateScanningState(IMediaRouter2Manager manager, @JavaPassthrough(annotation="@android.media.MediaRouter2.ScanningState") int scanningState); void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId, in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route, Loading
media/java/android/media/MediaRouter2.java +250 −6 Original line number 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_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_SCREEN_OFF_SCANNING; import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; Loading @@ -42,9 +44,12 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; Loading Loading @@ -73,6 +78,48 @@ import java.util.stream.Collectors; // Not only MediaRouter2, but also to service / manager / provider. // TODO: ensure thread-safe and document it 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 boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final Object sSystemRouterLock = new Object(); Loading Loading @@ -123,6 +170,13 @@ public final class MediaRouter2 { @GuardedBy("mLock") 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 Handler mHandler; Loading Loading @@ -335,6 +389,100 @@ public final class MediaRouter2 { 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) { mContext = appContext; mMediaRouterService = Loading Loading @@ -1428,6 +1576,78 @@ public final class MediaRouter2 { 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, * 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. */ private interface MediaRouter2Impl { void updateScanningState(@ScanningState int scanningState) throws RemoteException; void startScan(); 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 public void startScan() { if (!mIsScanning.getAndSet(true)) { if (mScanRequestCount.getAndIncrement() == 0) { try { mMediaRouterService.startScan(mClient); mMediaRouterService.updateScanningState( mClient, SCANNING_STATE_WHILE_INTERACTIVE); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } Loading @@ -2221,7 +2450,8 @@ public final class MediaRouter2 { }) == 0) { try { mMediaRouterService.stopScan(mClient); mMediaRouterService.updateScanningState( mClient, SCANNING_STATE_NOT_SCANNING); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } Loading Loading @@ -3041,6 +3271,18 @@ public final class MediaRouter2 { // 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 * MediaRouter2} instances. Loading Loading @@ -3103,7 +3345,7 @@ public final class MediaRouter2 { mStub, mDiscoveryPreference); } unregisterRouterStubIfNeededLocked(); unregisterRouterStubIfNeededLocked(/* isScanningStopping */ false); } catch (RemoteException ex) { Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.", ex); Loading Loading @@ -3313,7 +3555,7 @@ public final class MediaRouter2 { } try { unregisterRouterStubIfNeededLocked(); unregisterRouterStubIfNeededLocked(/* isScanningStopping */ false); } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } Loading @@ -3331,10 +3573,12 @@ public final class MediaRouter2 { } @GuardedBy("mLock") private void unregisterRouterStubIfNeededLocked() throws RemoteException { private void unregisterRouterStubIfNeededLocked(boolean isScanningStopping) throws RemoteException { if (mStub != null && mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) { && mNonSystemRoutingControllers.isEmpty() && (mScanRequestsMap.size() == 0 || isScanningStopping)) { mMediaRouterService.unregisterRouter2(mStub); mStub = null; } Loading
media/java/android/media/MediaRouter2Manager.java +5 −2 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ 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 android.Manifest; Loading Loading @@ -174,7 +177,7 @@ public final class MediaRouter2Manager { public void registerScanRequest() { if (mScanRequestCount.getAndIncrement() == 0) { try { mMediaRouterService.startScan(mClient); mMediaRouterService.updateScanningState(mClient, SCANNING_STATE_WHILE_INTERACTIVE); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } Loading @@ -201,7 +204,7 @@ public final class MediaRouter2Manager { }) == 0) { try { mMediaRouterService.stopScan(mClient); mMediaRouterService.updateScanningState(mClient, SCANNING_STATE_NOT_SCANNING); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } Loading
media/java/android/media/flags/media_better_together.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -90,3 +90,10 @@ flag { description: "Enables mechanisms to prevent route providers from keeping malicious apps alive." 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" }