Add init MediaSession2 and MediaController2

This CL only covers the connection logic between MediaSession2
and MediaController2.

Bug: 122055262
Test: build
Change-Id: I1773aa053ea1fce0cc548f334416e47d373f7f86
diff --git a/Android.bp b/Android.bp
index 4211162..8c68355 100644
--- a/Android.bp
+++ b/Android.bp
@@ -461,11 +461,13 @@
         "media/java/android/media/IAudioRoutesObserver.aidl",
         "media/java/android/media/IAudioService.aidl",
         "media/java/android/media/IAudioServerStateDispatcher.aidl",
+        "media/java/android/media/IMediaController2.aidl",
         "media/java/android/media/IMediaHTTPConnection.aidl",
         "media/java/android/media/IMediaHTTPService.aidl",
         "media/java/android/media/IMediaResourceMonitor.aidl",
         "media/java/android/media/IMediaRouterClient.aidl",
         "media/java/android/media/IMediaRouterService.aidl",
+        "media/java/android/media/IMediaSession2.aidl",
         "media/java/android/media/IMediaScannerListener.aidl",
         "media/java/android/media/IMediaScannerService.aidl",
         "media/java/android/media/IPlaybackConfigDispatcher.aidl",
diff --git a/media/java/android/media/Controller2Link.aidl b/media/java/android/media/Controller2Link.aidl
new file mode 100644
index 0000000..64edafc
--- /dev/null
+++ b/media/java/android/media/Controller2Link.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.media;
+
+parcelable Controller2Link;
diff --git a/media/java/android/media/Controller2Link.java b/media/java/android/media/Controller2Link.java
new file mode 100644
index 0000000..d6a6e14
--- /dev/null
+++ b/media/java/android/media/Controller2Link.java
@@ -0,0 +1,133 @@
+/*
+ * 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 android.media;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+/**
+ * Handles incoming commands from {@link MediaSession2} and {@link MediaLibrarySession}
+ * to both {@link MediaController2} and {@link MediaBrowser2}.
+ * @hide
+ */
+// @SystemApi
+public final class Controller2Link implements Parcelable {
+    private static final String TAG = "Controller2Link";
+    private static final boolean DEBUG = MediaController2.DEBUG;
+
+    public static final Parcelable.Creator<Controller2Link> CREATOR =
+            new Parcelable.Creator<Controller2Link>() {
+                @Override
+                public Controller2Link createFromParcel(Parcel in) {
+                    return new Controller2Link(in);
+                }
+
+                @Override
+                public Controller2Link[] newArray(int size) {
+                    return new Controller2Link[size];
+                }
+            };
+
+
+    private final MediaController2 mController;
+    private final IMediaController2 mIController;
+
+    public Controller2Link(MediaController2 controller) {
+        mController = controller;
+        mIController = new Controller2Stub();
+    }
+
+    Controller2Link(Parcel in) {
+        mController = null;
+        mIController = IMediaController2.Stub.asInterface(in.readStrongBinder());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongBinder(mIController.asBinder());
+    }
+
+    /** Interface method for IMediaController2.notifyConnected */
+    public void notifyConnected(int seq, Bundle connectionResult) {
+        try {
+            mIController.notifyConnected(seq, connectionResult);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Interface method for IMediaController2.notifyDisonnected */
+    public void notifyDisconnected(int seq) {
+        try {
+            mIController.notifyDisconnected(seq);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Interface method for IMediaController2.sendSessionCommand */
+    public void sendSessionCommand(int seq, Session2Command command, Bundle args) {
+        try {
+            mIController.sendSessionCommand(seq, command, args);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Stub implementation for IMediaController2.notifyConnected */
+    public void onConnected(int seq, Bundle connectionResult) {
+        if (connectionResult == null) {
+            onDisconnected(seq);
+            return;
+        }
+        mController.onConnected(seq, connectionResult);
+    }
+
+    /** Stub implementation for IMediaController2.notifyDisonnected */
+    public void onDisconnected(int seq) {
+        mController.onDisconnected(seq);
+    }
+
+    /** Stub implementation for IMediaController2.sendSessionCommand */
+    public void onSessionCommand(int seq, Session2Command command, Bundle args) {
+        mController.onSessionCommand(seq, command, args);
+    }
+
+    private class Controller2Stub extends IMediaController2.Stub {
+        @Override
+        public void notifyConnected(int seq, Bundle connectionResult) {
+            Controller2Link.this.onConnected(seq, connectionResult);
+        }
+
+        @Override
+        public void notifyDisconnected(int seq) {
+            Controller2Link.this.onDisconnected(seq);
+        }
+
+        @Override
+        public void sendSessionCommand(int seq, Session2Command command, Bundle args) {
+            Controller2Link.this.onSessionCommand(seq, command, args);
+        }
+    }
+}
diff --git a/media/java/android/media/IMediaController2.aidl b/media/java/android/media/IMediaController2.aidl
new file mode 100644
index 0000000..87399ab
--- /dev/null
+++ b/media/java/android/media/IMediaController2.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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 android.media;
+
+import android.os.Bundle;
+import android.media.Session2Command;
+
+/**
+ * Interface from MediaSession2 to MediaController2.
+ * <p>
+ * 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.
+ * @hide
+ */
+ // Code for AML only
+oneway interface IMediaController2 {
+    void notifyConnected(int seq, in Bundle connectionResult) = 0;
+    void notifyDisconnected(int seq) = 1;
+    void sendSessionCommand(int seq, in Session2Command command, in Bundle args) = 2;
+}
diff --git a/media/java/android/media/IMediaSession2.aidl b/media/java/android/media/IMediaSession2.aidl
new file mode 100644
index 0000000..d52cfc9
--- /dev/null
+++ b/media/java/android/media/IMediaSession2.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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 android.media;
+
+import android.os.Bundle;
+import android.media.Controller2Link;
+import android.media.Session2Command;
+
+/**
+ * Interface from MediaController2 to MediaSession2.
+ * <p>
+ * 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.
+ * @hide
+ */
+ // Code for AML only
+oneway interface IMediaSession2 {
+    void connect(in Controller2Link caller, int seq, in Bundle connectionRequest) = 0;
+    void disconnect(in Controller2Link caller, int seq) = 1;
+    void sendSessionCommand(in Controller2Link caller, int seq, in Session2Command sessionCommand,
+            in Bundle args) = 2;
+    // Next Id : 3
+}
diff --git a/media/java/android/media/MediaConstants.java b/media/java/android/media/MediaConstants.java
new file mode 100644
index 0000000..ffdca16d8
--- /dev/null
+++ b/media/java/android/media/MediaConstants.java
@@ -0,0 +1,33 @@
+/*
+ * 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 android.media;
+
+// Code for AML only
+class MediaConstants {
+    // Bundle key for int
+    static final String KEY_PID = "android.media.key.PID";
+
+    // Bundle key for String
+    static final String KEY_PACKAGE_NAME = "android.media.key.PACKAGE_NAME";
+
+    // Bundle key for Parcelable
+    static final String KEY_SESSION2_STUB = "android.media.key.SESSION2_STUB";
+    static final String KEY_ALLOWED_COMMANDS = "android.media.key.ALLOWED_COMMANDS";
+
+    private MediaConstants() {
+    }
+}
diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java
new file mode 100644
index 0000000..4a09bf1
--- /dev/null
+++ b/media/java/android/media/MediaController2.java
@@ -0,0 +1,220 @@
+/*
+ * 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 android.media;
+
+import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS;
+import static android.media.MediaConstants.KEY_PACKAGE_NAME;
+import static android.media.MediaConstants.KEY_PID;
+import static android.media.MediaConstants.KEY_SESSION2_STUB;
+import static android.media.Session2Token.TYPE_SESSION;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Process;
+import android.util.Log;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Allows an app to interact with an active {@link MediaSession2} or a
+ * {@link MediaSession2Service} which would provide {@link MediaSession2}. Media buttons and other
+ * commands can be sent to the session.
+ * <p>
+ * This API is not generally intended for third party application developers.
+ * Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link androidx.media2.MediaController} for consistent behavior across all devices.
+ * @hide
+ */
+public class MediaController2 implements AutoCloseable {
+    static final String TAG = "MediaController2";
+    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    final ControllerCallback mCallback;
+
+    private final IBinder.DeathRecipient mDeathRecipient = () -> close();
+    private final Context mContext;
+    private final Session2Token mSessionToken;
+    private final Executor mCallbackExecutor;
+    private final Controller2Link mControllerStub;
+
+    private final Object mLock = new Object();
+    //@GuardedBy("mLock")
+    private int mNextSeqNumber;
+    //@GuardedBy("mLock")
+    private Session2Link mSessionBinder;
+    //@GuardedBy("mLock")
+    private Session2CommandGroup mAllowedCommands;
+    //@GuardedBy("mLock")
+    private Session2Token mConnectedToken;
+
+    /**
+     * Create a {@link MediaController2} from the {@link Session2Token}.
+     * This connects to the session and may wake up the service if it's not available.
+     *
+     * @param context Context
+     * @param token token to connect to
+     * @param executor executor to run callbacks on.
+     * @param callback controller callback to receive changes in
+     */
+    public MediaController2(@NonNull final Context context, @NonNull final Session2Token token,
+            @NonNull final Executor executor, @NonNull final ControllerCallback callback) {
+        if (context == null) {
+            throw new IllegalArgumentException("context shouldn't be null");
+        }
+        if (token == null) {
+            throw new IllegalArgumentException("token shouldn't be null");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("callback shouldn't be null");
+        }
+        if (executor == null) {
+            throw new IllegalArgumentException("executor shouldn't be null");
+        }
+        mContext = context;
+        mSessionToken = token;
+        mCallbackExecutor = executor;
+        mCallback = callback;
+        mControllerStub = new Controller2Link(this);
+
+        mNextSeqNumber = 0;
+
+        Session2Link sessionBinder = token.getSessionLink();
+        if (token.getType() == TYPE_SESSION) {
+            connectToSession();
+        } else {
+            // TODO: Handle connect to session service.
+        }
+    }
+
+    @Override
+    public void close() {
+        synchronized (mLock) {
+            if (mSessionBinder != null) {
+                try {
+                    mSessionBinder.unlinkToDeath(mDeathRecipient, 0);
+                    mSessionBinder.disconnect(mControllerStub, mNextSeqNumber++);
+                } catch (RuntimeException e)  {
+                    // No-op
+                }
+            }
+            mCallbackExecutor.execute(() -> {
+                mCallback.onDisconnected(MediaController2.this);
+            });
+        }
+    }
+
+    void onConnected(int seq, Bundle connectionResult) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            Session2Link sessionBinder = connectionResult.getParcelable(KEY_SESSION2_STUB);
+            Session2CommandGroup allowedCommands =
+                    connectionResult.getParcelable(KEY_ALLOWED_COMMANDS);
+            if (DEBUG) {
+                Log.d(TAG, "notifyConnected sessionBinder=" + sessionBinder
+                        + ", allowedCommands=" + allowedCommands);
+            }
+            if (sessionBinder == null || allowedCommands == null) {
+                // Connection rejected.
+                close();
+                return;
+            }
+            synchronized (mLock) {
+                mSessionBinder = sessionBinder;
+                mAllowedCommands = allowedCommands;
+                // Implementation for the local binder is no-op,
+                // so can be used without worrying about deadlock.
+                sessionBinder.linkToDeath(mDeathRecipient, 0);
+                mConnectedToken = new Session2Token(mSessionToken.getUid(), TYPE_SESSION,
+                        mSessionToken.getPackageName(), sessionBinder);
+            }
+            mCallbackExecutor.execute(() -> {
+                mCallback.onConnected(MediaController2.this, allowedCommands);
+            });
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    void onDisconnected(int seq) {
+        // TODO: Implement this
+    }
+
+    void onSessionCommand(int seq, Session2Command command, Bundle args) {
+        // TODO: Implement this
+    }
+
+    private int getNextSeqNumber() {
+        synchronized (mLock) {
+            return mNextSeqNumber++;
+        }
+    }
+
+    private void connectToSession() {
+        Session2Link sessionBinder = mSessionToken.getSessionLink();
+        Bundle connectionRequest = new Bundle();
+        connectionRequest.putString(KEY_PACKAGE_NAME, mContext.getPackageName());
+        connectionRequest.putInt(KEY_PID, Process.myPid());
+
+        try {
+            sessionBinder.connect(mControllerStub, getNextSeqNumber(), connectionRequest);
+        } catch (RuntimeException e) {
+            Log.w(TAG, "Failed to call connection request. Framework will retry"
+                    + " automatically");
+        }
+    }
+
+    /**
+     * Interface for listening to change in activeness of the {@link MediaSession2}.
+     * <p>
+     * This API is not generally intended for third party application developers.
+     */
+    public abstract static class ControllerCallback {
+        /**
+         * Called when the controller is successfully connected to the session. The controller
+         * becomes available afterwards.
+         *
+         * @param controller the controller for this event
+         * @param allowedCommands commands that's allowed by the session.
+         */
+        public void onConnected(@NonNull MediaController2 controller,
+                @NonNull Session2CommandGroup allowedCommands) { }
+
+        /**
+         * Called when the session refuses the controller or the controller is disconnected from
+         * the session. The controller becomes unavailable afterwards and the callback wouldn't
+         * be called.
+         * <p>
+         * It will be also called after the {@link #close()}, so you can put clean up code here.
+         * You don't need to call {@link #close()} after this.
+         *
+         * @param controller the controller for this event
+         */
+        public void onDisconnected(@NonNull MediaController2 controller) { }
+
+        /**
+         * Called when a controller sent a session command.
+         */
+        public void onSessionCommand(@NonNull MediaController2 controller,
+                @NonNull Session2Command command, @Nullable Bundle args) {
+        }
+    }
+}
diff --git a/media/java/android/media/MediaItem2.java b/media/java/android/media/MediaItem2.java
index aa2a937..4e469e3 100644
--- a/media/java/android/media/MediaItem2.java
+++ b/media/java/android/media/MediaItem2.java
@@ -33,25 +33,12 @@
 import java.util.concurrent.Executor;
 
 /**
- * A class with information on a single media item with the metadata information. Here are use
- * cases.
- * <ul>
- * <li>Specify media items to {@link SessionPlayer2} for playback.
- * <li>Share media items across the processes.
- * </ul>
- * <p>
- * Subclasses of the session player may only accept certain subclasses of the media items. Check
- * the player documentation that you're interested in.
- * <p>
- * When it's shared across the processes, we cannot guarantee that they contain the right values
- * because media items are application dependent especially for the metadata.
- * <p>
- * This object is thread safe.
+ * A class with information on a single media item with the metadata information.
  * <p>
  * This API is not generally intended for third party application developers.
  * Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
  * {@link androidx.media2.MediaItem} for consistent behavior across all devices.
- * </p>
+ * <p>
  * @hide
  */
 public class MediaItem2 implements Parcelable {
diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java
new file mode 100644
index 0000000..222dfc0
--- /dev/null
+++ b/media/java/android/media/MediaSession2.java
@@ -0,0 +1,345 @@
+/*
+ * 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 android.media;
+
+import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS;
+import static android.media.MediaConstants.KEY_PACKAGE_NAME;
+import static android.media.MediaConstants.KEY_PID;
+import static android.media.MediaConstants.KEY_SESSION2_STUB;
+import static android.media.Session2Token.TYPE_SESSION;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.media.session.MediaSessionManager;
+import android.media.session.MediaSessionManager.RemoteUserInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Process;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Allows a media app to expose its transport controls and playback information in a process to
+ * other processes including the Android framework and other apps.
+ * <p>
+ * This API is not generally intended for third party application developers.
+ * Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link androidx.media2.MediaSession} for consistent behavior across all devices.
+ * @hide
+ */
+public class MediaSession2 implements AutoCloseable {
+    static final String TAG = "MediaSession";
+    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    final Object mLock = new Object();
+    //@GuardedBy("mLock")
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    final Map<Controller2Link, ControllerInfo> mConnectedControllers = new HashMap<>();
+
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    final Context mContext;
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    final Executor mCallbackExecutor;
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    final SessionCallback mCallback;
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    final Session2Link mSessionStub;
+
+    private final String mSessionId;
+    private final PendingIntent mSessionActivity;
+    private final Session2Token mSessionToken;
+    private final MediaSessionManager mSessionManager;
+
+    MediaSession2(Context context, String id, PendingIntent sessionActivity,
+            Executor callbackExecutor, SessionCallback callback) {
+        mContext = context;
+        mSessionId = id;
+        mSessionActivity = sessionActivity;
+        mCallbackExecutor = callbackExecutor;
+        mCallback = callback;
+        mSessionStub = new Session2Link(this);
+        mSessionToken = new Session2Token(Process.myUid(), TYPE_SESSION, context.getPackageName(),
+                mSessionStub);
+        mSessionManager = (MediaSessionManager) mContext.getSystemService(
+                Context.MEDIA_SESSION_SERVICE);
+    }
+
+    @Override
+    public void close() throws Exception {
+        // TODO: Implement this
+    }
+
+    // AML method
+    boolean isClosed() {
+        // TODO: Implement this
+        return true;
+    }
+
+    // AML method
+    void onConnect(final Controller2Link controller, int seq, Bundle connectionRequest) {
+        if (controller == null || connectionRequest == null) {
+            return;
+        }
+        final int uid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        final long token = Binder.clearCallingIdentity();
+        // Binder.getCallingPid() can be 0 for an oneway call from the remote process.
+        // If it's the case, use PID from the ConnectionRequest.
+        final int pid = (callingPid != 0) ? callingPid : connectionRequest.getInt(KEY_PID);
+        final String pkg = connectionRequest.getString(KEY_PACKAGE_NAME);
+        try {
+            RemoteUserInfo remoteUserInfo = new RemoteUserInfo(pkg, pid, uid);
+            final ControllerInfo controllerInfo = new ControllerInfo(remoteUserInfo,
+                    mSessionManager.isTrustedForMediaControl(remoteUserInfo), controller);
+            mCallbackExecutor.execute(() -> {
+                if (isClosed()) {
+                    return;
+                }
+                controllerInfo.mAllowedCommands =
+                        mCallback.onConnect(MediaSession2.this, controllerInfo);
+                // Don't reject connection for the request from trusted app.
+                // Otherwise server will fail to retrieve session's information to dispatch
+                // media keys to.
+                boolean accept =
+                        controllerInfo.mAllowedCommands != null || controllerInfo.isTrusted();
+                if (accept) {
+                    if (controllerInfo.mAllowedCommands == null) {
+                        // For trusted apps, send non-null allowed commands to keep
+                        // connection.
+                        controllerInfo.mAllowedCommands = new Session2CommandGroup();
+                    }
+                    if (DEBUG) {
+                        Log.d(TAG, "Accepting connection: " + controllerInfo);
+                    }
+                    synchronized (mLock) {
+                        if (mConnectedControllers.containsKey(controller)) {
+                            Log.w(TAG, "Controller " + controllerInfo + " has sent connection"
+                                    + " request multiple times");
+                        }
+                        mConnectedControllers.put(controller, controllerInfo);
+                    }
+                    // If connection is accepted, notify the current state to the controller.
+                    // It's needed because we cannot call synchronous calls between
+                    // session/controller.
+                    Bundle connectionResult = new Bundle();
+                    connectionResult.putParcelable(KEY_SESSION2_STUB, mSessionStub);
+                    connectionResult.putParcelable(KEY_ALLOWED_COMMANDS,
+                            controllerInfo.mAllowedCommands);
+
+                    // Double check if session is still there, because close() can be called in
+                    // another thread.
+                    if (isClosed()) {
+                        return;
+                    }
+                    try {
+                        controller.notifyConnected(
+                                controllerInfo.mNextSeqNumber++, connectionResult);
+                    } catch (RuntimeException e) {
+                        // Controller may be died prematurely.
+                    }
+                } else {
+                    if (DEBUG) {
+                        Log.d(TAG, "Rejecting connection, controllerInfo=" + controllerInfo);
+                    }
+                    try {
+                        controller.notifyDisconnected(controllerInfo.mNextSeqNumber++);
+                    } catch (RuntimeException e) {
+                        // Controller may be died prematurely.
+                    }
+                }
+            });
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // AML method
+    void onDisconnect(final Controller2Link controller, int seq) {
+        if (controller == null) {
+            return;
+        }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                final ControllerInfo controllerInfo = mConnectedControllers.get(controller);
+                mCallbackExecutor.execute(() -> {
+                    try {
+                        controller.notifyDisconnected(controllerInfo.mNextSeqNumber++);
+                    } catch (RuntimeException e) {
+                        // Controller may be died prematurely.
+                    }
+                });
+                mConnectedControllers.remove(controller);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // AML method
+    void onSessionCommand(final Controller2Link controller, final int seq,
+            final Session2Command command, final Bundle args) {
+        // TODO: Implement this
+    }
+
+    /**
+     * Information of a controller.
+     * <p>
+     * This API is not generally intended for third party application developers.
+     */
+    public static final class ControllerInfo {
+        private final RemoteUserInfo mRemoteUserInfo;
+        private final boolean mIsTrusted;
+        private final Controller2Link mControllerBinder;
+
+        @SuppressWarnings("WeakerAccess") /* synthetic access */
+        int mNextSeqNumber;
+        @SuppressWarnings("WeakerAccess") /* synthetic access */
+        Session2CommandGroup mAllowedCommands;
+
+        /**
+         * @param remoteUserInfo remote user info
+         * @param trusted {@code true} if trusted, {@code false} otherwise
+         * @param controllerBinder Controller2Link. Can be {@code null} only when a
+         *           MediaBrowserCompat connects to MediaSessionService and ControllerInfo is
+         *           needed for SessionCallback#onConnected().
+         */
+        ControllerInfo(@NonNull RemoteUserInfo remoteUserInfo, boolean trusted,
+                @Nullable Controller2Link controllerBinder) {
+            mRemoteUserInfo = remoteUserInfo;
+            mIsTrusted = trusted;
+            mControllerBinder = controllerBinder;
+        }
+
+        /**
+         * @hide
+         */
+        public @NonNull RemoteUserInfo getRemoteUserInfo() {
+            return mRemoteUserInfo;
+        }
+
+        /**
+         * @return package name of the controller. Can be
+         *         {@link androidx.media.MediaSessionManager.RemoteUserInfo#LEGACY_CONTROLLER} if
+         *         the package name cannot be obtained.
+         */
+        public @NonNull String getPackageName() {
+            return mRemoteUserInfo.getPackageName();
+        }
+
+        /**
+         * @return uid of the controller. Can be a negative value if the uid cannot be obtained.
+         */
+        public int getUid() {
+            return mRemoteUserInfo.getUid();
+        }
+
+        /**
+         * Return if the controller has granted {@code android.permission.MEDIA_CONTENT_CONTROL} or
+         * has a enabled notification listener so can be trusted to accept connection and incoming
+         * command request.
+         *
+         * @return {@code true} if the controller is trusted.
+         * @hide
+         */
+        public boolean isTrusted() {
+            return mIsTrusted;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mControllerBinder, mRemoteUserInfo);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof ControllerInfo)) {
+                return false;
+            }
+            if (this == obj) {
+                return true;
+            }
+            ControllerInfo other = (ControllerInfo) obj;
+            if (mControllerBinder != null || other.mControllerBinder != null) {
+                return Objects.equals(mControllerBinder, other.mControllerBinder);
+            }
+            return mRemoteUserInfo.equals(other.mRemoteUserInfo);
+        }
+
+        @Override
+        public String toString() {
+            return "ControllerInfo {pkg=" + mRemoteUserInfo.getPackageName() + ", uid="
+                    + mRemoteUserInfo.getUid() + ", allowedCommands=" + mAllowedCommands + "})";
+        }
+
+        @Nullable Controller2Link getControllerBinder() {
+            return mControllerBinder;
+        }
+    }
+
+    /**
+     * Callback to be called for all incoming commands from {@link MediaController2}s.
+     * <p>
+     * This API is not generally intended for third party application developers.
+     */
+    public abstract static class SessionCallback {
+        /**
+         * Called when a controller is created for this session. Return allowed commands for
+         * controller. By default it allows all connection requests and commands.
+         * <p>
+         * You can reject the connection by returning {@code null}. In that case, controller
+         * receives {@link MediaController2.ControllerCallback#onDisconnected(MediaController2)}
+         * and cannot be used.
+         *
+         * @param session the session for this event
+         * @param controller controller information.
+         * @return allowed commands. Can be {@code null} to reject connection.
+         */
+        @Nullable public Session2CommandGroup onConnect(@NonNull MediaSession2 session,
+                @NonNull ControllerInfo controller) {
+            Session2CommandGroup commands = new Session2CommandGroup.Builder()
+                    .addAllPredefinedCommands(Session2Command.COMMAND_VERSION_1)
+                    .build();
+            return commands;
+        }
+
+        /**
+         * Called when a controller is disconnected
+         *
+         * @param session the session for this event
+         * @param controller controller information
+         */
+        public void onDisconnected(@NonNull MediaSession2 session,
+                @NonNull ControllerInfo controller) { }
+
+        /**
+         * Called when a controller sent a session command.
+         */
+        public void onSessionCommand(@NonNull MediaSession2 session,
+                @NonNull ControllerInfo controller, @NonNull Session2Command command,
+                @Nullable Bundle args) {
+        }
+    }
+}
+
diff --git a/media/java/android/media/Session2Command.aidl b/media/java/android/media/Session2Command.aidl
new file mode 100644
index 0000000..43a7b12
--- /dev/null
+++ b/media/java/android/media/Session2Command.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.media;
+
+parcelable Session2Command;
diff --git a/media/java/android/media/Session2Link.java b/media/java/android/media/Session2Link.java
new file mode 100644
index 0000000..29f2685
--- /dev/null
+++ b/media/java/android/media/Session2Link.java
@@ -0,0 +1,171 @@
+/*
+ * 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 android.media;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * Handles incoming commands from {@link MediaController2} and {@link MediaBrowser2}
+ * to both {@link MediaSession2} and {@link MediaLibrarySession}.
+ * @hide
+ */
+// @SystemApi
+public final class Session2Link implements Parcelable {
+    private static final String TAG = "Session2Link";
+    private static final boolean DEBUG = MediaSession2.DEBUG;
+
+    public static final Parcelable.Creator<Session2Link> CREATOR =
+            new Parcelable.Creator<Session2Link>() {
+                @Override
+                public Session2Link createFromParcel(Parcel in) {
+                    return new Session2Link(in);
+                }
+
+                @Override
+                public Session2Link[] newArray(int size) {
+                    return new Session2Link[size];
+                }
+            };
+
+    private final MediaSession2 mSession;
+    private final IMediaSession2 mISession;
+
+    public Session2Link(MediaSession2 session) {
+        mSession = session;
+        mISession = new Session2Stub();
+    }
+
+    Session2Link(Parcel in) {
+        mSession = null;
+        mISession = IMediaSession2.Stub.asInterface(in.readStrongBinder());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongBinder(mISession.asBinder());
+    }
+
+    @Override
+    public int hashCode() {
+        return mISession.asBinder().hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof Session2Link)) {
+            return false;
+        }
+        Session2Link other = (Session2Link) obj;
+        return Objects.equals(mISession.asBinder(), other.mISession.asBinder());
+    }
+
+    /** Link to death with mISession */
+    public void linkToDeath(@NonNull IBinder.DeathRecipient recipient, int flags) {
+        if (mISession != null) {
+            try {
+                mISession.asBinder().linkToDeath(recipient, flags);
+            } catch (RemoteException e) {
+                if (DEBUG) {
+                    Log.d(TAG, "Session died too early.", e);
+                }
+            }
+        }
+    }
+
+    /** Unlink to death with mISession */
+    public boolean unlinkToDeath(@NonNull IBinder.DeathRecipient recipient, int flags) {
+        if (mISession != null) {
+            return mISession.asBinder().unlinkToDeath(recipient, flags);
+        }
+        return true;
+    }
+
+    /** Interface method for IMediaSession2.connect */
+    public void connect(final Controller2Link caller, int seq, Bundle connectionRequest) {
+        try {
+            mISession.connect(caller, seq, connectionRequest);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Interface method for IMediaSession2.disconnect */
+    public void disconnect(final Controller2Link caller, int seq) {
+        try {
+            mISession.disconnect(caller, seq);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Interface method for IMediaSession2.sendSessionCommand */
+    public void sendSessionCommand(final Controller2Link caller, final int seq,
+            final Session2Command command, final Bundle args) {
+        try {
+            mISession.sendSessionCommand(caller, seq, command, args);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Stub implementation for IMediaSession2.connect */
+    public void onConnect(final Controller2Link caller, int seq, Bundle connectionRequest) {
+        mSession.onConnect(caller, seq, connectionRequest);
+    }
+
+    /** Stub implementation for IMediaSession2.disconnect */
+    public void onDisconnect(final Controller2Link caller, int seq) {
+        mSession.onDisconnect(caller, seq);
+    }
+
+    /** Stub implementation for IMediaSession2.sendSessionCommand */
+    public void onSessionCommand(final Controller2Link caller, final int seq,
+            final Session2Command command, final Bundle args) {
+        mSession.onSessionCommand(caller, seq, command, args);
+    }
+
+    private class Session2Stub extends IMediaSession2.Stub {
+        @Override
+        public void connect(final Controller2Link caller, int seq, Bundle connectionRequest) {
+            Session2Link.this.onConnect(caller, seq, connectionRequest);
+        }
+
+        @Override
+        public void disconnect(final Controller2Link caller, int seq) {
+            Session2Link.this.onDisconnect(caller, seq);
+        }
+
+        @Override
+        public void sendSessionCommand(final Controller2Link caller, final int seq,
+                final Session2Command command, final Bundle args) {
+            Session2Link.this.onSessionCommand(caller, seq, command, args);
+        }
+    }
+}
diff --git a/media/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java
index af14078..4fa31cd 100644
--- a/media/java/android/media/Session2Token.java
+++ b/media/java/android/media/Session2Token.java
@@ -24,7 +24,6 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -39,7 +38,7 @@
  * If it's representing a session service, it may not be ongoing.
  * <p>
  * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx/">Support Library</a> instead.
+ * Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> instead.
  * {@link androidx.media2.SessionToken} for consistent behavior across all devices.
  * <p>
  * This may be passed to apps by the session owner to allow them to create a
@@ -85,12 +84,12 @@
     public static final int TYPE_SESSION = 0;
 
     /**
-     * Type for {@link MediaSessionService2}.
+     * Type for {@link MediaSession2Service}.
      */
     public static final int TYPE_SESSION_SERVICE = 1;
 
     /**
-     * Type for {@link MediaLibraryService2}.
+     * Type for {@link MediaLibrary2Service}.
      */
     public static final int TYPE_LIBRARY_SERVICE = 2;
 
@@ -98,12 +97,11 @@
     private final @TokenType int mType;
     private final String mPackageName;
     private final String mServiceName;
-    private final IBinder mISession;
+    private final Session2Link mSessionLink;
     private final ComponentName mComponentName;
 
     /**
-     * Constructor for the token. You can create token of {@link MediaSessionService2},
-     * {@link MediaLibraryService2} for {@link MediaController2} or {@link MediaBrowser2}.
+     * Constructor for the token.
      *
      * @param context The context.
      * @param serviceComponent The component name of the service.
@@ -120,7 +118,7 @@
         final int uid = getUid(manager, serviceComponent.getPackageName());
 
         // TODO: Uncomment below to stop hardcode type.
-        final int type = TYPE_SESSION_SERVICE;
+        final int type = TYPE_SESSION;
 //        final int type;
 //        if (isInterfaceDeclared(manager, MediaLibraryService2.SERVICE_INTERFACE,
 //                serviceComponent)) {
@@ -141,27 +139,26 @@
         mServiceName = serviceComponent.getClassName();
         mUid = uid;
         mType = type;
-        mISession = null;
+        mSessionLink = null;
     }
 
-    // TODO: Uncomment below
-//    Session2Token(int uid, int type, String packageName, IMediaSession2 iSession) {
-//        mUid = uid;
-//        mType = type;
-//        mPackageName = packageName;
-//        mServiceName = null;
-//        mComponentName = null;
-//        mISession = iSession.asBinder();
-//    }
+    Session2Token(int uid, int type, String packageName, Session2Link sessionLink) {
+        mUid = uid;
+        mType = type;
+        mPackageName = packageName;
+        mServiceName = null;
+        mComponentName = null;
+        mSessionLink = sessionLink;
+    }
 
     Session2Token(Parcel in) {
         mUid = in.readInt();
         mType = in.readInt();
         mPackageName = in.readString();
         mServiceName = in.readString();
-        // TODO: Uncomment below and stop hardcode mISession
-        mISession = null;
-        //mISession = ISession.Stub.asInterface(in.readStrongBinder());
+        // TODO: Uncomment below and stop hardcode mSessionLink
+        mSessionLink = null;
+        //mSessionLink = ISession.Stub.asInterface(in.readStrongBinder());
         mComponentName = ComponentName.unflattenFromString(in.readString());
     }
 
@@ -172,7 +169,7 @@
         dest.writeString(mPackageName);
         dest.writeString(mServiceName);
         // TODO: Uncomment below
-        //dest.writeStrongBinder(mISession.asBinder());
+        //dest.writeStrongBinder(mSessionLink.asBinder());
         dest.writeString(mComponentName == null ? "" : mComponentName.flattenToString());
     }
 
@@ -183,7 +180,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mType, mUid, mPackageName, mServiceName, mISession);
+        return Objects.hash(mType, mUid, mPackageName, mServiceName, mSessionLink);
     }
 
     @Override
@@ -196,13 +193,13 @@
                 && TextUtils.equals(mPackageName, other.mPackageName)
                 && TextUtils.equals(mServiceName, other.mServiceName)
                 && mType == other.mType
-                && Objects.equals(mISession, other.mISession);
+                && Objects.equals(mSessionLink, other.mSessionLink);
     }
 
     @Override
     public String toString() {
         return "Session2Token {pkg=" + mPackageName + " type=" + mType
-                + " service=" + mServiceName + " IMediaSession2=" + mISession + "}";
+                + " service=" + mServiceName + " Session2Link=" + mSessionLink + "}";
     }
 
     /**
@@ -249,8 +246,8 @@
     /**
      * @hide
      */
-    public Object getBinder() {
-        return mISession;
+    public Session2Link getSessionLink() {
+        return mSessionLink;
     }
 
     private static boolean isInterfaceDeclared(PackageManager manager, String serviceInterface,