SessionCommandGroup2.java

/*
 * Copyright 2018 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.media2;

import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import static androidx.media2.SessionCommand2.COMMAND_CODE_CUSTOM;

import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.versionedparcelable.ParcelField;
import androidx.versionedparcelable.VersionedParcelable;
import androidx.versionedparcelable.VersionedParcelize;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * A set of {@link SessionCommand2} which represents a command group.
 */
@VersionedParcelize
public final class SessionCommandGroup2 implements VersionedParcelable {
    private static final String TAG = "SessionCommandGroup2";
    private static final String KEY_COMMANDS = "android.media.session2.commandgroup.commands";

    @ParcelField(1)
    List<SessionCommand2> mCommands = new ArrayList<>();

    /**
     * Default Constructor.
     */
    public SessionCommandGroup2() { }

    /**
     * Creates a new SessionCommandGroup2 with commands copied from another object.
     *
     * @param commands The collection of commands to copy.
     */
    public SessionCommandGroup2(@Nullable Collection<SessionCommand2> commands) {
        if (commands != null) {
            mCommands.addAll(commands);
        }
    }

    /**
     * Adds a command to this command group.
     *
     * @param command A command to add. Shouldn't be {@code null}.
     * @hide TODO remove this method
     */
    public void addCommand(@NonNull SessionCommand2 command) {
        if (command == null) {
            throw new IllegalArgumentException("command shouldn't be null");
        }
        if (!hasCommand(command)) {
            mCommands.add(command);
        }
    }

    /**
     * Adds a predefined command with given {@code commandCode} to this command group.
     *
     * @param commandCode A command code to add.
     *                    Shouldn't be {@link SessionCommand2#COMMAND_CODE_CUSTOM}.
     * @hide TODO remove this method
     */
    public void addCommand(int commandCode) {
        if (commandCode == COMMAND_CODE_CUSTOM) {
            throw new IllegalArgumentException(
                    "Use addCommand(SessionCommand2) for COMMAND_CODE_CUSTOM.");
        }
        if (!hasCommand(commandCode)) {
            mCommands.add(new SessionCommand2(commandCode));
        }
    }

    /**
     * Checks whether this command group has a command that matches given {@code command}.
     *
     * @param command A command to find. Shouldn't be {@code null}.
     */
    public boolean hasCommand(@NonNull SessionCommand2 command) {
        if (command == null) {
            throw new IllegalArgumentException("command shouldn't be null");
        }
        return mCommands.contains(command);
    }

    /**
     * Checks whether this command group has a command that matches given {@code commandCode}.
     *
     * @param commandCode A command code to find.
     *                    Shouldn't be {@link SessionCommand2#COMMAND_CODE_CUSTOM}.
     */
    public boolean hasCommand(int commandCode) {
        if (commandCode == COMMAND_CODE_CUSTOM) {
            throw new IllegalArgumentException("Use hasCommand(Command) for custom command");
        }
        for (SessionCommand2 command : mCommands) {
            if (command.getCommandCode() == commandCode) {
                return true;
            }
        }
        return false;
    }

    /**
     * Gets all commands of this command group.
     */
    public @NonNull Set<SessionCommand2> getCommands() {
        return new HashSet<>(mCommands);
    }

    /**
     * @return A new {@link Bundle} instance from the SessionCommandGroup2.
     * @hide
     */
    @RestrictTo(LIBRARY_GROUP)
    public @NonNull Bundle toBundle() {
        ArrayList<Bundle> list = new ArrayList<>();
        for (SessionCommand2 command : mCommands) {
            list.add(command.toBundle());
        }
        Bundle bundle = new Bundle();
        bundle.putParcelableArrayList(KEY_COMMANDS, list);
        return bundle;
    }

    /**
     * @return A new {@link SessionCommandGroup2} instance from the bundle.
     * @hide
     */
    @RestrictTo(LIBRARY_GROUP)
    public static @Nullable SessionCommandGroup2 fromBundle(Bundle commands) {
        if (commands == null) {
            return null;
        }
        List<Parcelable> list = commands.getParcelableArrayList(KEY_COMMANDS);
        if (list == null) {
            return null;
        }
        SessionCommandGroup2 commandGroup = new SessionCommandGroup2();
        for (int i = 0; i < list.size(); i++) {
            Parcelable parcelable = list.get(i);
            if (!(parcelable instanceof Bundle)) {
                continue;
            }
            Bundle commandBundle = (Bundle) parcelable;
            SessionCommand2 command = SessionCommand2.fromBundle(commandBundle);
            if (command != null) {
                commandGroup.addCommand(command);
            }
        }
        return commandGroup;
    }

    /**
     * Builds a {@link SessionCommandGroup2} object.
     */
    public static final class Builder {
        private Set<SessionCommand2> mCommands;

        public Builder() {
            mCommands = new HashSet<>();
        }

        /**
         * Creates a new builder for {@link SessionCommandGroup2} with commands copied from another
         * {@link SessionCommandGroup2} object.
         * @param commandGroup
         */
        public Builder(SessionCommandGroup2 commandGroup) {
            mCommands = commandGroup.getCommands();
        }

        /**
         * Adds a command to this command group.
         *
         * @param command A command to add. Shouldn't be {@code null}.
         */
        public @NonNull Builder addCommand(SessionCommand2 command) {
            if (command == null) {
                throw new IllegalArgumentException("command shouldn't be null");
            }
            mCommands.add(command);
            return this;
        }

        /**
         * Adds a predefined command with given {@code commandCode} to this command group.
         *
         * @param commandCode A command code to add.
         *                    Shouldn't be {@link SessionCommand2#COMMAND_CODE_CUSTOM}.
         */
        public @NonNull Builder addCommand(int commandCode) {
            if (commandCode == COMMAND_CODE_CUSTOM) {
                throw new IllegalArgumentException(
                        "Use addCommand(SessionCommand2) for COMMAND_CODE_CUSTOM.");
            }
            mCommands.add(new SessionCommand2(commandCode));
            return this;
        }

        /**
         * Adds all predefined commands to this command group.
         */
        public @NonNull Builder addAllPredefinedCommands() {
            addAllPlaybackCommands();
            addAllPlaylistCommands();
            addAllVolumeCommands();
            addAllSessionCommands();
            addAllLibraryCommands();
            return this;
        }

        /**
         * Removes a command from this group which matches given {@code command}.
         *
         * @param command A command to find. Shouldn't be {@code null}.
         */
        public @NonNull Builder removeCommand(@NonNull SessionCommand2 command) {
            if (command == null) {
                throw new IllegalArgumentException("command shouldn't be null");
            }
            mCommands.remove(command);
            return this;
        }

        /**
         * Removes a command from this group which matches given {@code commandCode}.
         *
         * @param commandCode A command code to find.
         *                    Shouldn't be {@link SessionCommand2#COMMAND_CODE_CUSTOM}.
         */
        public @NonNull Builder removeCommand(int commandCode) {
            if (commandCode == COMMAND_CODE_CUSTOM) {
                throw new IllegalArgumentException("commandCode shouldn't be COMMAND_CODE_CUSTOM");
            }
            mCommands.remove(new SessionCommand2(commandCode));
            return this;
        }

        @NonNull Builder addAllPlaybackCommands() {
            addCommand(SessionCommand2.COMMAND_CODE_PLAYBACK_PAUSE);
            addCommand(SessionCommand2.COMMAND_CODE_PLAYBACK_PLAY);
            addCommand(SessionCommand2.COMMAND_CODE_PLAYBACK_PREPARE);
            addCommand(SessionCommand2.COMMAND_CODE_PLAYBACK_RESET);
            addCommand(SessionCommand2.COMMAND_CODE_PLAYBACK_SEEK_TO);
            addCommand(SessionCommand2.COMMAND_CODE_PLAYBACK_SET_SPEED);
            return this;
        }

        @NonNull Builder addAllPlaylistCommands() {
            addCommand(SessionCommand2.COMMAND_CODE_PLAYLIST_ADD_ITEM);
            addCommand(SessionCommand2.COMMAND_CODE_PLAYLIST_GET_CURRENT_MEDIA_ITEM);
            addCommand(SessionCommand2.COMMAND_CODE_PLAYLIST_GET_LIST);
            addCommand(SessionCommand2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA);
            addCommand(SessionCommand2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM);
            addCommand(SessionCommand2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM);
            addCommand(SessionCommand2.COMMAND_CODE_PLAYLIST_SET_LIST);
            addCommand(SessionCommand2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA);
            addCommand(SessionCommand2.COMMAND_CODE_PLAYLIST_SET_REPEAT_MODE);
            addCommand(SessionCommand2.COMMAND_CODE_PLAYLIST_SET_SHUFFLE_MODE);
            addCommand(SessionCommand2.COMMAND_CODE_PLAYLIST_SKIP_TO_NEXT_ITEM);
            addCommand(SessionCommand2.COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM);
            addCommand(SessionCommand2.COMMAND_CODE_PLAYLIST_SKIP_TO_PREV_ITEM);
            return this;
        }

        @NonNull Builder addAllVolumeCommands() {
            addCommand(SessionCommand2.COMMAND_CODE_VOLUME_ADJUST_VOLUME);
            addCommand(SessionCommand2.COMMAND_CODE_VOLUME_SET_VOLUME);
            return this;
        }

        @NonNull Builder addAllSessionCommands() {
            addCommand(SessionCommand2.COMMAND_CODE_SESSION_FAST_FORWARD);
            addCommand(SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID);
            addCommand(SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_SEARCH);
            addCommand(SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_URI);
            addCommand(SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID);
            addCommand(SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH);
            addCommand(SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_URI);
            addCommand(SessionCommand2.COMMAND_CODE_SESSION_REWIND);
            addCommand(SessionCommand2.COMMAND_CODE_SESSION_SELECT_ROUTE);
            addCommand(SessionCommand2.COMMAND_CODE_SESSION_SET_RATING);
            addCommand(SessionCommand2.COMMAND_CODE_SESSION_SUBSCRIBE_ROUTES_INFO);
            addCommand(SessionCommand2.COMMAND_CODE_SESSION_UNSUBSCRIBE_ROUTES_INFO);
            return this;
        }

        @NonNull Builder addAllLibraryCommands() {
            addCommand(SessionCommand2.COMMAND_CODE_LIBRARY_GET_CHILDREN);
            addCommand(SessionCommand2.COMMAND_CODE_LIBRARY_GET_ITEM);
            addCommand(SessionCommand2.COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT);
            addCommand(SessionCommand2.COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT);
            addCommand(SessionCommand2.COMMAND_CODE_LIBRARY_SEARCH);
            addCommand(SessionCommand2.COMMAND_CODE_LIBRARY_SUBSCRIBE);
            addCommand(SessionCommand2.COMMAND_CODE_LIBRARY_UNSUBSCRIBE);
            return this;
        }

        private void addCommandsWithPrefix(String prefix) {
            final Field[] fields = SessionCommand2.class.getFields();
            if (fields != null) {
                for (int i = 0; i < fields.length; i++) {
                    if (fields[i].getName().startsWith(prefix)
                            && !fields[i].getName().equals("COMMAND_CODE_CUSTOM")) {
                        try {
                            mCommands.add(new SessionCommand2(fields[i].getInt(null)));
                        } catch (IllegalAccessException e) {
                            Log.w(TAG, "Unexpected " + fields[i] + " in MediaSession2");
                        }
                    }
                }
            }
        }

        /**
         * Builds {@link SessionCommandGroup2}.
         *
         * @return a new {@link SessionCommandGroup2}.
         */
        public @NonNull SessionCommandGroup2 build() {
            return new SessionCommandGroup2(mCommands);
        }
    }
}