MediaSessionStatus.java

/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.mediarouter.media;

import android.app.PendingIntent;
import android.os.Bundle;
import android.os.SystemClock;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.TimeUtils;

/**
 * Describes the playback status of a media session.
 * <p>
 * This class is part of the remote playback protocol described by the
 * {@link MediaControlIntent MediaControlIntent} class.
 * </p><p>
 * When a media session is created, it is initially in the
 * {@link #SESSION_STATE_ACTIVE active} state.  When the media session ends
 * normally, it transitions to the {@link #SESSION_STATE_ENDED ended} state.
 * If the media session is invalidated due to another session forcibly taking
 * control of the route, then it transitions to the
 * {@link #SESSION_STATE_INVALIDATED invalidated} state.
 * Refer to the documentation of each state for an explanation of its meaning.
 * </p><p>
 * To monitor session status, the application should supply a {@link PendingIntent} to use as the
 * {@link MediaControlIntent#EXTRA_SESSION_STATUS_UPDATE_RECEIVER session status update receiver}
 * for a given {@link MediaControlIntent#ACTION_START_SESSION session start request}.
 * </p><p>
 * This object is immutable once created using a {@link Builder} instance.
 * </p>
 */
public final class MediaSessionStatus {
    static final String KEY_TIMESTAMP = "timestamp";
    static final String KEY_SESSION_STATE = "sessionState";
    static final String KEY_QUEUE_PAUSED = "queuePaused";
    static final String KEY_EXTRAS = "extras";

    final Bundle mBundle;

    /**
     * Session state: Active.
     * <p>
     * Indicates that the media session is active and in control of the route.
     * </p>
     */
    public static final int SESSION_STATE_ACTIVE = 0;

    /**
     * Session state: Ended.
     * <p>
     * Indicates that the media session was ended normally using the
     * {@link MediaControlIntent#ACTION_END_SESSION end session} action.
     * </p><p>
     * A terminated media session cannot be used anymore.  To play more media, the
     * application must start a new session.
     * </p>
     */
    public static final int SESSION_STATE_ENDED = 1;

    /**
     * Session state: Invalidated.
     * <p>
     * Indicates that the media session was invalidated involuntarily due to
     * another session taking control of the route.
     * </p><p>
     * An invalidated media session cannot be used anymore.  To play more media, the
     * application must start a new session.
     * </p>
     */
    public static final int SESSION_STATE_INVALIDATED = 2;

    MediaSessionStatus(Bundle bundle) {
        mBundle = bundle;
    }

    /**
     * Gets the timestamp associated with the status information in
     * milliseconds since boot in the {@link SystemClock#elapsedRealtime} time base.
     *
     * @return The status timestamp in the {@link SystemClock#elapsedRealtime()} time base.
     */
    public long getTimestamp() {
        return mBundle.getLong(KEY_TIMESTAMP);
    }

    /**
     * Gets the session state.
     *
     * @return The session state.  One of {@link #SESSION_STATE_ACTIVE},
     * {@link #SESSION_STATE_ENDED}, or {@link #SESSION_STATE_INVALIDATED}.
     */
    public int getSessionState() {
        return mBundle.getInt(KEY_SESSION_STATE, SESSION_STATE_INVALIDATED);
    }

    /**
     * Returns true if the session's queue is paused.
     *
     * @return True if the session's queue is paused.
     */
    public boolean isQueuePaused() {
        return mBundle.getBoolean(KEY_QUEUE_PAUSED);
    }

    /**
     * Gets a bundle of extras for this status object.
     * The extras will be ignored by the media router but they may be used
     * by applications.
     */
    @Nullable
    public Bundle getExtras() {
        return mBundle.getBundle(KEY_EXTRAS);
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder();
        result.append("MediaSessionStatus{ ");
        result.append("timestamp=");
        TimeUtils.formatDuration(SystemClock.elapsedRealtime() - getTimestamp(), result);
        result.append(" ms ago");
        result.append(", sessionState=").append(sessionStateToString(getSessionState()));
        result.append(", queuePaused=").append(isQueuePaused());
        result.append(", extras=").append(getExtras());
        result.append(" }");
        return result.toString();
    }

    private static String sessionStateToString(int sessionState) {
        switch (sessionState) {
            case SESSION_STATE_ACTIVE:
                return "active";
            case SESSION_STATE_ENDED:
                return "ended";
            case SESSION_STATE_INVALIDATED:
                return "invalidated";
        }
        return Integer.toString(sessionState);
    }

    /**
     * Converts this object to a bundle for serialization.
     *
     * @return The contents of the object represented as a bundle.
     */
    @NonNull
    public Bundle asBundle() {
        return mBundle;
    }

    /**
     * Creates an instance from a bundle.
     *
     * @param bundle The bundle, or null if none.
     * @return The new instance, or null if the bundle was null.
     */
    @Nullable
    public static MediaSessionStatus fromBundle(@Nullable Bundle bundle) {
        return bundle != null ? new MediaSessionStatus(bundle) : null;
    }

    /**
     * Builder for {@link MediaSessionStatus media session status objects}.
     */
    public static final class Builder {
        private final Bundle mBundle;

        /**
         * Creates a media session status builder using the current time as the
         * reference timestamp.
         *
         * @param sessionState The session state.
         */
        public Builder(int sessionState) {
            mBundle = new Bundle();
            setTimestamp(SystemClock.elapsedRealtime());
            setSessionState(sessionState);
        }

        /**
         * Creates a media session status builder whose initial contents are
         * copied from an existing status.
         */
        public Builder(@NonNull MediaSessionStatus status) {
            if (status == null) {
                throw new IllegalArgumentException("status must not be null");
            }

            mBundle = new Bundle(status.mBundle);
        }

        /**
         * Sets the timestamp associated with the status information in
         * milliseconds since boot in the {@link SystemClock#elapsedRealtime} time base.
         */
        @NonNull
        public Builder setTimestamp(long elapsedRealtimeTimestamp) {
            mBundle.putLong(KEY_TIMESTAMP, elapsedRealtimeTimestamp);
            return this;
        }

        /**
         * Sets the session state.
         */
        @NonNull
        public Builder setSessionState(int sessionState) {
            mBundle.putInt(KEY_SESSION_STATE, sessionState);
            return this;
        }

        /**
         * Sets whether the queue is paused.
         */
        @NonNull
        public Builder setQueuePaused(boolean queuePaused) {
            mBundle.putBoolean(KEY_QUEUE_PAUSED, queuePaused);
            return this;
        }

        /**
         * Sets a bundle of extras for this status object.
         * The extras will be ignored by the media router but they may be used
         * by applications.
         */
        @NonNull
        public Builder setExtras(@Nullable Bundle extras) {
            if (extras == null) {
                mBundle.putBundle(KEY_EXTRAS, null);
            } else {
                mBundle.putBundle(KEY_EXTRAS, new Bundle(extras));
            }
            return this;
        }

        /**
         * Builds the {@link MediaSessionStatus media session status object}.
         */
        @NonNull
        public MediaSessionStatus build() {
            return new MediaSessionStatus(mBundle);
        }
    }
}