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