Merge "Make MediaSession2.CommandGroup updatable"
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
index fc62a68..d5bd354 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
@@ -27,6 +27,7 @@
  * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
  * and holds calls from session to make session owner(s) frozen.
  */
+// TODO: Consider to make some methods oneway
 interface IMediaSession2 {
     // TODO(jaewan): add onCommand() to send private command
     // TODO(jaewan): Due to the nature of oneway calls, APIs can be called in out of order
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
index 5bb608d..53b35cc 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -48,11 +48,13 @@
 import android.media.session.MediaSessionManager;
 import android.media.update.MediaSession2Provider;
 import android.os.Bundle;
+import android.os.Parcelable;
 import android.os.Process;
 import android.os.IBinder;
 import android.os.ResultReceiver;
 import android.support.annotation.GuardedBy;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Log;
 
 import java.lang.ref.WeakReference;
@@ -108,6 +110,8 @@
         mId = id;
         mCallback = callback;
         mCallbackExecutor = callbackExecutor;
+        // Only remember player. Actual settings will be done in the initialize().
+        mPlayer = player;
         mSessionStub = new MediaSession2Stub(this);
 
         // Infer type from the id and package name.
@@ -126,9 +130,6 @@
             mSessionToken = new SessionToken2Impl(context, Process.myUid(), TYPE_SESSION,
                     mContext.getPackageName(), null, id, mSessionStub).getInstance();
         }
-
-        // Only remember player. Actual settings will be done in the initialize().
-        mPlayer = player;
     }
 
     private static String getServiceName(Context context, String serviceAction, String id) {
@@ -612,6 +613,113 @@
         }
     }
 
+    /**
+     * Represent set of {@link Command}.
+     */
+    public static class CommandGroupImpl implements CommandGroupProvider {
+        private static final String KEY_COMMANDS =
+                "android.media.mediasession2.commandgroup.commands";
+        private ArraySet<Command> mCommands = new ArraySet<>();
+        private final Context mContext;
+        private final CommandGroup mInstance;
+
+        public CommandGroupImpl(Context context, CommandGroup instance, Object other) {
+            mContext = context;
+            mInstance = instance;
+            if (other != null && other instanceof CommandGroupImpl) {
+                mCommands.addAll(((CommandGroupImpl) other).mCommands);
+            }
+        }
+
+        @Override
+        public void addCommand_impl(Command command) {
+            mCommands.add(command);
+        }
+
+        @Override
+        public void addAllPredefinedCommands_impl() {
+            // TODO(jaewan): Is there any better way than this?
+            mCommands.add(new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_START));
+            mCommands.add(new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE));
+            mCommands.add(new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_STOP));
+            mCommands.add(new Command(mContext,
+                    MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM));
+            mCommands.add(new Command(mContext,
+                    MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM));
+            mCommands.add(new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE));
+            mCommands.add(new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_FAST_FORWARD));
+            mCommands.add(new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_REWIND));
+            mCommands.add(new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_SEEK_TO));
+            mCommands.add(new Command(mContext,
+                    MediaSession2.COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM));
+        }
+
+        @Override
+        public void removeCommand_impl(Command command) {
+            mCommands.remove(command);
+        }
+
+        @Override
+        public boolean hasCommand_impl(Command command) {
+            return mCommands.contains(command);
+        }
+
+        @Override
+        public boolean hasCommand_impl(int code) {
+            if (code == MediaSession2.COMMAND_CODE_CUSTOM) {
+                throw new IllegalArgumentException("Use hasCommand(Command) for custom command");
+            }
+            for (int i = 0; i < mCommands.size(); i++) {
+                if (mCommands.valueAt(i).getCommandCode() == code) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /**
+         * @return new bundle from the CommandGroup
+         * @hide
+         */
+        @Override
+        public Bundle toBundle_impl() {
+            ArrayList<Bundle> list = new ArrayList<>();
+            for (int i = 0; i < mCommands.size(); i++) {
+                list.add(mCommands.valueAt(i).toBundle());
+            }
+            Bundle bundle = new Bundle();
+            bundle.putParcelableArrayList(KEY_COMMANDS, list);
+            return bundle;
+        }
+
+        /**
+         * @return new instance of CommandGroup from the bundle
+         * @hide
+         */
+        public static @Nullable CommandGroup fromBundle_impl(Context context, Bundle commands) {
+            if (commands == null) {
+                return null;
+            }
+            List<Parcelable> list = commands.getParcelableArrayList(KEY_COMMANDS);
+            if (list == null) {
+                return null;
+            }
+            CommandGroup commandGroup = new CommandGroup(context);
+            for (int i = 0; i < list.size(); i++) {
+                Parcelable parcelable = list.get(i);
+                if (!(parcelable instanceof Bundle)) {
+                    continue;
+                }
+                Bundle commandBundle = (Bundle) parcelable;
+                Command command = Command.fromBundle(context, commandBundle);
+                if (command != null) {
+                    commandGroup.addCommand(command);
+                }
+            }
+            return commandGroup;
+        }
+    }
+
     public static class ControllerInfoImpl implements ControllerInfoProvider {
         private final ControllerInfo mInstance;
         private final int mUid;
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
index 0fc1ac1..8fbbe5f 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -33,6 +33,7 @@
 import android.media.MediaPlayerInterface;
 import android.media.MediaSession2;
 import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
 import android.media.MediaSession2.SessionCallback;
 import android.media.MediaSessionService2;
@@ -103,16 +104,8 @@
     }
 
     @Override
-    public MediaSession2Provider.ControllerInfoProvider createMediaSession2ControllerInfoProvider(
-            Context context, ControllerInfo instance, int uid, int pid, String packageName,
-            IInterface callback) {
-        return new MediaSession2Impl.ControllerInfoImpl(context,
-                instance, uid, pid, packageName, (IMediaSession2Callback) callback);
-    }
-
-    @Override
-    public MediaSession2Provider.CommandProvider createMediaSession2Command(Command instance,
-            int commandCode, String action, Bundle extra) {
+    public MediaSession2Provider.CommandProvider createMediaSession2Command(
+            Command instance, int commandCode, String action, Bundle extra) {
         if (action == null && extra == null) {
             return new MediaSession2Impl.CommandImpl(instance, commandCode);
         }
@@ -125,6 +118,26 @@
     }
 
     @Override
+    public MediaSession2Provider.CommandGroupProvider createMediaSession2CommandGroup(
+            Context context, CommandGroup instance, CommandGroup other) {
+        return new MediaSession2Impl.CommandGroupImpl(context, instance,
+                (other == null) ? null : other.getProvider());
+    }
+
+    @Override
+    public CommandGroup fromBundle_MediaSession2CommandGroup(Context context, Bundle commands) {
+        return MediaSession2Impl.CommandGroupImpl.fromBundle_impl(context, commands);
+    }
+
+    @Override
+    public MediaSession2Provider.ControllerInfoProvider createMediaSession2ControllerInfoProvider(
+            Context context, ControllerInfo instance, int uid, int pid, String packageName,
+            IInterface callback) {
+        return new MediaSession2Impl.ControllerInfoImpl(context,
+                instance, uid, pid, packageName, (IMediaSession2Callback) callback);
+    }
+
+    @Override
     public MediaSessionService2Provider createMediaSessionService2(
             MediaSessionService2 instance) {
         return new MediaSessionService2Impl(instance);