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