600 lines
21 KiB
Java
600 lines
21 KiB
Java
/*
|
|
* Copyright (C) 2022 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.media;
|
|
|
|
import android.annotation.IntDef;
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.content.ComponentName;
|
|
import android.content.Intent;
|
|
import android.os.Parcel;
|
|
import android.os.Parcelable;
|
|
import android.text.TextUtils;
|
|
|
|
import com.android.internal.util.Preconditions;
|
|
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Objects;
|
|
|
|
/**
|
|
* Allows applications to customize the list of routes used for media routing (for example, in the
|
|
* System UI Output Switcher).
|
|
*
|
|
* @see MediaRouter2#setRouteListingPreference
|
|
* @see Item
|
|
*/
|
|
public final class RouteListingPreference implements Parcelable {
|
|
|
|
/**
|
|
* {@link Intent} action that the system uses to take the user the app when the user selects an
|
|
* {@link Item} whose {@link Item#getSelectionBehavior() selection behavior} is {@link
|
|
* Item#SELECTION_BEHAVIOR_GO_TO_APP}.
|
|
*
|
|
* <p>The launched intent will identify the selected item using the extra identified by {@link
|
|
* #EXTRA_ROUTE_ID}.
|
|
*
|
|
* @see #getLinkedItemComponentName()
|
|
* @see Item#SELECTION_BEHAVIOR_GO_TO_APP
|
|
*/
|
|
public static final String ACTION_TRANSFER_MEDIA = "android.media.action.TRANSFER_MEDIA";
|
|
|
|
/**
|
|
* {@link Intent} string extra key that contains the {@link Item#getRouteId() id} of the route
|
|
* to transfer to, as part of an {@link #ACTION_TRANSFER_MEDIA} intent.
|
|
*
|
|
* @see #getLinkedItemComponentName()
|
|
* @see Item#SELECTION_BEHAVIOR_GO_TO_APP
|
|
*/
|
|
public static final String EXTRA_ROUTE_ID = "android.media.extra.ROUTE_ID";
|
|
|
|
@NonNull
|
|
public static final Creator<RouteListingPreference> CREATOR =
|
|
new Creator<>() {
|
|
@Override
|
|
public RouteListingPreference createFromParcel(Parcel in) {
|
|
return new RouteListingPreference(in);
|
|
}
|
|
|
|
@Override
|
|
public RouteListingPreference[] newArray(int size) {
|
|
return new RouteListingPreference[size];
|
|
}
|
|
};
|
|
|
|
@NonNull private final List<Item> mItems;
|
|
private final boolean mUseSystemOrdering;
|
|
@Nullable private final ComponentName mLinkedItemComponentName;
|
|
|
|
private RouteListingPreference(Builder builder) {
|
|
mItems = builder.mItems;
|
|
mUseSystemOrdering = builder.mUseSystemOrdering;
|
|
mLinkedItemComponentName = builder.mLinkedItemComponentName;
|
|
}
|
|
|
|
private RouteListingPreference(Parcel in) {
|
|
List<Item> items =
|
|
in.readParcelableList(new ArrayList<>(), Item.class.getClassLoader(), Item.class);
|
|
mItems = List.copyOf(items);
|
|
mUseSystemOrdering = in.readBoolean();
|
|
mLinkedItemComponentName = ComponentName.readFromParcel(in);
|
|
}
|
|
|
|
/**
|
|
* Returns an unmodifiable list containing the {@link Item items} that the app wants to be
|
|
* listed for media routing.
|
|
*/
|
|
@NonNull
|
|
public List<Item> getItems() {
|
|
return mItems;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the application would like media route listing to use the system's ordering
|
|
* strategy, or false if the application would like route listing to respect the ordering
|
|
* obtained from {@link #getItems()}.
|
|
*
|
|
* <p>The system's ordering strategy is implementation-dependent, but may take into account each
|
|
* route's recency or frequency of use in order to rank them.
|
|
*/
|
|
public boolean getUseSystemOrdering() {
|
|
return mUseSystemOrdering;
|
|
}
|
|
|
|
/**
|
|
* Returns a {@link ComponentName} for navigating to the application.
|
|
*
|
|
* <p>Must not be null if any of the {@link #getItems() items} of this route listing preference
|
|
* has {@link Item#getSelectionBehavior() selection behavior} {@link
|
|
* Item#SELECTION_BEHAVIOR_GO_TO_APP}.
|
|
*
|
|
* <p>The system navigates to the application when the user selects {@link Item} with {@link
|
|
* Item#SELECTION_BEHAVIOR_GO_TO_APP} by launching an intent to the returned {@link
|
|
* ComponentName}, using action {@link #ACTION_TRANSFER_MEDIA}, with the extra {@link
|
|
* #EXTRA_ROUTE_ID}.
|
|
*/
|
|
@Nullable
|
|
public ComponentName getLinkedItemComponentName() {
|
|
return mLinkedItemComponentName;
|
|
}
|
|
|
|
// RouteListingPreference Parcelable implementation.
|
|
|
|
@Override
|
|
public int describeContents() {
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
|
dest.writeParcelableList(mItems, flags);
|
|
dest.writeBoolean(mUseSystemOrdering);
|
|
ComponentName.writeToParcel(mLinkedItemComponentName, dest);
|
|
}
|
|
|
|
// Equals and hashCode.
|
|
|
|
@Override
|
|
public boolean equals(Object other) {
|
|
if (this == other) {
|
|
return true;
|
|
}
|
|
if (!(other instanceof RouteListingPreference)) {
|
|
return false;
|
|
}
|
|
RouteListingPreference that = (RouteListingPreference) other;
|
|
return mItems.equals(that.mItems)
|
|
&& mUseSystemOrdering == that.mUseSystemOrdering
|
|
&& Objects.equals(mLinkedItemComponentName, that.mLinkedItemComponentName);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return Objects.hash(mItems, mUseSystemOrdering, mLinkedItemComponentName);
|
|
}
|
|
|
|
/** Builder for {@link RouteListingPreference}. */
|
|
public static final class Builder {
|
|
|
|
private List<Item> mItems;
|
|
private boolean mUseSystemOrdering;
|
|
private ComponentName mLinkedItemComponentName;
|
|
|
|
/** Creates a new instance with default values (documented in the setters). */
|
|
public Builder() {
|
|
mItems = List.of();
|
|
mUseSystemOrdering = true;
|
|
}
|
|
|
|
/**
|
|
* See {@link #getItems()}
|
|
*
|
|
* <p>The default value is an empty list.
|
|
*/
|
|
@NonNull
|
|
public Builder setItems(@NonNull List<Item> items) {
|
|
mItems = List.copyOf(Objects.requireNonNull(items));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* See {@link #getUseSystemOrdering()}
|
|
*
|
|
* <p>The default value is {@code true}.
|
|
*/
|
|
// Lint requires "isUseSystemOrdering", but "getUseSystemOrdering" is a better name.
|
|
@SuppressWarnings("MissingGetterMatchingBuilder")
|
|
@NonNull
|
|
public Builder setUseSystemOrdering(boolean useSystemOrdering) {
|
|
mUseSystemOrdering = useSystemOrdering;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* See {@link #getLinkedItemComponentName()}.
|
|
*
|
|
* <p>The default value is {@code null}.
|
|
*/
|
|
@NonNull
|
|
public Builder setLinkedItemComponentName(@Nullable ComponentName linkedItemComponentName) {
|
|
mLinkedItemComponentName = linkedItemComponentName;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates and returns a new {@link RouteListingPreference} instance with the given
|
|
* parameters.
|
|
*/
|
|
@NonNull
|
|
public RouteListingPreference build() {
|
|
return new RouteListingPreference(this);
|
|
}
|
|
}
|
|
|
|
/** Holds preference information for a specific route in a {@link RouteListingPreference}. */
|
|
public static final class Item implements Parcelable {
|
|
|
|
/** @hide */
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
@IntDef(
|
|
prefix = {"SELECTION_BEHAVIOR_"},
|
|
value = {
|
|
SELECTION_BEHAVIOR_NONE,
|
|
SELECTION_BEHAVIOR_TRANSFER,
|
|
SELECTION_BEHAVIOR_GO_TO_APP
|
|
})
|
|
public @interface SelectionBehavior {}
|
|
|
|
/** The corresponding route is not selectable by the user. */
|
|
public static final int SELECTION_BEHAVIOR_NONE = 0;
|
|
/** If the user selects the corresponding route, the media transfers to the said route. */
|
|
public static final int SELECTION_BEHAVIOR_TRANSFER = 1;
|
|
/**
|
|
* If the user selects the corresponding route, the system takes the user to the
|
|
* application.
|
|
*
|
|
* <p>The system uses {@link #getLinkedItemComponentName()} in order to navigate to the app.
|
|
*/
|
|
public static final int SELECTION_BEHAVIOR_GO_TO_APP = 2;
|
|
|
|
/** @hide */
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
@IntDef(
|
|
flag = true,
|
|
prefix = {"FLAG_"},
|
|
value = {FLAG_ONGOING_SESSION, FLAG_ONGOING_SESSION_MANAGED, FLAG_SUGGESTED})
|
|
public @interface Flags {}
|
|
|
|
/**
|
|
* The corresponding route is already hosting a session with the app that owns this listing
|
|
* preference.
|
|
*/
|
|
public static final int FLAG_ONGOING_SESSION = 1;
|
|
|
|
/**
|
|
* Signals that the ongoing session on the corresponding route is managed by the current
|
|
* user of the app.
|
|
*
|
|
* <p>The system can use this flag to provide visual indication that the route is not only
|
|
* hosting a session, but also that the user has ownership over said session.
|
|
*
|
|
* <p>This flag is ignored if {@link #FLAG_ONGOING_SESSION} is not set, or if the
|
|
* corresponding route is not currently selected.
|
|
*
|
|
* <p>This flag does not affect volume adjustment (see {@link VolumeProvider}, and {@link
|
|
* MediaRoute2Info#getVolumeHandling()}), or any aspect other than the visual representation
|
|
* of the corresponding item.
|
|
*/
|
|
public static final int FLAG_ONGOING_SESSION_MANAGED = 1 << 1;
|
|
|
|
/**
|
|
* The corresponding route is specially likely to be selected by the user.
|
|
*
|
|
* <p>A UI reflecting this preference may reserve a specific space for suggested routes,
|
|
* making it more accessible to the user. If the number of suggested routes exceeds the
|
|
* number supported by the UI, the routes listed first in {@link
|
|
* RouteListingPreference#getItems()} will take priority.
|
|
*/
|
|
public static final int FLAG_SUGGESTED = 1 << 2;
|
|
|
|
/** @hide */
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
@IntDef(
|
|
prefix = {"SUBTEXT_"},
|
|
value = {
|
|
SUBTEXT_NONE,
|
|
SUBTEXT_ERROR_UNKNOWN,
|
|
SUBTEXT_SUBSCRIPTION_REQUIRED,
|
|
SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED,
|
|
SUBTEXT_AD_ROUTING_DISALLOWED,
|
|
SUBTEXT_DEVICE_LOW_POWER,
|
|
SUBTEXT_UNAUTHORIZED,
|
|
SUBTEXT_TRACK_UNSUPPORTED,
|
|
SUBTEXT_CUSTOM
|
|
})
|
|
public @interface SubText {}
|
|
|
|
/** The corresponding route has no associated subtext. */
|
|
public static final int SUBTEXT_NONE = 0;
|
|
/**
|
|
* The corresponding route's subtext must indicate that it is not available because of an
|
|
* unknown error.
|
|
*/
|
|
public static final int SUBTEXT_ERROR_UNKNOWN = 1;
|
|
/**
|
|
* The corresponding route's subtext must indicate that it requires a special subscription
|
|
* in order to be available for routing.
|
|
*/
|
|
public static final int SUBTEXT_SUBSCRIPTION_REQUIRED = 2;
|
|
/**
|
|
* The corresponding route's subtext must indicate that downloaded content cannot be routed
|
|
* to it.
|
|
*/
|
|
public static final int SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED = 3;
|
|
/**
|
|
* The corresponding route's subtext must indicate that it is not available because an ad is
|
|
* in progress.
|
|
*/
|
|
public static final int SUBTEXT_AD_ROUTING_DISALLOWED = 4;
|
|
/**
|
|
* The corresponding route's subtext must indicate that it is not available because the
|
|
* device is in low-power mode.
|
|
*/
|
|
public static final int SUBTEXT_DEVICE_LOW_POWER = 5;
|
|
/**
|
|
* The corresponding route's subtext must indicate that it is not available because the user
|
|
* is not authorized to route to it.
|
|
*/
|
|
public static final int SUBTEXT_UNAUTHORIZED = 6;
|
|
/**
|
|
* The corresponding route's subtext must indicate that it is not available because the
|
|
* device does not support the current media track.
|
|
*/
|
|
public static final int SUBTEXT_TRACK_UNSUPPORTED = 7;
|
|
/**
|
|
* The corresponding route's subtext must be obtained from {@link
|
|
* #getCustomSubtextMessage()}.
|
|
*
|
|
* <p>Applications should strongly prefer one of the other disable reasons (for the full
|
|
* list, see {@link #getSubText()}) in order to guarantee correct localization and rendering
|
|
* across all form factors.
|
|
*/
|
|
public static final int SUBTEXT_CUSTOM = 10000;
|
|
|
|
@NonNull
|
|
public static final Creator<Item> CREATOR =
|
|
new Creator<>() {
|
|
@Override
|
|
public Item createFromParcel(Parcel in) {
|
|
return new Item(in);
|
|
}
|
|
|
|
@Override
|
|
public Item[] newArray(int size) {
|
|
return new Item[size];
|
|
}
|
|
};
|
|
|
|
@NonNull private final String mRouteId;
|
|
@SelectionBehavior private final int mSelectionBehavior;
|
|
@Flags private final int mFlags;
|
|
@SubText private final int mSubText;
|
|
|
|
@Nullable private final CharSequence mCustomSubtextMessage;
|
|
|
|
private Item(@NonNull Builder builder) {
|
|
mRouteId = builder.mRouteId;
|
|
mSelectionBehavior = builder.mSelectionBehavior;
|
|
mFlags = builder.mFlags;
|
|
mSubText = builder.mSubText;
|
|
mCustomSubtextMessage = builder.mCustomSubtextMessage;
|
|
validateCustomMessageSubtext();
|
|
}
|
|
|
|
private Item(Parcel in) {
|
|
mRouteId = in.readString();
|
|
Preconditions.checkArgument(!TextUtils.isEmpty(mRouteId));
|
|
mSelectionBehavior = in.readInt();
|
|
mFlags = in.readInt();
|
|
mSubText = in.readInt();
|
|
mCustomSubtextMessage = in.readCharSequence();
|
|
validateCustomMessageSubtext();
|
|
}
|
|
|
|
/**
|
|
* Returns the id of the route that corresponds to this route listing preference item.
|
|
*
|
|
* @see MediaRoute2Info#getId()
|
|
*/
|
|
@NonNull
|
|
public String getRouteId() {
|
|
return mRouteId;
|
|
}
|
|
|
|
/**
|
|
* Returns the behavior that the corresponding route has if the user selects it.
|
|
*
|
|
* @see #SELECTION_BEHAVIOR_NONE
|
|
* @see #SELECTION_BEHAVIOR_TRANSFER
|
|
* @see #SELECTION_BEHAVIOR_GO_TO_APP
|
|
*/
|
|
public int getSelectionBehavior() {
|
|
return mSelectionBehavior;
|
|
}
|
|
|
|
/**
|
|
* Returns the flags associated to the route that corresponds to this item.
|
|
*
|
|
* @see #FLAG_ONGOING_SESSION
|
|
* @see #FLAG_ONGOING_SESSION_MANAGED
|
|
* @see #FLAG_SUGGESTED
|
|
*/
|
|
@Flags
|
|
public int getFlags() {
|
|
return mFlags;
|
|
}
|
|
|
|
/**
|
|
* Returns the type of subtext associated to this route.
|
|
*
|
|
* <p>Subtext types other than {@link #SUBTEXT_NONE} and {@link #SUBTEXT_CUSTOM} must not
|
|
* have {@link #SELECTION_BEHAVIOR_TRANSFER}.
|
|
*
|
|
* <p>If this method returns {@link #SUBTEXT_CUSTOM}, then the subtext is obtained form
|
|
* {@link #getCustomSubtextMessage()}.
|
|
*
|
|
* @see #SUBTEXT_NONE
|
|
* @see #SUBTEXT_ERROR_UNKNOWN
|
|
* @see #SUBTEXT_SUBSCRIPTION_REQUIRED
|
|
* @see #SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED
|
|
* @see #SUBTEXT_AD_ROUTING_DISALLOWED
|
|
* @see #SUBTEXT_DEVICE_LOW_POWER
|
|
* @see #SUBTEXT_UNAUTHORIZED
|
|
* @see #SUBTEXT_TRACK_UNSUPPORTED
|
|
* @see #SUBTEXT_CUSTOM
|
|
*/
|
|
@SubText
|
|
public int getSubText() {
|
|
return mSubText;
|
|
}
|
|
|
|
/**
|
|
* Returns a human-readable {@link CharSequence} providing the subtext for the corresponding
|
|
* route.
|
|
*
|
|
* <p>This value is ignored if the {@link #getSubText() subtext} for this item is not {@link
|
|
* #SUBTEXT_CUSTOM}..
|
|
*
|
|
* <p>Applications must provide a localized message that matches the system's locale. See
|
|
* {@link Locale#getDefault()}.
|
|
*
|
|
* <p>Applications should avoid using custom messages (and instead use one of non-custom
|
|
* subtexts listed in {@link #getSubText()} in order to guarantee correct visual
|
|
* representation and localization on all form factors.
|
|
*/
|
|
@Nullable
|
|
public CharSequence getCustomSubtextMessage() {
|
|
return mCustomSubtextMessage;
|
|
}
|
|
|
|
// Item Parcelable implementation.
|
|
|
|
@Override
|
|
public int describeContents() {
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
|
dest.writeString(mRouteId);
|
|
dest.writeInt(mSelectionBehavior);
|
|
dest.writeInt(mFlags);
|
|
dest.writeInt(mSubText);
|
|
dest.writeCharSequence(mCustomSubtextMessage);
|
|
}
|
|
|
|
// Equals and hashCode.
|
|
|
|
@Override
|
|
public boolean equals(Object other) {
|
|
if (this == other) {
|
|
return true;
|
|
}
|
|
if (!(other instanceof Item)) {
|
|
return false;
|
|
}
|
|
Item item = (Item) other;
|
|
return mRouteId.equals(item.mRouteId)
|
|
&& mSelectionBehavior == item.mSelectionBehavior
|
|
&& mFlags == item.mFlags
|
|
&& mSubText == item.mSubText
|
|
&& TextUtils.equals(mCustomSubtextMessage, item.mCustomSubtextMessage);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return Objects.hash(
|
|
mRouteId, mSelectionBehavior, mFlags, mSubText, mCustomSubtextMessage);
|
|
}
|
|
|
|
// Internal methods.
|
|
|
|
private void validateCustomMessageSubtext() {
|
|
Preconditions.checkArgument(
|
|
mSubText != SUBTEXT_CUSTOM || mCustomSubtextMessage != null,
|
|
"The custom subtext message cannot be null if subtext is SUBTEXT_CUSTOM.");
|
|
}
|
|
|
|
// Internal classes.
|
|
|
|
/** Builder for {@link Item}. */
|
|
public static final class Builder {
|
|
|
|
private final String mRouteId;
|
|
private int mSelectionBehavior;
|
|
private int mFlags;
|
|
private int mSubText;
|
|
private CharSequence mCustomSubtextMessage;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param routeId See {@link Item#getRouteId()}.
|
|
*/
|
|
public Builder(@NonNull String routeId) {
|
|
Preconditions.checkArgument(!TextUtils.isEmpty(routeId));
|
|
mRouteId = routeId;
|
|
mSelectionBehavior = SELECTION_BEHAVIOR_TRANSFER;
|
|
mSubText = SUBTEXT_NONE;
|
|
}
|
|
|
|
/**
|
|
* See {@link Item#getSelectionBehavior()}.
|
|
*
|
|
* <p>The default value is {@link #ACTION_TRANSFER_MEDIA}.
|
|
*/
|
|
@NonNull
|
|
public Builder setSelectionBehavior(int selectionBehavior) {
|
|
mSelectionBehavior = selectionBehavior;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* See {@link Item#getFlags()}.
|
|
*
|
|
* <p>The default value is zero (no flags).
|
|
*/
|
|
@NonNull
|
|
public Builder setFlags(int flags) {
|
|
mFlags = flags;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* See {@link Item#getSubText()}.
|
|
*
|
|
* <p>The default value is {@link #SUBTEXT_NONE}.
|
|
*/
|
|
@NonNull
|
|
public Builder setSubText(int subText) {
|
|
mSubText = subText;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* See {@link Item#getCustomSubtextMessage()}.
|
|
*
|
|
* <p>The default value is {@code null}.
|
|
*/
|
|
@NonNull
|
|
public Builder setCustomSubtextMessage(@Nullable CharSequence customSubtextMessage) {
|
|
mCustomSubtextMessage = customSubtextMessage;
|
|
return this;
|
|
}
|
|
|
|
/** Creates and returns a new {@link Item} with the given parameters. */
|
|
@NonNull
|
|
public Item build() {
|
|
return new Item(this);
|
|
}
|
|
}
|
|
}
|
|
}
|