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

Commit 318425e7 authored by Antony Sargent's avatar Antony Sargent
Browse files

Allowlist and advertise hidden routes when they are selected

If a media route is hidden from an app due to the app lacking required
permissions for the route, when the route is selected in a privileged
UI such as the output switcher, we need to allowlist the app to have
access to it. Also, the app may miss the transfer event we send if it
uses androidx wrappers because the androidx wrapper currently ignores
MediaRouter2.TransferCallback events for routes which haven't been
previously advertised via a MediaRouter2.RouteCallback event.

To prevent this problem, this CL adds code to advertise hidden routes
route just after allowlisting then, before sending any transfer
events.

Bug: 388071860
Test: atest MediaRouter2HostSideTest
Flag: com.android.media.flags.enable_route_visibility_control_compat_fixes
Change-Id: Iec6df8fc4b1959285fdd4187ee71388a7ddf6c0f
parent 2b1decbe
Loading
Loading
Loading
Loading
+42 −3
Original line number Diff line number Diff line
@@ -648,6 +648,7 @@ public final class MediaRoute2Info implements Parcelable {
    private final boolean mAlsoAllowPrivilegedPackages;
    private final List<Set<String>> mRequiredPermissions;
    @SuitabilityStatus private final int mSuitabilityStatus;
    private Set<String> mTemporaryVisibilityPackages;

    MediaRoute2Info(@NonNull Builder builder) {
        mId = builder.mId;
@@ -673,6 +674,7 @@ public final class MediaRoute2Info implements Parcelable {
        mAlsoAllowPrivilegedPackages = builder.mAlsoAllowPrivilegedPackages;
        mSuitabilityStatus = builder.mSuitabilityStatus;
        mRequiredPermissions = List.copyOf(builder.mRequiredPermissions);
        mTemporaryVisibilityPackages = builder.mTemporaryVisibilityPackages;
    }

    MediaRoute2Info(@NonNull Parcel in) {
@@ -705,6 +707,7 @@ public final class MediaRoute2Info implements Parcelable {
        }
        mRequiredPermissions = List.copyOf(requiredPermissions); // Use copyOf to make it immutable.
        mSuitabilityStatus = in.readInt();
        mTemporaryVisibilityPackages = Set.of();
    }

    /**
@@ -962,6 +965,15 @@ public final class MediaRoute2Info implements Parcelable {
        return true;
    }

    /**
     * Returns whether this route can be seen by any router (i.e. has no visibility or permissions
     * restrictions).
     * @hide
     */
    public boolean isPublic() {
        return !mIsVisibilityRestricted && mRequiredPermissions.isEmpty();
    }

    /**
     * Returns whether this route is visible to the package with the given name.
     *
@@ -970,7 +982,8 @@ public final class MediaRoute2Info implements Parcelable {
    public boolean isVisibleTo(@NonNull String packageName) {
        return !mIsVisibilityRestricted
                || TextUtils.equals(getProviderPackageName(), packageName)
                || mAllowedPackages.contains(packageName);
                || mAllowedPackages.contains(packageName)
                || mTemporaryVisibilityPackages.contains(packageName);
    }

    /**
@@ -980,7 +993,8 @@ public final class MediaRoute2Info implements Parcelable {
        // TODO(b/426044649) - see the comment above on the mAlsoAllowPrivilegedPackages member
        //  variable when removing the enableRouteVisibilityControlApi flag.
        return isVisibleTo(packageName) || (Flags.enableRouteVisibilityControlApi()
                && callerIsPrivileged && mAlsoAllowPrivilegedPackages);
                && callerIsPrivileged && mAlsoAllowPrivilegedPackages)
                || mTemporaryVisibilityPackages.contains(packageName);
    }

    /**
@@ -993,6 +1007,16 @@ public final class MediaRoute2Info implements Parcelable {
        return mRequiredPermissions;
    }

    /**
     * Returns a set of packages that should be allowed to see this route regardless of other
     * visibility or permissions-based restrictions.
     *
     * @hide
     */
    public Set<String> getTemporaryVisibilityPackages() {
        return mTemporaryVisibilityPackages;
    }

    /**
     * Returns whether this route's type can only be published by the system route provider.
     *
@@ -1104,7 +1128,8 @@ public final class MediaRoute2Info implements Parcelable {
                && Objects.equals(mAllowedPackages, other.mAllowedPackages)
                && mAlsoAllowPrivilegedPackages == other.mAlsoAllowPrivilegedPackages
                && Objects.equals(mRequiredPermissions, other.mRequiredPermissions)
                && mSuitabilityStatus == other.mSuitabilityStatus;
                && mSuitabilityStatus == other.mSuitabilityStatus
                && Objects.equals(mTemporaryVisibilityPackages, other.mTemporaryVisibilityPackages);
    }

    @Override
@@ -1370,6 +1395,7 @@ public final class MediaRoute2Info implements Parcelable {
        //    VISIBILITY_RESTRICTED_TO_ALLOWLIST_AND_PRIVILEGED
        private boolean mAlsoAllowPrivilegedPackages;
        private List<Set<String>> mRequiredPermissions;
        private Set<String> mTemporaryVisibilityPackages;
        @SuitabilityStatus private int mSuitabilityStatus;

        /**
@@ -1396,6 +1422,7 @@ public final class MediaRoute2Info implements Parcelable {
            mAllowedPackages = Set.of();
            mSuitabilityStatus = SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
            mRequiredPermissions = List.of();
            mTemporaryVisibilityPackages = Set.of();
        }

        /**
@@ -1447,6 +1474,7 @@ public final class MediaRoute2Info implements Parcelable {
            mAlsoAllowPrivilegedPackages = routeInfo.mAlsoAllowPrivilegedPackages;
            mSuitabilityStatus = routeInfo.mSuitabilityStatus;
            mRequiredPermissions = routeInfo.mRequiredPermissions;
            mTemporaryVisibilityPackages = routeInfo.mTemporaryVisibilityPackages;
        }

        /**
@@ -1759,6 +1787,17 @@ public final class MediaRoute2Info implements Parcelable {
            return this;
        }

        /**
         * Sets a list of package names that will be temporarily given access to this route, even
         * if those apps otherwise would not have access due to visibility or permissions.
         *
         * @hide
         */
        public Builder setTemporaryAllowedPackages(@NonNull Set<String> packageNames) {
            mTemporaryVisibilityPackages = Set.copyOf(packageNames);
            return this;
        }

        /**
         * Sets route suitability status.
         *
+6 −1
Original line number Diff line number Diff line
@@ -144,10 +144,15 @@ public final class MediaRoute2ProviderInfo implements Parcelable {
        }

        public Builder(@NonNull MediaRoute2ProviderInfo descriptor) {
            this(descriptor, descriptor.mRoutes);
        }

        public Builder(@NonNull MediaRoute2ProviderInfo descriptor,
                ArrayMap<String, MediaRoute2Info> routes) {
            Objects.requireNonNull(descriptor, "descriptor must not be null");

            mUniqueId = descriptor.mUniqueId;
            mRoutes = new ArrayMap<>(descriptor.mRoutes);
            mRoutes = new ArrayMap<>(routes);
        }

        /**
+32 −10
Original line number Diff line number Diff line
@@ -980,6 +980,18 @@ public final class MediaRouter2 {
        }
        mDiscoveryPreference = newDiscoveryPreference;
        updateFilteredRoutesLocked();
        if (Flags.enableRouteVisibilityControlCompatFixes()) {
            mHandler.sendMessage(
                    obtainMessage(
                            MediaRouter2::dispatchFilteredRoutesUpdatedOnHandler,
                            this,
                            mFilteredRoutes));
            mHandler.sendMessage(
                    obtainMessage(
                            MediaRouter2::dispatchControllerUpdatedIfNeededOnHandler,
                            this,
                            new HashMap<>(mRoutes)));
        }
        return true;
    }

@@ -1426,6 +1438,10 @@ public final class MediaRouter2 {
                mRoutes.put(route.getId(), route);
            }
            updateFilteredRoutesLocked();
            if (Flags.enableRouteVisibilityControlCompatFixes()) {
                dispatchFilteredRoutesUpdatedOnHandler(mFilteredRoutes);
                dispatchControllerUpdatedIfNeededOnHandler(mRoutes);
            }
        }
    }

@@ -1435,6 +1451,7 @@ public final class MediaRouter2 {
        mFilteredRoutes =
                Collections.unmodifiableList(
                        filterRoutesWithCompositePreferenceLocked(List.copyOf(mRoutes.values())));
        if (!Flags.enableRouteVisibilityControlCompatFixes()) {
            mHandler.sendMessage(
                    obtainMessage(
                            MediaRouter2::dispatchFilteredRoutesUpdatedOnHandler,
@@ -1446,6 +1463,7 @@ public final class MediaRouter2 {
                            this,
                            new HashMap<>(mRoutes)));
        }
    }

    /**
     * Creates a controller and calls the {@link TransferCallback#onTransfer}. If the controller
@@ -3886,6 +3904,10 @@ public final class MediaRouter2 {
                updateFilteredRoutesLocked();
            }
            notifyPreferredFeaturesChanged(preference.getPreferredFeatures());
            if (Flags.enableRouteVisibilityControlCompatFixes()) {
                dispatchFilteredRoutesUpdatedOnHandler(mFilteredRoutes);
                dispatchControllerUpdatedIfNeededOnHandler(mRoutes);
            }
        }

        private void onRouteListingPreferenceChangedOnHandler(
+82 −0
Original line number Diff line number Diff line
@@ -28,11 +28,15 @@ import android.media.RouteDiscoveryPreference;
import android.media.RoutingSessionInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;

import com.android.internal.annotations.GuardedBy;
import com.android.media.flags.Flags;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -112,6 +116,17 @@ abstract class MediaRoute2Provider {
    void setProviderState(MediaRoute2ProviderInfo providerInfo) {
        if (providerInfo == null) {
            mProviderInfo = null;
            return;
        }

        List<MediaRoute2Info> possiblyUpdatedRoutes = null;
        if (Flags.enableRouteVisibilityControlCompatFixes()) {
            possiblyUpdatedRoutes =
                    getVisibilityUpdatedRoutesIfNeeded(providerInfo.getRoutes(), getSessionInfos());
        }

        if (possiblyUpdatedRoutes != null) {
            setProviderStateWithUpdatedRoutes(providerInfo, possiblyUpdatedRoutes);
        } else {
            mProviderInfo = new MediaRoute2ProviderInfo.Builder(providerInfo)
                    .setUniqueId(mComponentName.getPackageName(), mUniqueId)
@@ -120,6 +135,15 @@ abstract class MediaRoute2Provider {
        }
    }

    private void setProviderStateWithUpdatedRoutes(@NonNull MediaRoute2ProviderInfo providerInfo,
            @NonNull List<MediaRoute2Info> updatedRoutes) {
        mProviderInfo = new MediaRoute2ProviderInfo.Builder(providerInfo, new ArrayMap<>())
                .addRoutes(updatedRoutes)
                .setUniqueId(mComponentName.getPackageName(), mUniqueId)
                .setSystemRouteProvider(mIsSystemRouteProvider)
                .build();
    }

    protected boolean haveCallback() {
        return mCallback != null;
    }
@@ -132,6 +156,7 @@ abstract class MediaRoute2Provider {

    protected void notifySessionCreated(long requestId, @Nullable RoutingSessionInfo sessionInfo) {
        if (mCallback != null) {
            maybeUpdateProviderStateForRouteVisibility();
            mCallback.onSessionCreated(this, requestId, sessionInfo);
        }
    }
@@ -142,6 +167,7 @@ abstract class MediaRoute2Provider {
            Set<String> packageNamesWithRoutingSessionOverrides,
            boolean shouldShowVolumeSystemUi) {
        if (mCallback != null) {
            maybeUpdateProviderStateForRouteVisibility();
            mCallback.onSessionUpdated(this, sessionInfo,
                    packageNamesWithRoutingSessionOverrides, shouldShowVolumeSystemUi);
        }
@@ -150,6 +176,7 @@ abstract class MediaRoute2Provider {
    protected void notifySessionReleased(@NonNull RoutingSessionInfo sessionInfo) {
        if (mCallback != null) {
            mCallback.onSessionReleased(this, sessionInfo);
            maybeUpdateProviderStateForRouteVisibility();
        }
    }

@@ -312,4 +339,59 @@ abstract class MediaRoute2Provider {
                    .anyMatch(mTargetOriginalRouteId::equals);
        }
    }

    private void maybeUpdateProviderStateForRouteVisibility() {
        if (!Flags.enableRouteVisibilityControlCompatFixes()) {
            return;
        }
        List<MediaRoute2Info> possiblyUpdatedRoutes =
                getVisibilityUpdatedRoutesIfNeeded(mProviderInfo.getRoutes(), mSessionInfos);
        if (possiblyUpdatedRoutes != null) {
            setProviderStateWithUpdatedRoutes(mProviderInfo, possiblyUpdatedRoutes);
            notifyProviderStateChanged();
        }
    }

    /**
     * Returns a copy of routes with any missing visibility added, or null if the existing
     * visibility is sufficient.
     *
     * <p>We consider visibility to be missing when a route is not visible to a given app, but a
     * routing session exists where that app is the {@link #getClientPackageName client} and that
     * route is selected.
     *
     * <p>In summary, this method ensures that all routes which are selected by an app are visible
     * to that app.
     */
    @Nullable
    private List<MediaRoute2Info> getVisibilityUpdatedRoutesIfNeeded(
            Collection<MediaRoute2Info> routes, List<RoutingSessionInfo> sessions) {
        ArrayMap<String, Set<String>> selectedRouteToClient = new ArrayMap<>();
        for (RoutingSessionInfo session : sessions) {
            session.getSelectedRoutes().forEach(routeId -> {
                Set<String> clients =
                        selectedRouteToClient.computeIfAbsent(routeId, k -> new ArraySet<>());
                clients.add(session.getClientPackageName());
            });
        }

        boolean updatedSomeRoute = false;
        ArrayList<MediaRoute2Info> updatedRoutes = new ArrayList<>();
        for (MediaRoute2Info route : routes) {
            String fullId = MediaRouter2Utils.toUniqueId(mUniqueId, route.getOriginalId());
            MediaRoute2Info routeToAdd = route;
            if (!route.isPublic()) {
                Set<String> clients = selectedRouteToClient.getOrDefault(fullId, Set.of());
                if (!clients.equals(route.getTemporaryVisibilityPackages())) {
                    routeToAdd = new MediaRoute2Info.Builder(route)
                            .setTemporaryAllowedPackages(clients)
                            .build();
                    updatedSomeRoute = true;
                }
            }
            updatedRoutes.add(routeToAdd);
        }

        return updatedSomeRoute ? updatedRoutes : null;
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -2852,6 +2852,10 @@ class MediaRouter2ServiceImpl {
            if (!Flags.enableRouteVisibilityControlApi()) {
                return true;
            }
            if (Flags.enableRouteVisibilityControlCompatFixes()
                    && route.getTemporaryVisibilityPackages().contains(mPackageName)) {
                return true;
            }
            List<Set<String>> permissionSets = route.getRequiredPermissions();
            if (permissionSets.isEmpty()) {
                return true;