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 static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20
21import android.os.Build;
22import android.os.Bundle;
23import android.os.Parcel;
24import android.os.Parcelable;
25import android.os.SystemClock;
26import android.support.annotation.IntDef;
27import android.support.annotation.Nullable;
28import android.support.annotation.RestrictTo;
29import android.text.TextUtils;
30import android.view.KeyEvent;
31
32import java.lang.annotation.Retention;
33import java.lang.annotation.RetentionPolicy;
34import java.util.ArrayList;
35import java.util.List;
36
37/**
38 * Playback state for a {@link MediaSessionCompat}. This includes a state like
39 * {@link PlaybackStateCompat#STATE_PLAYING}, the current playback position,
40 * and the current control capabilities.
41 */
42public final class PlaybackStateCompat implements Parcelable {
43
44    /**
45     * @hide
46     */
47    @RestrictTo(LIBRARY_GROUP)
48    @IntDef(flag=true, value={ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND,
49            ACTION_SKIP_TO_PREVIOUS, ACTION_SKIP_TO_NEXT, ACTION_FAST_FORWARD, ACTION_SET_RATING,
50            ACTION_SEEK_TO, ACTION_PLAY_PAUSE, ACTION_PLAY_FROM_MEDIA_ID, ACTION_PLAY_FROM_SEARCH,
51            ACTION_SKIP_TO_QUEUE_ITEM, ACTION_PLAY_FROM_URI, ACTION_PREPARE,
52            ACTION_PREPARE_FROM_MEDIA_ID, ACTION_PREPARE_FROM_SEARCH, ACTION_PREPARE_FROM_URI,
53            ACTION_SET_REPEAT_MODE, ACTION_SET_SHUFFLE_MODE_ENABLED, ACTION_SET_CAPTIONING_ENABLED})
54    @Retention(RetentionPolicy.SOURCE)
55    public @interface Actions {}
56
57    /**
58     * @hide
59     */
60    @RestrictTo(LIBRARY_GROUP)
61    @IntDef({ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND, ACTION_SKIP_TO_PREVIOUS,
62            ACTION_SKIP_TO_NEXT, ACTION_FAST_FORWARD, ACTION_PLAY_PAUSE})
63    @Retention(RetentionPolicy.SOURCE)
64    public @interface MediaKeyAction {}
65
66    /**
67     * Indicates this session supports the stop command.
68     *
69     * @see Builder#setActions(long)
70     */
71    public static final long ACTION_STOP = 1 << 0;
72
73    /**
74     * Indicates this session supports the pause command.
75     *
76     * @see Builder#setActions(long)
77     */
78    public static final long ACTION_PAUSE = 1 << 1;
79
80    /**
81     * Indicates this session supports the play command.
82     *
83     * @see Builder#setActions(long)
84     */
85    public static final long ACTION_PLAY = 1 << 2;
86
87    /**
88     * Indicates this session supports the rewind command.
89     *
90     * @see Builder#setActions(long)
91     */
92    public static final long ACTION_REWIND = 1 << 3;
93
94    /**
95     * Indicates this session supports the previous command.
96     *
97     * @see Builder#setActions(long)
98     */
99    public static final long ACTION_SKIP_TO_PREVIOUS = 1 << 4;
100
101    /**
102     * Indicates this session supports the next command.
103     *
104     * @see Builder#setActions(long)
105     */
106    public static final long ACTION_SKIP_TO_NEXT = 1 << 5;
107
108    /**
109     * Indicates this session supports the fast forward command.
110     *
111     * @see Builder#setActions(long)
112     */
113    public static final long ACTION_FAST_FORWARD = 1 << 6;
114
115    /**
116     * Indicates this session supports the set rating command.
117     *
118     * @see Builder#setActions(long)
119     */
120    public static final long ACTION_SET_RATING = 1 << 7;
121
122    /**
123     * Indicates this session supports the seek to command.
124     *
125     * @see Builder#setActions(long)
126     */
127    public static final long ACTION_SEEK_TO = 1 << 8;
128
129    /**
130     * Indicates this session supports the play/pause toggle command.
131     *
132     * @see Builder#setActions(long)
133     */
134    public static final long ACTION_PLAY_PAUSE = 1 << 9;
135
136    /**
137     * Indicates this session supports the play from media id command.
138     *
139     * @see Builder#setActions(long)
140     */
141    public static final long ACTION_PLAY_FROM_MEDIA_ID = 1 << 10;
142
143    /**
144     * Indicates this session supports the play from search command.
145     *
146     * @see Builder#setActions(long)
147     */
148    public static final long ACTION_PLAY_FROM_SEARCH = 1 << 11;
149
150    /**
151     * Indicates this session supports the skip to queue item command.
152     *
153     * @see Builder#setActions(long)
154     */
155    public static final long ACTION_SKIP_TO_QUEUE_ITEM = 1 << 12;
156
157    /**
158     * Indicates this session supports the play from URI command.
159     *
160     * @see Builder#setActions(long)
161     */
162    public static final long ACTION_PLAY_FROM_URI = 1 << 13;
163
164    /**
165     * Indicates this session supports the prepare command.
166     *
167     * @see Builder#setActions(long)
168     */
169    public static final long ACTION_PREPARE = 1 << 14;
170
171    /**
172     * Indicates this session supports the prepare from media id command.
173     *
174     * @see Builder#setActions(long)
175     */
176    public static final long ACTION_PREPARE_FROM_MEDIA_ID = 1 << 15;
177
178    /**
179     * Indicates this session supports the prepare from search command.
180     *
181     * @see Builder#setActions(long)
182     */
183    public static final long ACTION_PREPARE_FROM_SEARCH = 1 << 16;
184
185    /**
186     * Indicates this session supports the prepare from URI command.
187     *
188     * @see Builder#setActions(long)
189     */
190    public static final long ACTION_PREPARE_FROM_URI = 1 << 17;
191
192    /**
193     * Indicates this session supports the set repeat mode command.
194     *
195     * @see Builder#setActions(long)
196     */
197    public static final long ACTION_SET_REPEAT_MODE = 1 << 18;
198
199    /**
200     * Indicates this session supports the set shuffle mode enabled command.
201     *
202     * @see Builder#setActions(long)
203     */
204    public static final long ACTION_SET_SHUFFLE_MODE_ENABLED = 1 << 19;
205
206    /**
207     * Indicates this session supports the set captioning enabled command.
208     *
209     * @see Builder#setActions(long)
210     */
211    public static final long ACTION_SET_CAPTIONING_ENABLED = 1 << 20;
212
213    /**
214     * @hide
215     */
216    @RestrictTo(LIBRARY_GROUP)
217    @IntDef({STATE_NONE, STATE_STOPPED, STATE_PAUSED, STATE_PLAYING, STATE_FAST_FORWARDING,
218            STATE_REWINDING, STATE_BUFFERING, STATE_ERROR, STATE_CONNECTING,
219            STATE_SKIPPING_TO_PREVIOUS, STATE_SKIPPING_TO_NEXT, STATE_SKIPPING_TO_QUEUE_ITEM})
220    @Retention(RetentionPolicy.SOURCE)
221    public @interface State {}
222
223    /**
224     * This is the default playback state and indicates that no media has been
225     * added yet, or the performer has been reset and has no content to play.
226     *
227     * @see Builder#setState
228     */
229    public final static int STATE_NONE = 0;
230
231    /**
232     * State indicating this item is currently stopped.
233     *
234     * @see Builder#setState
235     */
236    public final static int STATE_STOPPED = 1;
237
238    /**
239     * State indicating this item is currently paused.
240     *
241     * @see Builder#setState
242     */
243    public final static int STATE_PAUSED = 2;
244
245    /**
246     * State indicating this item is currently playing.
247     *
248     * @see Builder#setState
249     */
250    public final static int STATE_PLAYING = 3;
251
252    /**
253     * State indicating this item is currently fast forwarding.
254     *
255     * @see Builder#setState
256     */
257    public final static int STATE_FAST_FORWARDING = 4;
258
259    /**
260     * State indicating this item is currently rewinding.
261     *
262     * @see Builder#setState
263     */
264    public final static int STATE_REWINDING = 5;
265
266    /**
267     * State indicating this item is currently buffering and will begin playing
268     * when enough data has buffered.
269     *
270     * @see Builder#setState
271     */
272    public final static int STATE_BUFFERING = 6;
273
274    /**
275     * State indicating this item is currently in an error state. The error
276     * code should also be set when entering this state.
277     *
278     * @see Builder#setState
279     * @see Builder#setErrorMessage(int, CharSequence)
280     */
281    public final static int STATE_ERROR = 7;
282
283    /**
284     * State indicating the class doing playback is currently connecting to a
285     * route. Depending on the implementation you may return to the previous
286     * state when the connection finishes or enter {@link #STATE_NONE}. If
287     * the connection failed {@link #STATE_ERROR} should be used.
288     * <p>
289     * On devices earlier than API 21, this will appear as {@link #STATE_BUFFERING}
290     * </p>
291     *
292     * @see Builder#setState
293     */
294    public final static int STATE_CONNECTING = 8;
295
296    /**
297     * State indicating the player is currently skipping to the previous item.
298     *
299     * @see Builder#setState
300     */
301    public final static int STATE_SKIPPING_TO_PREVIOUS = 9;
302
303    /**
304     * State indicating the player is currently skipping to the next item.
305     *
306     * @see Builder#setState
307     */
308    public final static int STATE_SKIPPING_TO_NEXT = 10;
309
310    /**
311     * State indicating the player is currently skipping to a specific item in
312     * the queue.
313     * <p>
314     * On devices earlier than API 21, this will appear as {@link #STATE_SKIPPING_TO_NEXT}
315     * </p>
316     *
317     * @see Builder#setState
318     */
319    public final static int STATE_SKIPPING_TO_QUEUE_ITEM = 11;
320
321    /**
322     * Use this value for the position to indicate the position is not known.
323     */
324    public final static long PLAYBACK_POSITION_UNKNOWN = -1;
325
326    /**
327     * @hide
328     */
329    @RestrictTo(LIBRARY_GROUP)
330    @IntDef({REPEAT_MODE_NONE, REPEAT_MODE_ONE, REPEAT_MODE_ALL, REPEAT_MODE_GROUP})
331    @Retention(RetentionPolicy.SOURCE)
332    public @interface RepeatMode {}
333
334    /**
335     * Use this value with {@link MediaControllerCompat.TransportControls#setRepeatMode}
336     * to indicate that the playback will be stopped at the end of the playing media list.
337     */
338    public static final int REPEAT_MODE_NONE = 0;
339
340    /**
341     * Use this value with {@link MediaControllerCompat.TransportControls#setRepeatMode}
342     * to indicate that the playback of the current playing media item will be repeated.
343     */
344    public static final int REPEAT_MODE_ONE = 1;
345
346    /**
347     * Use this value with {@link MediaControllerCompat.TransportControls#setRepeatMode}
348     * to indicate that the playback of the playing media list will be repeated.
349     */
350    public static final int REPEAT_MODE_ALL = 2;
351
352    /**
353     * Use this value with {@link MediaControllerCompat.TransportControls#setRepeatMode}
354     * to indicate that the playback of the playing media group will be repeated.
355     * A group is a logical block of media items which is specified in the section 5.7 of the
356     * Bluetooth AVRCP 1.6.
357     */
358    public static final int REPEAT_MODE_GROUP = 3;
359
360    /**
361     * @hide
362     */
363    @RestrictTo(LIBRARY_GROUP)
364    @IntDef({SHUFFLE_MODE_NONE, SHUFFLE_MODE_ALL, SHUFFLE_MODE_GROUP})
365    @Retention(RetentionPolicy.SOURCE)
366    public @interface ShuffleMode {}
367
368    /**
369     * Use this value with {@link MediaControllerCompat.TransportControls#setShuffleMode}
370     * to indicate that the media list will be played in order.
371     */
372    public static final int SHUFFLE_MODE_NONE = 0;
373
374    /**
375     * Use this value with {@link MediaControllerCompat.TransportControls#setShuffleMode}
376     * to indicate that the media list will be played in shuffled order.
377     */
378    public static final int SHUFFLE_MODE_ALL = 1;
379
380    /**
381     * Use this value with {@link MediaControllerCompat.TransportControls#setShuffleMode}
382     * to indicate that the media group will be played in shuffled order.
383     * A group is a logical block of media items which is specified in the section 5.7 of the
384     * Bluetooth AVRCP 1.6.
385     */
386    public static final int SHUFFLE_MODE_GROUP = 2;
387
388    /**
389     * @hide
390     */
391    @RestrictTo(LIBRARY_GROUP)
392    @IntDef({ERROR_CODE_UNKNOWN_ERROR, ERROR_CODE_APP_ERROR, ERROR_CODE_NOT_SUPPORTED,
393            ERROR_CODE_AUTHENTICATION_EXPIRED, ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED,
394            ERROR_CODE_CONCURRENT_STREAM_LIMIT, ERROR_CODE_PARENTAL_CONTROL_RESTRICTED,
395            ERROR_CODE_NOT_AVAILABLE_IN_REGION, ERROR_CODE_CONTENT_ALREADY_PLAYING,
396            ERROR_CODE_SKIP_LIMIT_REACHED, ERROR_CODE_ACTION_ABORTED, ERROR_CODE_END_OF_QUEUE})
397    @Retention(RetentionPolicy.SOURCE)
398    public @interface ErrorCode {}
399
400    /**
401     * This is the default error code and indicates that none of the other error codes applies.
402     * The error code should be set when entering {@link #STATE_ERROR}.
403     */
404    public static final int ERROR_CODE_UNKNOWN_ERROR = 0;
405
406    /**
407     * Error code when the application state is invalid to fulfill the request.
408     * The error code should be set when entering {@link #STATE_ERROR}.
409     */
410    public static final int ERROR_CODE_APP_ERROR = 1;
411
412    /**
413     * Error code when the request is not supported by the application.
414     * The error code should be set when entering {@link #STATE_ERROR}.
415     */
416    public static final int ERROR_CODE_NOT_SUPPORTED = 2;
417
418    /**
419     * Error code when the request cannot be performed because authentication has expired.
420     * The error code should be set when entering {@link #STATE_ERROR}.
421     */
422    public static final int ERROR_CODE_AUTHENTICATION_EXPIRED = 3;
423
424    /**
425     * Error code when a premium account is required for the request to succeed.
426     * The error code should be set when entering {@link #STATE_ERROR}.
427     */
428    public static final int ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED = 4;
429
430    /**
431     * Error code when too many concurrent streams are detected.
432     * The error code should be set when entering {@link #STATE_ERROR}.
433     */
434    public static final int ERROR_CODE_CONCURRENT_STREAM_LIMIT = 5;
435
436    /**
437     * Error code when the content is blocked due to parental controls.
438     * The error code should be set when entering {@link #STATE_ERROR}.
439     */
440    public static final int ERROR_CODE_PARENTAL_CONTROL_RESTRICTED = 6;
441
442    /**
443     * Error code when the content is blocked due to being regionally unavailable.
444     * The error code should be set when entering {@link #STATE_ERROR}.
445     */
446    public static final int ERROR_CODE_NOT_AVAILABLE_IN_REGION = 7;
447
448    /**
449     * Error code when the requested content is already playing.
450     * The error code should be set when entering {@link #STATE_ERROR}.
451     */
452    public static final int ERROR_CODE_CONTENT_ALREADY_PLAYING = 8;
453
454    /**
455     * Error code when the application cannot skip any more songs because skip limit is reached.
456     * The error code should be set when entering {@link #STATE_ERROR}.
457     */
458    public static final int ERROR_CODE_SKIP_LIMIT_REACHED = 9;
459
460    /**
461     * Error code when the action is interrupted due to some external event.
462     * The error code should be set when entering {@link #STATE_ERROR}.
463     */
464    public static final int ERROR_CODE_ACTION_ABORTED = 10;
465
466    /**
467     * Error code when the playback navigation (previous, next) is not possible because the queue
468     * was exhausted.
469     * The error code should be set when entering {@link #STATE_ERROR}.
470     */
471    public static final int ERROR_CODE_END_OF_QUEUE = 11;
472
473    // KeyEvent constants only available on API 11+
474    private static final int KEYCODE_MEDIA_PAUSE = 127;
475    private static final int KEYCODE_MEDIA_PLAY = 126;
476
477    /**
478     * Translates a given action into a matched key code defined in {@link KeyEvent}. The given
479     * action should be one of the following:
480     * <ul>
481     * <li>{@link PlaybackStateCompat#ACTION_PLAY}</li>
482     * <li>{@link PlaybackStateCompat#ACTION_PAUSE}</li>
483     * <li>{@link PlaybackStateCompat#ACTION_SKIP_TO_NEXT}</li>
484     * <li>{@link PlaybackStateCompat#ACTION_SKIP_TO_PREVIOUS}</li>
485     * <li>{@link PlaybackStateCompat#ACTION_STOP}</li>
486     * <li>{@link PlaybackStateCompat#ACTION_FAST_FORWARD}</li>
487     * <li>{@link PlaybackStateCompat#ACTION_REWIND}</li>
488     * <li>{@link PlaybackStateCompat#ACTION_PLAY_PAUSE}</li>
489     * </ul>
490     *
491     * @param action The action to be translated.
492     *
493     * @return the key code matched to the given action.
494     */
495    public static int toKeyCode(@MediaKeyAction long action) {
496        if (action == ACTION_PLAY) {
497            return KEYCODE_MEDIA_PLAY;
498        } else if (action == ACTION_PAUSE) {
499            return KEYCODE_MEDIA_PAUSE;
500        } else if (action == ACTION_SKIP_TO_NEXT) {
501            return KeyEvent.KEYCODE_MEDIA_NEXT;
502        } else if (action == ACTION_SKIP_TO_PREVIOUS) {
503            return KeyEvent.KEYCODE_MEDIA_PREVIOUS;
504        } else if (action == ACTION_STOP) {
505            return KeyEvent.KEYCODE_MEDIA_STOP;
506        } else if (action == ACTION_FAST_FORWARD) {
507            return KeyEvent.KEYCODE_MEDIA_FAST_FORWARD;
508        } else if (action == ACTION_REWIND) {
509            return KeyEvent.KEYCODE_MEDIA_REWIND;
510        } else if (action == ACTION_PLAY_PAUSE) {
511            return KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE;
512        }
513        return KeyEvent.KEYCODE_UNKNOWN;
514    }
515
516    final int mState;
517    final long mPosition;
518    final long mBufferedPosition;
519    final float mSpeed;
520    final long mActions;
521    final int mErrorCode;
522    final CharSequence mErrorMessage;
523    final long mUpdateTime;
524    List<PlaybackStateCompat.CustomAction> mCustomActions;
525    final long mActiveItemId;
526    final Bundle mExtras;
527
528    private Object mStateObj;
529
530    PlaybackStateCompat(int state, long position, long bufferedPosition,
531            float rate, long actions, int errorCode, CharSequence errorMessage, long updateTime,
532            List<PlaybackStateCompat.CustomAction> customActions,
533            long activeItemId, Bundle extras) {
534        mState = state;
535        mPosition = position;
536        mBufferedPosition = bufferedPosition;
537        mSpeed = rate;
538        mActions = actions;
539        mErrorCode = errorCode;
540        mErrorMessage = errorMessage;
541        mUpdateTime = updateTime;
542        mCustomActions = new ArrayList<>(customActions);
543        mActiveItemId = activeItemId;
544        mExtras = extras;
545    }
546
547    PlaybackStateCompat(Parcel in) {
548        mState = in.readInt();
549        mPosition = in.readLong();
550        mSpeed = in.readFloat();
551        mUpdateTime = in.readLong();
552        mBufferedPosition = in.readLong();
553        mActions = in.readLong();
554        mErrorMessage = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
555        mCustomActions = in.createTypedArrayList(CustomAction.CREATOR);
556        mActiveItemId = in.readLong();
557        mExtras = in.readBundle();
558        // New attributes should be added at the end for backward compatibility.
559        mErrorCode = in.readInt();
560    }
561
562    @Override
563    public String toString() {
564        StringBuilder bob = new StringBuilder("PlaybackState {");
565        bob.append("state=").append(mState);
566        bob.append(", position=").append(mPosition);
567        bob.append(", buffered position=").append(mBufferedPosition);
568        bob.append(", speed=").append(mSpeed);
569        bob.append(", updated=").append(mUpdateTime);
570        bob.append(", actions=").append(mActions);
571        bob.append(", error code=").append(mErrorCode);
572        bob.append(", error message=").append(mErrorMessage);
573        bob.append(", custom actions=").append(mCustomActions);
574        bob.append(", active item id=").append(mActiveItemId);
575        bob.append("}");
576        return bob.toString();
577    }
578
579    @Override
580    public int describeContents() {
581        return 0;
582    }
583
584    @Override
585    public void writeToParcel(Parcel dest, int flags) {
586        dest.writeInt(mState);
587        dest.writeLong(mPosition);
588        dest.writeFloat(mSpeed);
589        dest.writeLong(mUpdateTime);
590        dest.writeLong(mBufferedPosition);
591        dest.writeLong(mActions);
592        TextUtils.writeToParcel(mErrorMessage, dest, flags);
593        dest.writeTypedList(mCustomActions);
594        dest.writeLong(mActiveItemId);
595        dest.writeBundle(mExtras);
596        // New attributes should be added at the end for backward compatibility.
597        dest.writeInt(mErrorCode);
598    }
599
600    /**
601     * Get the current state of playback. One of the following:
602     * <ul>
603     * <li> {@link PlaybackStateCompat#STATE_NONE}</li>
604     * <li> {@link PlaybackStateCompat#STATE_STOPPED}</li>
605     * <li> {@link PlaybackStateCompat#STATE_PLAYING}</li>
606     * <li> {@link PlaybackStateCompat#STATE_PAUSED}</li>
607     * <li> {@link PlaybackStateCompat#STATE_FAST_FORWARDING}</li>
608     * <li> {@link PlaybackStateCompat#STATE_REWINDING}</li>
609     * <li> {@link PlaybackStateCompat#STATE_BUFFERING}</li>
610     * <li> {@link PlaybackStateCompat#STATE_ERROR}</li>
611     * <li> {@link PlaybackStateCompat#STATE_CONNECTING}</li>
612     * <li> {@link PlaybackStateCompat#STATE_SKIPPING_TO_PREVIOUS}</li>
613     * <li> {@link PlaybackStateCompat#STATE_SKIPPING_TO_NEXT}</li>
614     * <li> {@link PlaybackStateCompat#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
615     */
616    @State
617    public int getState() {
618        return mState;
619    }
620
621    /**
622     * Get the current playback position in ms.
623     */
624    public long getPosition() {
625        return mPosition;
626    }
627
628    /**
629     * Get the current buffered position in ms. This is the farthest playback
630     * point that can be reached from the current position using only buffered
631     * content.
632     */
633    public long getBufferedPosition() {
634        return mBufferedPosition;
635    }
636
637    /**
638     * Get the current playback speed as a multiple of normal playback. This
639     * should be negative when rewinding. A value of 1 means normal playback and
640     * 0 means paused.
641     *
642     * @return The current speed of playback.
643     */
644    public float getPlaybackSpeed() {
645        return mSpeed;
646    }
647
648    /**
649     * Get the current actions available on this session. This should use a
650     * bitmask of the available actions.
651     * <ul>
652     * <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_PREVIOUS}</li>
653     * <li> {@link PlaybackStateCompat#ACTION_REWIND}</li>
654     * <li> {@link PlaybackStateCompat#ACTION_PLAY}</li>
655     * <li> {@link PlaybackStateCompat#ACTION_PLAY_PAUSE}</li>
656     * <li> {@link PlaybackStateCompat#ACTION_PAUSE}</li>
657     * <li> {@link PlaybackStateCompat#ACTION_STOP}</li>
658     * <li> {@link PlaybackStateCompat#ACTION_FAST_FORWARD}</li>
659     * <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_NEXT}</li>
660     * <li> {@link PlaybackStateCompat#ACTION_SEEK_TO}</li>
661     * <li> {@link PlaybackStateCompat#ACTION_SET_RATING}</li>
662     * <li> {@link PlaybackStateCompat#ACTION_PLAY_FROM_MEDIA_ID}</li>
663     * <li> {@link PlaybackStateCompat#ACTION_PLAY_FROM_SEARCH}</li>
664     * <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_QUEUE_ITEM}</li>
665     * <li> {@link PlaybackStateCompat#ACTION_PLAY_FROM_URI}</li>
666     * <li> {@link PlaybackStateCompat#ACTION_PREPARE}</li>
667     * <li> {@link PlaybackStateCompat#ACTION_PREPARE_FROM_MEDIA_ID}</li>
668     * <li> {@link PlaybackStateCompat#ACTION_PREPARE_FROM_SEARCH}</li>
669     * <li> {@link PlaybackStateCompat#ACTION_PREPARE_FROM_URI}</li>
670     * <li> {@link PlaybackStateCompat#ACTION_SET_REPEAT_MODE}</li>
671     * <li> {@link PlaybackStateCompat#ACTION_SET_SHUFFLE_MODE_ENABLED}</li>
672     * <li> {@link PlaybackStateCompat#ACTION_SET_CAPTIONING_ENABLED}</li>
673     * </ul>
674     */
675    @Actions
676    public long getActions() {
677        return mActions;
678    }
679
680    /**
681     * Get the list of custom actions.
682     */
683    public List<PlaybackStateCompat.CustomAction> getCustomActions() {
684        return mCustomActions;
685    }
686
687    /**
688     * Get the error code. This should be set when the state is
689     * {@link PlaybackStateCompat#STATE_ERROR}.
690     *
691     * @see #ERROR_CODE_UNKNOWN_ERROR
692     * @see #ERROR_CODE_APP_ERROR
693     * @see #ERROR_CODE_NOT_SUPPORTED
694     * @see #ERROR_CODE_AUTHENTICATION_EXPIRED
695     * @see #ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED
696     * @see #ERROR_CODE_CONCURRENT_STREAM_LIMIT
697     * @see #ERROR_CODE_PARENTAL_CONTROL_RESTRICTED
698     * @see #ERROR_CODE_NOT_AVAILABLE_IN_REGION
699     * @see #ERROR_CODE_CONTENT_ALREADY_PLAYING
700     * @see #ERROR_CODE_SKIP_LIMIT_REACHED
701     * @see #ERROR_CODE_ACTION_ABORTED
702     * @see #ERROR_CODE_END_OF_QUEUE
703     * @see #getErrorMessage()
704     */
705    @ErrorCode
706    public int getErrorCode() {
707        return mErrorCode;
708    }
709
710    /**
711     * Get the user readable optional error message. This may be set when the state is
712     * {@link PlaybackStateCompat#STATE_ERROR}.
713     *
714     * @see #getErrorCode()
715     */
716    public CharSequence getErrorMessage() {
717        return mErrorMessage;
718    }
719
720    /**
721     * Get the elapsed real time at which position was last updated. If the
722     * position has never been set this will return 0;
723     *
724     * @return The last time the position was updated.
725     */
726    public long getLastPositionUpdateTime() {
727        return mUpdateTime;
728    }
729
730    /**
731     * Get the id of the currently active item in the queue. If there is no
732     * queue or a queue is not supported by the session this will be
733     * {@link MediaSessionCompat.QueueItem#UNKNOWN_ID}.
734     *
735     * @return The id of the currently active item in the queue or
736     *         {@link MediaSessionCompat.QueueItem#UNKNOWN_ID}.
737     */
738    public long getActiveQueueItemId() {
739        return mActiveItemId;
740    }
741
742    /**
743     * Get any custom extras that were set on this playback state.
744     *
745     * @return The extras for this state or null.
746     */
747    public @Nullable Bundle getExtras() {
748        return mExtras;
749    }
750
751    /**
752     * Creates an instance from a framework {@link android.media.session.PlaybackState} object.
753     * <p>
754     * This method is only supported on API 21+.
755     * </p>
756     *
757     * @param stateObj A {@link android.media.session.PlaybackState} object, or null if none.
758     * @return An equivalent {@link PlaybackStateCompat} object, or null if none.
759     */
760    public static PlaybackStateCompat fromPlaybackState(Object stateObj) {
761        if (stateObj != null && Build.VERSION.SDK_INT >= 21) {
762            List<Object> customActionObjs = PlaybackStateCompatApi21.getCustomActions(stateObj);
763            List<PlaybackStateCompat.CustomAction> customActions = null;
764            if (customActionObjs != null) {
765                customActions = new ArrayList<>(customActionObjs.size());
766                for (Object customActionObj : customActionObjs) {
767                    customActions.add(CustomAction.fromCustomAction(customActionObj));
768                }
769            }
770            Bundle extras;
771            if (Build.VERSION.SDK_INT >= 22) {
772                extras = PlaybackStateCompatApi22.getExtras(stateObj);
773            } else {
774                extras = null;
775            }
776            PlaybackStateCompat state = new PlaybackStateCompat(
777                    PlaybackStateCompatApi21.getState(stateObj),
778                    PlaybackStateCompatApi21.getPosition(stateObj),
779                    PlaybackStateCompatApi21.getBufferedPosition(stateObj),
780                    PlaybackStateCompatApi21.getPlaybackSpeed(stateObj),
781                    PlaybackStateCompatApi21.getActions(stateObj),
782                    ERROR_CODE_UNKNOWN_ERROR,
783                    PlaybackStateCompatApi21.getErrorMessage(stateObj),
784                    PlaybackStateCompatApi21.getLastPositionUpdateTime(stateObj),
785                    customActions,
786                    PlaybackStateCompatApi21.getActiveQueueItemId(stateObj),
787                    extras);
788            state.mStateObj = stateObj;
789            return state;
790        } else {
791            return null;
792        }
793    }
794
795    /**
796     * Gets the underlying framework {@link android.media.session.PlaybackState} object.
797     * <p>
798     * This method is only supported on API 21+.
799     * </p>
800     *
801     * @return An equivalent {@link android.media.session.PlaybackState} object, or null if none.
802     */
803    public Object getPlaybackState() {
804        if (mStateObj == null && Build.VERSION.SDK_INT >= 21) {
805            List<Object> customActions = null;
806            if (mCustomActions != null) {
807                customActions = new ArrayList<>(mCustomActions.size());
808                for (PlaybackStateCompat.CustomAction customAction : mCustomActions) {
809                    customActions.add(customAction.getCustomAction());
810                }
811            }
812            if (Build.VERSION.SDK_INT >= 22) {
813                mStateObj = PlaybackStateCompatApi22.newInstance(mState, mPosition,
814                        mBufferedPosition,
815                        mSpeed, mActions, mErrorMessage, mUpdateTime,
816                        customActions, mActiveItemId, mExtras);
817            } else {
818                //noinspection AndroidLintNewApi - NewApi lint fails to handle nested checks.
819                mStateObj = PlaybackStateCompatApi21.newInstance(mState, mPosition,
820                        mBufferedPosition, mSpeed, mActions, mErrorMessage, mUpdateTime,
821                        customActions, mActiveItemId);
822            }
823        }
824        return mStateObj;
825    }
826
827    public static final Parcelable.Creator<PlaybackStateCompat> CREATOR =
828            new Parcelable.Creator<PlaybackStateCompat>() {
829        @Override
830        public PlaybackStateCompat createFromParcel(Parcel in) {
831            return new PlaybackStateCompat(in);
832        }
833
834        @Override
835        public PlaybackStateCompat[] newArray(int size) {
836            return new PlaybackStateCompat[size];
837        }
838    };
839
840    /**
841     * {@link PlaybackStateCompat.CustomAction CustomActions} can be used to
842     * extend the capabilities of the standard transport controls by exposing
843     * app specific actions to {@link MediaControllerCompat Controllers}.
844     */
845    public static final class CustomAction implements Parcelable {
846        private final String mAction;
847        private final CharSequence mName;
848        private final int mIcon;
849        private final Bundle mExtras;
850
851        private Object mCustomActionObj;
852
853        /**
854         * Use {@link PlaybackStateCompat.CustomAction.Builder#build()}.
855         */
856        CustomAction(String action, CharSequence name, int icon, Bundle extras) {
857            mAction = action;
858            mName = name;
859            mIcon = icon;
860            mExtras = extras;
861        }
862
863        CustomAction(Parcel in) {
864            mAction = in.readString();
865            mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
866            mIcon = in.readInt();
867            mExtras = in.readBundle();
868        }
869
870        @Override
871        public void writeToParcel(Parcel dest, int flags) {
872            dest.writeString(mAction);
873            TextUtils.writeToParcel(mName, dest, flags);
874            dest.writeInt(mIcon);
875            dest.writeBundle(mExtras);
876        }
877
878        @Override
879        public int describeContents() {
880            return 0;
881        }
882
883        /**
884         * Creates an instance from a framework
885         * {@link android.media.session.PlaybackState.CustomAction} object.
886         * <p>
887         * This method is only supported on API 21+.
888         * </p>
889         *
890         * @param customActionObj A {@link android.media.session.PlaybackState.CustomAction} object,
891         * or null if none.
892         * @return An equivalent {@link PlaybackStateCompat.CustomAction} object, or null if none.
893         */
894        public static PlaybackStateCompat.CustomAction fromCustomAction(Object customActionObj) {
895            if (customActionObj == null || Build.VERSION.SDK_INT < 21) {
896                return null;
897            }
898
899            PlaybackStateCompat.CustomAction customAction = new PlaybackStateCompat.CustomAction(
900                    PlaybackStateCompatApi21.CustomAction.getAction(customActionObj),
901                    PlaybackStateCompatApi21.CustomAction.getName(customActionObj),
902                    PlaybackStateCompatApi21.CustomAction.getIcon(customActionObj),
903                    PlaybackStateCompatApi21.CustomAction.getExtras(customActionObj));
904            customAction.mCustomActionObj = customActionObj;
905            return customAction;
906        }
907
908        /**
909         * Gets the underlying framework {@link android.media.session.PlaybackState.CustomAction}
910         * object.
911         * <p>
912         * This method is only supported on API 21+.
913         * </p>
914         *
915         * @return An equivalent {@link android.media.session.PlaybackState.CustomAction} object,
916         * or null if none.
917         */
918        public Object getCustomAction() {
919            if (mCustomActionObj != null || Build.VERSION.SDK_INT < 21) {
920                return mCustomActionObj;
921            }
922
923            mCustomActionObj = PlaybackStateCompatApi21.CustomAction.newInstance(mAction,
924                    mName, mIcon, mExtras);
925            return mCustomActionObj;
926        }
927
928        public static final Parcelable.Creator<PlaybackStateCompat.CustomAction> CREATOR
929                = new Parcelable.Creator<PlaybackStateCompat.CustomAction>() {
930
931                    @Override
932                    public PlaybackStateCompat.CustomAction createFromParcel(Parcel p) {
933                        return new PlaybackStateCompat.CustomAction(p);
934                    }
935
936                    @Override
937                    public PlaybackStateCompat.CustomAction[] newArray(int size) {
938                        return new PlaybackStateCompat.CustomAction[size];
939                    }
940                };
941
942        /**
943         * Returns the action of the {@link CustomAction}.
944         *
945         * @return The action of the {@link CustomAction}.
946         */
947        public String getAction() {
948            return mAction;
949        }
950
951        /**
952         * Returns the display name of this action. e.g. "Favorite"
953         *
954         * @return The display name of this {@link CustomAction}.
955         */
956        public CharSequence getName() {
957            return mName;
958        }
959
960        /**
961         * Returns the resource id of the icon in the {@link MediaSessionCompat
962         * Session's} package.
963         *
964         * @return The resource id of the icon in the {@link MediaSessionCompat
965         *         Session's} package.
966         */
967        public int getIcon() {
968            return mIcon;
969        }
970
971        /**
972         * Returns extras which provide additional application-specific
973         * information about the action, or null if none. These arguments are
974         * meant to be consumed by a {@link MediaControllerCompat} if it knows
975         * how to handle them.
976         *
977         * @return Optional arguments for the {@link CustomAction}.
978         */
979        public Bundle getExtras() {
980            return mExtras;
981        }
982
983        @Override
984        public String toString() {
985            return "Action:" +
986                    "mName='" + mName +
987                    ", mIcon=" + mIcon +
988                    ", mExtras=" + mExtras;
989        }
990
991        /**
992         * Builder for {@link CustomAction} objects.
993         */
994        public static final class Builder {
995            private final String mAction;
996            private final CharSequence mName;
997            private final int mIcon;
998            private Bundle mExtras;
999
1000            /**
1001             * Creates a {@link CustomAction} builder with the id, name, and
1002             * icon set.
1003             *
1004             * @param action The action of the {@link CustomAction}.
1005             * @param name The display name of the {@link CustomAction}. This
1006             *            name will be displayed along side the action if the UI
1007             *            supports it.
1008             * @param icon The icon resource id of the {@link CustomAction}.
1009             *            This resource id must be in the same package as the
1010             *            {@link MediaSessionCompat}. It will be displayed with
1011             *            the custom action if the UI supports it.
1012             */
1013            public Builder(String action, CharSequence name, int icon) {
1014                if (TextUtils.isEmpty(action)) {
1015                    throw new IllegalArgumentException(
1016                            "You must specify an action to build a CustomAction.");
1017                }
1018                if (TextUtils.isEmpty(name)) {
1019                    throw new IllegalArgumentException(
1020                            "You must specify a name to build a CustomAction.");
1021                }
1022                if (icon == 0) {
1023                    throw new IllegalArgumentException(
1024                            "You must specify an icon resource id to build a CustomAction.");
1025                }
1026                mAction = action;
1027                mName = name;
1028                mIcon = icon;
1029            }
1030
1031            /**
1032             * Set optional extras for the {@link CustomAction}. These extras
1033             * are meant to be consumed by a {@link MediaControllerCompat} if it
1034             * knows how to handle them. Keys should be fully qualified (e.g.
1035             * "com.example.MY_ARG") to avoid collisions.
1036             *
1037             * @param extras Optional extras for the {@link CustomAction}.
1038             * @return this.
1039             */
1040            public Builder setExtras(Bundle extras) {
1041                mExtras = extras;
1042                return this;
1043            }
1044
1045            /**
1046             * Build and return the {@link CustomAction} instance with the
1047             * specified values.
1048             *
1049             * @return A new {@link CustomAction} instance.
1050             */
1051            public CustomAction build() {
1052                return new CustomAction(mAction, mName, mIcon, mExtras);
1053            }
1054        }
1055    }
1056
1057    /**
1058     * Builder for {@link PlaybackStateCompat} objects.
1059     */
1060    public static final class Builder {
1061        private final List<PlaybackStateCompat.CustomAction> mCustomActions = new ArrayList<>();
1062
1063        private int mState;
1064        private long mPosition;
1065        private long mBufferedPosition;
1066        private float mRate;
1067        private long mActions;
1068        private int mErrorCode;
1069        private CharSequence mErrorMessage;
1070        private long mUpdateTime;
1071        private long mActiveItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID;
1072        private Bundle mExtras;
1073
1074        /**
1075         * Create an empty Builder.
1076         */
1077        public Builder() {
1078        }
1079
1080        /**
1081         * Create a Builder using a {@link PlaybackStateCompat} instance to set the
1082         * initial values.
1083         *
1084         * @param source The playback state to copy.
1085         */
1086        public Builder(PlaybackStateCompat source) {
1087            mState = source.mState;
1088            mPosition = source.mPosition;
1089            mRate = source.mSpeed;
1090            mUpdateTime = source.mUpdateTime;
1091            mBufferedPosition = source.mBufferedPosition;
1092            mActions = source.mActions;
1093            mErrorCode = source.mErrorCode;
1094            mErrorMessage = source.mErrorMessage;
1095            if (source.mCustomActions != null) {
1096                mCustomActions.addAll(source.mCustomActions);
1097            }
1098            mActiveItemId = source.mActiveItemId;
1099            mExtras = source.mExtras;
1100        }
1101
1102        /**
1103         * Set the current state of playback.
1104         * <p>
1105         * The position must be in ms and indicates the current playback
1106         * position within the track. If the position is unknown use
1107         * {@link #PLAYBACK_POSITION_UNKNOWN}.
1108         * <p>
1109         * The rate is a multiple of normal playback and should be 0 when paused
1110         * and negative when rewinding. Normal playback rate is 1.0.
1111         * <p>
1112         * The state must be one of the following:
1113         * <ul>
1114         * <li> {@link PlaybackStateCompat#STATE_NONE}</li>
1115         * <li> {@link PlaybackStateCompat#STATE_STOPPED}</li>
1116         * <li> {@link PlaybackStateCompat#STATE_PLAYING}</li>
1117         * <li> {@link PlaybackStateCompat#STATE_PAUSED}</li>
1118         * <li> {@link PlaybackStateCompat#STATE_FAST_FORWARDING}</li>
1119         * <li> {@link PlaybackStateCompat#STATE_REWINDING}</li>
1120         * <li> {@link PlaybackStateCompat#STATE_BUFFERING}</li>
1121         * <li> {@link PlaybackStateCompat#STATE_ERROR}</li>
1122         * <li> {@link PlaybackStateCompat#STATE_CONNECTING}</li>
1123         * <li> {@link PlaybackStateCompat#STATE_SKIPPING_TO_PREVIOUS}</li>
1124         * <li> {@link PlaybackStateCompat#STATE_SKIPPING_TO_NEXT}</li>
1125         * <li> {@link PlaybackStateCompat#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
1126         * </ul>
1127         *
1128         * @param state The current state of playback.
1129         * @param position The position in the current track in ms.
1130         * @param playbackSpeed The current rate of playback as a multiple of
1131         *            normal playback.
1132         */
1133        public Builder setState(@State int state, long position, float playbackSpeed) {
1134            return setState(state, position, playbackSpeed, SystemClock.elapsedRealtime());
1135        }
1136
1137        /**
1138         * Set the current state of playback.
1139         * <p>
1140         * The position must be in ms and indicates the current playback
1141         * position within the track. If the position is unknown use
1142         * {@link #PLAYBACK_POSITION_UNKNOWN}.
1143         * <p>
1144         * The rate is a multiple of normal playback and should be 0 when paused
1145         * and negative when rewinding. Normal playback rate is 1.0.
1146         * <p>
1147         * The state must be one of the following:
1148         * <ul>
1149         * <li> {@link PlaybackStateCompat#STATE_NONE}</li>
1150         * <li> {@link PlaybackStateCompat#STATE_STOPPED}</li>
1151         * <li> {@link PlaybackStateCompat#STATE_PLAYING}</li>
1152         * <li> {@link PlaybackStateCompat#STATE_PAUSED}</li>
1153         * <li> {@link PlaybackStateCompat#STATE_FAST_FORWARDING}</li>
1154         * <li> {@link PlaybackStateCompat#STATE_REWINDING}</li>
1155         * <li> {@link PlaybackStateCompat#STATE_BUFFERING}</li>
1156         * <li> {@link PlaybackStateCompat#STATE_ERROR}</li>
1157         * <li> {@link PlaybackStateCompat#STATE_CONNECTING}</li>
1158         * <li> {@link PlaybackStateCompat#STATE_SKIPPING_TO_PREVIOUS}</li>
1159         * <li> {@link PlaybackStateCompat#STATE_SKIPPING_TO_NEXT}</li>
1160         * <li> {@link PlaybackStateCompat#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
1161         * </ul>
1162         *
1163         * @param state The current state of playback.
1164         * @param position The position in the current item in ms.
1165         * @param playbackSpeed The current speed of playback as a multiple of
1166         *            normal playback.
1167         * @param updateTime The time in the {@link SystemClock#elapsedRealtime}
1168         *            timebase that the position was updated at.
1169         * @return this
1170         */
1171        public Builder setState(@State int state, long position, float playbackSpeed,
1172                long updateTime) {
1173            mState = state;
1174            mPosition = position;
1175            mUpdateTime = updateTime;
1176            mRate = playbackSpeed;
1177            return this;
1178        }
1179
1180        /**
1181         * Set the current buffered position in ms. This is the farthest
1182         * playback point that can be reached from the current position using
1183         * only buffered content.
1184         *
1185         * @return this
1186         */
1187        public Builder setBufferedPosition(long bufferPosition) {
1188            mBufferedPosition = bufferPosition;
1189            return this;
1190        }
1191
1192        /**
1193         * Set the current capabilities available on this session. This should
1194         * use a bitmask of the available capabilities.
1195         * <ul>
1196         * <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_PREVIOUS}</li>
1197         * <li> {@link PlaybackStateCompat#ACTION_REWIND}</li>
1198         * <li> {@link PlaybackStateCompat#ACTION_PLAY}</li>
1199         * <li> {@link PlaybackStateCompat#ACTION_PLAY_PAUSE}</li>
1200         * <li> {@link PlaybackStateCompat#ACTION_PAUSE}</li>
1201         * <li> {@link PlaybackStateCompat#ACTION_STOP}</li>
1202         * <li> {@link PlaybackStateCompat#ACTION_FAST_FORWARD}</li>
1203         * <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_NEXT}</li>
1204         * <li> {@link PlaybackStateCompat#ACTION_SEEK_TO}</li>
1205         * <li> {@link PlaybackStateCompat#ACTION_SET_RATING}</li>
1206         * <li> {@link PlaybackStateCompat#ACTION_PLAY_FROM_MEDIA_ID}</li>
1207         * <li> {@link PlaybackStateCompat#ACTION_PLAY_FROM_SEARCH}</li>
1208         * <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_QUEUE_ITEM}</li>
1209         * <li> {@link PlaybackStateCompat#ACTION_PLAY_FROM_URI}</li>
1210         * <li> {@link PlaybackStateCompat#ACTION_PREPARE}</li>
1211         * <li> {@link PlaybackStateCompat#ACTION_PREPARE_FROM_MEDIA_ID}</li>
1212         * <li> {@link PlaybackStateCompat#ACTION_PREPARE_FROM_SEARCH}</li>
1213         * <li> {@link PlaybackStateCompat#ACTION_PREPARE_FROM_URI}</li>
1214         * <li> {@link PlaybackStateCompat#ACTION_SET_REPEAT_MODE}</li>
1215         * <li> {@link PlaybackStateCompat#ACTION_SET_SHUFFLE_MODE_ENABLED}</li>
1216         * <li> {@link PlaybackStateCompat#ACTION_SET_CAPTIONING_ENABLED}</li>
1217         * </ul>
1218         *
1219         * @return this
1220         */
1221        public Builder setActions(@Actions long capabilities) {
1222            mActions = capabilities;
1223            return this;
1224        }
1225
1226        /**
1227         * Add a custom action to the playback state. Actions can be used to
1228         * expose additional functionality to {@link MediaControllerCompat
1229         * Controllers} beyond what is offered by the standard transport
1230         * controls.
1231         * <p>
1232         * e.g. start a radio station based on the current item or skip ahead by
1233         * 30 seconds.
1234         *
1235         * @param action An identifier for this action. It can be sent back to
1236         *            the {@link MediaSessionCompat} through
1237         *            {@link MediaControllerCompat.TransportControls#sendCustomAction(String, Bundle)}.
1238         * @param name The display name for the action. If text is shown with
1239         *            the action or used for accessibility, this is what should
1240         *            be used.
1241         * @param icon The resource action of the icon that should be displayed
1242         *            for the action. The resource should be in the package of
1243         *            the {@link MediaSessionCompat}.
1244         * @return this
1245         */
1246        public Builder addCustomAction(String action, String name, int icon) {
1247            return addCustomAction(new PlaybackStateCompat.CustomAction(action, name, icon, null));
1248        }
1249
1250        /**
1251         * Add a custom action to the playback state. Actions can be used to expose additional
1252         * functionality to {@link MediaControllerCompat Controllers} beyond what is offered
1253         * by the standard transport controls.
1254         * <p>
1255         * An example of an action would be to start a radio station based on the current item
1256         * or to skip ahead by 30 seconds.
1257         *
1258         * @param customAction The custom action to add to the {@link PlaybackStateCompat}.
1259         * @return this
1260         */
1261        public Builder addCustomAction(PlaybackStateCompat.CustomAction customAction) {
1262            if (customAction == null) {
1263                throw new IllegalArgumentException(
1264                        "You may not add a null CustomAction to PlaybackStateCompat.");
1265            }
1266            mCustomActions.add(customAction);
1267            return this;
1268        }
1269
1270        /**
1271         * Set the active item in the play queue by specifying its id. The
1272         * default value is {@link MediaSessionCompat.QueueItem#UNKNOWN_ID}
1273         *
1274         * @param id The id of the active item.
1275         * @return this
1276         */
1277        public Builder setActiveQueueItemId(long id) {
1278            mActiveItemId = id;
1279            return this;
1280        }
1281
1282        /**
1283         * Set a user readable error message. This should be set when the state
1284         * is {@link PlaybackStateCompat#STATE_ERROR}.
1285         *
1286         * @return this
1287         * @deprecated Use {@link #setErrorMessage(int, CharSequence)} instead.
1288         */
1289        public Builder setErrorMessage(CharSequence errorMessage) {
1290            mErrorMessage = errorMessage;
1291            return this;
1292        }
1293
1294        /**
1295         * Set the error code with an optional user readable error message. This should be set when
1296         * the state is {@link PlaybackStateCompat#STATE_ERROR}.
1297         *
1298         * @param errorCode The errorCode to set.
1299         * @param errorMessage The user readable error message. Can be null.
1300         * @return this
1301         */
1302        public Builder setErrorMessage(@ErrorCode int errorCode, CharSequence errorMessage) {
1303            mErrorCode = errorCode;
1304            mErrorMessage = errorMessage;
1305            return this;
1306        }
1307
1308        /**
1309         * Set any custom extras to be included with the playback state.
1310         *
1311         * @param extras The extras to include.
1312         * @return this
1313         */
1314        public Builder setExtras(Bundle extras) {
1315            mExtras = extras;
1316            return this;
1317        }
1318
1319        /**
1320         * Creates the playback state object.
1321         */
1322        public PlaybackStateCompat build() {
1323            return new PlaybackStateCompat(mState, mPosition, mBufferedPosition,
1324                    mRate, mActions, mErrorCode, mErrorMessage, mUpdateTime,
1325                    mCustomActions, mActiveItemId, mExtras);
1326        }
1327    }
1328}
1329