/* * 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}. * *
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 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.
*
* 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}.
*
* 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 The default value is an empty list.
*/
@NonNull
public Builder setItems(@NonNull List 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()}.
*
* 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.
*
* 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.
*
* 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.
*
* This flag is ignored if {@link #FLAG_ONGOING_SESSION} is not set, or if the
* corresponding route is not currently selected.
*
* 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.
*
* 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()}.
*
* 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 Subtext types other than {@link #SUBTEXT_NONE} and {@link #SUBTEXT_CUSTOM} must not
* have {@link #SELECTION_BEHAVIOR_TRANSFER}.
*
* 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.
*
* This value is ignored if the {@link #getSubText() subtext} for this item is not {@link
* #SUBTEXT_CUSTOM}..
*
* Applications must provide a localized message that matches the system's locale. See
* {@link Locale#getDefault()}.
*
* 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()}.
*
* The default value is {@link #ACTION_TRANSFER_MEDIA}.
*/
@NonNull
public Builder setSelectionBehavior(int selectionBehavior) {
mSelectionBehavior = selectionBehavior;
return this;
}
/**
* See {@link Item#getFlags()}.
*
* The default value is zero (no flags).
*/
@NonNull
public Builder setFlags(int flags) {
mFlags = flags;
return this;
}
/**
* See {@link Item#getSubText()}.
*
* The default value is {@link #SUBTEXT_NONE}.
*/
@NonNull
public Builder setSubText(int subText) {
mSubText = subText;
return this;
}
/**
* See {@link Item#getCustomSubtextMessage()}.
*
* 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);
}
}
}
}