1 /*
2  * Copyright (C) 2014 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 package android.media.session;
17 
18 import android.annotation.DrawableRes;
19 import android.annotation.IntDef;
20 import android.annotation.LongDef;
21 import android.annotation.Nullable;
22 import android.os.Bundle;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.os.SystemClock;
26 import android.text.TextUtils;
27 
28 import java.lang.annotation.Retention;
29 import java.lang.annotation.RetentionPolicy;
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 /**
34  * Playback state for a {@link MediaSession}. This includes a state like
35  * {@link PlaybackState#STATE_PLAYING}, the current playback position,
36  * and the current control capabilities.
37  */
38 public final class PlaybackState implements Parcelable {
39     private static final String TAG = "PlaybackState";
40 
41     /**
42      * @hide
43      */
44     @LongDef(flag = true, value = {ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND,
45             ACTION_SKIP_TO_PREVIOUS, ACTION_SKIP_TO_NEXT, ACTION_FAST_FORWARD, ACTION_SET_RATING,
46             ACTION_SEEK_TO, ACTION_PLAY_PAUSE, ACTION_PLAY_FROM_MEDIA_ID, ACTION_PLAY_FROM_SEARCH,
47             ACTION_SKIP_TO_QUEUE_ITEM, ACTION_PLAY_FROM_URI, ACTION_PREPARE,
48             ACTION_PREPARE_FROM_MEDIA_ID, ACTION_PREPARE_FROM_SEARCH, ACTION_PREPARE_FROM_URI,
49             ACTION_SET_PLAYBACK_SPEED})
50     @Retention(RetentionPolicy.SOURCE)
51     public @interface Actions {}
52 
53     /**
54      * Indicates this session supports the stop command.
55      *
56      * @see Builder#setActions(long)
57      */
58     public static final long ACTION_STOP = 1 << 0;
59 
60     /**
61      * Indicates this session supports the pause command.
62      *
63      * @see Builder#setActions(long)
64      */
65     public static final long ACTION_PAUSE = 1 << 1;
66 
67     /**
68      * Indicates this session supports the play command.
69      *
70      * @see Builder#setActions(long)
71      */
72     public static final long ACTION_PLAY = 1 << 2;
73 
74     /**
75      * Indicates this session supports the rewind command.
76      *
77      * @see Builder#setActions(long)
78      */
79     public static final long ACTION_REWIND = 1 << 3;
80 
81     /**
82      * Indicates this session supports the previous command.
83      *
84      * @see Builder#setActions(long)
85      */
86     public static final long ACTION_SKIP_TO_PREVIOUS = 1 << 4;
87 
88     /**
89      * Indicates this session supports the next command.
90      *
91      * @see Builder#setActions(long)
92      */
93     public static final long ACTION_SKIP_TO_NEXT = 1 << 5;
94 
95     /**
96      * Indicates this session supports the fast forward command.
97      *
98      * @see Builder#setActions(long)
99      */
100     public static final long ACTION_FAST_FORWARD = 1 << 6;
101 
102     /**
103      * Indicates this session supports the set rating command.
104      *
105      * @see Builder#setActions(long)
106      */
107     public static final long ACTION_SET_RATING = 1 << 7;
108 
109     /**
110      * Indicates this session supports the seek to command.
111      *
112      * @see Builder#setActions(long)
113      */
114     public static final long ACTION_SEEK_TO = 1 << 8;
115 
116     /**
117      * Indicates this session supports the play/pause toggle command.
118      *
119      * @see Builder#setActions(long)
120      */
121     public static final long ACTION_PLAY_PAUSE = 1 << 9;
122 
123     /**
124      * Indicates this session supports the play from media id command.
125      *
126      * @see Builder#setActions(long)
127      */
128     public static final long ACTION_PLAY_FROM_MEDIA_ID = 1 << 10;
129 
130     /**
131      * Indicates this session supports the play from search command.
132      *
133      * @see Builder#setActions(long)
134      */
135     public static final long ACTION_PLAY_FROM_SEARCH = 1 << 11;
136 
137     /**
138      * Indicates this session supports the skip to queue item command.
139      *
140      * @see Builder#setActions(long)
141      */
142     public static final long ACTION_SKIP_TO_QUEUE_ITEM = 1 << 12;
143 
144     /**
145      * Indicates this session supports the play from URI command.
146      *
147      * @see Builder#setActions(long)
148      */
149     public static final long ACTION_PLAY_FROM_URI = 1 << 13;
150 
151     /**
152      * Indicates this session supports the prepare command.
153      *
154      * @see Builder#setActions(long)
155      */
156     public static final long ACTION_PREPARE = 1 << 14;
157 
158     /**
159      * Indicates this session supports the prepare from media id command.
160      *
161      * @see Builder#setActions(long)
162      */
163     public static final long ACTION_PREPARE_FROM_MEDIA_ID = 1 << 15;
164 
165     /**
166      * Indicates this session supports the prepare from search command.
167      *
168      * @see Builder#setActions(long)
169      */
170     public static final long ACTION_PREPARE_FROM_SEARCH = 1 << 16;
171 
172     /**
173      * Indicates this session supports the prepare from URI command.
174      *
175      * @see Builder#setActions(long)
176      */
177     public static final long ACTION_PREPARE_FROM_URI = 1 << 17;
178 
179     // Note: The value jumps from 1 << 17 to 1 << 22 for matching same value with AndroidX.
180     /**
181      * Indicates this session supports the set playback speed command.
182      *
183      * @see Builder#setActions(long)
184      */
185     public static final long ACTION_SET_PLAYBACK_SPEED = 1 << 22;
186 
187     /**
188      * @hide
189      */
190     @IntDef({STATE_NONE, STATE_STOPPED, STATE_PAUSED, STATE_PLAYING, STATE_FAST_FORWARDING,
191             STATE_REWINDING, STATE_BUFFERING, STATE_ERROR, STATE_CONNECTING,
192             STATE_SKIPPING_TO_PREVIOUS, STATE_SKIPPING_TO_NEXT, STATE_SKIPPING_TO_QUEUE_ITEM})
193     @Retention(RetentionPolicy.SOURCE)
194     public @interface State {}
195 
196     /**
197      * This is the default playback state and indicates that no media has been
198      * added yet, or the performer has been reset and has no content to play.
199      *
200      * @see Builder#setState(int, long, float)
201      * @see Builder#setState(int, long, float, long)
202      */
203     public static final int STATE_NONE = 0;
204 
205     /**
206      * State indicating this item is currently stopped.
207      *
208      * @see Builder#setState
209      */
210     public static final int STATE_STOPPED = 1;
211 
212     /**
213      * State indicating this item is currently paused.
214      *
215      * @see Builder#setState
216      */
217     public static final int STATE_PAUSED = 2;
218 
219     /**
220      * State indicating this item is currently playing.
221      *
222      * @see Builder#setState
223      */
224     public static final int STATE_PLAYING = 3;
225 
226     /**
227      * State indicating this item is currently fast forwarding.
228      *
229      * @see Builder#setState
230      */
231     public static final int STATE_FAST_FORWARDING = 4;
232 
233     /**
234      * State indicating this item is currently rewinding.
235      *
236      * @see Builder#setState
237      */
238     public static final int STATE_REWINDING = 5;
239 
240     /**
241      * State indicating this item is currently buffering and will begin playing
242      * when enough data has buffered.
243      *
244      * @see Builder#setState
245      */
246     public static final int STATE_BUFFERING = 6;
247 
248     /**
249      * State indicating this item is currently in an error state. The error
250      * message should also be set when entering this state.
251      *
252      * @see Builder#setState
253      */
254     public static final int STATE_ERROR = 7;
255 
256     /**
257      * State indicating the class doing playback is currently connecting to a
258      * new destination.  Depending on the implementation you may return to the previous
259      * state when the connection finishes or enter {@link #STATE_NONE}.
260      * If the connection failed {@link #STATE_ERROR} should be used.
261      *
262      * @see Builder#setState
263      */
264     public static final int STATE_CONNECTING = 8;
265 
266     /**
267      * State indicating the player is currently skipping to the previous item.
268      *
269      * @see Builder#setState
270      */
271     public static final int STATE_SKIPPING_TO_PREVIOUS = 9;
272 
273     /**
274      * State indicating the player is currently skipping to the next item.
275      *
276      * @see Builder#setState
277      */
278     public static final int STATE_SKIPPING_TO_NEXT = 10;
279 
280     /**
281      * State indicating the player is currently skipping to a specific item in
282      * the queue.
283      *
284      * @see Builder#setState
285      */
286     public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11;
287 
288     /**
289      * Use this value for the position to indicate the position is not known.
290      */
291     public static final long PLAYBACK_POSITION_UNKNOWN = -1;
292 
293     private final int mState;
294     private final long mPosition;
295     private final long mBufferedPosition;
296     private final float mSpeed;
297     private final long mActions;
298     private List<PlaybackState.CustomAction> mCustomActions;
299     private final CharSequence mErrorMessage;
300     private final long mUpdateTime;
301     private final long mActiveItemId;
302     private final Bundle mExtras;
303 
PlaybackState(int state, long position, long updateTime, float speed, long bufferedPosition, long transportControls, List<PlaybackState.CustomAction> customActions, long activeItemId, CharSequence error, Bundle extras)304     private PlaybackState(int state, long position, long updateTime, float speed,
305             long bufferedPosition, long transportControls,
306             List<PlaybackState.CustomAction> customActions, long activeItemId,
307             CharSequence error, Bundle extras) {
308         mState = state;
309         mPosition = position;
310         mSpeed = speed;
311         mUpdateTime = updateTime;
312         mBufferedPosition = bufferedPosition;
313         mActions = transportControls;
314         mCustomActions = new ArrayList<>(customActions);
315         mActiveItemId = activeItemId;
316         mErrorMessage = error;
317         mExtras = extras;
318     }
319 
PlaybackState(Parcel in)320     private PlaybackState(Parcel in) {
321         mState = in.readInt();
322         mPosition = in.readLong();
323         mSpeed = in.readFloat();
324         mUpdateTime = in.readLong();
325         mBufferedPosition = in.readLong();
326         mActions = in.readLong();
327         mCustomActions = in.createTypedArrayList(CustomAction.CREATOR);
328         mActiveItemId = in.readLong();
329         mErrorMessage = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
330         mExtras = in.readBundle();
331     }
332 
333     @Override
toString()334     public String toString() {
335         StringBuilder bob = new StringBuilder("PlaybackState {");
336         bob.append("state=")
337                 .append(getStringForStateInt(mState))
338                 .append("(")
339                 .append(mState)
340                 .append(")");
341         bob.append(", position=").append(mPosition);
342         bob.append(", buffered position=").append(mBufferedPosition);
343         bob.append(", speed=").append(mSpeed);
344         bob.append(", updated=").append(mUpdateTime);
345         bob.append(", actions=").append(mActions);
346         bob.append(", custom actions=").append(mCustomActions);
347         bob.append(", active item id=").append(mActiveItemId);
348         bob.append(", error=").append(mErrorMessage);
349         bob.append("}");
350         return bob.toString();
351     }
352 
353     @Override
describeContents()354     public int describeContents() {
355         return 0;
356     }
357 
358     @Override
writeToParcel(Parcel dest, int flags)359     public void writeToParcel(Parcel dest, int flags) {
360         dest.writeInt(mState);
361         dest.writeLong(mPosition);
362         dest.writeFloat(mSpeed);
363         dest.writeLong(mUpdateTime);
364         dest.writeLong(mBufferedPosition);
365         dest.writeLong(mActions);
366         dest.writeTypedList(mCustomActions);
367         dest.writeLong(mActiveItemId);
368         TextUtils.writeToParcel(mErrorMessage, dest, 0);
369         dest.writeBundle(mExtras);
370     }
371 
372     /**
373      * Get the current state of playback. One of the following:
374      * <ul>
375      * <li> {@link PlaybackState#STATE_NONE}</li>
376      * <li> {@link PlaybackState#STATE_STOPPED}</li>
377      * <li> {@link PlaybackState#STATE_PLAYING}</li>
378      * <li> {@link PlaybackState#STATE_PAUSED}</li>
379      * <li> {@link PlaybackState#STATE_FAST_FORWARDING}</li>
380      * <li> {@link PlaybackState#STATE_REWINDING}</li>
381      * <li> {@link PlaybackState#STATE_BUFFERING}</li>
382      * <li> {@link PlaybackState#STATE_ERROR}</li>
383      * <li> {@link PlaybackState#STATE_CONNECTING}</li>
384      * <li> {@link PlaybackState#STATE_SKIPPING_TO_PREVIOUS}</li>
385      * <li> {@link PlaybackState#STATE_SKIPPING_TO_NEXT}</li>
386      * <li> {@link PlaybackState#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
387      * </ul>
388      */
389     @State
getState()390     public int getState() {
391         return mState;
392     }
393 
394     /**
395      * Get the current playback position in ms.
396      */
getPosition()397     public long getPosition() {
398         return mPosition;
399     }
400 
401     /**
402      * Get the current buffered position in ms. This is the farthest playback
403      * point that can be reached from the current position using only buffered
404      * content.
405      */
getBufferedPosition()406     public long getBufferedPosition() {
407         return mBufferedPosition;
408     }
409 
410     /**
411      * Get the current playback speed as a multiple of normal playback. This
412      * should be negative when rewinding. A value of 1 means normal playback and
413      * 0 means paused.
414      *
415      * @return The current speed of playback.
416      */
getPlaybackSpeed()417     public float getPlaybackSpeed() {
418         return mSpeed;
419     }
420 
421     /**
422      * Get the current actions available on this session. This should use a
423      * bitmask of the available actions.
424      * <ul>
425      * <li> {@link PlaybackState#ACTION_SKIP_TO_PREVIOUS}</li>
426      * <li> {@link PlaybackState#ACTION_REWIND}</li>
427      * <li> {@link PlaybackState#ACTION_PLAY}</li>
428      * <li> {@link PlaybackState#ACTION_PAUSE}</li>
429      * <li> {@link PlaybackState#ACTION_STOP}</li>
430      * <li> {@link PlaybackState#ACTION_FAST_FORWARD}</li>
431      * <li> {@link PlaybackState#ACTION_SKIP_TO_NEXT}</li>
432      * <li> {@link PlaybackState#ACTION_SEEK_TO}</li>
433      * <li> {@link PlaybackState#ACTION_SET_RATING}</li>
434      * <li> {@link PlaybackState#ACTION_PLAY_PAUSE}</li>
435      * <li> {@link PlaybackState#ACTION_PLAY_FROM_MEDIA_ID}</li>
436      * <li> {@link PlaybackState#ACTION_PLAY_FROM_SEARCH}</li>
437      * <li> {@link PlaybackState#ACTION_SKIP_TO_QUEUE_ITEM}</li>
438      * <li> {@link PlaybackState#ACTION_PLAY_FROM_URI}</li>
439      * <li> {@link PlaybackState#ACTION_PREPARE}</li>
440      * <li> {@link PlaybackState#ACTION_PREPARE_FROM_MEDIA_ID}</li>
441      * <li> {@link PlaybackState#ACTION_PREPARE_FROM_SEARCH}</li>
442      * <li> {@link PlaybackState#ACTION_PREPARE_FROM_URI}</li>
443      * <li> {@link PlaybackState#ACTION_SET_PLAYBACK_SPEED}</li>
444      * </ul>
445      */
446     @Actions
getActions()447     public long getActions() {
448         return mActions;
449     }
450 
451     /**
452      * Get the list of custom actions.
453      */
getCustomActions()454     public List<PlaybackState.CustomAction> getCustomActions() {
455         return mCustomActions;
456     }
457 
458     /**
459      * Get a user readable error message. This should be set when the state is
460      * {@link PlaybackState#STATE_ERROR}.
461      */
getErrorMessage()462     public CharSequence getErrorMessage() {
463         return mErrorMessage;
464     }
465 
466     /**
467      * Get the elapsed real time at which position was last updated. If the
468      * position has never been set this will return 0;
469      *
470      * @return The last time the position was updated.
471      */
getLastPositionUpdateTime()472     public long getLastPositionUpdateTime() {
473         return mUpdateTime;
474     }
475 
476     /**
477      * Get the id of the currently active item in the queue. If there is no
478      * queue or a queue is not supported by the session this will be
479      * {@link MediaSession.QueueItem#UNKNOWN_ID}.
480      *
481      * @return The id of the currently active item in the queue or
482      *         {@link MediaSession.QueueItem#UNKNOWN_ID}.
483      */
getActiveQueueItemId()484     public long getActiveQueueItemId() {
485         return mActiveItemId;
486     }
487 
488     /**
489      * Get any custom extras that were set on this playback state.
490      *
491      * @return The extras for this state or null.
492      */
getExtras()493     public @Nullable Bundle getExtras() {
494         return mExtras;
495     }
496 
497     /**
498      * Returns whether this is considered as an active playback state.
499      * <p>
500      * The playback state is considered as an active if the state is one of the following:
501      * <ul>
502      * <li>{@link #STATE_BUFFERING}</li>
503      * <li>{@link #STATE_CONNECTING}</li>
504      * <li>{@link #STATE_FAST_FORWARDING}</li>
505      * <li>{@link #STATE_PLAYING}</li>
506      * <li>{@link #STATE_REWINDING}</li>
507      * <li>{@link #STATE_SKIPPING_TO_NEXT}</li>
508      * <li>{@link #STATE_SKIPPING_TO_PREVIOUS}</li>
509      * <li>{@link #STATE_SKIPPING_TO_QUEUE_ITEM}</li>
510      * </ul>
511      */
isActive()512     public boolean isActive() {
513         switch (mState) {
514             case PlaybackState.STATE_FAST_FORWARDING:
515             case PlaybackState.STATE_REWINDING:
516             case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
517             case PlaybackState.STATE_SKIPPING_TO_NEXT:
518             case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
519             case PlaybackState.STATE_BUFFERING:
520             case PlaybackState.STATE_CONNECTING:
521             case PlaybackState.STATE_PLAYING:
522                 return true;
523         }
524         return false;
525     }
526 
527     public static final @android.annotation.NonNull Parcelable.Creator<PlaybackState> CREATOR =
528             new Parcelable.Creator<PlaybackState>() {
529         @Override
530         public PlaybackState createFromParcel(Parcel in) {
531             return new PlaybackState(in);
532         }
533 
534         @Override
535         public PlaybackState[] newArray(int size) {
536             return new PlaybackState[size];
537         }
538     };
539 
540     /** Returns a human readable string representation of the given int {@code state} */
getStringForStateInt(int state)541     private static String getStringForStateInt(int state) {
542         switch (state) {
543             case STATE_NONE:
544                 return "NONE";
545             case STATE_STOPPED:
546                 return "STOPPED";
547             case STATE_PAUSED:
548                 return "PAUSED";
549             case STATE_PLAYING:
550                 return "PLAYING";
551             case STATE_FAST_FORWARDING:
552                 return "FAST_FORWARDING";
553             case STATE_REWINDING:
554                 return "REWINDING";
555             case STATE_BUFFERING:
556                 return "BUFFERING";
557             case STATE_ERROR:
558                 return "ERROR";
559             case STATE_CONNECTING:
560                 return "CONNECTING";
561             case STATE_SKIPPING_TO_PREVIOUS:
562                 return "SKIPPING_TO_PREVIOUS";
563             case STATE_SKIPPING_TO_NEXT:
564                 return "SKIPPING_TO_NEXT";
565             case STATE_SKIPPING_TO_QUEUE_ITEM:
566                 return "SKIPPING_TO_QUEUE_ITEM";
567             default:
568                 return "UNKNOWN";
569         }
570     }
571 
572     /**
573      * {@link PlaybackState.CustomAction CustomActions} can be used to extend the capabilities of
574      * the standard transport controls by exposing app specific actions to
575      * {@link MediaController MediaControllers}.
576      */
577     public static final class CustomAction implements Parcelable {
578         private final String mAction;
579         private final CharSequence mName;
580         private final int mIcon;
581         private final Bundle mExtras;
582 
583         /**
584          * Use {@link PlaybackState.CustomAction.Builder#build()}.
585          */
CustomAction(String action, CharSequence name, int icon, Bundle extras)586         private CustomAction(String action, CharSequence name, int icon, Bundle extras) {
587             mAction = action;
588             mName = name;
589             mIcon = icon;
590             mExtras = extras;
591         }
592 
CustomAction(Parcel in)593         private CustomAction(Parcel in) {
594             mAction = in.readString();
595             mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
596             mIcon = in.readInt();
597             mExtras = in.readBundle();
598         }
599 
600         @Override
writeToParcel(Parcel dest, int flags)601         public void writeToParcel(Parcel dest, int flags) {
602             dest.writeString(mAction);
603             TextUtils.writeToParcel(mName, dest, flags);
604             dest.writeInt(mIcon);
605             dest.writeBundle(mExtras);
606         }
607 
608         @Override
describeContents()609         public int describeContents() {
610             return 0;
611         }
612 
613         public static final @android.annotation.NonNull Parcelable.Creator<PlaybackState.CustomAction> CREATOR =
614                 new Parcelable.Creator<PlaybackState.CustomAction>() {
615 
616                     @Override
617                     public PlaybackState.CustomAction createFromParcel(Parcel p) {
618                         return new PlaybackState.CustomAction(p);
619                     }
620 
621                     @Override
622                     public PlaybackState.CustomAction[] newArray(int size) {
623                         return new PlaybackState.CustomAction[size];
624                     }
625                 };
626 
627         /**
628          * Returns the action of the {@link CustomAction}.
629          *
630          * @return The action of the {@link CustomAction}.
631          */
getAction()632         public String getAction() {
633             return mAction;
634         }
635 
636         /**
637          * Returns the display name of this action. e.g. "Favorite"
638          *
639          * @return The display name of this {@link CustomAction}.
640          */
getName()641         public CharSequence getName() {
642             return mName;
643         }
644 
645         /**
646          * Returns the resource id of the icon in the {@link MediaSession MediaSession's} package.
647          *
648          * @return The resource id of the icon in the {@link MediaSession MediaSession's} package.
649          */
getIcon()650         public int getIcon() {
651             return mIcon;
652         }
653 
654         /**
655          * Returns extras which provide additional application-specific information about the
656          * action, or null if none. These arguments are meant to be consumed by a
657          * {@link MediaController} if it knows how to handle them.
658          *
659          * @return Optional arguments for the {@link CustomAction}.
660          */
getExtras()661         public Bundle getExtras() {
662             return mExtras;
663         }
664 
665         @Override
toString()666         public String toString() {
667             return "Action:" + "mName='" + mName + ", mIcon=" + mIcon + ", mExtras=" + mExtras;
668         }
669 
670         /**
671          * Builder for {@link CustomAction} objects.
672          */
673         public static final class Builder {
674             private final String mAction;
675             private final CharSequence mName;
676             private final int mIcon;
677             private Bundle mExtras;
678 
679             /**
680              * Creates a {@link CustomAction} builder with the id, name, and icon set.
681              *
682              * @param action The action of the {@link CustomAction}.
683              * @param name The display name of the {@link CustomAction}. This name will be displayed
684              *             along side the action if the UI supports it.
685              * @param icon The icon resource id of the {@link CustomAction}. This resource id
686              *             must be in the same package as the {@link MediaSession}. It will be
687              *             displayed with the custom action if the UI supports it.
688              */
Builder(String action, CharSequence name, @DrawableRes int icon)689             public Builder(String action, CharSequence name, @DrawableRes int icon) {
690                 if (TextUtils.isEmpty(action)) {
691                     throw new IllegalArgumentException(
692                             "You must specify an action to build a CustomAction.");
693                 }
694                 if (TextUtils.isEmpty(name)) {
695                     throw new IllegalArgumentException(
696                             "You must specify a name to build a CustomAction.");
697                 }
698                 if (icon == 0) {
699                     throw new IllegalArgumentException(
700                             "You must specify an icon resource id to build a CustomAction.");
701                 }
702                 mAction = action;
703                 mName = name;
704                 mIcon = icon;
705             }
706 
707             /**
708              * Set optional extras for the {@link CustomAction}. These extras are meant to be
709              * consumed by a {@link MediaController} if it knows how to handle them.
710              * Keys should be fully qualified (e.g. "com.example.MY_ARG") to avoid collisions.
711              *
712              * @param extras Optional extras for the {@link CustomAction}.
713              * @return this.
714              */
setExtras(Bundle extras)715             public Builder setExtras(Bundle extras) {
716                 mExtras = extras;
717                 return this;
718             }
719 
720             /**
721              * Build and return the {@link CustomAction} instance with the specified values.
722              *
723              * @return A new {@link CustomAction} instance.
724              */
build()725             public CustomAction build() {
726                 return new CustomAction(mAction, mName, mIcon, mExtras);
727             }
728         }
729     }
730 
731     /**
732      * Builder for {@link PlaybackState} objects.
733      */
734     public static final class Builder {
735         private final List<PlaybackState.CustomAction> mCustomActions = new ArrayList<>();
736 
737         private int mState;
738         private long mPosition;
739         private long mBufferedPosition;
740         private float mSpeed;
741         private long mActions;
742         private CharSequence mErrorMessage;
743         private long mUpdateTime;
744         private long mActiveItemId = MediaSession.QueueItem.UNKNOWN_ID;
745         private Bundle mExtras;
746 
747         /**
748          * Creates an initially empty state builder.
749          */
Builder()750         public Builder() {
751         }
752 
753         /**
754          * Creates a builder with the same initial values as those in the from
755          * state.
756          *
757          * @param from The state to use for initializing the builder.
758          */
Builder(PlaybackState from)759         public Builder(PlaybackState from) {
760             if (from == null) {
761                 return;
762             }
763             mState = from.mState;
764             mPosition = from.mPosition;
765             mBufferedPosition = from.mBufferedPosition;
766             mSpeed = from.mSpeed;
767             mActions = from.mActions;
768             if (from.mCustomActions != null) {
769                 mCustomActions.addAll(from.mCustomActions);
770             }
771             mErrorMessage = from.mErrorMessage;
772             mUpdateTime = from.mUpdateTime;
773             mActiveItemId = from.mActiveItemId;
774             mExtras = from.mExtras;
775         }
776 
777         /**
778          * Set the current state of playback.
779          * <p>
780          * The position must be in ms and indicates the current playback
781          * position within the item. If the position is unknown use
782          * {@link #PLAYBACK_POSITION_UNKNOWN}. When not using an unknown
783          * position the time at which the position was updated must be provided.
784          * It is okay to use {@link SystemClock#elapsedRealtime()} if the
785          * current position was just retrieved.
786          * <p>
787          * The speed is a multiple of normal playback and should be 0 when
788          * paused and negative when rewinding. Normal playback speed is 1.0.
789          * <p>
790          * The state must be one of the following:
791          * <ul>
792          * <li> {@link PlaybackState#STATE_NONE}</li>
793          * <li> {@link PlaybackState#STATE_STOPPED}</li>
794          * <li> {@link PlaybackState#STATE_PLAYING}</li>
795          * <li> {@link PlaybackState#STATE_PAUSED}</li>
796          * <li> {@link PlaybackState#STATE_FAST_FORWARDING}</li>
797          * <li> {@link PlaybackState#STATE_REWINDING}</li>
798          * <li> {@link PlaybackState#STATE_BUFFERING}</li>
799          * <li> {@link PlaybackState#STATE_ERROR}</li>
800          * <li> {@link PlaybackState#STATE_CONNECTING}</li>
801          * <li> {@link PlaybackState#STATE_SKIPPING_TO_PREVIOUS}</li>
802          * <li> {@link PlaybackState#STATE_SKIPPING_TO_NEXT}</li>
803          * <li> {@link PlaybackState#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
804          * </ul>
805          *
806          * @param state The current state of playback.
807          * @param position The position in the current item in ms.
808          * @param playbackSpeed The current speed of playback as a multiple of
809          *            normal playback.
810          * @param updateTime The time in the {@link SystemClock#elapsedRealtime}
811          *            timebase that the position was updated at.
812          * @return this
813          */
setState(@tate int state, long position, float playbackSpeed, long updateTime)814         public Builder setState(@State int state, long position, float playbackSpeed,
815                 long updateTime) {
816             mState = state;
817             mPosition = position;
818             mUpdateTime = updateTime;
819             mSpeed = playbackSpeed;
820             return this;
821         }
822 
823         /**
824          * Set the current state of playback.
825          * <p>
826          * The position must be in ms and indicates the current playback
827          * position within the item. If the position is unknown use
828          * {@link #PLAYBACK_POSITION_UNKNOWN}. The update time will be set to
829          * the current {@link SystemClock#elapsedRealtime()}.
830          * <p>
831          * The speed is a multiple of normal playback and should be 0 when
832          * paused and negative when rewinding. Normal playback speed is 1.0.
833          * <p>
834          * The state must be one of the following:
835          * <ul>
836          * <li> {@link PlaybackState#STATE_NONE}</li>
837          * <li> {@link PlaybackState#STATE_STOPPED}</li>
838          * <li> {@link PlaybackState#STATE_PLAYING}</li>
839          * <li> {@link PlaybackState#STATE_PAUSED}</li>
840          * <li> {@link PlaybackState#STATE_FAST_FORWARDING}</li>
841          * <li> {@link PlaybackState#STATE_REWINDING}</li>
842          * <li> {@link PlaybackState#STATE_BUFFERING}</li>
843          * <li> {@link PlaybackState#STATE_ERROR}</li>
844          * <li> {@link PlaybackState#STATE_CONNECTING}</li>
845          * <li> {@link PlaybackState#STATE_SKIPPING_TO_PREVIOUS}</li>
846          * <li> {@link PlaybackState#STATE_SKIPPING_TO_NEXT}</li>
847          * <li> {@link PlaybackState#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
848          * </ul>
849          *
850          * @param state The current state of playback.
851          * @param position The position in the current item in ms.
852          * @param playbackSpeed The current speed of playback as a multiple of
853          *            normal playback.
854          * @return this
855          */
setState(@tate int state, long position, float playbackSpeed)856         public Builder setState(@State int state, long position, float playbackSpeed) {
857             return setState(state, position, playbackSpeed, SystemClock.elapsedRealtime());
858         }
859 
860         /**
861          * Set the current actions available on this session. This should use a
862          * bitmask of possible actions.
863          * <ul>
864          * <li> {@link PlaybackState#ACTION_SKIP_TO_PREVIOUS}</li>
865          * <li> {@link PlaybackState#ACTION_REWIND}</li>
866          * <li> {@link PlaybackState#ACTION_PLAY}</li>
867          * <li> {@link PlaybackState#ACTION_PAUSE}</li>
868          * <li> {@link PlaybackState#ACTION_STOP}</li>
869          * <li> {@link PlaybackState#ACTION_FAST_FORWARD}</li>
870          * <li> {@link PlaybackState#ACTION_SKIP_TO_NEXT}</li>
871          * <li> {@link PlaybackState#ACTION_SEEK_TO}</li>
872          * <li> {@link PlaybackState#ACTION_SET_RATING}</li>
873          * <li> {@link PlaybackState#ACTION_PLAY_PAUSE}</li>
874          * <li> {@link PlaybackState#ACTION_PLAY_FROM_MEDIA_ID}</li>
875          * <li> {@link PlaybackState#ACTION_PLAY_FROM_SEARCH}</li>
876          * <li> {@link PlaybackState#ACTION_SKIP_TO_QUEUE_ITEM}</li>
877          * <li> {@link PlaybackState#ACTION_PLAY_FROM_URI}</li>
878          * <li> {@link PlaybackState#ACTION_PREPARE}</li>
879          * <li> {@link PlaybackState#ACTION_PREPARE_FROM_MEDIA_ID}</li>
880          * <li> {@link PlaybackState#ACTION_PREPARE_FROM_SEARCH}</li>
881          * <li> {@link PlaybackState#ACTION_PREPARE_FROM_URI}</li>
882          * <li> {@link PlaybackState#ACTION_SET_PLAYBACK_SPEED}</li>
883          * </ul>
884          *
885          * @param actions The set of actions allowed.
886          * @return this
887          */
setActions(@ctions long actions)888         public Builder setActions(@Actions long actions) {
889             mActions = actions;
890             return this;
891         }
892 
893         /**
894          * Add a custom action to the playback state. Actions can be used to
895          * expose additional functionality to {@link MediaController
896          * MediaControllers} beyond what is offered by the standard transport
897          * controls.
898          * <p>
899          * e.g. start a radio station based on the current item or skip ahead by
900          * 30 seconds.
901          *
902          * @param action An identifier for this action. It can be sent back to
903          *            the {@link MediaSession} through
904          *            {@link MediaController.TransportControls#sendCustomAction(String, Bundle)}.
905          * @param name The display name for the action. If text is shown with
906          *            the action or used for accessibility, this is what should
907          *            be used.
908          * @param icon The resource action of the icon that should be displayed
909          *            for the action. The resource should be in the package of
910          *            the {@link MediaSession}.
911          * @return this
912          */
addCustomAction(String action, String name, int icon)913         public Builder addCustomAction(String action, String name, int icon) {
914             return addCustomAction(new PlaybackState.CustomAction(action, name, icon, null));
915         }
916 
917         /**
918          * Add a custom action to the playback state. Actions can be used to expose additional
919          * functionality to {@link MediaController MediaControllers} beyond what is offered by the
920          * standard transport controls.
921          * <p>
922          * An example of an action would be to start a radio station based on the current item
923          * or to skip ahead by 30 seconds.
924          *
925          * @param customAction The custom action to add to the {@link PlaybackState}.
926          * @return this
927          */
addCustomAction(PlaybackState.CustomAction customAction)928         public Builder addCustomAction(PlaybackState.CustomAction customAction) {
929             if (customAction == null) {
930                 throw new IllegalArgumentException(
931                         "You may not add a null CustomAction to PlaybackState.");
932             }
933             mCustomActions.add(customAction);
934             return this;
935         }
936 
937         /**
938          * Set the current buffered position in ms. This is the farthest
939          * playback point that can be reached from the current position using
940          * only buffered content.
941          *
942          * @param bufferedPosition The position in ms that playback is buffered
943          *            to.
944          * @return this
945          */
setBufferedPosition(long bufferedPosition)946         public Builder setBufferedPosition(long bufferedPosition) {
947             mBufferedPosition = bufferedPosition;
948             return this;
949         }
950 
951         /**
952          * Set the active item in the play queue by specifying its id. The
953          * default value is {@link MediaSession.QueueItem#UNKNOWN_ID}
954          *
955          * @param id The id of the active item.
956          * @return this
957          */
setActiveQueueItemId(long id)958         public Builder setActiveQueueItemId(long id) {
959             mActiveItemId = id;
960             return this;
961         }
962 
963         /**
964          * Set a user readable error message. This should be set when the state
965          * is {@link PlaybackState#STATE_ERROR}.
966          *
967          * @param error The error message for display to the user.
968          * @return this
969          */
setErrorMessage(CharSequence error)970         public Builder setErrorMessage(CharSequence error) {
971             mErrorMessage = error;
972             return this;
973         }
974 
975         /**
976          * Set any custom extras to be included with the playback state.
977          *
978          * @param extras The extras to include.
979          * @return this
980          */
setExtras(Bundle extras)981         public Builder setExtras(Bundle extras) {
982             mExtras = extras;
983             return this;
984         }
985 
986         /**
987          * Build and return the {@link PlaybackState} instance with these
988          * values.
989          *
990          * @return A new state instance.
991          */
build()992         public PlaybackState build() {
993             return new PlaybackState(mState, mPosition, mUpdateTime, mSpeed, mBufferedPosition,
994                     mActions, mCustomActions, mActiveItemId, mErrorMessage, mExtras);
995         }
996     }
997 }
998