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