Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 13e06b44 authored by Kyunglyul Hyun's avatar Kyunglyul Hyun
Browse files

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
parent a2a7cee3
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -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);
  }
+3 −1
Original line number Diff line number Diff line
@@ -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);
+82 −3
Original line number Diff line number Diff line
@@ -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>
+45 −6
Original line number Diff line number Diff line
@@ -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() {
+89 −79
Original line number Diff line number Diff line
@@ -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,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);
    }

    /**
@@ -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
Loading