Merge "Add DISABLE_REASON_APP_ONLY and parameter to navigate to app"
diff --git a/core/api/current.txt b/core/api/current.txt
index 4e281ec..5a6c8ee 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -24326,15 +24326,19 @@
public final class RouteListingPreference implements android.os.Parcelable {
method public int describeContents();
+ method @Nullable public android.content.ComponentName getInAppOnlyItemRoutingReceiver();
method @NonNull public java.util.List<android.media.RouteListingPreference.Item> getItems();
method public boolean getUseSystemOrdering();
method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final String ACTION_TRANSFER_MEDIA = "android.media.action.TRANSFER_MEDIA";
field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference> CREATOR;
+ field public static final String EXTRA_ROUTE_ID = "android.media.extra.ROUTE_ID";
}
public static final class RouteListingPreference.Builder {
ctor public RouteListingPreference.Builder();
method @NonNull public android.media.RouteListingPreference build();
+ method @NonNull public android.media.RouteListingPreference.Builder setInAppOnlyItemRoutingReceiver(@Nullable android.content.ComponentName);
method @NonNull public android.media.RouteListingPreference.Builder setItems(@NonNull java.util.List<android.media.RouteListingPreference.Item>);
method @NonNull public android.media.RouteListingPreference.Builder setUseSystemOrdering(boolean);
}
@@ -24349,6 +24353,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference.Item> CREATOR;
field public static final int DISABLE_REASON_AD = 3; // 0x3
field public static final int DISABLE_REASON_DOWNLOADED_CONTENT = 2; // 0x2
+ field public static final int DISABLE_REASON_IN_APP_ONLY = 4; // 0x4
field public static final int DISABLE_REASON_NONE = 0; // 0x0
field public static final int DISABLE_REASON_SUBSCRIPTION_REQUIRED = 1; // 0x1
field public static final int FLAG_ONGOING_SESSION = 1; // 0x1
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
index b1d74d4..b03653c0 100644
--- a/media/java/android/media/RouteListingPreference.java
+++ b/media/java/android/media/RouteListingPreference.java
@@ -19,6 +19,9 @@
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -40,6 +43,18 @@
*/
public final class RouteListingPreference implements Parcelable {
+ /**
+ * {@link Intent} action for apps to take the user to a screen for transferring media playback
+ * to the route with the id provided by the extra with key {@link #EXTRA_ROUTE_ID}.
+ */
+ public static final String ACTION_TRANSFER_MEDIA = "android.media.action.TRANSFER_MEDIA";
+
+ /**
+ * {@link Intent} string extra key that contains the {@link Item#getRouteId() id} of the route
+ * to transfer to, as part of an {@link #ACTION_TRANSFER_MEDIA} intent.
+ */
+ public static final String EXTRA_ROUTE_ID = "android.media.extra.ROUTE_ID";
+
@NonNull
public static final Creator<RouteListingPreference> CREATOR =
new Creator<>() {
@@ -56,10 +71,12 @@
@NonNull private final List<Item> mItems;
private final boolean mUseSystemOrdering;
+ @Nullable private final ComponentName mInAppOnlyItemRoutingReceiver;
private RouteListingPreference(Builder builder) {
mItems = builder.mItems;
mUseSystemOrdering = builder.mUseSystemOrdering;
+ mInAppOnlyItemRoutingReceiver = builder.mInAppOnlyItemRoutingReceiver;
}
private RouteListingPreference(Parcel in) {
@@ -67,6 +84,7 @@
in.readParcelableList(new ArrayList<>(), Item.class.getClassLoader(), Item.class);
mItems = List.copyOf(items);
mUseSystemOrdering = in.readBoolean();
+ mInAppOnlyItemRoutingReceiver = ComponentName.readFromParcel(in);
}
/**
@@ -90,6 +108,21 @@
return mUseSystemOrdering;
}
+ /**
+ * Returns a {@link ComponentName} for handling routes disabled via {@link
+ * Item#DISABLE_REASON_IN_APP_ONLY}, or null if the user needs to manually navigate to the app
+ * in order to route to select the corresponding routes.
+ *
+ * <p>If the user selects an {@link Item} disabled via {@link Item#DISABLE_REASON_IN_APP_ONLY},
+ * and this method returns a non-null {@link ComponentName}, the system takes the user back to
+ * the app by launching an intent to the returned {@link ComponentName}, using action {@link
+ * #ACTION_TRANSFER_MEDIA}, with the extra {@link #EXTRA_ROUTE_ID}.
+ */
+ @Nullable
+ public ComponentName getInAppOnlyItemRoutingReceiver() {
+ return mInAppOnlyItemRoutingReceiver;
+ }
+
// RouteListingPreference Parcelable implementation.
@Override
@@ -101,6 +134,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeParcelableList(mItems, flags);
dest.writeBoolean(mUseSystemOrdering);
+ ComponentName.writeToParcel(mInAppOnlyItemRoutingReceiver, dest);
}
// Equals and hashCode.
@@ -114,12 +148,15 @@
return false;
}
RouteListingPreference that = (RouteListingPreference) other;
- return mItems.equals(that.mItems) && mUseSystemOrdering == that.mUseSystemOrdering;
+ return mItems.equals(that.mItems)
+ && mUseSystemOrdering == that.mUseSystemOrdering
+ && Objects.equals(
+ mInAppOnlyItemRoutingReceiver, that.mInAppOnlyItemRoutingReceiver);
}
@Override
public int hashCode() {
- return Objects.hash(mItems, mUseSystemOrdering);
+ return Objects.hash(mItems, mUseSystemOrdering, mInAppOnlyItemRoutingReceiver);
}
/** Builder for {@link RouteListingPreference}. */
@@ -127,6 +164,7 @@
private List<Item> mItems;
private boolean mUseSystemOrdering;
+ private ComponentName mInAppOnlyItemRoutingReceiver;
/** Creates a new instance with default values (documented in the setters). */
public Builder() {
@@ -159,6 +197,18 @@
}
/**
+ * See {@link #getInAppOnlyItemRoutingReceiver()}.
+ *
+ * <p>The default value is {@code null}.
+ */
+ @NonNull
+ public Builder setInAppOnlyItemRoutingReceiver(
+ @Nullable ComponentName inAppOnlyItemRoutingReceiver) {
+ mInAppOnlyItemRoutingReceiver = inAppOnlyItemRoutingReceiver;
+ return this;
+ }
+
+ /**
* Creates and returns a new {@link RouteListingPreference} instance with the given
* parameters.
*/
@@ -203,7 +253,8 @@
DISABLE_REASON_NONE,
DISABLE_REASON_SUBSCRIPTION_REQUIRED,
DISABLE_REASON_DOWNLOADED_CONTENT,
- DISABLE_REASON_AD
+ DISABLE_REASON_AD,
+ DISABLE_REASON_IN_APP_ONLY
})
public @interface DisableReason {}
@@ -221,6 +272,14 @@
public static final int DISABLE_REASON_DOWNLOADED_CONTENT = 2;
/** The corresponding route is not available because an ad is in progress. */
public static final int DISABLE_REASON_AD = 3;
+ /**
+ * The corresponding route is only available for routing from within the app.
+ *
+ * <p>The user may still select the corresponding route if the app provides an {@link
+ * #getInAppOnlyItemRoutingReceiver() in-app routing receiver}, in which case the system
+ * will take the user to the app.
+ */
+ public static final int DISABLE_REASON_IN_APP_ONLY = 4;
@NonNull
public static final Creator<Item> CREATOR =
@@ -257,7 +316,11 @@
Preconditions.checkArgument(mSessionParticipantCount >= 0);
}
- /** Returns the id of the route that corresponds to this route listing preference item. */
+ /**
+ * Returns the id of the route that corresponds to this route listing preference item.
+ *
+ * @see MediaRoute2Info#getId()
+ */
@NonNull
public String getRouteId() {
return mRouteId;
@@ -282,6 +345,7 @@
* @see #DISABLE_REASON_SUBSCRIPTION_REQUIRED
* @see #DISABLE_REASON_DOWNLOADED_CONTENT
* @see #DISABLE_REASON_AD
+ * @see #DISABLE_REASON_IN_APP_ONLY
*/
@DisableReason
public int getDisableReason() {
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index cc485ba..90d7569 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -31,6 +31,7 @@
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -285,6 +286,15 @@
public void setRouteListingPreference(
@NonNull IMediaRouter2 router,
@Nullable RouteListingPreference routeListingPreference) {
+ ComponentName inAppOnlyItemRoutingReceiver =
+ routeListingPreference != null
+ ? routeListingPreference.getInAppOnlyItemRoutingReceiver()
+ : null;
+ if (inAppOnlyItemRoutingReceiver != null) {
+ MediaServerUtils.enforcePackageName(
+ inAppOnlyItemRoutingReceiver.getPackageName(), Binder.getCallingUid());
+ }
+
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -787,6 +797,12 @@
obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
routerRecord.mUserRecord.mHandler,
routerRecord.mPackageName, null));
+ routerRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(
+ UserHandler::notifyRouteListingPreferenceChangeToManagers,
+ routerRecord.mUserRecord.mHandler,
+ routerRecord.mPackageName,
+ /* routeListingPreference= */ null));
userRecord.mHandler.sendMessage(
obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
userRecord.mHandler));
diff --git a/services/core/java/com/android/server/media/MediaServerUtils.java b/services/core/java/com/android/server/media/MediaServerUtils.java
index 5fa2b1c..a4a99af 100644
--- a/services/core/java/com/android/server/media/MediaServerUtils.java
+++ b/services/core/java/com/android/server/media/MediaServerUtils.java
@@ -18,7 +18,13 @@
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.os.Binder;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import com.android.server.LocalServices;
import java.io.PrintWriter;
@@ -26,6 +32,39 @@
* Util class for media server.
*/
class MediaServerUtils {
+
+ /**
+ * Throws if the given {@code packageName} does not correspond to the given {@code uid}.
+ *
+ * <p>This method trusts calls from {@link Process#ROOT_UID} and {@link Process#SHELL_UID}.
+ *
+ * @param packageName A package name to verify (usually sent over binder by an app).
+ * @param uid The calling uid, obtained via {@link Binder#getCallingUid()}.
+ * @throws IllegalArgumentException If the given {@code packageName} does not correspond to the
+ * given {@code uid}, and {@code uid} is not the root uid, or the shell uid.
+ */
+ public static void enforcePackageName(String packageName, int uid) {
+ if (uid == Process.ROOT_UID || uid == Process.SHELL_UID) {
+ return;
+ }
+ if (TextUtils.isEmpty(packageName)) {
+ throw new IllegalArgumentException("packageName may not be empty");
+ }
+ final PackageManagerInternal packageManagerInternal =
+ LocalServices.getService(PackageManagerInternal.class);
+ final int actualUid =
+ packageManagerInternal.getPackageUid(
+ packageName, 0 /* flags */, UserHandle.getUserId(uid));
+ if (!UserHandle.isSameApp(uid, actualUid)) {
+ throw new IllegalArgumentException(
+ "packageName does not belong to the calling uid; "
+ + "pkg="
+ + packageName
+ + ", uid="
+ + uid);
+ }
+ }
+
/**
* Verify that caller holds {@link android.Manifest.permission#DUMP}.
*/
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index e51ed1b..3a20cd9 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -538,30 +538,11 @@
mHandler.postSessionsChanged(session);
}
- private void enforcePackageName(String packageName, int uid) {
- if (TextUtils.isEmpty(packageName)) {
- throw new IllegalArgumentException("packageName may not be empty");
- }
- if (uid == Process.ROOT_UID || uid == Process.SHELL_UID) {
- // If the caller is shell, then trust the packageName given and allow it
- // to proceed.
- return;
- }
- final PackageManagerInternal packageManagerInternal =
- LocalServices.getService(PackageManagerInternal.class);
- final int actualUid = packageManagerInternal.getPackageUid(
- packageName, 0 /* flags */, UserHandle.getUserId(uid));
- if (!UserHandle.isSameApp(uid, actualUid)) {
- throw new IllegalArgumentException("packageName does not belong to the calling uid; "
- + "pkg=" + packageName + ", uid=" + uid);
- }
- }
-
void tempAllowlistTargetPkgIfPossible(int targetUid, String targetPackage,
int callingPid, int callingUid, String callingPackage, String reason) {
final long token = Binder.clearCallingIdentity();
try {
- enforcePackageName(callingPackage, callingUid);
+ MediaServerUtils.enforcePackageName(callingPackage, callingUid);
if (targetUid != callingUid) {
boolean canAllowWhileInUse = mActivityManagerLocal
.canAllowWhileInUsePermissionInFgs(callingPid, callingUid, callingPackage);
@@ -1206,7 +1187,7 @@
final int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
- enforcePackageName(packageName, uid);
+ MediaServerUtils.enforcePackageName(packageName, uid);
int resolvedUserId = handleIncomingUser(pid, uid, userId, packageName);
if (cb == null) {
throw new IllegalArgumentException("Controller callback cannot be null");
@@ -1258,7 +1239,7 @@
final int userId = userHandle.getIdentifier();
final long token = Binder.clearCallingIdentity();
try {
- enforcePackageName(packageName, uid);
+ MediaServerUtils.enforcePackageName(packageName, uid);
enforceMediaPermissions(packageName, pid, uid, userId);
MediaSessionRecordImpl record;
@@ -1289,7 +1270,7 @@
final int userId = userHandle.getIdentifier();
final long token = Binder.clearCallingIdentity();
try {
- enforcePackageName(packageName, uid);
+ MediaServerUtils.enforcePackageName(packageName, uid);
enforceMediaPermissions(packageName, pid, uid, userId);
MediaSessionRecordImpl record;
@@ -1615,7 +1596,7 @@
final int userId = userHandle.getIdentifier();
final long token = Binder.clearCallingIdentity();
try {
- enforcePackageName(packageName, uid);
+ MediaServerUtils.enforcePackageName(packageName, uid);
enforceMediaPermissions(packageName, pid, uid, userId);
synchronized (mLock) {
@@ -2129,7 +2110,7 @@
// If they gave us a component name verify they own the
// package
packageName = componentName.getPackageName();
- enforcePackageName(packageName, uid);
+ MediaServerUtils.enforcePackageName(packageName, uid);
}
// Check that they can make calls on behalf of the user and get the final user id
int resolvedUserId = handleIncomingUser(pid, uid, userId, packageName);