Add more options on RouteDiscoveryPreference
Specifically, - mandatory features - allowed list of packages - package order for de-duplication Those can be used to filter out unwanted routes or duplicate routes. Bug: 193631822 Test: atest MediaRoute2InfoTest RouteDiscoveryPreferenceTest Change-Id: I506e6dd2c5152d9a3127d6f34856c258bb750daa
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
@@ -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) {
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -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>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
public RouteDiscoveryPreference getDiscoveryPreference(@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");
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
@@ -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
|
||||
|
||||
@@ -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,12 +106,52 @@ 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() {
|
||||
return mPreferredFeatures;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
@@ -113,6 +163,16 @@ public final class RouteDiscoveryPreference implements Parcelable {
|
||||
return mShouldPerformActiveScan;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@@ -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
|
||||
@@ -223,6 +300,30 @@ public final class RouteDiscoveryPreference implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
@@ -236,6 +337,27 @@ public final class RouteDiscoveryPreference implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user