summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt9
-rw-r--r--media/java/android/media/IMediaRouter2Manager.aidl4
-rw-r--r--media/java/android/media/MediaRoute2Info.java85
-rw-r--r--media/java/android/media/MediaRouter2.java51
-rw-r--r--media/java/android/media/MediaRouter2Manager.java168
-rw-r--r--media/java/android/media/RouteDiscoveryPreference.java128
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java23
7 files changed, 365 insertions, 103 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 8c06bdd6be5d..b3a793108635 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -22721,6 +22721,7 @@ package android.media {
method public int describeContents();
method @Nullable public String getClientPackageName();
method public int getConnectionState();
+ method @NonNull public java.util.Set<java.lang.String> getDeduplicationIds();
method @Nullable public CharSequence getDescription();
method @Nullable public android.os.Bundle getExtras();
method @NonNull public java.util.List<java.lang.String> getFeatures();
@@ -22754,6 +22755,7 @@ package android.media {
method @NonNull public android.media.MediaRoute2Info.Builder clearFeatures();
method @NonNull public android.media.MediaRoute2Info.Builder setClientPackageName(@Nullable String);
method @NonNull public android.media.MediaRoute2Info.Builder setConnectionState(int);
+ method @NonNull public android.media.MediaRoute2Info.Builder setDeduplicationIds(@NonNull java.util.Set<java.lang.String>);
method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
@@ -23280,8 +23282,12 @@ package android.media {
public final class RouteDiscoveryPreference implements android.os.Parcelable {
method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getAllowedPackages();
+ method @NonNull public java.util.List<java.lang.String> getDeduplicationPackageOrder();
method @NonNull public java.util.List<java.lang.String> getPreferredFeatures();
+ method @NonNull public java.util.List<java.lang.String> getRequiredFeatures();
method public boolean shouldPerformActiveScan();
+ method public boolean shouldRemoveDuplicates();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteDiscoveryPreference> CREATOR;
}
@@ -23290,7 +23296,10 @@ package android.media {
ctor public RouteDiscoveryPreference.Builder(@NonNull java.util.List<java.lang.String>, boolean);
ctor public RouteDiscoveryPreference.Builder(@NonNull android.media.RouteDiscoveryPreference);
method @NonNull public android.media.RouteDiscoveryPreference build();
+ method @NonNull public android.media.RouteDiscoveryPreference.Builder setAllowedPackages(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.media.RouteDiscoveryPreference.Builder setDeduplicationPackageOrder(@Nullable java.util.List<java.lang.String>);
method @NonNull public android.media.RouteDiscoveryPreference.Builder setPreferredFeatures(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.media.RouteDiscoveryPreference.Builder setRequiredFeatures(@NonNull java.util.List<java.lang.String>);
method @NonNull public android.media.RouteDiscoveryPreference.Builder setShouldPerformActiveScan(boolean);
}
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index 5113dc2058e0..71dc2a781ba9 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -18,6 +18,7 @@ package android.media;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2Info;
+import android.media.RouteDiscoveryPreference;
import android.media.RoutingSessionInfo;
/**
@@ -27,7 +28,8 @@ oneway interface IMediaRouter2Manager {
void notifySessionCreated(int requestId, in RoutingSessionInfo session);
void notifySessionUpdated(in RoutingSessionInfo session);
void notifySessionReleased(in RoutingSessionInfo session);
- void notifyPreferredFeaturesChanged(String packageName, in List<String> preferredFeatures);
+ void notifyDiscoveryPreferenceChanged(String packageName,
+ in RouteDiscoveryPreference discoveryPreference);
void notifyRoutesAdded(in List<MediaRoute2Info> routes);
void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
void notifyRoutesChanged(in List<MediaRoute2Info> routes);
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 2427fa64562d..ee0293d629b1 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -34,6 +34,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* Describes the properties of a route.
@@ -340,10 +341,12 @@ public final class MediaRoute2Info implements Parcelable {
@ConnectionState
final int mConnectionState;
final String mClientPackageName;
+ final String mPackageName;
final int mVolumeHandling;
final int mVolumeMax;
final int mVolume;
final String mAddress;
+ final Set<String> mDeduplicationIds;
final Bundle mExtras;
final String mProviderId;
@@ -357,10 +360,12 @@ public final class MediaRoute2Info implements Parcelable {
mDescription = builder.mDescription;
mConnectionState = builder.mConnectionState;
mClientPackageName = builder.mClientPackageName;
+ mPackageName = builder.mPackageName;
mVolumeHandling = builder.mVolumeHandling;
mVolumeMax = builder.mVolumeMax;
mVolume = builder.mVolume;
mAddress = builder.mAddress;
+ mDeduplicationIds = builder.mDeduplicationIds;
mExtras = builder.mExtras;
mProviderId = builder.mProviderId;
}
@@ -375,10 +380,12 @@ public final class MediaRoute2Info implements Parcelable {
mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mConnectionState = in.readInt();
mClientPackageName = in.readString();
+ mPackageName = in.readString();
mVolumeHandling = in.readInt();
mVolumeMax = in.readInt();
mVolume = in.readInt();
mAddress = in.readString();
+ mDeduplicationIds = Set.of(in.readStringArray());
mExtras = in.readBundle();
mProviderId = in.readString();
}
@@ -486,6 +493,17 @@ public final class MediaRoute2Info implements Parcelable {
}
/**
+ * Gets the package name of the provider that published the route.
+ * <p>
+ * It is set by the system service.
+ * @hide
+ */
+ @Nullable
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
* Gets information about how volume is handled on the route.
*
* @return {@link #PLAYBACK_VOLUME_FIXED} or {@link #PLAYBACK_VOLUME_VARIABLE}
@@ -518,6 +536,18 @@ public final class MediaRoute2Info implements Parcelable {
return mAddress;
}
+ /**
+ * Gets the Deduplication ID of the route if available.
+ * @see RouteDiscoveryPreference#shouldRemoveDuplicates()
+ */
+ @NonNull
+ public Set<String> getDeduplicationIds() {
+ return mDeduplicationIds;
+ }
+
+ /**
+ * Gets an optional bundle with extra data.
+ */
@Nullable
public Bundle getExtras() {
return mExtras == null ? null : new Bundle(mExtras);
@@ -549,7 +579,7 @@ public final class MediaRoute2Info implements Parcelable {
* Returns if the route has at least one of the specified route features.
*
* @param features the list of route features to consider
- * @return true if the route has at least one feature in the list
+ * @return {@code true} if the route has at least one feature in the list
* @hide
*/
public boolean hasAnyFeatures(@NonNull Collection<String> features) {
@@ -563,6 +593,21 @@ public final class MediaRoute2Info implements Parcelable {
}
/**
+ * Returns if the route has all the specified route features.
+ *
+ * @hide
+ */
+ public boolean hasAllFeatures(@NonNull Collection<String> features) {
+ Objects.requireNonNull(features, "features must not be null");
+ for (String feature : features) {
+ if (!getFeatures().contains(feature)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Returns true if the route info has all of the required field.
* A route is valid if and only if it is obtained from
* {@link com.android.server.media.MediaRouterService}.
@@ -596,10 +641,12 @@ public final class MediaRoute2Info implements Parcelable {
&& Objects.equals(mDescription, other.mDescription)
&& (mConnectionState == other.mConnectionState)
&& Objects.equals(mClientPackageName, other.mClientPackageName)
+ && Objects.equals(mPackageName, other.mPackageName)
&& (mVolumeHandling == other.mVolumeHandling)
&& (mVolumeMax == other.mVolumeMax)
&& (mVolume == other.mVolume)
&& Objects.equals(mAddress, other.mAddress)
+ && Objects.equals(mDeduplicationIds, other.mDeduplicationIds)
&& Objects.equals(mProviderId, other.mProviderId);
}
@@ -607,8 +654,8 @@ public final class MediaRoute2Info implements Parcelable {
public int hashCode() {
// Note: mExtras is not included.
return Objects.hash(mId, mName, mFeatures, mType, mIsSystem, mIconUri, mDescription,
- mConnectionState, mClientPackageName, mVolumeHandling, mVolumeMax, mVolume,
- mAddress, mProviderId);
+ mConnectionState, mClientPackageName, mPackageName, mVolumeHandling, mVolumeMax,
+ mVolume, mAddress, mDeduplicationIds, mProviderId);
}
@Override
@@ -626,6 +673,7 @@ public final class MediaRoute2Info implements Parcelable {
.append(", volumeHandling=").append(getVolumeHandling())
.append(", volumeMax=").append(getVolumeMax())
.append(", volume=").append(getVolume())
+ .append(", deduplicationIds=").append(String.join(",", getDeduplicationIds()))
.append(", providerId=").append(getProviderId())
.append(" }");
return result.toString();
@@ -647,10 +695,12 @@ public final class MediaRoute2Info implements Parcelable {
TextUtils.writeToParcel(mDescription, dest, flags);
dest.writeInt(mConnectionState);
dest.writeString(mClientPackageName);
+ dest.writeString(mPackageName);
dest.writeInt(mVolumeHandling);
dest.writeInt(mVolumeMax);
dest.writeInt(mVolume);
dest.writeString(mAddress);
+ dest.writeStringArray(mDeduplicationIds.toArray(new String[mDeduplicationIds.size()]));
dest.writeBundle(mExtras);
dest.writeString(mProviderId);
}
@@ -671,10 +721,12 @@ public final class MediaRoute2Info implements Parcelable {
@ConnectionState
int mConnectionState;
String mClientPackageName;
+ String mPackageName;
int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
int mVolumeMax;
int mVolume;
String mAddress;
+ Set<String> mDeduplicationIds;
Bundle mExtras;
String mProviderId;
@@ -698,6 +750,7 @@ public final class MediaRoute2Info implements Parcelable {
mId = id;
mName = name;
mFeatures = new ArrayList<>();
+ mDeduplicationIds = Set.of();
}
/**
@@ -733,10 +786,12 @@ public final class MediaRoute2Info implements Parcelable {
mDescription = routeInfo.mDescription;
mConnectionState = routeInfo.mConnectionState;
mClientPackageName = routeInfo.mClientPackageName;
+ mPackageName = routeInfo.mPackageName;
mVolumeHandling = routeInfo.mVolumeHandling;
mVolumeMax = routeInfo.mVolumeMax;
mVolume = routeInfo.mVolume;
mAddress = routeInfo.mAddress;
+ mDeduplicationIds = Set.copyOf(routeInfo.mDeduplicationIds);
if (routeInfo.mExtras != null) {
mExtras = new Bundle(routeInfo.mExtras);
}
@@ -860,6 +915,16 @@ public final class MediaRoute2Info implements Parcelable {
}
/**
+ * Sets the package name of the route.
+ * @hide
+ */
+ @NonNull
+ public Builder setPackageName(@NonNull String packageName) {
+ mPackageName = packageName;
+ return this;
+ }
+
+ /**
* Sets the route's volume handling.
*/
@NonNull
@@ -897,6 +962,20 @@ public final class MediaRoute2Info implements Parcelable {
}
/**
+ * Sets the deduplication ID of the route.
+ * Routes have the same ID could be removed even when
+ * they are from different providers.
+ * <p>
+ * If it's {@code null}, the route will not be removed.
+ * @see RouteDiscoveryPreference#shouldRemoveDuplicates()
+ */
+ @NonNull
+ public Builder setDeduplicationIds(@NonNull Set<String> id) {
+ mDeduplicationIds = Set.copyOf(id);
+ return this;
+ }
+
+ /**
* Sets a bundle of extras for the route.
* <p>
* Note: The extras will not affect the result of {@link MediaRoute2Info#equals(Object)}.
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 4b32dbfc42f5..b485eb51380d 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -34,15 +34,18 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
@@ -302,8 +305,7 @@ public final class MediaRouter2 {
mSystemController = new SystemRoutingController(
ensureClientPackageNameForSystemSession(
sManager.getSystemRoutingSession(clientPackageName)));
- mDiscoveryPreference = new RouteDiscoveryPreference.Builder(
- sManager.getPreferredFeatures(clientPackageName), true).build();
+ mDiscoveryPreference = sManager.getDiscoveryPreference(clientPackageName);
updateAllRoutesFromManager();
// Only used by non-system MediaRouter2.
@@ -1060,11 +1062,48 @@ public final class MediaRouter2 {
.build();
}
+ private List<MediaRoute2Info> getSortedRoutes(List<MediaRoute2Info> routes,
+ RouteDiscoveryPreference preference) {
+ if (!preference.shouldRemoveDuplicates()) {
+ return routes;
+ }
+ Map<String, Integer> packagePriority = new ArrayMap<>();
+ int count = preference.getDeduplicationPackageOrder().size();
+ for (int i = 0; i < count; i++) {
+ // the last package will have 1 as the priority
+ packagePriority.put(preference.getDeduplicationPackageOrder().get(i), count - i);
+ }
+ ArrayList<MediaRoute2Info> sortedRoutes = new ArrayList<>(routes);
+ // take the negative for descending order
+ sortedRoutes.sort(Comparator.comparingInt(
+ r -> -packagePriority.getOrDefault(r.getPackageName(), 0)));
+ return sortedRoutes;
+ }
+
private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
- RouteDiscoveryPreference discoveryRequest) {
- return routes.stream()
- .filter(route -> route.hasAnyFeatures(discoveryRequest.getPreferredFeatures()))
- .collect(Collectors.toList());
+ RouteDiscoveryPreference discoveryPreference) {
+
+ Set<String> deduplicationIdSet = new ArraySet<>();
+
+ List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
+ for (MediaRoute2Info route : getSortedRoutes(routes, discoveryPreference)) {
+ if (!route.hasAllFeatures(discoveryPreference.getRequiredFeatures())
+ || !route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) {
+ continue;
+ }
+ if (!discoveryPreference.getAllowedPackages().isEmpty()
+ && !discoveryPreference.getAllowedPackages().contains(route.getPackageName())) {
+ continue;
+ }
+ if (discoveryPreference.shouldRemoveDuplicates()) {
+ if (Collections.disjoint(deduplicationIdSet, route.getDeduplicationIds())) {
+ continue;
+ }
+ deduplicationIdSet.addAll(route.getDeduplicationIds());
+ }
+ filteredRoutes.add(route);
+ }
+ return filteredRoutes;
}
private void updateAllRoutesFromManager() {
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 83fa7c255f9f..8635c0ea762c 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -29,6 +29,8 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -36,15 +38,18 @@ import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
@@ -84,7 +89,8 @@ public final class MediaRouter2Manager {
@GuardedBy("mRoutesLock")
private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
@NonNull
- final ConcurrentMap<String, List<String>> mPreferredFeaturesMap = new ConcurrentHashMap<>();
+ final ConcurrentMap<String, RouteDiscoveryPreference> mDiscoveryPreferenceMap =
+ new ConcurrentHashMap<>();
private final AtomicInteger mNextRequestId = new AtomicInteger(1);
private final CopyOnWriteArrayList<TransferRequest> mTransferRequests =
@@ -247,25 +253,8 @@ public final class MediaRouter2Manager {
*/
@NonNull
public List<MediaRoute2Info> getAvailableRoutes(@NonNull RoutingSessionInfo sessionInfo) {
- Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
-
- List<MediaRoute2Info> routes = new ArrayList<>();
-
- String packageName = sessionInfo.getClientPackageName();
- List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
- if (preferredFeatures == null) {
- preferredFeatures = Collections.emptyList();
- }
- synchronized (mRoutesLock) {
- for (MediaRoute2Info route : mRoutes.values()) {
- if (route.hasAnyFeatures(preferredFeatures)
- || sessionInfo.getSelectedRoutes().contains(route.getId())
- || sessionInfo.getTransferableRoutes().contains(route.getId())) {
- routes.add(route);
- }
- }
- }
- return routes;
+ return getFilteredRoutes(sessionInfo, /*includeSelectedRoutes=*/true,
+ null);
}
/**
@@ -281,27 +270,70 @@ public final class MediaRouter2Manager {
*/
@NonNull
public List<MediaRoute2Info> getTransferableRoutes(@NonNull RoutingSessionInfo sessionInfo) {
+ return getFilteredRoutes(sessionInfo, /*includeSelectedRoutes=*/false,
+ (route) -> sessionInfo.isSystemSession() ^ route.isSystemRoute());
+ }
+
+ private List<MediaRoute2Info> getSortedRoutes(RouteDiscoveryPreference preference) {
+ if (!preference.shouldRemoveDuplicates()) {
+ synchronized (mRoutesLock) {
+ return List.copyOf(mRoutes.values());
+ }
+ }
+ Map<String, Integer> packagePriority = new ArrayMap<>();
+ int count = preference.getDeduplicationPackageOrder().size();
+ for (int i = 0; i < count; i++) {
+ // the last package will have 1 as the priority
+ packagePriority.put(preference.getDeduplicationPackageOrder().get(i), count - i);
+ }
+ ArrayList<MediaRoute2Info> routes;
+ synchronized (mRoutesLock) {
+ routes = new ArrayList<>(mRoutes.values());
+ }
+ // take the negative for descending order
+ routes.sort(Comparator.comparingInt(
+ r -> -packagePriority.getOrDefault(r.getPackageName(), 0)));
+ return routes;
+ }
+
+ private List<MediaRoute2Info> getFilteredRoutes(@NonNull RoutingSessionInfo sessionInfo,
+ boolean includeSelectedRoutes,
+ @Nullable Predicate<MediaRoute2Info> additionalFilter) {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
List<MediaRoute2Info> routes = new ArrayList<>();
+ Set<String> deduplicationIdSet = new ArraySet<>();
String packageName = sessionInfo.getClientPackageName();
- List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
- if (preferredFeatures == null) {
- preferredFeatures = Collections.emptyList();
- }
- synchronized (mRoutesLock) {
- for (MediaRoute2Info route : mRoutes.values()) {
- if (sessionInfo.getTransferableRoutes().contains(route.getId())) {
- routes.add(route);
+ RouteDiscoveryPreference discoveryPreference =
+ mDiscoveryPreferenceMap.getOrDefault(packageName, RouteDiscoveryPreference.EMPTY);
+
+ for (MediaRoute2Info route : getSortedRoutes(discoveryPreference)) {
+ if (sessionInfo.getTransferableRoutes().contains(route.getId())
+ || (includeSelectedRoutes
+ && sessionInfo.getSelectedRoutes().contains(route.getId()))) {
+ routes.add(route);
+ continue;
+ }
+ if (!route.hasAllFeatures(discoveryPreference.getRequiredFeatures())
+ || !route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) {
+ continue;
+ }
+ if (!discoveryPreference.getAllowedPackages().isEmpty()
+ && !discoveryPreference.getAllowedPackages()
+ .contains(route.getPackageName())) {
+ continue;
+ }
+ if (additionalFilter != null && !additionalFilter.test(route)) {
+ continue;
+ }
+ if (discoveryPreference.shouldRemoveDuplicates()) {
+ if (Collections.disjoint(deduplicationIdSet, route.getDeduplicationIds())) {
continue;
}
- // Add Phone -> Cast and Cast -> Phone
- if (route.hasAnyFeatures(preferredFeatures)
- && (sessionInfo.isSystemSession() ^ route.isSystemRoute())) {
- routes.add(route);
- }
+ deduplicationIdSet.addAll(route.getDeduplicationIds());
}
+ routes.add(route);
}
return routes;
}
@@ -310,44 +342,10 @@ public final class MediaRouter2Manager {
* Returns the preferred features of the specified package name.
*/
@NonNull
- public List<String> getPreferredFeatures(@NonNull String packageName) {
- Objects.requireNonNull(packageName, "packageName must not be null");
-
- List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
- if (preferredFeatures == null) {
- preferredFeatures = Collections.emptyList();
- }
- return preferredFeatures;
- }
-
- /**
- * Returns a list of routes which are related to the given package name in the given route list.
- */
- @NonNull
- public List<MediaRoute2Info> filterRoutesForPackage(@NonNull List<MediaRoute2Info> routes,
- @NonNull String packageName) {
- Objects.requireNonNull(routes, "routes must not be null");
+ public RouteDiscoveryPreference getDiscoveryPreference(@NonNull String packageName) {
Objects.requireNonNull(packageName, "packageName must not be null");
- List<RoutingSessionInfo> sessions = getRoutingSessions(packageName);
- RoutingSessionInfo sessionInfo = sessions.get(sessions.size() - 1);
-
- List<MediaRoute2Info> result = new ArrayList<>();
- List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
- if (preferredFeatures == null) {
- preferredFeatures = Collections.emptyList();
- }
-
- synchronized (mRoutesLock) {
- for (MediaRoute2Info route : routes) {
- if (route.hasAnyFeatures(preferredFeatures)
- || sessionInfo.getSelectedRoutes().contains(route.getId())
- || sessionInfo.getTransferableRoutes().contains(route.getId())) {
- result.add(route);
- }
- }
- }
- return result;
+ return mDiscoveryPreferenceMap.getOrDefault(packageName, RouteDiscoveryPreference.EMPTY);
}
/**
@@ -713,19 +711,19 @@ public final class MediaRouter2Manager {
}
}
- void updatePreferredFeatures(String packageName, List<String> preferredFeatures) {
- if (preferredFeatures == null) {
- mPreferredFeaturesMap.remove(packageName);
+ void updateDiscoveryPreference(String packageName, RouteDiscoveryPreference preference) {
+ if (preference == null) {
+ mDiscoveryPreferenceMap.remove(packageName);
return;
}
- List<String> prevFeatures = mPreferredFeaturesMap.put(packageName, preferredFeatures);
- if ((prevFeatures == null && preferredFeatures.size() == 0)
- || Objects.equals(preferredFeatures, prevFeatures)) {
+ RouteDiscoveryPreference prevPreference =
+ mDiscoveryPreferenceMap.put(packageName, preference);
+ if (Objects.equals(preference, prevPreference)) {
return;
}
for (CallbackRecord record : mCallbackRecords) {
record.mExecutor.execute(() -> record.mCallback
- .onPreferredFeaturesChanged(packageName, preferredFeatures));
+ .onDiscoveryPreferenceChanged(packageName, preference));
}
}
@@ -1047,6 +1045,17 @@ public final class MediaRouter2Manager {
@NonNull List<String> preferredFeatures) {}
/**
+ * Called when the preferred route features of an app is changed.
+ *
+ * @param packageName the package name of the application
+ * @param discoveryPreference the new discovery preference set by the application.
+ */
+ default void onDiscoveryPreferenceChanged(@NonNull String packageName,
+ @NonNull RouteDiscoveryPreference discoveryPreference) {
+ onPreferredFeaturesChanged(packageName, discoveryPreference.getPreferredFeatures());
+ }
+
+ /**
* Called when a previous request has failed.
*
* @param reason the reason that the request has failed. Can be one of followings:
@@ -1125,9 +1134,10 @@ public final class MediaRouter2Manager {
}
@Override
- public void notifyPreferredFeaturesChanged(String packageName, List<String> features) {
- mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updatePreferredFeatures,
- MediaRouter2Manager.this, packageName, features));
+ public void notifyDiscoveryPreferenceChanged(String packageName,
+ RouteDiscoveryPreference discoveryPreference) {
+ mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updateDiscoveryPreference,
+ MediaRouter2Manager.this, packageName, discoveryPreference));
}
@Override
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
index 37fee8466859..06dc498afe5e 100644
--- a/media/java/android/media/RouteDiscoveryPreference.java
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -64,6 +64,10 @@ public final class RouteDiscoveryPreference implements Parcelable {
@NonNull
private final List<String> mPreferredFeatures;
+ private final List<String> mRequiredFeatures;
+ private final List<String> mPackagesOrder;
+ private final List<String> mAllowedPackages;
+
private final boolean mShouldPerformActiveScan;
@Nullable
private final Bundle mExtras;
@@ -78,12 +82,18 @@ public final class RouteDiscoveryPreference implements Parcelable {
RouteDiscoveryPreference(@NonNull Builder builder) {
mPreferredFeatures = builder.mPreferredFeatures;
+ mRequiredFeatures = builder.mRequiredFeatures;
+ mPackagesOrder = builder.mPackageOrder;
+ mAllowedPackages = builder.mAllowedPackages;
mShouldPerformActiveScan = builder.mActiveScan;
mExtras = builder.mExtras;
}
RouteDiscoveryPreference(@NonNull Parcel in) {
mPreferredFeatures = in.createStringArrayList();
+ mRequiredFeatures = in.createStringArrayList();
+ mPackagesOrder = in.createStringArrayList();
+ mAllowedPackages = in.createStringArrayList();
mShouldPerformActiveScan = in.readBoolean();
mExtras = in.readBundle();
}
@@ -96,6 +106,8 @@ public final class RouteDiscoveryPreference implements Parcelable {
* {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, {@link MediaRoute2Info#FEATURE_LIVE_VIDEO},
* or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} or custom features defined by a provider.
* </p>
+ *
+ * @see #getRequiredFeatures()
*/
@NonNull
public List<String> getPreferredFeatures() {
@@ -103,6 +115,44 @@ public final class RouteDiscoveryPreference implements Parcelable {
}
/**
+ * Gets the required features of routes that media router would like to discover.
+ * <p>
+ * Routes that have all the required features will be discovered.
+ * This precedes {@link #getPreferredFeatures()}.
+ * They may include predefined features such as
+ * {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, {@link MediaRoute2Info#FEATURE_LIVE_VIDEO},
+ * or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} or custom features defined by a provider.
+ *
+ * @see #getPreferredFeatures()
+ */
+ @NonNull
+ public List<String> getRequiredFeatures() {
+ return mRequiredFeatures;
+ }
+
+ /**
+ * Gets the ordered list of package names used to remove duplicate routes.
+ * <p>
+ * When the app enables duplicate removal, all the routes that have the same deduplication ID
+ * except one from the provider whose package name appears first in the list will be removed.
+ */
+ @NonNull
+ public List<String> getDeduplicationPackageOrder() {
+ return mPackagesOrder == null ? List.of() : mPackagesOrder;
+ }
+
+ /**
+ * Gets the list of allowed packages.
+ * <p>
+ * If it's not empty, it will only discover routes from the provider whose package name
+ * belongs to the list.
+ */
+ @NonNull
+ public List<String> getAllowedPackages() {
+ return mAllowedPackages;
+ }
+
+ /**
* Gets whether active scanning should be performed.
* <p>
* If any of discovery preferences sets this as {@code true}, active scanning will
@@ -114,6 +164,16 @@ public final class RouteDiscoveryPreference implements Parcelable {
}
/**
+ * Gets whether duplicate routes should be removed.
+ * <p>
+ * If it is {@code true}, only one of routes that have
+ * the same deduplication ID will be obtained.
+ */
+ public boolean shouldRemoveDuplicates() {
+ return !mPackagesOrder.isEmpty();
+ }
+
+ /**
* @hide
*/
public Bundle getExtras() {
@@ -128,6 +188,9 @@ public final class RouteDiscoveryPreference implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeStringList(mPreferredFeatures);
+ dest.writeStringList(mRequiredFeatures);
+ dest.writeStringList(mPackagesOrder);
+ dest.writeStringList(mAllowedPackages);
dest.writeBoolean(mShouldPerformActiveScan);
dest.writeBundle(mExtras);
}
@@ -155,14 +218,17 @@ public final class RouteDiscoveryPreference implements Parcelable {
return false;
}
RouteDiscoveryPreference other = (RouteDiscoveryPreference) o;
- //TODO: Make this order-free
return Objects.equals(mPreferredFeatures, other.mPreferredFeatures)
+ && Objects.equals(mRequiredFeatures, other.mRequiredFeatures)
+ && Objects.equals(mPackagesOrder, other.mPackagesOrder)
+ && Objects.equals(mAllowedPackages, other.mAllowedPackages)
&& mShouldPerformActiveScan == other.mShouldPerformActiveScan;
}
@Override
public int hashCode() {
- return Objects.hash(mPreferredFeatures, mShouldPerformActiveScan);
+ return Objects.hash(mPreferredFeatures, mRequiredFeatures, mPackagesOrder, mAllowedPackages,
+ mShouldPerformActiveScan);
}
/**
@@ -170,13 +236,21 @@ public final class RouteDiscoveryPreference implements Parcelable {
*/
public static final class Builder {
List<String> mPreferredFeatures;
+ List<String> mRequiredFeatures;
+ List<String> mPackageOrder;
+ List<String> mAllowedPackages;
+
boolean mActiveScan;
+
Bundle mExtras;
public Builder(@NonNull List<String> preferredFeatures, boolean activeScan) {
Objects.requireNonNull(preferredFeatures, "preferredFeatures must not be null");
mPreferredFeatures = preferredFeatures.stream().filter(str -> !TextUtils.isEmpty(str))
.collect(Collectors.toList());
+ mRequiredFeatures = List.of();
+ mPackageOrder = List.of();
+ mAllowedPackages = List.of();
mActiveScan = activeScan;
}
@@ -184,12 +258,15 @@ public final class RouteDiscoveryPreference implements Parcelable {
Objects.requireNonNull(preference, "preference must not be null");
mPreferredFeatures = preference.getPreferredFeatures();
+ mRequiredFeatures = preference.getRequiredFeatures();
+ mPackageOrder = preference.getDeduplicationPackageOrder();
+ mAllowedPackages = preference.getAllowedPackages();
mActiveScan = preference.shouldPerformActiveScan();
mExtras = preference.getExtras();
}
/**
- * A constructor to combine all of the preferences into a single preference.
+ * A constructor to combine all the preferences into a single preference.
* It ignores extras of preferences.
*
* @hide
@@ -224,6 +301,30 @@ public final class RouteDiscoveryPreference implements Parcelable {
}
/**
+ * Sets the required route features to discover.
+ */
+ @NonNull
+ public Builder setRequiredFeatures(@NonNull List<String> requiredFeatures) {
+ Objects.requireNonNull(requiredFeatures, "preferredFeatures must not be null");
+ mRequiredFeatures = requiredFeatures.stream().filter(str -> !TextUtils.isEmpty(str))
+ .collect(Collectors.toList());
+ return this;
+ }
+
+ /**
+ * Sets the list of package names of providers that media router would like to discover.
+ * <p>
+ * If it's non-empty, media router only discovers route from the provider in the list.
+ * The default value is empty, which discovers routes from all providers.
+ */
+ @NonNull
+ public Builder setAllowedPackages(@NonNull List<String> allowedPackages) {
+ Objects.requireNonNull(allowedPackages, "allowedPackages must not be null");
+ mAllowedPackages = List.copyOf(allowedPackages);
+ return this;
+ }
+
+ /**
* Sets if active scanning should be performed.
* <p>
* Since active scanning uses more system resources, set this as {@code true} only
@@ -237,6 +338,27 @@ public final class RouteDiscoveryPreference implements Parcelable {
}
/**
+ * Sets the order of packages when removing duplicate routes.
+ * <p>
+ * Routes are removed based on its
+ * {@link MediaRoute2Info#getDeduplicationIds() deduplication ID}.
+ * If two routes have the same ID, even if they are from different providers,
+ * one of them is removed from the list.
+ * <p>
+ * Routes from the provider whose package name appears first in the given package order
+ * will remain.
+ * If unspecified, any route can be selected.
+ *
+ * @param packageOrder list of package names for choosing routes to be removed or
+ * {@code null} not to remove duplicate routes.
+ */
+ @NonNull
+ public Builder setDeduplicationPackageOrder(@Nullable List<String> packageOrder) {
+ mPackageOrder = (packageOrder == null) ? null : List.copyOf(packageOrder);
+ return this;
+ }
+
+ /**
* Sets the extras of the route.
* @hide
*/
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 303ab4669855..7f997df3b222 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -677,9 +677,9 @@ class MediaRouter2ServiceImpl {
UserRecord userRecord = routerRecord.mUserRecord;
userRecord.mRouterRecords.remove(routerRecord);
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManagers,
+ obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
routerRecord.mUserRecord.mHandler,
- routerRecord.mPackageName, /* preferredFeatures=*/ null));
+ routerRecord.mPackageName, null));
userRecord.mHandler.sendMessage(
obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
userRecord.mHandler));
@@ -694,10 +694,10 @@ class MediaRouter2ServiceImpl {
}
routerRecord.mDiscoveryPreference = discoveryRequest;
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManagers,
+ obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
routerRecord.mUserRecord.mHandler,
routerRecord.mPackageName,
- routerRecord.mDiscoveryPreference.getPreferredFeatures()));
+ routerRecord.mDiscoveryPreference));
routerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
routerRecord.mUserRecord.mHandler));
@@ -921,7 +921,7 @@ class MediaRouter2ServiceImpl {
// TODO: UserRecord <-> routerRecord, why do they reference each other?
// How about removing mUserRecord from routerRecord?
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManager,
+ obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManager,
routerRecord.mUserRecord.mHandler, routerRecord, manager));
}
@@ -2118,19 +2118,19 @@ class MediaRouter2ServiceImpl {
}
}
- private void notifyPreferredFeaturesChangedToManager(@NonNull RouterRecord routerRecord,
+ private void notifyDiscoveryPreferenceChangedToManager(@NonNull RouterRecord routerRecord,
@NonNull IMediaRouter2Manager manager) {
try {
- manager.notifyPreferredFeaturesChanged(routerRecord.mPackageName,
- routerRecord.mDiscoveryPreference.getPreferredFeatures());
+ manager.notifyDiscoveryPreferenceChanged(routerRecord.mPackageName,
+ routerRecord.mDiscoveryPreference);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify preferred features changed."
+ " Manager probably died.", ex);
}
}
- private void notifyPreferredFeaturesChangedToManagers(@NonNull String routerPackageName,
- @Nullable List<String> preferredFeatures) {
+ private void notifyDiscoveryPreferenceChangedToManagers(@NonNull String routerPackageName,
+ @Nullable RouteDiscoveryPreference discoveryPreference) {
MediaRouter2ServiceImpl service = mServiceRef.get();
if (service == null) {
return;
@@ -2143,7 +2143,8 @@ class MediaRouter2ServiceImpl {
}
for (IMediaRouter2Manager manager : managers) {
try {
- manager.notifyPreferredFeaturesChanged(routerPackageName, preferredFeatures);
+ manager.notifyDiscoveryPreferenceChanged(routerPackageName,
+ discoveryPreference);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify preferred features changed."
+ " Manager probably died.", ex);