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

Commit 50144b52 authored by Alex Dadukin's avatar Alex Dadukin
Browse files

Add suitability status and transfer request to the built-in route

1. Added an API to read availability status from MediaRoute2Info
2. Supported transfer reason and uid of a calling processes in
   RoutingSessionInfo
3. Implemented a system property to setup the built-in suitability
   status
4. All changes are hidden under the dedicated feature flag

There is still pending line of work:
- Modify SysUI to provide transfer meta-information from the Output
  Switcher
- Investigate whether it is necessary to propagate this
  meta-information to the user-defined routes

Bug: b/279555229
Test: mp droid
Change-Id: I0a45e52f7cc7b7158719cdaba0752a312e5d6543
parent 787f5eee
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -23972,6 +23972,7 @@ package android.media {
    method @Nullable public android.net.Uri getIconUri();
    method @NonNull public String getId();
    method @NonNull public CharSequence getName();
    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getSuitabilityStatus();
    method public int getType();
    method public int getVolume();
    method public int getVolumeHandling();
@@ -23989,6 +23990,9 @@ package android.media {
    field public static final String FEATURE_REMOTE_VIDEO_PLAYBACK = "android.media.route.feature.REMOTE_VIDEO_PLAYBACK";
    field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
    field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
    field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER = 2; // 0x2
    field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER = 0; // 0x0
    field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER = 1; // 0x1
    field public static final int TYPE_BLE_HEADSET = 26; // 0x1a
    field public static final int TYPE_BLUETOOTH_A2DP = 8; // 0x8
    field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2
@@ -24029,6 +24033,7 @@ package android.media {
    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);
    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.MediaRoute2Info.Builder setSuitabilityStatus(int);
    method @NonNull public android.media.MediaRoute2Info.Builder setType(int);
    method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityPublic();
    method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityRestricted(@NonNull java.util.Set<java.lang.String>);
@@ -24242,6 +24247,7 @@ package android.media {
    method public void release();
    method public void selectRoute(@NonNull android.media.MediaRoute2Info);
    method public void setVolume(int);
    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferRequestedBySelf();
  }
  public abstract static class MediaRouter2.TransferCallback {
@@ -24639,12 +24645,16 @@ package android.media {
    method @Nullable public CharSequence getName();
    method @NonNull public java.util.List<java.lang.String> getSelectableRoutes();
    method @NonNull public java.util.List<java.lang.String> getSelectedRoutes();
    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getTransferReason();
    method @NonNull public java.util.List<java.lang.String> getTransferableRoutes();
    method public int getVolume();
    method public int getVolumeHandling();
    method public int getVolumeMax();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.media.RoutingSessionInfo> CREATOR;
    field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int TRANSFER_REASON_APP = 2; // 0x2
    field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int TRANSFER_REASON_FALLBACK = 0; // 0x0
    field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int TRANSFER_REASON_SYSTEM_REQUEST = 1; // 0x1
  }
  public static final class RoutingSessionInfo.Builder {
@@ -24665,6 +24675,8 @@ package android.media {
    method @NonNull public android.media.RoutingSessionInfo.Builder removeTransferableRoute(@NonNull String);
    method @NonNull public android.media.RoutingSessionInfo.Builder setControlHints(@Nullable android.os.Bundle);
    method @NonNull public android.media.RoutingSessionInfo.Builder setName(@Nullable CharSequence);
    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.RoutingSessionInfo.Builder setTransferInitiator(@Nullable android.os.UserHandle, @Nullable String);
    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.RoutingSessionInfo.Builder setTransferReason(int);
    method @NonNull public android.media.RoutingSessionInfo.Builder setVolume(int);
    method @NonNull public android.media.RoutingSessionInfo.Builder setVolumeHandling(int);
    method @NonNull public android.media.RoutingSessionInfo.Builder setVolumeMax(int);
+4 −0
Original line number Diff line number Diff line
@@ -6862,4 +6862,8 @@

    <!-- Whether the media player is shown on the quick settings -->
    <bool name="config_quickSettingsShowMediaPlayer">true</bool>

    <!-- Defines suitability of the built-in speaker route.
         Refer to {@link MediaRoute2Info} to see supported values.  -->
    <integer name="config_mediaRouter_builtInSpeakerSuitability">0</integer>
</resources>
+3 −0
Original line number Diff line number Diff line
@@ -5306,4 +5306,7 @@
  <java-symbol type="bool" name="config_viewBasedRotaryEncoderHapticsEnabled" />

  <java-symbol type="bool" name="config_quickSettingsShowMediaPlayer" />

  <!-- Android MediaRouter framework configs. -->
  <java-symbol type="integer" name="config_mediaRouter_builtInSpeakerSuitability" />
</resources>
+3 −1
Original line number Diff line number Diff line
@@ -89,8 +89,10 @@ interface IMediaRouterService {
            String sessionId, in MediaRoute2Info route);
    void deselectRouteWithManager(IMediaRouter2Manager manager, int requestId,
            String sessionId, in MediaRoute2Info route);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)")
    void transferToRouteWithManager(IMediaRouter2Manager manager, int requestId,
            String sessionId, in MediaRoute2Info route);
            String sessionId, in MediaRoute2Info route,
            in UserHandle transferInitiatorUserHandle, String transferInitiatorPackageName);
    void setSessionVolumeWithManager(IMediaRouter2Manager manager, int requestId,
            String sessionId, int volume);
    void releaseSessionWithManager(IMediaRouter2Manager manager, int requestId, String sessionId);
+122 −23
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.media;
import static android.media.MediaRouter2Utils.toUniqueId;

import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
import static com.android.media.flags.Flags.FLAG_ENABLE_NEW_MEDIA_ROUTE_2_INFO_TYPES;

import android.annotation.FlaggedApi;
@@ -479,6 +480,37 @@ public final class MediaRoute2Info implements Parcelable {
    public static final String FEATURE_REMOTE_GROUP_PLAYBACK =
            "android.media.route.feature.REMOTE_GROUP_PLAYBACK";

    /** Indicates the route is always suitable for media playback. */
    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
    public static final int SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER = 0;

    /**
     * Indicates that the route is suitable for media playback only after explicit user selection.
     */
    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
    public static final int SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER = 1;

    /** Indicates that the route is never suitable for media playback. */
    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
    public static final int SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER = 2;

    /**
     * Route suitability status.
     *
     * <p>Signals whether the route is suitable to play media.
     *
     * @hide
     */
    @IntDef(
            value = {
                SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER,
                SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER,
                SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER
            })
    @Retention(RetentionPolicy.SOURCE)
    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
    public @interface SuitabilityStatus {}

    private final String mId;
    private final CharSequence mName;
    private final List<String> mFeatures;
@@ -500,6 +532,7 @@ public final class MediaRoute2Info implements Parcelable {
    private final String mProviderId;
    private final boolean mIsVisibilityRestricted;
    private final Set<String> mAllowedPackages;
    @SuitabilityStatus private final int mSuitabilityStatus;

    MediaRoute2Info(@NonNull Builder builder) {
        mId = builder.mId;
@@ -521,6 +554,7 @@ public final class MediaRoute2Info implements Parcelable {
        mProviderId = builder.mProviderId;
        mIsVisibilityRestricted = builder.mIsVisibilityRestricted;
        mAllowedPackages = builder.mAllowedPackages;
        mSuitabilityStatus = builder.mSuitabilityStatus;
    }

    MediaRoute2Info(@NonNull Parcel in) {
@@ -544,6 +578,7 @@ public final class MediaRoute2Info implements Parcelable {
        mProviderId = in.readString();
        mIsVisibilityRestricted = in.readBoolean();
        mAllowedPackages = Set.of(in.createString8Array());
        mSuitabilityStatus = in.readInt();
    }

    /**
@@ -778,6 +813,13 @@ public final class MediaRoute2Info implements Parcelable {
                || mAllowedPackages.contains(packageName);
    }

    /** Returns the route suitability status. */
    @SuitabilityStatus
    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
    public int getSuitabilityStatus() {
        return mSuitabilityStatus;
    }

    /**
     * Dumps the current state of the object to the given {@code pw} as a human-readable string.
     *
@@ -809,6 +851,7 @@ public final class MediaRoute2Info implements Parcelable {
        pw.println(indent + "mProviderId=" + mProviderId);
        pw.println(indent + "mIsVisibilityRestricted=" + mIsVisibilityRestricted);
        pw.println(indent + "mAllowedPackages=" + mAllowedPackages);
        pw.println(indent + "mSuitabilityStatus=" + mSuitabilityStatus);
    }

    private void dumpVolume(@NonNull PrintWriter pw, @NonNull String prefix) {
@@ -861,38 +904,73 @@ public final class MediaRoute2Info implements Parcelable {
                && Objects.equals(mDeduplicationIds, other.mDeduplicationIds)
                && Objects.equals(mProviderId, other.mProviderId)
                && (mIsVisibilityRestricted == other.mIsVisibilityRestricted)
                && Objects.equals(mAllowedPackages, other.mAllowedPackages);
                && Objects.equals(mAllowedPackages, other.mAllowedPackages)
                && mSuitabilityStatus == other.mSuitabilityStatus;
    }

    @Override
    public int hashCode() {
        // Note: mExtras is not included.
        return Objects.hash(mId, mName, mFeatures, mType, mIsSystem, mIconUri, mDescription,
                mConnectionState, mClientPackageName, mPackageName, mVolumeHandling, mVolumeMax,
                mVolume, mAddress, mDeduplicationIds, mProviderId, mIsVisibilityRestricted,
                mAllowedPackages);
        return Objects.hash(
                mId,
                mName,
                mFeatures,
                mType,
                mIsSystem,
                mIconUri,
                mDescription,
                mConnectionState,
                mClientPackageName,
                mPackageName,
                mVolumeHandling,
                mVolumeMax,
                mVolume,
                mAddress,
                mDeduplicationIds,
                mProviderId,
                mIsVisibilityRestricted,
                mAllowedPackages,
                mSuitabilityStatus);
    }

    @Override
    public String toString() {
        // Note: mExtras is not printed here.
        StringBuilder result = new StringBuilder()
        StringBuilder result =
                new StringBuilder()
                        .append("MediaRoute2Info{ ")
                .append("id=").append(getId())
                .append(", name=").append(getName())
                .append(", features=").append(getFeatures())
                .append(", iconUri=").append(getIconUri())
                .append(", description=").append(getDescription())
                .append(", connectionState=").append(getConnectionState())
                .append(", clientPackageName=").append(getClientPackageName())
                .append(", volumeHandling=").append(getVolumeHandling())
                .append(", volumeMax=").append(getVolumeMax())
                .append(", volume=").append(getVolume())
                .append(", address=").append(getAddress())
                .append(", deduplicationIds=").append(String.join(",", getDeduplicationIds()))
                .append(", providerId=").append(getProviderId())
                .append(", isVisibilityRestricted=").append(mIsVisibilityRestricted)
                .append(", allowedPackages=").append(String.join(",", mAllowedPackages))
                        .append("id=")
                        .append(getId())
                        .append(", name=")
                        .append(getName())
                        .append(", features=")
                        .append(getFeatures())
                        .append(", iconUri=")
                        .append(getIconUri())
                        .append(", description=")
                        .append(getDescription())
                        .append(", connectionState=")
                        .append(getConnectionState())
                        .append(", clientPackageName=")
                        .append(getClientPackageName())
                        .append(", volumeHandling=")
                        .append(getVolumeHandling())
                        .append(", volumeMax=")
                        .append(getVolumeMax())
                        .append(", volume=")
                        .append(getVolume())
                        .append(", address=")
                        .append(getAddress())
                        .append(", deduplicationIds=")
                        .append(String.join(",", getDeduplicationIds()))
                        .append(", providerId=")
                        .append(getProviderId())
                        .append(", isVisibilityRestricted=")
                        .append(mIsVisibilityRestricted)
                        .append(", allowedPackages=")
                        .append(String.join(",", mAllowedPackages))
                        .append(", suitabilityStatus=")
                        .append(mSuitabilityStatus)
                        .append(" }");
        return result.toString();
    }
@@ -923,6 +1001,7 @@ public final class MediaRoute2Info implements Parcelable {
        dest.writeString(mProviderId);
        dest.writeBoolean(mIsVisibilityRestricted);
        dest.writeString8Array(mAllowedPackages.toArray(new String[0]));
        dest.writeInt(mSuitabilityStatus);
    }

    private static String getDeviceTypeString(@Type int deviceType) {
@@ -1005,6 +1084,7 @@ public final class MediaRoute2Info implements Parcelable {
        private String mProviderId;
        private boolean mIsVisibilityRestricted;
        private Set<String> mAllowedPackages;
        @SuitabilityStatus private int mSuitabilityStatus;

        /**
         * Constructor for builder to create {@link MediaRoute2Info}.
@@ -1028,6 +1108,7 @@ public final class MediaRoute2Info implements Parcelable {
            mFeatures = new ArrayList<>();
            mDeduplicationIds = Set.of();
            mAllowedPackages = Set.of();
            mSuitabilityStatus = SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
        }

        /**
@@ -1075,6 +1156,7 @@ public final class MediaRoute2Info implements Parcelable {
            mProviderId = routeInfo.mProviderId;
            mIsVisibilityRestricted = routeInfo.mIsVisibilityRestricted;
            mAllowedPackages = routeInfo.mAllowedPackages;
            mSuitabilityStatus = routeInfo.mSuitabilityStatus;
        }

        /**
@@ -1317,6 +1399,23 @@ public final class MediaRoute2Info implements Parcelable {
            return this;
        }

        /**
         * Sets route suitability status.
         *
         * <p>The default value is {@link
         * MediaRoute2Info#SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER}.
         *
         * <p> Apps are not supposed to set {@link
         * MediaRoute2Info#SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER}. Publishing a non-system
         * route with such status throws {@link SecurityException}.
         */
        @NonNull
        @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
        public Builder setSuitabilityStatus(@SuitabilityStatus int suitabilityStatus) {
            mSuitabilityStatus = suitabilityStatus;
            return this;
        }

        /**
         * Builds the {@link MediaRoute2Info media route info}.
         *
Loading