diff options
| -rw-r--r-- | core/api/current.txt | 9 | ||||
| -rw-r--r-- | media/java/android/media/IMediaRouter2Manager.aidl | 4 | ||||
| -rw-r--r-- | media/java/android/media/MediaRoute2Info.java | 85 | ||||
| -rw-r--r-- | media/java/android/media/MediaRouter2.java | 51 | ||||
| -rw-r--r-- | media/java/android/media/MediaRouter2Manager.java | 168 | ||||
| -rw-r--r-- | media/java/android/media/RouteDiscoveryPreference.java | 128 | ||||
| -rw-r--r-- | services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java | 23 |
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); |