• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.content.ComponentName;
23 import android.content.Intent;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.text.TextUtils;
27 
28 import com.android.internal.util.Preconditions;
29 
30 import java.lang.annotation.Retention;
31 import java.lang.annotation.RetentionPolicy;
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.Locale;
35 import java.util.Objects;
36 
37 /**
38  * Allows applications to customize the list of routes used for media routing (for example, in the
39  * System UI Output Switcher).
40  *
41  * @see MediaRouter2#setRouteListingPreference
42  * @see Item
43  */
44 public final class RouteListingPreference implements Parcelable {
45 
46     /**
47      * {@link Intent} action that the system uses to take the user the app when the user selects an
48      * {@link Item} whose {@link Item#getSelectionBehavior() selection behavior} is {@link
49      * Item#SELECTION_BEHAVIOR_GO_TO_APP}.
50      *
51      * <p>The launched intent will identify the selected item using the extra identified by {@link
52      * #EXTRA_ROUTE_ID}.
53      *
54      * @see #getLinkedItemComponentName()
55      * @see Item#SELECTION_BEHAVIOR_GO_TO_APP
56      */
57     public static final String ACTION_TRANSFER_MEDIA = "android.media.action.TRANSFER_MEDIA";
58 
59     /**
60      * {@link Intent} string extra key that contains the {@link Item#getRouteId() id} of the route
61      * to transfer to, as part of an {@link #ACTION_TRANSFER_MEDIA} intent.
62      *
63      * @see #getLinkedItemComponentName()
64      * @see Item#SELECTION_BEHAVIOR_GO_TO_APP
65      */
66     public static final String EXTRA_ROUTE_ID = "android.media.extra.ROUTE_ID";
67 
68     @NonNull
69     public static final Creator<RouteListingPreference> CREATOR =
70             new Creator<>() {
71                 @Override
72                 public RouteListingPreference createFromParcel(Parcel in) {
73                     return new RouteListingPreference(in);
74                 }
75 
76                 @Override
77                 public RouteListingPreference[] newArray(int size) {
78                     return new RouteListingPreference[size];
79                 }
80             };
81 
82     @NonNull private final List<Item> mItems;
83     private final boolean mUseSystemOrdering;
84     @Nullable private final ComponentName mLinkedItemComponentName;
85 
RouteListingPreference(Builder builder)86     private RouteListingPreference(Builder builder) {
87         mItems = builder.mItems;
88         mUseSystemOrdering = builder.mUseSystemOrdering;
89         mLinkedItemComponentName = builder.mLinkedItemComponentName;
90     }
91 
RouteListingPreference(Parcel in)92     private RouteListingPreference(Parcel in) {
93         List<Item> items =
94                 in.readParcelableList(new ArrayList<>(), Item.class.getClassLoader(), Item.class);
95         mItems = List.copyOf(items);
96         mUseSystemOrdering = in.readBoolean();
97         mLinkedItemComponentName = ComponentName.readFromParcel(in);
98     }
99 
100     /**
101      * Returns an unmodifiable list containing the {@link Item items} that the app wants to be
102      * listed for media routing.
103      */
104     @NonNull
getItems()105     public List<Item> getItems() {
106         return mItems;
107     }
108 
109     /**
110      * Returns true if the application would like media route listing to use the system's ordering
111      * strategy, or false if the application would like route listing to respect the ordering
112      * obtained from {@link #getItems()}.
113      *
114      * <p>The system's ordering strategy is implementation-dependent, but may take into account each
115      * route's recency or frequency of use in order to rank them.
116      */
getUseSystemOrdering()117     public boolean getUseSystemOrdering() {
118         return mUseSystemOrdering;
119     }
120 
121     /**
122      * Returns a {@link ComponentName} for navigating to the application.
123      *
124      * <p>Must not be null if any of the {@link #getItems() items} of this route listing preference
125      * has {@link Item#getSelectionBehavior() selection behavior} {@link
126      * Item#SELECTION_BEHAVIOR_GO_TO_APP}.
127      *
128      * <p>The system navigates to the application when the user selects {@link Item} with {@link
129      * Item#SELECTION_BEHAVIOR_GO_TO_APP} by launching an intent to the returned {@link
130      * ComponentName}, using action {@link #ACTION_TRANSFER_MEDIA}, with the extra {@link
131      * #EXTRA_ROUTE_ID}.
132      */
133     @Nullable
getLinkedItemComponentName()134     public ComponentName getLinkedItemComponentName() {
135         return mLinkedItemComponentName;
136     }
137 
138     // RouteListingPreference Parcelable implementation.
139 
140     @Override
describeContents()141     public int describeContents() {
142         return 0;
143     }
144 
145     @Override
writeToParcel(@onNull Parcel dest, int flags)146     public void writeToParcel(@NonNull Parcel dest, int flags) {
147         dest.writeParcelableList(mItems, flags);
148         dest.writeBoolean(mUseSystemOrdering);
149         ComponentName.writeToParcel(mLinkedItemComponentName, dest);
150     }
151 
152     // Equals and hashCode.
153 
154     @Override
equals(Object other)155     public boolean equals(Object other) {
156         if (this == other) {
157             return true;
158         }
159         if (!(other instanceof RouteListingPreference)) {
160             return false;
161         }
162         RouteListingPreference that = (RouteListingPreference) other;
163         return mItems.equals(that.mItems)
164                 && mUseSystemOrdering == that.mUseSystemOrdering
165                 && Objects.equals(mLinkedItemComponentName, that.mLinkedItemComponentName);
166     }
167 
168     @Override
hashCode()169     public int hashCode() {
170         return Objects.hash(mItems, mUseSystemOrdering, mLinkedItemComponentName);
171     }
172 
173     /** Builder for {@link RouteListingPreference}. */
174     public static final class Builder {
175 
176         private List<Item> mItems;
177         private boolean mUseSystemOrdering;
178         private ComponentName mLinkedItemComponentName;
179 
180         /** Creates a new instance with default values (documented in the setters). */
Builder()181         public Builder() {
182             mItems = List.of();
183             mUseSystemOrdering = true;
184         }
185 
186         /**
187          * See {@link #getItems()}
188          *
189          * <p>The default value is an empty list.
190          */
191         @NonNull
setItems(@onNull List<Item> items)192         public Builder setItems(@NonNull List<Item> items) {
193             mItems = List.copyOf(Objects.requireNonNull(items));
194             return this;
195         }
196 
197         /**
198          * See {@link #getUseSystemOrdering()}
199          *
200          * <p>The default value is {@code true}.
201          */
202         // Lint requires "isUseSystemOrdering", but "getUseSystemOrdering" is a better name.
203         @SuppressWarnings("MissingGetterMatchingBuilder")
204         @NonNull
setUseSystemOrdering(boolean useSystemOrdering)205         public Builder setUseSystemOrdering(boolean useSystemOrdering) {
206             mUseSystemOrdering = useSystemOrdering;
207             return this;
208         }
209 
210         /**
211          * See {@link #getLinkedItemComponentName()}.
212          *
213          * <p>The default value is {@code null}.
214          */
215         @NonNull
setLinkedItemComponentName(@ullable ComponentName linkedItemComponentName)216         public Builder setLinkedItemComponentName(@Nullable ComponentName linkedItemComponentName) {
217             mLinkedItemComponentName = linkedItemComponentName;
218             return this;
219         }
220 
221         /**
222          * Creates and returns a new {@link RouteListingPreference} instance with the given
223          * parameters.
224          */
225         @NonNull
build()226         public RouteListingPreference build() {
227             return new RouteListingPreference(this);
228         }
229     }
230 
231     /** Holds preference information for a specific route in a {@link RouteListingPreference}. */
232     public static final class Item implements Parcelable {
233 
234         /** @hide */
235         @Retention(RetentionPolicy.SOURCE)
236         @IntDef(
237                 prefix = {"SELECTION_BEHAVIOR_"},
238                 value = {
239                     SELECTION_BEHAVIOR_NONE,
240                     SELECTION_BEHAVIOR_TRANSFER,
241                     SELECTION_BEHAVIOR_GO_TO_APP
242                 })
243         public @interface SelectionBehavior {}
244 
245         /** The corresponding route is not selectable by the user. */
246         public static final int SELECTION_BEHAVIOR_NONE = 0;
247         /** If the user selects the corresponding route, the media transfers to the said route. */
248         public static final int SELECTION_BEHAVIOR_TRANSFER = 1;
249         /**
250          * If the user selects the corresponding route, the system takes the user to the
251          * application.
252          *
253          * <p>The system uses {@link #getLinkedItemComponentName()} in order to navigate to the app.
254          */
255         public static final int SELECTION_BEHAVIOR_GO_TO_APP = 2;
256 
257         /** @hide */
258         @Retention(RetentionPolicy.SOURCE)
259         @IntDef(
260                 flag = true,
261                 prefix = {"FLAG_"},
262                 value = {FLAG_ONGOING_SESSION, FLAG_ONGOING_SESSION_MANAGED, FLAG_SUGGESTED})
263         public @interface Flags {}
264 
265         /**
266          * The corresponding route is already hosting a session with the app that owns this listing
267          * preference.
268          */
269         public static final int FLAG_ONGOING_SESSION = 1;
270 
271         /**
272          * Signals that the ongoing session on the corresponding route is managed by the current
273          * user of the app.
274          *
275          * <p>The system can use this flag to provide visual indication that the route is not only
276          * hosting a session, but also that the user has ownership over said session.
277          *
278          * <p>This flag is ignored if {@link #FLAG_ONGOING_SESSION} is not set, or if the
279          * corresponding route is not currently selected.
280          *
281          * <p>This flag does not affect volume adjustment (see {@link VolumeProvider}, and {@link
282          * MediaRoute2Info#getVolumeHandling()}), or any aspect other than the visual representation
283          * of the corresponding item.
284          */
285         public static final int FLAG_ONGOING_SESSION_MANAGED = 1 << 1;
286 
287         /**
288          * The corresponding route is specially likely to be selected by the user.
289          *
290          * <p>A UI reflecting this preference may reserve a specific space for suggested routes,
291          * making it more accessible to the user. If the number of suggested routes exceeds the
292          * number supported by the UI, the routes listed first in {@link
293          * RouteListingPreference#getItems()} will take priority.
294          */
295         public static final int FLAG_SUGGESTED = 1 << 2;
296 
297         /** @hide */
298         @Retention(RetentionPolicy.SOURCE)
299         @IntDef(
300                 prefix = {"SUBTEXT_"},
301                 value = {
302                     SUBTEXT_NONE,
303                     SUBTEXT_ERROR_UNKNOWN,
304                     SUBTEXT_SUBSCRIPTION_REQUIRED,
305                     SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED,
306                     SUBTEXT_AD_ROUTING_DISALLOWED,
307                     SUBTEXT_DEVICE_LOW_POWER,
308                     SUBTEXT_UNAUTHORIZED,
309                     SUBTEXT_TRACK_UNSUPPORTED,
310                     SUBTEXT_CUSTOM
311                 })
312         public @interface SubText {}
313 
314         /** The corresponding route has no associated subtext. */
315         public static final int SUBTEXT_NONE = 0;
316         /**
317          * The corresponding route's subtext must indicate that it is not available because of an
318          * unknown error.
319          */
320         public static final int SUBTEXT_ERROR_UNKNOWN = 1;
321         /**
322          * The corresponding route's subtext must indicate that it requires a special subscription
323          * in order to be available for routing.
324          */
325         public static final int SUBTEXT_SUBSCRIPTION_REQUIRED = 2;
326         /**
327          * The corresponding route's subtext must indicate that downloaded content cannot be routed
328          * to it.
329          */
330         public static final int SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED = 3;
331         /**
332          * The corresponding route's subtext must indicate that it is not available because an ad is
333          * in progress.
334          */
335         public static final int SUBTEXT_AD_ROUTING_DISALLOWED = 4;
336         /**
337          * The corresponding route's subtext must indicate that it is not available because the
338          * device is in low-power mode.
339          */
340         public static final int SUBTEXT_DEVICE_LOW_POWER = 5;
341         /**
342          * The corresponding route's subtext must indicate that it is not available because the user
343          * is not authorized to route to it.
344          */
345         public static final int SUBTEXT_UNAUTHORIZED = 6;
346         /**
347          * The corresponding route's subtext must indicate that it is not available because the
348          * device does not support the current media track.
349          */
350         public static final int SUBTEXT_TRACK_UNSUPPORTED = 7;
351         /**
352          * The corresponding route's subtext must be obtained from {@link
353          * #getCustomSubtextMessage()}.
354          *
355          * <p>Applications should strongly prefer one of the other disable reasons (for the full
356          * list, see {@link #getSubText()}) in order to guarantee correct localization and rendering
357          * across all form factors.
358          */
359         public static final int SUBTEXT_CUSTOM = 10000;
360 
361         @NonNull
362         public static final Creator<Item> CREATOR =
363                 new Creator<>() {
364                     @Override
365                     public Item createFromParcel(Parcel in) {
366                         return new Item(in);
367                     }
368 
369                     @Override
370                     public Item[] newArray(int size) {
371                         return new Item[size];
372                     }
373                 };
374 
375         @NonNull private final String mRouteId;
376         @SelectionBehavior private final int mSelectionBehavior;
377         @Flags private final int mFlags;
378         @SubText private final int mSubText;
379 
380         @Nullable private final CharSequence mCustomSubtextMessage;
381 
Item(@onNull Builder builder)382         private Item(@NonNull Builder builder) {
383             mRouteId = builder.mRouteId;
384             mSelectionBehavior = builder.mSelectionBehavior;
385             mFlags = builder.mFlags;
386             mSubText = builder.mSubText;
387             mCustomSubtextMessage = builder.mCustomSubtextMessage;
388             validateCustomMessageSubtext();
389         }
390 
Item(Parcel in)391         private Item(Parcel in) {
392             mRouteId = in.readString();
393             Preconditions.checkArgument(!TextUtils.isEmpty(mRouteId));
394             mSelectionBehavior = in.readInt();
395             mFlags = in.readInt();
396             mSubText = in.readInt();
397             mCustomSubtextMessage = in.readCharSequence();
398             validateCustomMessageSubtext();
399         }
400 
401         /**
402          * Returns the id of the route that corresponds to this route listing preference item.
403          *
404          * @see MediaRoute2Info#getId()
405          */
406         @NonNull
getRouteId()407         public String getRouteId() {
408             return mRouteId;
409         }
410 
411         /**
412          * Returns the behavior that the corresponding route has if the user selects it.
413          *
414          * @see #SELECTION_BEHAVIOR_NONE
415          * @see #SELECTION_BEHAVIOR_TRANSFER
416          * @see #SELECTION_BEHAVIOR_GO_TO_APP
417          */
getSelectionBehavior()418         public int getSelectionBehavior() {
419             return mSelectionBehavior;
420         }
421 
422         /**
423          * Returns the flags associated to the route that corresponds to this item.
424          *
425          * @see #FLAG_ONGOING_SESSION
426          * @see #FLAG_ONGOING_SESSION_MANAGED
427          * @see #FLAG_SUGGESTED
428          */
429         @Flags
getFlags()430         public int getFlags() {
431             return mFlags;
432         }
433 
434         /**
435          * Returns the type of subtext associated to this route.
436          *
437          * <p>Subtext types other than {@link #SUBTEXT_NONE} and {@link #SUBTEXT_CUSTOM} must not
438          * have {@link #SELECTION_BEHAVIOR_TRANSFER}.
439          *
440          * <p>If this method returns {@link #SUBTEXT_CUSTOM}, then the subtext is obtained form
441          * {@link #getCustomSubtextMessage()}.
442          *
443          * @see #SUBTEXT_NONE
444          * @see #SUBTEXT_ERROR_UNKNOWN
445          * @see #SUBTEXT_SUBSCRIPTION_REQUIRED
446          * @see #SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED
447          * @see #SUBTEXT_AD_ROUTING_DISALLOWED
448          * @see #SUBTEXT_DEVICE_LOW_POWER
449          * @see #SUBTEXT_UNAUTHORIZED
450          * @see #SUBTEXT_TRACK_UNSUPPORTED
451          * @see #SUBTEXT_CUSTOM
452          */
453         @SubText
getSubText()454         public int getSubText() {
455             return mSubText;
456         }
457 
458         /**
459          * Returns a human-readable {@link CharSequence} providing the subtext for the corresponding
460          * route.
461          *
462          * <p>This value is ignored if the {@link #getSubText() subtext} for this item is not {@link
463          * #SUBTEXT_CUSTOM}..
464          *
465          * <p>Applications must provide a localized message that matches the system's locale. See
466          * {@link Locale#getDefault()}.
467          *
468          * <p>Applications should avoid using custom messages (and instead use one of non-custom
469          * subtexts listed in {@link #getSubText()} in order to guarantee correct visual
470          * representation and localization on all form factors.
471          */
472         @Nullable
getCustomSubtextMessage()473         public CharSequence getCustomSubtextMessage() {
474             return mCustomSubtextMessage;
475         }
476 
477         // Item Parcelable implementation.
478 
479         @Override
describeContents()480         public int describeContents() {
481             return 0;
482         }
483 
484         @Override
writeToParcel(@onNull Parcel dest, int flags)485         public void writeToParcel(@NonNull Parcel dest, int flags) {
486             dest.writeString(mRouteId);
487             dest.writeInt(mSelectionBehavior);
488             dest.writeInt(mFlags);
489             dest.writeInt(mSubText);
490             dest.writeCharSequence(mCustomSubtextMessage);
491         }
492 
493         // Equals and hashCode.
494 
495         @Override
equals(Object other)496         public boolean equals(Object other) {
497             if (this == other) {
498                 return true;
499             }
500             if (!(other instanceof Item)) {
501                 return false;
502             }
503             Item item = (Item) other;
504             return mRouteId.equals(item.mRouteId)
505                     && mSelectionBehavior == item.mSelectionBehavior
506                     && mFlags == item.mFlags
507                     && mSubText == item.mSubText
508                     && TextUtils.equals(mCustomSubtextMessage, item.mCustomSubtextMessage);
509         }
510 
511         @Override
hashCode()512         public int hashCode() {
513             return Objects.hash(
514                     mRouteId, mSelectionBehavior, mFlags, mSubText, mCustomSubtextMessage);
515         }
516 
517         // Internal methods.
518 
validateCustomMessageSubtext()519         private void validateCustomMessageSubtext() {
520             Preconditions.checkArgument(
521                     mSubText != SUBTEXT_CUSTOM || mCustomSubtextMessage != null,
522                     "The custom subtext message cannot be null if subtext is SUBTEXT_CUSTOM.");
523         }
524 
525         // Internal classes.
526 
527         /** Builder for {@link Item}. */
528         public static final class Builder {
529 
530             private final String mRouteId;
531             private int mSelectionBehavior;
532             private int mFlags;
533             private int mSubText;
534             private CharSequence mCustomSubtextMessage;
535 
536             /**
537              * Constructor.
538              *
539              * @param routeId See {@link Item#getRouteId()}.
540              */
Builder(@onNull String routeId)541             public Builder(@NonNull String routeId) {
542                 Preconditions.checkArgument(!TextUtils.isEmpty(routeId));
543                 mRouteId = routeId;
544                 mSelectionBehavior = SELECTION_BEHAVIOR_TRANSFER;
545                 mSubText = SUBTEXT_NONE;
546             }
547 
548             /**
549              * See {@link Item#getSelectionBehavior()}.
550              *
551              * <p>The default value is {@link #ACTION_TRANSFER_MEDIA}.
552              */
553             @NonNull
setSelectionBehavior(int selectionBehavior)554             public Builder setSelectionBehavior(int selectionBehavior) {
555                 mSelectionBehavior = selectionBehavior;
556                 return this;
557             }
558 
559             /**
560              * See {@link Item#getFlags()}.
561              *
562              * <p>The default value is zero (no flags).
563              */
564             @NonNull
setFlags(int flags)565             public Builder setFlags(int flags) {
566                 mFlags = flags;
567                 return this;
568             }
569 
570             /**
571              * See {@link Item#getSubText()}.
572              *
573              * <p>The default value is {@link #SUBTEXT_NONE}.
574              */
575             @NonNull
setSubText(int subText)576             public Builder setSubText(int subText) {
577                 mSubText = subText;
578                 return this;
579             }
580 
581             /**
582              * See {@link Item#getCustomSubtextMessage()}.
583              *
584              * <p>The default value is {@code null}.
585              */
586             @NonNull
setCustomSubtextMessage(@ullable CharSequence customSubtextMessage)587             public Builder setCustomSubtextMessage(@Nullable CharSequence customSubtextMessage) {
588                 mCustomSubtextMessage = customSubtextMessage;
589                 return this;
590             }
591 
592             /** Creates and returns a new {@link Item} with the given parameters. */
593             @NonNull
build()594             public Item build() {
595                 return new Item(this);
596             }
597         }
598     }
599 }
600