Loading core/api/current.txt +9 −0 Original line number Diff line number Diff line Loading @@ -22728,6 +22728,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(); Loading Loading @@ -22761,6 +22762,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); Loading Loading @@ -23287,8 +23289,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; } Loading @@ -23297,7 +23303,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); } media/java/android/media/IMediaRouter2Manager.aidl +3 −1 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.media; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2Info; import android.media.RouteDiscoveryPreference; import android.media.RoutingSessionInfo; /** Loading @@ -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); Loading media/java/android/media/MediaRoute2Info.java +82 −3 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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; Loading @@ -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; } Loading @@ -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(); } Loading Loading @@ -485,6 +492,17 @@ public final class MediaRoute2Info implements Parcelable { return mClientPackageName; } /** * 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. * Loading Loading @@ -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); Loading Loading @@ -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) { Loading @@ -562,6 +592,21 @@ public final class MediaRoute2Info implements Parcelable { return false; } /** * 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 Loading Loading @@ -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); } Loading @@ -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 Loading @@ -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(); Loading @@ -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); } Loading @@ -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; Loading @@ -698,6 +750,7 @@ public final class MediaRoute2Info implements Parcelable { mId = id; mName = name; mFeatures = new ArrayList<>(); mDeduplicationIds = Set.of(); } /** Loading Loading @@ -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); } Loading Loading @@ -859,6 +914,16 @@ public final class MediaRoute2Info implements Parcelable { return this; } /** * 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. */ Loading Loading @@ -896,6 +961,20 @@ public final class MediaRoute2Info implements Parcelable { return this; } /** * 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> Loading media/java/android/media/MediaRouter2.java +45 −6 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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() { Loading media/java/android/media/MediaRouter2Manager.java +89 −79 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; /** Loading Loading @@ -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 = Loading Loading @@ -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); } /** Loading @@ -281,73 +270,82 @@ 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())) { 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; } // Add Phone -> Cast and Cast -> Phone if (route.hasAnyFeatures(preferredFeatures) && (sessionInfo.isSystemSession() ^ route.isSystemRoute())) { routes.add(route); 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; } return routes; deduplicationIdSet.addAll(route.getDeduplicationIds()); } /** * 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(); routes.add(route); } return preferredFeatures; return routes; } /** * Returns a list of routes which are related to the given package name in the given route list. * Returns the preferred features of the specified package name. */ @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); } /** Loading Loading @@ -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)); } } Loading Loading @@ -1046,6 +1044,17 @@ public final class MediaRouter2Manager { default void onPreferredFeaturesChanged(@NonNull String packageName, @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. * Loading Loading @@ -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 Loading Loading
core/api/current.txt +9 −0 Original line number Diff line number Diff line Loading @@ -22728,6 +22728,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(); Loading Loading @@ -22761,6 +22762,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); Loading Loading @@ -23287,8 +23289,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; } Loading @@ -23297,7 +23303,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); }
media/java/android/media/IMediaRouter2Manager.aidl +3 −1 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.media; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2Info; import android.media.RouteDiscoveryPreference; import android.media.RoutingSessionInfo; /** Loading @@ -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); Loading
media/java/android/media/MediaRoute2Info.java +82 −3 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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; Loading @@ -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; } Loading @@ -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(); } Loading Loading @@ -485,6 +492,17 @@ public final class MediaRoute2Info implements Parcelable { return mClientPackageName; } /** * 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. * Loading Loading @@ -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); Loading Loading @@ -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) { Loading @@ -562,6 +592,21 @@ public final class MediaRoute2Info implements Parcelable { return false; } /** * 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 Loading Loading @@ -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); } Loading @@ -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 Loading @@ -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(); Loading @@ -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); } Loading @@ -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; Loading @@ -698,6 +750,7 @@ public final class MediaRoute2Info implements Parcelable { mId = id; mName = name; mFeatures = new ArrayList<>(); mDeduplicationIds = Set.of(); } /** Loading Loading @@ -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); } Loading Loading @@ -859,6 +914,16 @@ public final class MediaRoute2Info implements Parcelable { return this; } /** * 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. */ Loading Loading @@ -896,6 +961,20 @@ public final class MediaRoute2Info implements Parcelable { return this; } /** * 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> Loading
media/java/android/media/MediaRouter2.java +45 −6 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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() { Loading
media/java/android/media/MediaRouter2Manager.java +89 −79 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; /** Loading Loading @@ -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 = Loading Loading @@ -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); } /** Loading @@ -281,73 +270,82 @@ 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())) { 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; } // Add Phone -> Cast and Cast -> Phone if (route.hasAnyFeatures(preferredFeatures) && (sessionInfo.isSystemSession() ^ route.isSystemRoute())) { routes.add(route); 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; } return routes; deduplicationIdSet.addAll(route.getDeduplicationIds()); } /** * 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(); routes.add(route); } return preferredFeatures; return routes; } /** * Returns a list of routes which are related to the given package name in the given route list. * Returns the preferred features of the specified package name. */ @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); } /** Loading Loading @@ -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)); } } Loading Loading @@ -1046,6 +1044,17 @@ public final class MediaRouter2Manager { default void onPreferredFeaturesChanged(@NonNull String packageName, @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. * Loading Loading @@ -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 Loading