summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt15
-rw-r--r--media/java/android/media/IMediaRouterService.aidl5
-rw-r--r--media/java/android/media/MediaRouter2.java256
-rw-r--r--media/java/android/media/MediaRouter2Manager.java7
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig7
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java309
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java19
7 files changed, 511 insertions, 107 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index d410686c9ea1..7be8319e86ae 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -24376,6 +24376,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);
@@ -24387,6 +24388,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();
@@ -24433,6 +24435,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);
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 04e99ea8a57f..7727078a42ec 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -27,7 +27,6 @@ import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
import android.os.Bundle;
import android.os.UserHandle;
-
/**
* {@hide}
*/
@@ -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,
@@ -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,
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 425db06ce55f..062c44275223 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -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;
@@ -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;
@@ -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();
@@ -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;
@@ -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 =
@@ -1429,6 +1577,78 @@ public final class MediaRouter2 {
}
/**
+ * 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
* are created when {@link TransferCallback#onTransfer(RoutingController, RoutingController)} is
@@ -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();
@@ -2195,11 +2418,17 @@ 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();
}
@@ -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();
}
@@ -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.
@@ -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);
@@ -3313,7 +3555,7 @@ public final class MediaRouter2 {
}
try {
- unregisterRouterStubIfNeededLocked();
+ unregisterRouterStubIfNeededLocked(/* isScanningStopping */ false);
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
}
@@ -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;
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 06c0996785c2..488d54486561 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -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;
@@ -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();
}
@@ -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();
}
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index df9ecdc98e85..9e812f349d79 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -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"
+}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 85a131579497..1f7d5490dd6d 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -20,6 +20,9 @@ import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREG
import static android.content.Intent.ACTION_SCREEN_OFF;
import static android.content.Intent.ACTION_SCREEN_ON;
import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
+import static android.media.MediaRouter2.SCANNING_STATE_NOT_SCANNING;
+import static android.media.MediaRouter2.SCANNING_STATE_SCANNING_FULL;
+import static android.media.MediaRouter2.SCANNING_STATE_WHILE_INTERACTIVE;
import static android.media.MediaRouter2Utils.getOriginalId;
import static android.media.MediaRouter2Utils.getProviderId;
@@ -42,6 +45,7 @@ import android.media.IMediaRouter2Manager;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
+import android.media.MediaRouter2.ScanningState;
import android.media.MediaRouter2Manager;
import android.media.RouteDiscoveryPreference;
import android.media.RouteListingPreference;
@@ -224,17 +228,27 @@ class MediaRouter2ServiceImpl {
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
- final boolean hasConfigureWifiDisplayPermission = mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
- == PackageManager.PERMISSION_GRANTED;
+ final boolean hasConfigureWifiDisplayPermission =
+ mContext.checkCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY)
+ == PackageManager.PERMISSION_GRANTED;
final boolean hasModifyAudioRoutingPermission =
checkCallerHasModifyAudioRoutingPermission(pid, uid);
+ boolean hasMediaRoutingControlPermission =
+ checkMediaRoutingControlPermission(uid, pid, packageName);
+
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- registerRouter2Locked(router, uid, pid, packageName, userId,
- hasConfigureWifiDisplayPermission, hasModifyAudioRoutingPermission);
+ registerRouter2Locked(
+ router,
+ uid,
+ pid,
+ packageName,
+ userId,
+ hasConfigureWifiDisplayPermission,
+ hasModifyAudioRoutingPermission,
+ hasMediaRoutingControlPermission);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -254,6 +268,21 @@ class MediaRouter2ServiceImpl {
}
}
+ public void updateScanningState(
+ @NonNull IMediaRouter2 router, @ScanningState int scanningState) {
+ Objects.requireNonNull(router, "router must not be null");
+ validateScanningStateValue(scanningState);
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ updateScanningStateLocked(router, scanningState);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
public void setDiscoveryRequestWithRouter2(@NonNull IMediaRouter2 router,
@NonNull RouteDiscoveryPreference preference) {
Objects.requireNonNull(router, "router must not be null");
@@ -570,24 +599,15 @@ class MediaRouter2ServiceImpl {
}
}
- public void startScan(@NonNull IMediaRouter2Manager manager) {
+ public void updateScanningState(
+ @NonNull IMediaRouter2Manager manager, @ScanningState int scanningState) {
Objects.requireNonNull(manager, "manager must not be null");
- final long token = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- startScanLocked(manager);
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
+ validateScanningStateValue(scanningState);
- public void stopScan(@NonNull IMediaRouter2Manager manager) {
- Objects.requireNonNull(manager, "manager must not be null");
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- stopScanLocked(manager);
+ updateScanningStateLocked(manager, scanningState);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -825,7 +845,16 @@ class MediaRouter2ServiceImpl {
throw new SecurityException("Must hold MEDIA_CONTENT_CONTROL");
}
- if (PermissionChecker.checkPermissionForDataDelivery(
+ if (!checkMediaRoutingControlPermission(callerUid, callerPid, callerPackageName)) {
+ throw new SecurityException(
+ "Must hold MEDIA_CONTENT_CONTROL or MEDIA_ROUTING_CONTROL permissions.");
+ }
+ }
+
+ @RequiresPermission(value = Manifest.permission.MEDIA_ROUTING_CONTROL, conditional = true)
+ private boolean checkMediaRoutingControlPermission(
+ int callerUid, int callerPid, @Nullable String callerPackageName) {
+ return PermissionChecker.checkPermissionForDataDelivery(
mContext,
Manifest.permission.MEDIA_ROUTING_CONTROL,
callerPid,
@@ -833,11 +862,8 @@ class MediaRouter2ServiceImpl {
callerPackageName,
/* attributionTag */ null,
/* message */ "Checking permissions for registering manager in"
- + " MediaRouter2ServiceImpl.")
- != PermissionChecker.PERMISSION_GRANTED) {
- throw new SecurityException(
- "Must hold MEDIA_CONTENT_CONTROL or MEDIA_ROUTING_CONTROL permissions.");
- }
+ + " MediaRouter2ServiceImpl.")
+ == PermissionChecker.PERMISSION_GRANTED;
}
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS)
@@ -944,9 +970,15 @@ class MediaRouter2ServiceImpl {
// Start of locked methods that are used by MediaRouter2.
@GuardedBy("mLock")
- private void registerRouter2Locked(@NonNull IMediaRouter2 router, int uid, int pid,
- @NonNull String packageName, int userId, boolean hasConfigureWifiDisplayPermission,
- boolean hasModifyAudioRoutingPermission) {
+ private void registerRouter2Locked(
+ @NonNull IMediaRouter2 router,
+ int uid,
+ int pid,
+ @NonNull String packageName,
+ int userId,
+ boolean hasConfigureWifiDisplayPermission,
+ boolean hasModifyAudioRoutingPermission,
+ boolean hasMediaRoutingControlPermission) {
final IBinder binder = router.asBinder();
if (mAllRouterRecords.get(binder) != null) {
Slog.w(TAG, "registerRouter2Locked: Same router already exists. packageName="
@@ -955,8 +987,16 @@ class MediaRouter2ServiceImpl {
}
UserRecord userRecord = getOrCreateUserRecordLocked(userId);
- RouterRecord routerRecord = new RouterRecord(userRecord, router, uid, pid, packageName,
- hasConfigureWifiDisplayPermission, hasModifyAudioRoutingPermission);
+ RouterRecord routerRecord =
+ new RouterRecord(
+ userRecord,
+ router,
+ uid,
+ pid,
+ packageName,
+ hasConfigureWifiDisplayPermission,
+ hasModifyAudioRoutingPermission,
+ hasMediaRoutingControlPermission);
try {
binder.linkToDeath(routerRecord, 0);
} catch (RemoteException ex) {
@@ -970,9 +1010,16 @@ class MediaRouter2ServiceImpl {
obtainMessage(UserHandler::notifyRouterRegistered,
userRecord.mHandler, routerRecord));
- Slog.i(TAG, TextUtils.formatSimple(
- "registerRouter2 | package: %s, uid: %d, pid: %d, router id: %d",
- packageName, uid, pid, routerRecord.mRouterId));
+ Slog.i(
+ TAG,
+ TextUtils.formatSimple(
+ "registerRouter2 | package: %s, uid: %d, pid: %d, router id: %d,"
+ + " hasMediaRoutingControl: %b",
+ packageName,
+ uid,
+ pid,
+ routerRecord.mRouterId,
+ hasMediaRoutingControlPermission));
}
@GuardedBy("mLock")
@@ -1012,6 +1059,33 @@ class MediaRouter2ServiceImpl {
}
@GuardedBy("mLock")
+ private void updateScanningStateLocked(
+ @NonNull IMediaRouter2 router, @ScanningState int scanningState) {
+ final IBinder binder = router.asBinder();
+ RouterRecord routerRecord = mAllRouterRecords.get(binder);
+ if (routerRecord == null) {
+ Slog.w(TAG, "Router record not found. Ignoring updateScanningState call.");
+ return;
+ }
+
+ if (scanningState == SCANNING_STATE_SCANNING_FULL
+ && !routerRecord.mHasMediaRoutingControl) {
+ throw new SecurityException("Screen off scan requires MEDIA_ROUTING_CONTROL");
+ }
+
+ Slog.i(
+ TAG,
+ TextUtils.formatSimple(
+ "updateScanningStateLocked | router: %d, packageName: %s, scanningState:"
+ + " %d",
+ routerRecord.mRouterId,
+ routerRecord.mPackageName,
+ getScanningStateString(scanningState)));
+
+ routerRecord.updateScanningState(scanningState);
+ }
+
+ @GuardedBy("mLock")
private void setDiscoveryRequestWithRouter2Locked(@NonNull RouterRecord routerRecord,
@NonNull RouteDiscoveryPreference discoveryRequest) {
if (routerRecord.mDiscoveryPreference.equals(discoveryRequest)) {
@@ -1347,14 +1421,24 @@ class MediaRouter2ServiceImpl {
return;
}
+ boolean hasMediaRoutingControl =
+ checkMediaRoutingControlPermission(callerUid, callerPid, callerPackageName);
+
Slog.i(
TAG,
TextUtils.formatSimple(
"registerManager | callerUid: %d, callerPid: %d, callerPackage: %s,"
- + "targetPackageName: %s, targetUserId: %d",
- callerUid, callerPid, callerPackageName, targetPackageName, targetUser));
+ + " targetPackageName: %s, targetUserId: %d, hasMediaRoutingControl:"
+ + " %b",
+ callerUid,
+ callerPid,
+ callerPackageName,
+ targetPackageName,
+ targetUser,
+ hasMediaRoutingControl));
UserRecord userRecord = getOrCreateUserRecordLocked(targetUser.getIdentifier());
+
managerRecord =
new ManagerRecord(
userRecord,
@@ -1362,7 +1446,8 @@ class MediaRouter2ServiceImpl {
callerUid,
callerPid,
callerPackageName,
- targetPackageName);
+ targetPackageName,
+ hasMediaRoutingControl);
try {
binder.linkToDeath(managerRecord, 0);
} catch (RemoteException ex) {
@@ -1427,41 +1512,31 @@ class MediaRouter2ServiceImpl {
}
@GuardedBy("mLock")
- private void startScanLocked(@NonNull IMediaRouter2Manager manager) {
+ private void updateScanningStateLocked(
+ @NonNull IMediaRouter2Manager manager, @ScanningState int scanningState) {
final IBinder binder = manager.asBinder();
ManagerRecord managerRecord = mAllManagerRecords.get(binder);
if (managerRecord == null) {
+ Slog.w(TAG, "Manager record not found. Ignoring updateScanningState call.");
return;
}
- Slog.i(
- TAG,
- TextUtils.formatSimple(
- "startScan | manager: %d, ownerPackageName: %s, targetPackageName: %s",
- managerRecord.mManagerId,
- managerRecord.mOwnerPackageName,
- managerRecord.mTargetPackageName));
-
- managerRecord.startScan();
- }
-
- @GuardedBy("mLock")
- private void stopScanLocked(@NonNull IMediaRouter2Manager manager) {
- final IBinder binder = manager.asBinder();
- ManagerRecord managerRecord = mAllManagerRecords.get(binder);
- if (managerRecord == null) {
- return;
+ if (!managerRecord.mHasMediaRoutingControl
+ && scanningState == SCANNING_STATE_SCANNING_FULL) {
+ throw new SecurityException("Screen off scan requires MEDIA_ROUTING_CONTROL");
}
Slog.i(
TAG,
TextUtils.formatSimple(
- "stopScan | manager: %d, ownerPackageName: %s, targetPackageName: %s",
+ "updateScanningState | manager: %d, ownerPackageName: %s,"
+ + " targetPackageName: %s, scanningState: %d",
managerRecord.mManagerId,
managerRecord.mOwnerPackageName,
- managerRecord.mTargetPackageName));
+ managerRecord.mTargetPackageName,
+ getScanningStateString(scanningState)));
- managerRecord.stopScan();
+ managerRecord.updateScanningState(scanningState);
}
@GuardedBy("mLock")
@@ -1738,6 +1813,24 @@ class MediaRouter2ServiceImpl {
return (int) uniqueRequestId;
}
+ private static String getScanningStateString(@ScanningState int scanningState) {
+ return switch (scanningState) {
+ case SCANNING_STATE_NOT_SCANNING -> "NOT_SCANNING";
+ case SCANNING_STATE_WHILE_INTERACTIVE -> "SCREEN_ON_ONLY";
+ case SCANNING_STATE_SCANNING_FULL -> "FULL";
+ default -> "Invalid scanning state: " + scanningState;
+ };
+ }
+
+ private static void validateScanningStateValue(@ScanningState int scanningState) {
+ if (scanningState != SCANNING_STATE_NOT_SCANNING
+ && scanningState != SCANNING_STATE_WHILE_INTERACTIVE
+ && scanningState != SCANNING_STATE_SCANNING_FULL) {
+ throw new IllegalArgumentException(
+ TextUtils.formatSimple("Scanning state %d is not valid.", scanningState));
+ }
+ }
+
final class UserRecord {
public final int mUserId;
//TODO: make records private for thread-safety
@@ -1817,13 +1910,21 @@ class MediaRouter2ServiceImpl {
public final boolean mHasModifyAudioRoutingPermission;
public final AtomicBoolean mHasBluetoothRoutingPermission;
public final int mRouterId;
+ public final boolean mHasMediaRoutingControl;
+ public @ScanningState int mScanningState = SCANNING_STATE_NOT_SCANNING;
public RouteDiscoveryPreference mDiscoveryPreference;
@Nullable public RouteListingPreference mRouteListingPreference;
- RouterRecord(UserRecord userRecord, IMediaRouter2 router, int uid, int pid,
- String packageName, boolean hasConfigureWifiDisplayPermission,
- boolean hasModifyAudioRoutingPermission) {
+ RouterRecord(
+ UserRecord userRecord,
+ IMediaRouter2 router,
+ int uid,
+ int pid,
+ String packageName,
+ boolean hasConfigureWifiDisplayPermission,
+ boolean hasModifyAudioRoutingPermission,
+ boolean hasMediaRoutingControl) {
mUserRecord = userRecord;
mPackageName = packageName;
mSelectRouteSequenceNumbers = new ArrayList<>();
@@ -1835,6 +1936,7 @@ class MediaRouter2ServiceImpl {
mHasModifyAudioRoutingPermission = hasModifyAudioRoutingPermission;
mHasBluetoothRoutingPermission =
new AtomicBoolean(checkCallerHasBluetoothPermissions(mPid, mUid));
+ mHasMediaRoutingControl = hasMediaRoutingControl;
mRouterId = mNextRouterOrManagerId.getAndIncrement();
}
@@ -1846,6 +1948,12 @@ class MediaRouter2ServiceImpl {
return mHasModifyAudioRoutingPermission || mHasBluetoothRoutingPermission.get();
}
+ public boolean isActivelyScanning() {
+ return mScanningState == SCANNING_STATE_WHILE_INTERACTIVE
+ || mScanningState == SCANNING_STATE_SCANNING_FULL
+ || mDiscoveryPreference.shouldPerformActiveScan();
+ }
+
@GuardedBy("mLock")
public void maybeUpdateSystemRoutingPermissionLocked() {
boolean oldSystemRoutingPermissionValue = hasSystemRoutingPermission();
@@ -1877,6 +1985,18 @@ class MediaRouter2ServiceImpl {
routerDied(this);
}
+ public void updateScanningState(@ScanningState int scanningState) {
+ if (mScanningState == scanningState) {
+ return;
+ }
+
+ mScanningState = scanningState;
+
+ mUserRecord.mHandler.sendMessage(
+ obtainMessage(
+ UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
+ }
+
public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
pw.println(prefix + "RouterRecord");
@@ -2002,8 +2122,11 @@ class MediaRouter2ServiceImpl {
public final int mManagerId;
// TODO (b/281072508): Document behaviour around nullability for mTargetPackageName.
@Nullable public final String mTargetPackageName;
+
+ public final boolean mHasMediaRoutingControl;
@Nullable public SessionCreationRequest mLastSessionCreationRequest;
- public boolean mIsScanning;
+
+ public @ScanningState int mScanningState = SCANNING_STATE_NOT_SCANNING;
ManagerRecord(
@NonNull UserRecord userRecord,
@@ -2011,7 +2134,8 @@ class MediaRouter2ServiceImpl {
int ownerUid,
int ownerPid,
@NonNull String ownerPackageName,
- @Nullable String targetPackageName) {
+ @Nullable String targetPackageName,
+ boolean hasMediaRoutingControl) {
mUserRecord = userRecord;
mManager = manager;
mOwnerUid = ownerUid;
@@ -2019,6 +2143,7 @@ class MediaRouter2ServiceImpl {
mOwnerPackageName = ownerPackageName;
mTargetPackageName = targetPackageName;
mManagerId = mNextRouterOrManagerId.getAndIncrement();
+ mHasMediaRoutingControl = hasMediaRoutingControl;
}
public void dispose() {
@@ -2040,29 +2165,23 @@ class MediaRouter2ServiceImpl {
pw.println(indent + "mManagerId=" + mManagerId);
pw.println(indent + "mOwnerUid=" + mOwnerUid);
pw.println(indent + "mOwnerPid=" + mOwnerPid);
- pw.println(indent + "mIsScanning=" + mIsScanning);
+ pw.println(indent + "mScanningState=" + getScanningStateString(mScanningState));
if (mLastSessionCreationRequest != null) {
mLastSessionCreationRequest.dump(pw, indent);
}
}
- public void startScan() {
- if (mIsScanning) {
+ private void updateScanningState(@ScanningState int scanningState) {
+ if (mScanningState == scanningState) {
return;
}
- mIsScanning = true;
- mUserRecord.mHandler.sendMessage(PooledLambda.obtainMessage(
- UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
- }
- public void stopScan() {
- if (!mIsScanning) {
- return;
- }
- mIsScanning = false;
- mUserRecord.mHandler.sendMessage(PooledLambda.obtainMessage(
- UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
+ mScanningState = scanningState;
+
+ mUserRecord.mHandler.sendMessage(
+ obtainMessage(
+ UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
}
@Override
@@ -3103,6 +3222,13 @@ class MediaRouter2ServiceImpl {
buildCompositeDiscoveryPreference(
activeRouterRecords, areManagersScanning, activelyScanningPackages);
+ Slog.i(
+ TAG,
+ TextUtils.formatSimple(
+ "Updating composite discovery preference | preference: %s, active"
+ + " routers: %s",
+ newPreference, activelyScanningPackages));
+
if (updateScanningOnUserRecord(service, activelyScanningPackages, newPreference)) {
updateDiscoveryPreferenceForProviders(activelyScanningPackages);
}
@@ -3152,7 +3278,7 @@ class MediaRouter2ServiceImpl {
for (RouterRecord activeRouterRecord : activeRouterRecords) {
RouteDiscoveryPreference preference = activeRouterRecord.mDiscoveryPreference;
preferredFeatures.addAll(preference.getPreferredFeatures());
- if (preference.shouldPerformActiveScan()) {
+ if (activeRouterRecord.isActivelyScanning()) {
activeScan = true;
activelyScanningPackages.add(activeRouterRecord.mPackageName);
}
@@ -3175,33 +3301,40 @@ class MediaRouter2ServiceImpl {
private static List<RouterRecord> getIndividuallyActiveRouters(
MediaRouter2ServiceImpl service, List<RouterRecord> allRouterRecords) {
if (!Flags.disableScreenOffBroadcastReceiver()
- && !service.mPowerManager.isInteractive()) {
+ && !service.mPowerManager.isInteractive()
+ && !Flags.enableScreenOffScanning()) {
return Collections.emptyList();
}
return allRouterRecords.stream()
.filter(
record ->
- service.mActivityManager.getPackageImportance(
- record.mPackageName)
- <= REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING)
+ isPackageImportanceSufficientForScanning(
+ service, record.mPackageName)
+ || record.mScanningState
+ == SCANNING_STATE_SCANNING_FULL)
.collect(Collectors.toList());
}
private static boolean areManagersScanning(
MediaRouter2ServiceImpl service, List<ManagerRecord> managerRecords) {
if (!Flags.disableScreenOffBroadcastReceiver()
- && !service.mPowerManager.isInteractive()) {
+ && !service.mPowerManager.isInteractive()
+ && !Flags.enableScreenOffScanning()) {
return false;
}
- return managerRecords.stream()
- .anyMatch(
- manager ->
- manager.mIsScanning
- && service.mActivityManager.getPackageImportance(
- manager.mOwnerPackageName)
- <= REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING);
+ return managerRecords.stream().anyMatch(manager ->
+ (manager.mScanningState == SCANNING_STATE_WHILE_INTERACTIVE
+ && isPackageImportanceSufficientForScanning(service,
+ manager.mOwnerPackageName))
+ || manager.mScanningState == SCANNING_STATE_SCANNING_FULL);
+ }
+
+ private static boolean isPackageImportanceSufficientForScanning(
+ MediaRouter2ServiceImpl service, String packageName) {
+ return service.mActivityManager.getPackageImportance(packageName)
+ <= REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING;
}
private MediaRoute2Provider findProvider(@Nullable String providerId) {
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 7dd13142f52e..6af3480989fa 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -43,6 +43,7 @@ import android.media.IMediaRouterClient;
import android.media.IMediaRouterService;
import android.media.MediaRoute2Info;
import android.media.MediaRouter;
+import android.media.MediaRouter2.ScanningState;
import android.media.MediaRouterClientState;
import android.media.RemoteDisplayState;
import android.media.RemoteDisplayState.RemoteDisplayInfo;
@@ -439,6 +440,13 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
+ public void updateScanningStateWithRouter2(
+ IMediaRouter2 router, @ScanningState int scanningState) {
+ mService2.updateScanningState(router, scanningState);
+ }
+
+ // Binder call
+ @Override
public void setDiscoveryRequestWithRouter2(IMediaRouter2 router,
RouteDiscoveryPreference request) {
mService2.setDiscoveryRequestWithRouter2(router, request);
@@ -574,14 +582,9 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
- public void startScan(IMediaRouter2Manager manager) {
- mService2.startScan(manager);
- }
-
- // Binder call
- @Override
- public void stopScan(IMediaRouter2Manager manager) {
- mService2.stopScan(manager);
+ public void updateScanningState(
+ IMediaRouter2Manager manager, @ScanningState int scanningState) {
+ mService2.updateScanningState(manager, scanningState);
}
// Binder call