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