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
18import android.os.Build;
19import android.os.Bundle;
20import android.os.Parcel;
21import android.os.Parcelable;
22import android.os.SystemClock;
23import android.text.TextUtils;
24
25/**
26 * Playback state for a {@link MediaSessionCompat}. This includes a state like
27 * {@link PlaybackStateCompat#STATE_PLAYING}, the current playback position,
28 * and the current control capabilities.
29 */
30public final class PlaybackStateCompat implements Parcelable {
31
32    /**
33     * Indicates this session supports the stop command.
34     *
35     * @see Builder#setActions(long)
36     */
37    public static final long ACTION_STOP = 1 << 0;
38
39    /**
40     * Indicates this session supports the pause command.
41     *
42     * @see Builder#setActions(long)
43     */
44    public static final long ACTION_PAUSE = 1 << 1;
45
46    /**
47     * Indicates this session supports the play command.
48     *
49     * @see Builder#setActions(long)
50     */
51    public static final long ACTION_PLAY = 1 << 2;
52
53    /**
54     * Indicates this session supports the rewind command.
55     *
56     * @see Builder#setActions(long)
57     */
58    public static final long ACTION_REWIND = 1 << 3;
59
60    /**
61     * Indicates this session supports the previous command.
62     *
63     * @see Builder#setActions(long)
64     */
65    public static final long ACTION_SKIP_TO_PREVIOUS = 1 << 4;
66
67    /**
68     * Indicates this session supports the next command.
69     *
70     * @see Builder#setActions(long)
71     */
72    public static final long ACTION_SKIP_TO_NEXT = 1 << 5;
73
74    /**
75     * Indicates this session supports the fast forward command.
76     *
77     * @see Builder#setActions(long)
78     */
79    public static final long ACTION_FAST_FORWARD = 1 << 6;
80
81    /**
82     * Indicates this session supports the set rating command.
83     *
84     * @see Builder#setActions(long)
85     */
86    public static final long ACTION_SET_RATING = 1 << 7;
87
88    /**
89     * Indicates this session supports the seek to command.
90     *
91     * @see Builder#setActions(long)
92     */
93    public static final long ACTION_SEEK_TO = 1 << 8;
94
95    /**
96     * Indicates this session supports the play/pause toggle command.
97     *
98     * @see Builder#setActions(long)
99     */
100    public static final long ACTION_PLAY_PAUSE = 1 << 9;
101
102    /**
103     * Indicates this session supports the play from media id command.
104     *
105     * @see Builder#setActions(long)
106     */
107    public static final long ACTION_PLAY_FROM_MEDIA_ID = 1 << 10;
108
109    /**
110     * Indicates this session supports the play from search command.
111     *
112     * @see Builder#setActions(long)
113     */
114    public static final long ACTION_PLAY_FROM_SEARCH = 1 << 11;
115
116    /**
117     * Indicates this session supports the skip to queue item command.
118     *
119     * @see Builder#setActions(long)
120     */
121    public static final long ACTION_SKIP_TO_QUEUE_ITEM = 1 << 12;
122
123    /**
124     * This is the default playback state and indicates that no media has been
125     * added yet, or the performer has been reset and has no content to play.
126     *
127     * @see Builder#setState
128     */
129    public final static int STATE_NONE = 0;
130
131    /**
132     * State indicating this item is currently stopped.
133     *
134     * @see Builder#setState
135     */
136    public final static int STATE_STOPPED = 1;
137
138    /**
139     * State indicating this item is currently paused.
140     *
141     * @see Builder#setState
142     */
143    public final static int STATE_PAUSED = 2;
144
145    /**
146     * State indicating this item is currently playing.
147     *
148     * @see Builder#setState
149     */
150    public final static int STATE_PLAYING = 3;
151
152    /**
153     * State indicating this item is currently fast forwarding.
154     *
155     * @see Builder#setState
156     */
157    public final static int STATE_FAST_FORWARDING = 4;
158
159    /**
160     * State indicating this item is currently rewinding.
161     *
162     * @see Builder#setState
163     */
164    public final static int STATE_REWINDING = 5;
165
166    /**
167     * State indicating this item is currently buffering and will begin playing
168     * when enough data has buffered.
169     *
170     * @see Builder#setState
171     */
172    public final static int STATE_BUFFERING = 6;
173
174    /**
175     * State indicating this item is currently in an error state. The error
176     * message should also be set when entering this state.
177     *
178     * @see Builder#setState
179     */
180    public final static int STATE_ERROR = 7;
181
182    /**
183     * State indicating the class doing playback is currently connecting to a
184     * route. Depending on the implementation you may return to the previous
185     * state when the connection finishes or enter {@link #STATE_NONE}. If
186     * the connection failed {@link #STATE_ERROR} should be used.
187     * @hide
188     */
189    public final static int STATE_CONNECTING = 8;
190
191    /**
192     * State indicating the player is currently skipping to the previous item.
193     *
194     * @see Builder#setState
195     */
196    public final static int STATE_SKIPPING_TO_PREVIOUS = 9;
197
198    /**
199     * State indicating the player is currently skipping to the next item.
200     *
201     * @see Builder#setState
202     */
203    public final static int STATE_SKIPPING_TO_NEXT = 10;
204
205    /**
206     * Use this value for the position to indicate the position is not known.
207     */
208    public final static long PLAYBACK_POSITION_UNKNOWN = -1;
209
210    private final int mState;
211    private final long mPosition;
212    private final long mBufferedPosition;
213    private final float mSpeed;
214    private final long mActions;
215    private final CharSequence mErrorMessage;
216    private final long mUpdateTime;
217
218    private Object mStateObj;
219
220    private PlaybackStateCompat(int state, long position, long bufferedPosition,
221            float rate, long actions, CharSequence errorMessage, long updateTime) {
222        mState = state;
223        mPosition = position;
224        mBufferedPosition = bufferedPosition;
225        mSpeed = rate;
226        mActions = actions;
227        mErrorMessage = errorMessage;
228        mUpdateTime = updateTime;
229    }
230
231    private PlaybackStateCompat(Parcel in) {
232        mState = in.readInt();
233        mPosition = in.readLong();
234        mSpeed = in.readFloat();
235        mUpdateTime = in.readLong();
236        mBufferedPosition = in.readLong();
237        mActions = in.readLong();
238        mErrorMessage = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
239    }
240
241    @Override
242    public String toString() {
243        StringBuilder bob = new StringBuilder("PlaybackState {");
244        bob.append("state=").append(mState);
245        bob.append(", position=").append(mPosition);
246        bob.append(", buffered position=").append(mBufferedPosition);
247        bob.append(", speed=").append(mSpeed);
248        bob.append(", updated=").append(mUpdateTime);
249        bob.append(", actions=").append(mActions);
250        bob.append(", error=").append(mErrorMessage);
251        bob.append("}");
252        return bob.toString();
253    }
254
255    @Override
256    public int describeContents() {
257        return 0;
258    }
259
260    @Override
261    public void writeToParcel(Parcel dest, int flags) {
262        dest.writeInt(mState);
263        dest.writeLong(mPosition);
264        dest.writeFloat(mSpeed);
265        dest.writeLong(mUpdateTime);
266        dest.writeLong(mBufferedPosition);
267        dest.writeLong(mActions);
268        TextUtils.writeToParcel(mErrorMessage, dest, flags);
269    }
270
271    /**
272     * Get the current state of playback. One of the following:
273     * <ul>
274     * <li> {@link PlaybackStateCompat#STATE_NONE}</li>
275     * <li> {@link PlaybackStateCompat#STATE_STOPPED}</li>
276     * <li> {@link PlaybackStateCompat#STATE_PLAYING}</li>
277     * <li> {@link PlaybackStateCompat#STATE_PAUSED}</li>
278     * <li> {@link PlaybackStateCompat#STATE_FAST_FORWARDING}</li>
279     * <li> {@link PlaybackStateCompat#STATE_REWINDING}</li>
280     * <li> {@link PlaybackStateCompat#STATE_BUFFERING}</li>
281     * <li> {@link PlaybackStateCompat#STATE_ERROR}</li>
282     */
283    public int getState() {
284        return mState;
285    }
286
287    /**
288     * Get the current playback position in ms.
289     */
290    public long getPosition() {
291        return mPosition;
292    }
293
294    /**
295     * Get the current buffered position in ms. This is the farthest playback
296     * point that can be reached from the current position using only buffered
297     * content.
298     */
299    public long getBufferedPosition() {
300        return mBufferedPosition;
301    }
302
303    /**
304     * Get the current playback speed as a multiple of normal playback. This
305     * should be negative when rewinding. A value of 1 means normal playback and
306     * 0 means paused.
307     *
308     * @return The current speed of playback.
309     */
310    public float getPlaybackSpeed() {
311        return mSpeed;
312    }
313
314    /**
315     * Get the current actions available on this session. This should use a
316     * bitmask of the available actions.
317     * <ul>
318     * <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_PREVIOUS}</li>
319     * <li> {@link PlaybackStateCompat#ACTION_REWIND}</li>
320     * <li> {@link PlaybackStateCompat#ACTION_PLAY}</li>
321     * <li> {@link PlaybackStateCompat#ACTION_PAUSE}</li>
322     * <li> {@link PlaybackStateCompat#ACTION_STOP}</li>
323     * <li> {@link PlaybackStateCompat#ACTION_FAST_FORWARD}</li>
324     * <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_NEXT}</li>
325     * <li> {@link PlaybackStateCompat#ACTION_SEEK_TO}</li>
326     * <li> {@link PlaybackStateCompat#ACTION_SET_RATING}</li>
327     * </ul>
328     */
329    public long getActions() {
330        return mActions;
331    }
332
333    /**
334     * Get a user readable error message. This should be set when the state is
335     * {@link PlaybackStateCompat#STATE_ERROR}.
336     */
337    public CharSequence getErrorMessage() {
338        return mErrorMessage;
339    }
340
341    /**
342     * Get the elapsed real time at which position was last updated. If the
343     * position has never been set this will return 0;
344     *
345     * @return The last time the position was updated.
346     */
347    public long getLastPositionUpdateTime() {
348        return mUpdateTime;
349    }
350
351    /**
352     * Creates an instance from a framework {@link android.media.session.PlaybackState} object.
353     * <p>
354     * This method is only supported on API 21+.
355     * </p>
356     *
357     * @param stateObj A {@link android.media.session.PlaybackState} object, or null if none.
358     * @return An equivalent {@link PlaybackStateCompat} object, or null if none.
359     */
360    public static PlaybackStateCompat fromPlaybackState(Object stateObj) {
361        if (stateObj == null || Build.VERSION.SDK_INT < 21) {
362            return null;
363        }
364
365        PlaybackStateCompat state = new PlaybackStateCompat(
366                PlaybackStateCompatApi21.getState(stateObj),
367                PlaybackStateCompatApi21.getPosition(stateObj),
368                PlaybackStateCompatApi21.getBufferedPosition(stateObj),
369                PlaybackStateCompatApi21.getPlaybackSpeed(stateObj),
370                PlaybackStateCompatApi21.getActions(stateObj),
371                PlaybackStateCompatApi21.getErrorMessage(stateObj),
372                PlaybackStateCompatApi21.getLastPositionUpdateTime(stateObj));
373        state.mStateObj = stateObj;
374        return state;
375    }
376
377    /**
378     * Gets the underlying framework {@link android.media.session.PlaybackState} object.
379     * <p>
380     * This method is only supported on API 21+.
381     * </p>
382     *
383     * @return An equivalent {@link android.media.session.PlaybackState} object, or null if none.
384     */
385    public Object getPlaybackState() {
386        if (mStateObj != null || Build.VERSION.SDK_INT < 21) {
387            return mStateObj;
388        }
389
390        mStateObj = PlaybackStateCompatApi21.newInstance(mState, mPosition, mBufferedPosition,
391                mSpeed, mActions, mErrorMessage, mUpdateTime);
392        return mStateObj;
393    }
394
395    public static final Parcelable.Creator<PlaybackStateCompat> CREATOR =
396            new Parcelable.Creator<PlaybackStateCompat>() {
397        @Override
398        public PlaybackStateCompat createFromParcel(Parcel in) {
399            return new PlaybackStateCompat(in);
400        }
401
402        @Override
403        public PlaybackStateCompat[] newArray(int size) {
404            return new PlaybackStateCompat[size];
405        }
406    };
407
408    /**
409     * {@link PlaybackStateCompat.CustomAction CustomActions} can be used to
410     * extend the capabilities of the standard transport controls by exposing
411     * app specific actions to {@link MediaControllerCompat Controllers}.
412     */
413    public static final class CustomAction implements Parcelable {
414        private final String mAction;
415        private final CharSequence mName;
416        private final int mIcon;
417        private final Bundle mExtras;
418
419        /**
420         * Use {@link PlaybackStateCompat.CustomAction.Builder#build()}.
421         */
422        private CustomAction(String action, CharSequence name, int icon, Bundle extras) {
423            mAction = action;
424            mName = name;
425            mIcon = icon;
426            mExtras = extras;
427        }
428
429        private CustomAction(Parcel in) {
430            mAction = in.readString();
431            mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
432            mIcon = in.readInt();
433            mExtras = in.readBundle();
434        }
435
436        @Override
437        public void writeToParcel(Parcel dest, int flags) {
438            dest.writeString(mAction);
439            TextUtils.writeToParcel(mName, dest, flags);
440            dest.writeInt(mIcon);
441            dest.writeBundle(mExtras);
442        }
443
444        @Override
445        public int describeContents() {
446            return 0;
447        }
448
449        public static final Parcelable.Creator<PlaybackStateCompat.CustomAction> CREATOR
450                = new Parcelable.Creator<PlaybackStateCompat.CustomAction>() {
451
452                    @Override
453                    public PlaybackStateCompat.CustomAction createFromParcel(Parcel p) {
454                        return new PlaybackStateCompat.CustomAction(p);
455                    }
456
457                    @Override
458                    public PlaybackStateCompat.CustomAction[] newArray(int size) {
459                        return new PlaybackStateCompat.CustomAction[size];
460                    }
461                };
462
463        /**
464         * Returns the action of the {@link CustomAction}.
465         *
466         * @return The action of the {@link CustomAction}.
467         */
468        public String getAction() {
469            return mAction;
470        }
471
472        /**
473         * Returns the display name of this action. e.g. "Favorite"
474         *
475         * @return The display name of this {@link CustomAction}.
476         */
477        public CharSequence getName() {
478            return mName;
479        }
480
481        /**
482         * Returns the resource id of the icon in the {@link MediaSessionCompat
483         * Session's} package.
484         *
485         * @return The resource id of the icon in the {@link MediaSessionCompat
486         *         Session's} package.
487         */
488        public int getIcon() {
489            return mIcon;
490        }
491
492        /**
493         * Returns extras which provide additional application-specific
494         * information about the action, or null if none. These arguments are
495         * meant to be consumed by a {@link MediaControllerCompat} if it knows
496         * how to handle them.
497         *
498         * @return Optional arguments for the {@link CustomAction}.
499         */
500        public Bundle getExtras() {
501            return mExtras;
502        }
503
504        @Override
505        public String toString() {
506            return "Action:" +
507                    "mName='" + mName +
508                    ", mIcon=" + mIcon +
509                    ", mExtras=" + mExtras;
510        }
511
512        /**
513         * Builder for {@link CustomAction} objects.
514         */
515        public static final class Builder {
516            private final String mAction;
517            private final CharSequence mName;
518            private final int mIcon;
519            private Bundle mExtras;
520
521            /**
522             * Creates a {@link CustomAction} builder with the id, name, and
523             * icon set.
524             *
525             * @param action The action of the {@link CustomAction}.
526             * @param name The display name of the {@link CustomAction}. This
527             *            name will be displayed along side the action if the UI
528             *            supports it.
529             * @param icon The icon resource id of the {@link CustomAction}.
530             *            This resource id must be in the same package as the
531             *            {@link MediaSessionCompat}. It will be displayed with
532             *            the custom action if the UI supports it.
533             */
534            public Builder(String action, CharSequence name, int icon) {
535                if (TextUtils.isEmpty(action)) {
536                    throw new IllegalArgumentException(
537                            "You must specify an action to build a CustomAction.");
538                }
539                if (TextUtils.isEmpty(name)) {
540                    throw new IllegalArgumentException(
541                            "You must specify a name to build a CustomAction.");
542                }
543                if (icon == 0) {
544                    throw new IllegalArgumentException(
545                            "You must specify an icon resource id to build a CustomAction.");
546                }
547                mAction = action;
548                mName = name;
549                mIcon = icon;
550            }
551
552            /**
553             * Set optional extras for the {@link CustomAction}. These extras
554             * are meant to be consumed by a {@link MediaControllerCompat} if it
555             * knows how to handle them. Keys should be fully qualified (e.g.
556             * "com.example.MY_ARG") to avoid collisions.
557             *
558             * @param extras Optional extras for the {@link CustomAction}.
559             * @return this.
560             */
561            public Builder setExtras(Bundle extras) {
562                mExtras = extras;
563                return this;
564            }
565
566            /**
567             * Build and return the {@link CustomAction} instance with the
568             * specified values.
569             *
570             * @return A new {@link CustomAction} instance.
571             */
572            public CustomAction build() {
573                return new CustomAction(mAction, mName, mIcon, mExtras);
574            }
575        }
576    }
577
578    /**
579     * Builder for {@link PlaybackStateCompat} objects.
580     */
581    public static final class Builder {
582        private int mState;
583        private long mPosition;
584        private long mBufferedPosition;
585        private float mRate;
586        private long mActions;
587        private CharSequence mErrorMessage;
588        private long mUpdateTime;
589
590        /**
591         * Create an empty Builder.
592         */
593        public Builder() {
594        }
595
596        /**
597         * Create a Builder using a {@link PlaybackStateCompat} instance to set the
598         * initial values.
599         *
600         * @param source The playback state to copy.
601         */
602        public Builder(PlaybackStateCompat source) {
603            mState = source.mState;
604            mPosition = source.mPosition;
605            mRate = source.mSpeed;
606            mUpdateTime = source.mUpdateTime;
607            mBufferedPosition = source.mBufferedPosition;
608            mActions = source.mActions;
609            mErrorMessage = source.mErrorMessage;
610        }
611
612        /**
613         * Set the current state of playback.
614         * <p>
615         * The position must be in ms and indicates the current playback
616         * position within the track. If the position is unknown use
617         * {@link #PLAYBACK_POSITION_UNKNOWN}.
618         * <p>
619         * The rate is a multiple of normal playback and should be 0 when paused
620         * and negative when rewinding. Normal playback rate is 1.0.
621         * <p>
622         * The state must be one of the following:
623         * <ul>
624         * <li> {@link PlaybackStateCompat#STATE_NONE}</li>
625         * <li> {@link PlaybackStateCompat#STATE_STOPPED}</li>
626         * <li> {@link PlaybackStateCompat#STATE_PLAYING}</li>
627         * <li> {@link PlaybackStateCompat#STATE_PAUSED}</li>
628         * <li> {@link PlaybackStateCompat#STATE_FAST_FORWARDING}</li>
629         * <li> {@link PlaybackStateCompat#STATE_REWINDING}</li>
630         * <li> {@link PlaybackStateCompat#STATE_BUFFERING}</li>
631         * <li> {@link PlaybackStateCompat#STATE_ERROR}</li>
632         * </ul>
633         *
634         * @param state The current state of playback.
635         * @param position The position in the current track in ms.
636         * @param playbackSpeed The current rate of playback as a multiple of
637         *            normal playback.
638         */
639        public Builder setState(int state, long position, float playbackSpeed) {
640            return setState(state, position, playbackSpeed, SystemClock.elapsedRealtime());
641        }
642
643        /**
644         * Set the current state of playback.
645         * <p>
646         * The position must be in ms and indicates the current playback
647         * position within the track. If the position is unknown use
648         * {@link #PLAYBACK_POSITION_UNKNOWN}.
649         * <p>
650         * The rate is a multiple of normal playback and should be 0 when paused
651         * and negative when rewinding. Normal playback rate is 1.0.
652         * <p>
653         * The state must be one of the following:
654         * <ul>
655         * <li> {@link PlaybackStateCompat#STATE_NONE}</li>
656         * <li> {@link PlaybackStateCompat#STATE_STOPPED}</li>
657         * <li> {@link PlaybackStateCompat#STATE_PLAYING}</li>
658         * <li> {@link PlaybackStateCompat#STATE_PAUSED}</li>
659         * <li> {@link PlaybackStateCompat#STATE_FAST_FORWARDING}</li>
660         * <li> {@link PlaybackStateCompat#STATE_REWINDING}</li>
661         * <li> {@link PlaybackStateCompat#STATE_BUFFERING}</li>
662         * <li> {@link PlaybackStateCompat#STATE_ERROR}</li>
663         * </ul>
664         *
665         * @param state The current state of playback.
666         * @param position The position in the current item in ms.
667         * @param playbackSpeed The current speed of playback as a multiple of
668         *            normal playback.
669         * @param updateTime The time in the {@link SystemClock#elapsedRealtime}
670         *            timebase that the position was updated at.
671         * @return this
672         */
673        public Builder setState(int state, long position, float playbackSpeed, long updateTime) {
674            mState = state;
675            mPosition = position;
676            mUpdateTime = updateTime;
677            mRate = playbackSpeed;
678            return this;
679        }
680
681        /**
682         * Set the current buffered position in ms. This is the farthest
683         * playback point that can be reached from the current position using
684         * only buffered content.
685         *
686         * @return this
687         */
688        public Builder setBufferedPosition(long bufferPosition) {
689            mBufferedPosition = bufferPosition;
690            return this;
691        }
692
693        /**
694         * Set the current capabilities available on this session. This should
695         * use a bitmask of the available capabilities.
696         * <ul>
697         * <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_PREVIOUS}</li>
698         * <li> {@link PlaybackStateCompat#ACTION_REWIND}</li>
699         * <li> {@link PlaybackStateCompat#ACTION_PLAY}</li>
700         * <li> {@link PlaybackStateCompat#ACTION_PAUSE}</li>
701         * <li> {@link PlaybackStateCompat#ACTION_STOP}</li>
702         * <li> {@link PlaybackStateCompat#ACTION_FAST_FORWARD}</li>
703         * <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_NEXT}</li>
704         * <li> {@link PlaybackStateCompat#ACTION_SEEK_TO}</li>
705         * <li> {@link PlaybackStateCompat#ACTION_SET_RATING}</li>
706         * </ul>
707         *
708         * @return this
709         */
710        public Builder setActions(long capabilities) {
711            mActions = capabilities;
712            return this;
713        }
714
715        /**
716         * Set a user readable error message. This should be set when the state
717         * is {@link PlaybackStateCompat#STATE_ERROR}.
718         *
719         * @return this
720         */
721        public Builder setErrorMessage(CharSequence errorMessage) {
722            mErrorMessage = errorMessage;
723            return this;
724        }
725
726        /**
727         * Creates the playback state object.
728         */
729        public PlaybackStateCompat build() {
730            return new PlaybackStateCompat(mState, mPosition, mBufferedPosition,
731                    mRate, mActions, mErrorMessage, mUpdateTime);
732        }
733    }
734}
735