Create MEDIA_ROUTING_CONTROL app op permission for proxy routing
The new permission allows holders of COMPANION_DEVICE_WATCH to use
MediaRouter2 to control the routing of other apps from the watch.
Users will grant the permission from a Special App Access setting.
Bug: 305919655
Bug: 192657812
Test: atest CtsMediaBetterTogetherTestCases
Change-Id: I204ddbf545c3e8952bd6bec1ef86bffadbe58cbd
diff --git a/core/api/current.txt b/core/api/current.txt
index 7d51574..d8fd7a3 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -211,6 +211,7 @@
field public static final String MANAGE_WIFI_NETWORK_SELECTION = "android.permission.MANAGE_WIFI_NETWORK_SELECTION";
field public static final String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
field public static final String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL";
+ field @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") public static final String MEDIA_ROUTING_CONTROL = "android.permission.MEDIA_ROUTING_CONTROL";
field public static final String MODIFY_AUDIO_SETTINGS = "android.permission.MODIFY_AUDIO_SETTINGS";
field public static final String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE";
field public static final String MOUNT_FORMAT_FILESYSTEMS = "android.permission.MOUNT_FORMAT_FILESYSTEMS";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7691c1e..3309aee 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -639,6 +639,7 @@
field public static final String OPSTR_MANAGE_EXTERNAL_STORAGE = "android:manage_external_storage";
field public static final String OPSTR_MANAGE_IPSEC_TUNNELS = "android:manage_ipsec_tunnels";
field public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls";
+ field @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") public static final String OPSTR_MEDIA_ROUTING_CONTROL = "android:media_routing_control";
field public static final String OPSTR_MUTE_MICROPHONE = "android:mute_microphone";
field public static final String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells";
field public static final String OPSTR_PHONE_CALL_CAMERA = "android:phone_call_camera";
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index b03bd59..a99dfa6 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -48,6 +48,7 @@
import android.database.DatabaseUtils;
import android.health.connect.HealthConnectManager;
import android.media.AudioAttributes.AttributeUsage;
+import android.media.MediaRouter2;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -89,6 +90,7 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Parcelling;
import com.android.internal.util.Preconditions;
+import com.android.media.flags.Flags;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -1506,9 +1508,15 @@
public static final int OP_CREATE_ACCESSIBILITY_OVERLAY =
AppProtoEnums.APP_OP_CREATE_ACCESSIBILITY_OVERLAY;
+ /**
+ * See {@link #OPSTR_MEDIA_ROUTING_CONTROL}.
+ * @hide
+ */
+ public static final int OP_MEDIA_ROUTING_CONTROL = AppProtoEnums.APP_OP_MEDIA_ROUTING_CONTROL;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 139;
+ public static final int _NUM_OP = 140;
/**
* All app ops represented as strings.
@@ -1654,6 +1662,7 @@
OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
OPSTR_CREATE_ACCESSIBILITY_OVERLAY,
+ OPSTR_MEDIA_ROUTING_CONTROL,
})
public @interface AppOpString {}
@@ -1981,6 +1990,19 @@
public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls";
/**
+ * Allows apps holding this permission to control the routing of other apps via {@link
+ * MediaRouter2}.
+ *
+ * <p>For example, holding this permission allows watches (via companion apps) to control the
+ * routing of applications running on the phone.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_PRIVILEGED_ROUTING_FOR_MEDIA_ROUTING_CONTROL)
+ public static final String OPSTR_MEDIA_ROUTING_CONTROL = "android:media_routing_control";
+
+ /**
* AppOp granted to apps that we are started via {@code am instrument -e --no-isolated-storage}
*
* <p>MediaProvider is the only component (outside of system server) that should care about this
@@ -2404,7 +2426,8 @@
OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
OP_USE_FULL_SCREEN_INTENT,
OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
- OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA
+ OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
+ OP_MEDIA_ROUTING_CONTROL,
};
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2846,6 +2869,9 @@
OPSTR_CREATE_ACCESSIBILITY_OVERLAY,
"CREATE_ACCESSIBILITY_OVERLAY")
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_MEDIA_ROUTING_CONTROL, OPSTR_MEDIA_ROUTING_CONTROL,
+ "MEDIA_ROUTING_CONTROL")
+ .setPermission(Manifest.permission.MEDIA_ROUTING_CONTROL).build(),
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c75f996..72e0fe9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5981,6 +5981,13 @@
<permission android:name="android.permission.MEDIA_CONTENT_CONTROL"
android:protectionLevel="signature|privileged" />
+ <!-- Allows an application to control the routing of media apps.
+ <p>Only for use by role COMPANION_DEVICE_WATCH</p>
+ @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control")
+ -->
+ <permission android:name="android.permission.MEDIA_ROUTING_CONTROL"
+ android:protectionLevel="signature|appop" />
+
<!-- @SystemApi @hide Allows an application to set the volume key long-press listener.
<p>When it's set, the application will receive the volume key long-press event
instead of changing volume.</p>
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index dd1df47..283d61b 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -48,3 +48,10 @@
description: "Enables the following type constants in MediaRoute2Info: CAR, COMPUTER, GAME_CONSOLE, SMARTPHONE, SMARTWATCH, TABLET, TABLET_DOCKED. Note that this doesn't gate any behavior. It only guards some API int symbols."
bug: "301713440"
}
+
+flag {
+ name: "enable_privileged_routing_for_media_routing_control"
+ namespace: "media_solutions"
+ description: "Allow access to privileged routing capabilities to MEDIA_ROUTING_CONTROL holders."
+ bug: "305919655"
+}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 10d04d3..c7e5bf9 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -668,6 +668,7 @@
<!-- Permission required for CTS test - SystemMediaRouter2Test -->
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
+ <uses-permission android:name="android.permission.MEDIA_ROUTING_CONTROL" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
<!-- Permission required for CTS test - SoundDoseHelperTest -->
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index d456a74..6a43697 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -36,6 +36,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.media.IMediaRouter2;
import android.media.IMediaRouter2Manager;
@@ -200,7 +201,8 @@
final long token = Binder.clearCallingIdentity();
try {
- enforcePrivilegedRoutingPermissions(uid, pid);
+ // TODO (b/305919655) - Handle revoking of MEDIA_ROUTING_CONTROL at runtime.
+ enforcePrivilegedRoutingPermissions(uid, pid, /* callerPackageName */ null);
PackageManager pm = mContext.getPackageManager();
pm.getPackageInfo(clientPackageName, PackageManager.PackageInfoFlags.of(0));
return true;
@@ -727,13 +729,36 @@
return hasBluetoothRoutingPermission;
}
- @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
- private void enforcePrivilegedRoutingPermissions(int callerUid, int callerPid) {
- mContext.enforcePermission(
- Manifest.permission.MEDIA_CONTENT_CONTROL,
- callerPid,
- callerUid,
- "Must hold MEDIA_CONTENT_CONTROL permission.");
+ @RequiresPermission(
+ anyOf = {
+ Manifest.permission.MEDIA_ROUTING_CONTROL,
+ Manifest.permission.MEDIA_CONTENT_CONTROL
+ })
+ private void enforcePrivilegedRoutingPermissions(
+ int callerUid, int callerPid, @Nullable String callerPackageName) {
+ if (mContext.checkPermission(
+ Manifest.permission.MEDIA_CONTENT_CONTROL, callerPid, callerUid)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+
+ if (!Flags.enablePrivilegedRoutingForMediaRoutingControl()) {
+ throw new SecurityException("Must hold MEDIA_CONTENT_CONTROL");
+ }
+
+ if (PermissionChecker.checkPermissionForDataDelivery(
+ mContext,
+ Manifest.permission.MEDIA_ROUTING_CONTROL,
+ callerPid,
+ callerUid,
+ 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.");
+ }
}
// End of methods that implements operations for both MediaRouter2 and MediaRouter2Manager.
@@ -1195,7 +1220,8 @@
+ " callerUserId: %d",
callerUid, callerPid, callerPackageName, callerUserId));
- enforcePrivilegedRoutingPermissions(callerUid, callerPid);
+ // TODO (b/305919655) - Handle revoking of MEDIA_ROUTING_CONTROL at runtime.
+ enforcePrivilegedRoutingPermissions(callerUid, callerPid, callerPackageName);
UserRecord userRecord = getOrCreateUserRecordLocked(callerUserId);
managerRecord = new ManagerRecord(