Introduce link classes for media mainline module

Bug: 121239200
Test: make update-api -j / make -j
      atest CtsMediaTestCases:android.media.cts.MediaSessionTest
      atest CtsMediaTestCases:android.media.cts.MediaControllerTest
Change-Id: I93662e83a82f111bfdf25e22525c1466dd66079f
diff --git a/api/current.txt b/api/current.txt
index 6122e52..d378416 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -27286,21 +27286,6 @@
 
 package android.media.session {
 
-  public final class ControllerCallbackLink implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public android.os.IBinder getBinder();
-    method public void notifyEvent(@NonNull String, @Nullable android.os.Bundle);
-    method public void notifyExtrasChanged(@Nullable android.os.Bundle);
-    method public void notifyMetadataChanged(@Nullable android.media.MediaMetadata);
-    method public void notifyPlaybackStateChanged(@Nullable android.media.session.PlaybackState);
-    method public void notifyQueueChanged(@Nullable java.util.List<android.media.session.MediaSession.QueueItem>);
-    method public void notifyQueueTitleChanged(@Nullable CharSequence);
-    method public void notifySessionDestroyed();
-    method public void notifyVolumeInfoChanged(@NonNull android.media.session.MediaController.PlaybackInfo);
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.media.session.ControllerCallbackLink> CREATOR;
-  }
-
   public final class MediaController {
     ctor public MediaController(@NonNull android.content.Context, @NonNull android.media.session.MediaSession.Token);
     method public void adjustVolume(int, int);
@@ -27535,36 +27520,6 @@
     method public android.media.session.PlaybackState.CustomAction.Builder setExtras(android.os.Bundle);
   }
 
-  public final class SessionCallbackLink implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public android.os.IBinder getBinder();
-    method public void notifyAdjustVolume(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, int);
-    method public void notifyCommand(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle, @Nullable android.os.ResultReceiver);
-    method public void notifyCustomAction(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
-    method public void notifyFastForward(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
-    method public void notifyMediaButton(@NonNull String, int, int, @NonNull android.content.Intent, int, @Nullable android.os.ResultReceiver);
-    method public void notifyMediaButtonFromController(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.content.Intent);
-    method public void notifyNext(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
-    method public void notifyPause(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
-    method public void notifyPlay(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
-    method public void notifyPlayFromMediaId(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
-    method public void notifyPlayFromSearch(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
-    method public void notifyPlayFromUri(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.net.Uri, @Nullable android.os.Bundle);
-    method public void notifyPrepare(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
-    method public void notifyPrepareFromMediaId(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
-    method public void notifyPrepareFromSearch(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
-    method public void notifyPrepareFromUri(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.net.Uri, @Nullable android.os.Bundle);
-    method public void notifyPrevious(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
-    method public void notifyRate(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.media.Rating);
-    method public void notifyRewind(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
-    method public void notifySeekTo(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, long);
-    method public void notifySetVolumeTo(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, int);
-    method public void notifySkipToTrack(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, long);
-    method public void notifyStop(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.media.session.SessionCallbackLink> CREATOR;
-  }
-
 }
 
 package android.media.tv {
diff --git a/api/system-current.txt b/api/system-current.txt
index ce0ef38..810b237 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3448,7 +3448,20 @@
 package android.media.session {
 
   public final class ControllerCallbackLink implements android.os.Parcelable {
-    ctor public ControllerCallbackLink(@NonNull android.media.session.ControllerCallbackLink.CallbackStub);
+    ctor public ControllerCallbackLink(@NonNull android.content.Context, @NonNull android.media.session.ControllerCallbackLink.CallbackStub);
+    ctor public ControllerCallbackLink(android.os.IBinder);
+    method public int describeContents();
+    method @NonNull public android.os.IBinder getBinder();
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyEvent(@NonNull String, @Nullable android.os.Bundle);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyExtrasChanged(@Nullable android.os.Bundle);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyMetadataChanged(@Nullable android.media.MediaMetadata);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPlaybackStateChanged(@Nullable android.media.session.PlaybackState);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyQueueChanged(@Nullable java.util.List<android.media.session.MediaSession.QueueItem>);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyQueueTitleChanged(@Nullable CharSequence);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifySessionDestroyed();
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyVolumeInfoChanged(@NonNull android.media.session.MediaController.PlaybackInfo);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.media.session.ControllerCallbackLink> CREATOR;
   }
 
   public abstract static class ControllerCallbackLink.CallbackStub {
@@ -3463,6 +3476,60 @@
     method public void onVolumeInfoChanged(@NonNull android.media.session.MediaController.PlaybackInfo);
   }
 
+  public final class ControllerLink implements android.os.Parcelable {
+    ctor public ControllerLink(@NonNull android.media.session.ControllerLink.ControllerStub);
+    ctor public ControllerLink(android.os.IBinder);
+    method public int describeContents();
+    method @NonNull public android.os.IBinder getBinder();
+    method @Nullable public android.os.Bundle getExtras();
+    method @Nullable public android.media.MediaMetadata getMetadata();
+    method @Nullable public android.media.session.PlaybackState getPlaybackState();
+    method @Nullable public java.util.List<android.media.session.MediaSession.QueueItem> getQueue();
+    method @Nullable public CharSequence getQueueTitle();
+    method public int getRatingType();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.media.session.ControllerLink> CREATOR;
+  }
+
+  public abstract static class ControllerLink.ControllerStub {
+    ctor public ControllerLink.ControllerStub();
+    method public void adjustVolume(@NonNull String, @NonNull String, @NonNull android.media.session.ControllerCallbackLink, boolean, int, int);
+    method public void fastForward(@NonNull String, @NonNull android.media.session.ControllerCallbackLink);
+    method @Nullable public android.os.Bundle getExtras();
+    method public long getFlags();
+    method @Nullable public android.app.PendingIntent getLaunchPendingIntent();
+    method @Nullable public android.media.MediaMetadata getMetadata();
+    method @NonNull public String getPackageName();
+    method @Nullable public android.media.session.PlaybackState getPlaybackState();
+    method @Nullable public java.util.List<android.media.session.MediaSession.QueueItem> getQueue();
+    method @Nullable public CharSequence getQueueTitle();
+    method public int getRatingType();
+    method @NonNull public String getTag();
+    method @NonNull public android.media.session.MediaController.PlaybackInfo getVolumeAttributes();
+    method public void next(@NonNull String, @NonNull android.media.session.ControllerCallbackLink);
+    method public void pause(@NonNull String, @NonNull android.media.session.ControllerCallbackLink);
+    method public void play(@NonNull String, @NonNull android.media.session.ControllerCallbackLink);
+    method public void playFromMediaId(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+    method public void playFromSearch(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+    method public void playFromUri(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.net.Uri, @Nullable android.os.Bundle);
+    method public void prepare(@NonNull String, @NonNull android.media.session.ControllerCallbackLink);
+    method public void prepareFromMediaId(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+    method public void prepareFromSearch(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+    method public void prepareFromUri(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.net.Uri, @Nullable android.os.Bundle);
+    method public void previous(@NonNull String, @NonNull android.media.session.ControllerCallbackLink);
+    method public void rate(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.media.Rating);
+    method public void registerCallback(@NonNull String, @NonNull android.media.session.ControllerCallbackLink);
+    method public void rewind(@NonNull String, @NonNull android.media.session.ControllerCallbackLink);
+    method public void seekTo(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, long);
+    method public void sendCommand(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle, @Nullable android.os.ResultReceiver);
+    method public void sendCustomAction(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+    method public boolean sendMediaButton(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, boolean, @NonNull android.view.KeyEvent);
+    method public void setVolumeTo(@NonNull String, @NonNull String, @NonNull android.media.session.ControllerCallbackLink, int, int);
+    method public void skipToQueueItem(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, long);
+    method public void stop(@NonNull String, @NonNull android.media.session.ControllerCallbackLink);
+    method public void unregisterCallback(@NonNull android.media.session.ControllerCallbackLink);
+  }
+
   public final class MediaSessionManager {
     method @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER) public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, @Nullable android.os.Handler);
     method @RequiresPermission(android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER) public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, @Nullable android.os.Handler);
@@ -3476,6 +3543,66 @@
     method public void onVolumeKeyLongPress(android.view.KeyEvent);
   }
 
+  public final class SessionCallbackLink implements android.os.Parcelable {
+    ctor public SessionCallbackLink(android.os.IBinder);
+    method public int describeContents();
+    method @NonNull public android.os.IBinder getBinder();
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyAdjustVolume(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, int);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyCommand(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle, @Nullable android.os.ResultReceiver);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyCustomAction(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyFastForward(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyMediaButton(@NonNull String, int, int, @NonNull android.content.Intent, int, @Nullable android.os.ResultReceiver);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyMediaButtonFromController(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.content.Intent);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyNext(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPause(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPlay(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPlayFromMediaId(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPlayFromSearch(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPlayFromUri(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.net.Uri, @Nullable android.os.Bundle);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPrepare(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPrepareFromMediaId(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPrepareFromSearch(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPrepareFromUri(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.net.Uri, @Nullable android.os.Bundle);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPrevious(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyRate(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.media.Rating);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyRewind(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifySeekTo(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, long);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifySetVolumeTo(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, int);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifySkipToTrack(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, long);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyStop(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.media.session.SessionCallbackLink> CREATOR;
+  }
+
+  public final class SessionLink implements android.os.Parcelable {
+    ctor public SessionLink(@NonNull android.media.session.SessionLink.SessionStub);
+    ctor public SessionLink(android.os.IBinder);
+    method public int describeContents();
+    method @NonNull public android.os.IBinder getBinder();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.media.session.SessionLink> CREATOR;
+  }
+
+  public abstract static class SessionLink.SessionStub {
+    ctor public SessionLink.SessionStub();
+    method public void destroySession();
+    method @NonNull public android.media.session.ControllerLink getController();
+    method public void sendEvent(@NonNull String, @Nullable android.os.Bundle);
+    method public void setActive(boolean);
+    method public void setCurrentVolume(int);
+    method public void setExtras(@Nullable android.os.Bundle);
+    method public void setFlags(int);
+    method public void setLaunchPendingIntent(@Nullable android.app.PendingIntent);
+    method public void setMediaButtonReceiver(@Nullable android.app.PendingIntent);
+    method public void setMetadata(@Nullable android.media.MediaMetadata, long, @Nullable String);
+    method public void setPlaybackState(@Nullable android.media.session.PlaybackState);
+    method public void setPlaybackToLocal(@NonNull android.media.AudioAttributes);
+    method public void setPlaybackToRemote(int, int);
+    method public void setQueue(@Nullable java.util.List<android.media.session.MediaSession.QueueItem>);
+    method public void setQueueTitle(@Nullable CharSequence);
+    method public void setRatingType(int);
+  }
+
 }
 
 package android.media.soundtrigger {
diff --git a/media/java/android/media/IRemoteVolumeController.aidl b/media/java/android/media/IRemoteVolumeController.aidl
index e4a4a42..a591c11 100644
--- a/media/java/android/media/IRemoteVolumeController.aidl
+++ b/media/java/android/media/IRemoteVolumeController.aidl
@@ -25,8 +25,8 @@
  * @hide
  */
 oneway interface IRemoteVolumeController {
-    void remoteVolumeChanged(ISessionController session, int flags);
+    void remoteVolumeChanged(in ISessionController session, int flags);
     // sets the default session to use with the slider, replaces remoteSliderVisibility
     // on IVolumeController
-    void updateRemoteController(ISessionController session);
+    void updateRemoteController(in ISessionController session);
 }
diff --git a/media/java/android/media/session/ControllerCallbackLink.java b/media/java/android/media/session/ControllerCallbackLink.java
index a143c9b..95e19d2 100644
--- a/media/java/android/media/session/ControllerCallbackLink.java
+++ b/media/java/android/media/session/ControllerCallbackLink.java
@@ -16,35 +16,41 @@
 
 package android.media.session;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.media.MediaMetadata;
 import android.media.session.MediaController.PlaybackInfo;
 import android.media.session.MediaSession.QueueItem;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.Process;
 import android.os.RemoteException;
 
 import java.util.List;
 
 /**
  * Handles incoming commands to {@link MediaController.Callback}.
- * <p>
- * This API is not generally intended for third party application developers.
+ * @hide
  */
+@SystemApi
 public final class ControllerCallbackLink implements Parcelable {
+    final Context mContext;
     final CallbackStub mCallbackStub;
     final ISessionControllerCallback mIControllerCallback;
 
     /**
      * Constructor for stub (Callee)
-     * @hide
      */
-    @SystemApi
-    public ControllerCallbackLink(@NonNull CallbackStub callbackStub) {
+    public ControllerCallbackLink(@NonNull Context context, @NonNull CallbackStub callbackStub) {
+        mContext = context;
         mCallbackStub = callbackStub;
         mIControllerCallback = new CallbackStubProxy();
     }
@@ -52,14 +58,16 @@
     /**
      * Constructor for interface (Caller)
      */
-    ControllerCallbackLink(Parcel in) {
+    public ControllerCallbackLink(IBinder binder) {
+        mContext = null;
         mCallbackStub = null;
-        mIControllerCallback = ISessionControllerCallback.Stub.asInterface(in.readStrongBinder());
+        mIControllerCallback = ISessionControllerCallback.Stub.asInterface(binder);
     }
 
     /**
      * Notify controller that the connected session is destroyed.
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifySessionDestroyed() {
         try {
             mIControllerCallback.notifySessionDestroyed();
@@ -74,6 +82,7 @@
      * @param event the name of the event
      * @param extras the extras included with the event
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyEvent(@NonNull String event, @Nullable Bundle extras) {
         try {
             mIControllerCallback.notifyEvent(event, extras);
@@ -87,6 +96,7 @@
      *
      * @param state the new playback state
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyPlaybackStateChanged(@Nullable PlaybackState state) {
         try {
             mIControllerCallback.notifyPlaybackStateChanged(state);
@@ -100,6 +110,7 @@
      *
      * @param metadata the new metadata
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyMetadataChanged(@Nullable MediaMetadata metadata) {
         try {
             mIControllerCallback.notifyMetadataChanged(metadata);
@@ -113,6 +124,7 @@
      *
      * @param queue the new queue
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyQueueChanged(@Nullable List<QueueItem> queue) {
         try {
             mIControllerCallback.notifyQueueChanged(queue);
@@ -126,6 +138,7 @@
      *
      * @param title the new queue title
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyQueueTitleChanged(@Nullable CharSequence title) {
         try {
             mIControllerCallback.notifyQueueTitleChanged(title);
@@ -139,6 +152,7 @@
      *
      * @param extras the new extras
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyExtrasChanged(@Nullable Bundle extras) {
         try {
             mIControllerCallback.notifyExtrasChanged(extras);
@@ -152,6 +166,7 @@
      *
      * @param info the new playback info
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyVolumeInfoChanged(@NonNull PlaybackInfo info) {
         try {
             mIControllerCallback.notifyVolumeInfoChanged(info);
@@ -180,7 +195,7 @@
             new Parcelable.Creator<ControllerCallbackLink>() {
         @Override
         public ControllerCallbackLink createFromParcel(Parcel in) {
-            return new ControllerCallbackLink(in);
+            return new ControllerCallbackLink(in.readStrongBinder());
         }
 
         @Override
@@ -191,9 +206,7 @@
 
     /**
      * Class for Stub implementation
-     * @hide
      */
-    @SystemApi
     public abstract static class CallbackStub {
         /** Stub method for ISessionControllerCallback.notifySessionDestroyed */
         public void onSessionDestroyed() {
@@ -241,22 +254,46 @@
 
         @Override
         public void notifyPlaybackStateChanged(PlaybackState state) {
-            mCallbackStub.onPlaybackStateChanged(state);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onPlaybackStateChanged(state);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyMetadataChanged(MediaMetadata metadata) {
-            mCallbackStub.onMetadataChanged(metadata);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onMetadataChanged(metadata);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyQueueChanged(List<QueueItem> queue) {
-            mCallbackStub.onQueueChanged(queue);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onQueueChanged(queue);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyQueueTitleChanged(CharSequence title) {
-            mCallbackStub.onQueueTitleChanged(title);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onQueueTitleChanged(title);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
@@ -266,7 +303,31 @@
 
         @Override
         public void notifyVolumeInfoChanged(PlaybackInfo info) {
-            mCallbackStub.onVolumeInfoChanged(info);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onVolumeInfoChanged(info);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        private void ensureMediasControlPermission() {
+            // Allow API calls from the System UI
+            if (mContext.checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
+                    == PackageManager.PERMISSION_GRANTED) {
+                return;
+            }
+
+            // Check if it's system server or has MEDIA_CONTENT_CONTROL.
+            // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
+            // check here.
+            if (getCallingUid() == Process.SYSTEM_UID || mContext.checkCallingPermission(
+                    android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+                    == PackageManager.PERMISSION_GRANTED) {
+                return;
+            }
+            throw new SecurityException("Must hold the MEDIA_CONTENT_CONTROL permission.");
         }
     }
 }
diff --git a/media/java/android/media/session/ControllerLink.aidl b/media/java/android/media/session/ControllerLink.aidl
new file mode 100644
index 0000000..532df59
--- /dev/null
+++ b/media/java/android/media/session/ControllerLink.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2019 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.session;
+
+parcelable ControllerLink;
diff --git a/media/java/android/media/session/ControllerLink.java b/media/java/android/media/session/ControllerLink.java
new file mode 100644
index 0000000..df1d649
--- /dev/null
+++ b/media/java/android/media/session/ControllerLink.java
@@ -0,0 +1,983 @@
+/*
+ * Copyright 2019 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.session;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.media.MediaMetadata;
+import android.media.Rating;
+import android.media.session.MediaController.PlaybackInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.view.KeyEvent;
+
+import java.util.List;
+
+/**
+ * Handles incoming commands from {@link MediaController}.
+ * @hide
+ */
+@SystemApi
+public final class ControllerLink implements Parcelable {
+    public static final Parcelable.Creator<ControllerLink> CREATOR =
+            new Parcelable.Creator<ControllerLink>() {
+                @Override
+                public ControllerLink createFromParcel(Parcel in) {
+                    return new ControllerLink(in.readStrongBinder());
+                }
+
+                @Override
+                public ControllerLink[] newArray(int size) {
+                    return new ControllerLink[size];
+                }
+            };
+
+    final ControllerStub mControllerStub;
+    final ISessionController mISessionController;
+
+    /**
+     * Constructor for stub (Callee)
+     */
+    public ControllerLink(@NonNull ControllerStub controllerStub) {
+        mControllerStub = controllerStub;
+        mISessionController = new StubProxy();
+    }
+
+    /**
+     * Constructor for interface (Caller)
+     */
+    public ControllerLink(IBinder binder) {
+        mControllerStub = null;
+        mISessionController = ISessionController.Stub.asInterface(binder);
+    }
+
+    /**
+     * Tell system that a controller sends a command.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param command the name of the command
+     * @param args the arguments included with the command
+     * @param cb the result receiver for getting the result of the command
+     */
+    void sendCommand(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+            @NonNull String command, @Nullable Bundle args, @Nullable ResultReceiver cb) {
+        try {
+            mISessionController.sendCommand(packageName, caller, command, args, cb);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller sends a media button event.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param asSystemService whether this event should be considered as from system service
+     * @param mediaButton the media button key event
+     */
+    boolean sendMediaButton(@NonNull String packageName,
+            @NonNull ControllerCallbackLink caller, boolean asSystemService,
+            @NonNull KeyEvent mediaButton) {
+        try {
+            return mISessionController.sendMediaButton(packageName, caller, asSystemService,
+                    mediaButton);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Registers a controller callback link to the system.
+     *
+     * @param packageName the package name of the controller
+     * @param cb the controller callback link to register
+     */
+    void registerCallback(@NonNull String packageName, @NonNull ControllerCallbackLink cb) {
+        try {
+            mISessionController.registerCallback(packageName, cb);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Unregisters a controller callback link from the system.
+     *
+     * @param cb the controller callback link to register
+     */
+    void unregisterCallback(@NonNull ControllerCallbackLink cb) {
+        try {
+            mISessionController.unregisterCallback(cb);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Gets the package name of the connected session.
+     */
+    @NonNull
+    String getPackageName() {
+        try {
+            return mISessionController.getPackageName();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Gets the tag of the connected session.
+     */
+    @NonNull
+    String getTag() {
+        try {
+            return mISessionController.getTag();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Gets the {@link PendingIntent} for launching UI of the connected session.
+     */
+    @Nullable
+    PendingIntent getLaunchPendingIntent() {
+        try {
+            return mISessionController.getLaunchPendingIntent();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Gets the flags of the connected session.
+     */
+    long getFlags() {
+        try {
+            return mISessionController.getFlags();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Gets the volume attributes of the connected session.
+     */
+    @NonNull
+    PlaybackInfo getVolumeAttributes() {
+        try {
+            return mISessionController.getVolumeAttributes();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests adjusting the volume.
+     *
+     * @param packageName the package name of the controller
+     * @param opPackageName the op package name of this request
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param asSystemService whether this event should be considered as from system service
+     * @param direction the direction to adjust the volume in
+     * @param flags the flags with this volume change request
+     */
+    void adjustVolume(@NonNull String packageName, @NonNull String opPackageName,
+            @NonNull ControllerCallbackLink caller, boolean asSystemService, int direction,
+            int flags) {
+        try {
+            mISessionController.adjustVolume(packageName, opPackageName, caller, asSystemService,
+                    direction, flags);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests setting the volume.
+     *
+     * @param packageName the package name of the controller
+     * @param opPackageName the op package name of this request
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param flags the flags with this volume change request
+     */
+    void setVolumeTo(@NonNull String packageName, @NonNull String opPackageName,
+            @NonNull ControllerCallbackLink caller, int value, int flags) {
+        try {
+            mISessionController.setVolumeTo(packageName, opPackageName, caller, value, flags);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests preparing media.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     */
+    void prepare(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+        try {
+            mISessionController.prepare(packageName, caller);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests preparing media from given media ID.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param mediaId the ID of the media
+     * @param extras the extras included with this request.
+     */
+    void prepareFromMediaId(@NonNull String packageName,
+            @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
+            @Nullable Bundle extras) {
+        try {
+            mISessionController.prepareFromMediaId(packageName, caller, mediaId, extras);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests preparing media from given search query.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param query the search query
+     * @param extras the extras included with this request.
+     */
+    void prepareFromSearch(@NonNull String packageName,
+            @NonNull ControllerCallbackLink caller, @NonNull String query,
+            @Nullable Bundle extras) {
+        try {
+            mISessionController.prepareFromSearch(packageName, caller, query, extras);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests preparing media from given uri.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param uri the uri of the media
+     * @param extras the extras included with this request.
+     */
+    void prepareFromUri(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+            @NonNull Uri uri, @Nullable Bundle extras) {
+        try {
+            mISessionController.prepareFromUri(packageName, caller, uri, extras);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests playing media.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     */
+    void play(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+        try {
+            mISessionController.play(packageName, caller);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests playing media from given media ID.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param mediaId the ID of the media
+     * @param extras the extras included with this request.
+     */
+    void playFromMediaId(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+            @NonNull String mediaId, @Nullable Bundle extras) {
+        try {
+            mISessionController.playFromMediaId(packageName, caller, mediaId, extras);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests playing media from given search query.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param query the search query
+     * @param extras the extras included with this request.
+     */
+    void playFromSearch(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+            @NonNull String query, @Nullable Bundle extras) {
+        try {
+            mISessionController.playFromSearch(packageName, caller, query, extras);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests playing media from given uri.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param uri the uri of the media
+     * @param extras the extras included with this request.
+     */
+    void playFromUri(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+            @NonNull Uri uri, @Nullable Bundle extras) {
+        try {
+            mISessionController.playFromUri(packageName, caller, uri, extras);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests skipping to the queue item with given ID.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param id the queue id of the item
+     */
+    void skipToQueueItem(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+            long id) {
+        try {
+            mISessionController.skipToQueueItem(packageName, caller, id);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests pausing media.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     */
+    void pause(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+        try {
+            mISessionController.pause(packageName, caller);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests stopping media.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     */
+    void stop(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+        try {
+            mISessionController.stop(packageName, caller);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests skipping to the next queue item.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     */
+    void next(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+        try {
+            mISessionController.next(packageName, caller);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests skipping to the previous queue item.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     */
+    void previous(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+        try {
+            mISessionController.previous(packageName, caller);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests fast-forwarding.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     */
+    void fastForward(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+        try {
+            mISessionController.fastForward(packageName, caller);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests rewinding.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     */
+    void rewind(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+        try {
+            mISessionController.rewind(packageName, caller);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests seeking to the specific position.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param pos the position to move to, in milliseconds
+     */
+    void seekTo(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+            long pos) {
+        try {
+            mISessionController.seekTo(packageName, caller, pos);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller requests rating of the current media.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param rating the rating of the current media
+     */
+    void rate(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+            @NonNull Rating rating) {
+        try {
+            mISessionController.rate(packageName, caller, rating);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that a controller sends a custom action.
+     *
+     * @param packageName the package name of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param action the name of the action
+     * @param args the arguments included with this action
+     */
+    void sendCustomAction(@NonNull String packageName,
+            @NonNull ControllerCallbackLink caller, @NonNull String action, @Nullable Bundle args) {
+        try {
+            mISessionController.sendCustomAction(packageName, caller, action, args);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Gets the current metadata of the connected session.
+     */
+    @Nullable
+    public MediaMetadata getMetadata() {
+        try {
+            return mISessionController.getMetadata();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Gets the current playback state of the connected session.
+     */
+    @Nullable
+    public PlaybackState getPlaybackState() {
+        try {
+            return mISessionController.getPlaybackState();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Gets the current queue of the connected session.
+     */
+    @Nullable
+    public List<MediaSession.QueueItem> getQueue() {
+        try {
+            return mISessionController.getQueue();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Gets the current queue title of the connected session.
+     */
+    @Nullable
+    public CharSequence getQueueTitle() {
+        try {
+            return mISessionController.getQueueTitle();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Gets the current extras of the connected session.
+     */
+    @Nullable
+    public Bundle getExtras() {
+        try {
+            return mISessionController.getExtras();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Gets the current rating type of the connected session.
+     */
+    public int getRatingType() {
+        try {
+            return mISessionController.getRatingType();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /** Gets the binder */
+    @NonNull
+    public IBinder getBinder() {
+        return mISessionController.asBinder();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongBinder(mISessionController.asBinder());
+    }
+
+    /**
+     * Class for Stub implementation
+     */
+    public abstract static class ControllerStub {
+        /** Stub method for ISessionController.sendCommand */
+        public void sendCommand(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+                @NonNull String command, @Nullable Bundle args, @Nullable ResultReceiver cb) {
+        }
+
+        /** Stub method for ISessionController.sendMediaButton */
+        public boolean sendMediaButton(@NonNull String packageName,
+                @NonNull ControllerCallbackLink caller, boolean asSystemService,
+                @NonNull KeyEvent mediaButton) {
+            return false;
+        }
+
+        /** Stub method for ISessionController.registerCallback */
+        public void registerCallback(@NonNull String packageName,
+                @NonNull ControllerCallbackLink cb) {
+        }
+
+        /** Stub method for ISessionController.unregisterCallback */
+        public void unregisterCallback(@NonNull ControllerCallbackLink cb) {
+        }
+
+        /** Stub method for ISessionController.getPackageName */
+        @NonNull
+        public String getPackageName() {
+            return null;
+        }
+
+        /** Stub method for ISessionController.getTag */
+        @NonNull
+        public String getTag() {
+            return null;
+        }
+
+        /** Stub method for ISessionController.getLaunchPendingIntent */
+        @Nullable
+        public PendingIntent getLaunchPendingIntent() {
+            return null;
+        }
+
+        /** Stub method for ISessionController.getFlags */
+        public long getFlags() {
+            return 0;
+        }
+
+        /** Stub method for ISessionController.getVolumeAttributes */
+        @NonNull
+        public PlaybackInfo getVolumeAttributes() {
+            return null;
+        }
+
+        /** Stub method for ISessionController.adjustVolume */
+        public void adjustVolume(@NonNull String packageName, @NonNull String opPackageName,
+                @NonNull ControllerCallbackLink caller, boolean asSystemService, int direction,
+                int flags) {
+        }
+
+        /** Stub method for ISessionController.setVolumeTo */
+        public void setVolumeTo(@NonNull String packageName, @NonNull String opPackageName,
+                @NonNull ControllerCallbackLink caller, int value, int flags) {
+        }
+
+        /** Stub method for ISessionController.prepare */
+        public void prepare(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+        }
+
+        /** Stub method for ISessionController.prepareFromMediaId */
+        public void prepareFromMediaId(@NonNull String packageName,
+                @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
+                @Nullable Bundle extras) {
+        }
+
+        /** Stub method for ISessionController.prepareFromSearch */
+        public void prepareFromSearch(@NonNull String packageName,
+                @NonNull ControllerCallbackLink caller, @NonNull String query,
+                @Nullable Bundle extras) {
+        }
+
+        /** Stub method for ISessionController.prepareFromUri */
+        public void prepareFromUri(@NonNull String packageName,
+                @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) {
+        }
+
+        /** Stub method for ISessionController.play */
+        public void play(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+        }
+
+        /** Stub method for ISessionController.playFromMediaId */
+        public void playFromMediaId(@NonNull String packageName,
+                @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
+                @Nullable Bundle extras) {
+        }
+
+        /** Stub method for ISessionController.playFromSearch */
+        public void playFromSearch(@NonNull String packageName,
+                @NonNull ControllerCallbackLink caller, @NonNull String query,
+                @Nullable Bundle extras) {
+        }
+
+        /** Stub method for ISessionController.playFromUri */
+        public void playFromUri(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+                @NonNull Uri uri, @Nullable Bundle extras) {
+        }
+
+        /** Stub method for ISessionController.skipToQueueItem */
+        public void skipToQueueItem(@NonNull String packageName,
+                @NonNull ControllerCallbackLink caller, long id) {
+        }
+
+        /** Stub method for ISessionController.pause */
+        public void pause(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+        }
+
+        /** Stub method for ISessionController.stop */
+        public void stop(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+        }
+
+        /** Stub method for ISessionController.next */
+        public void next(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+        }
+
+        /** Stub method for ISessionController.previous */
+        public void previous(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+        }
+
+        /** Stub method for ISessionController.fastForward */
+        public void fastForward(@NonNull String packageName,
+                @NonNull ControllerCallbackLink caller) {
+        }
+
+        /** Stub method for ISessionController.rewind */
+        public void rewind(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+        }
+
+        /** Stub method for ISessionController.seekTo */
+        public void seekTo(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+                long pos) {
+        }
+
+        /** Stub method for ISessionController.rate */
+        public void rate(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+                @NonNull Rating rating) {
+        }
+
+        /** Stub method for ISessionController.sendCustomAction */
+        public void sendCustomAction(@NonNull String packageName,
+                @NonNull ControllerCallbackLink caller, @NonNull String action,
+                @Nullable Bundle args) {
+        }
+
+        /** Stub method for ISessionController.getMetadata */
+        @Nullable
+        public MediaMetadata getMetadata() {
+            return null;
+        }
+
+        /** Stub method for ISessionController.getPlaybackState */
+        @Nullable
+        public PlaybackState getPlaybackState() {
+            return null;
+        }
+
+        /** Stub method for ISessionController.getQueue */
+        @Nullable
+        public List<MediaSession.QueueItem> getQueue() {
+            return null;
+        }
+
+        /** Stub method for ISessionController.getQueueTitle */
+        @Nullable
+        public CharSequence getQueueTitle() {
+            return null;
+        }
+
+        /** Stub method for ISessionController.getExtras */
+        @Nullable
+        public Bundle getExtras() {
+            return null;
+        }
+
+        /** Stub method for ISessionController.getRatingType */
+        public int getRatingType() {
+            return Rating.RATING_NONE;
+        }
+    }
+
+    private class StubProxy extends ISessionController.Stub {
+        @Override
+        public void sendCommand(String packageName, ControllerCallbackLink caller,
+                String command, Bundle args, ResultReceiver cb) {
+            mControllerStub.sendCommand(packageName, caller, command, args, cb);
+        }
+
+        @Override
+        public boolean sendMediaButton(String packageName, ControllerCallbackLink caller,
+                boolean asSystemService, KeyEvent mediaButton) {
+            return mControllerStub.sendMediaButton(packageName, caller, asSystemService,
+                    mediaButton);
+        }
+
+        @Override
+        public void registerCallback(String packageName, ControllerCallbackLink cb) {
+            mControllerStub.registerCallback(packageName, cb);
+        }
+
+        @Override
+        public void unregisterCallback(ControllerCallbackLink cb) {
+            mControllerStub.unregisterCallback(cb);
+        }
+
+        @Override
+        public String getPackageName() {
+            return mControllerStub.getPackageName();
+        }
+
+        @Override
+        public String getTag() {
+            return mControllerStub.getTag();
+        }
+
+        @Override
+        public PendingIntent getLaunchPendingIntent() {
+            return mControllerStub.getLaunchPendingIntent();
+        }
+
+        @Override
+        public long getFlags() {
+            return mControllerStub.getFlags();
+        }
+
+        @Override
+        public PlaybackInfo getVolumeAttributes() {
+            return mControllerStub.getVolumeAttributes();
+        }
+
+        @Override
+        public void adjustVolume(String packageName, String opPackageName,
+                ControllerCallbackLink caller, boolean asSystemService, int direction,
+                int flags) {
+            mControllerStub.adjustVolume(packageName, opPackageName, caller, asSystemService,
+                    direction, flags);
+        }
+
+        @Override
+        public void setVolumeTo(String packageName, String opPackageName,
+                ControllerCallbackLink caller, int value, int flags) {
+            mControllerStub.setVolumeTo(packageName, opPackageName, caller, value, flags);
+        }
+
+        @Override
+        public void prepare(String packageName, ControllerCallbackLink caller) {
+            mControllerStub.prepare(packageName, caller);
+        }
+
+        @Override
+        public void prepareFromMediaId(String packageName, ControllerCallbackLink caller,
+                String mediaId, Bundle extras) {
+            mControllerStub.prepareFromMediaId(packageName, caller, mediaId, extras);
+        }
+
+        @Override
+        public void prepareFromSearch(String packageName, ControllerCallbackLink caller,
+                String query, Bundle extras) {
+            mControllerStub.prepareFromSearch(packageName, caller, query, extras);
+        }
+
+        @Override
+        public void prepareFromUri(String packageName, ControllerCallbackLink caller,
+                Uri uri, Bundle extras) {
+            mControllerStub.prepareFromUri(packageName, caller, uri, extras);
+        }
+
+        @Override
+        public void play(String packageName, ControllerCallbackLink caller) {
+            mControllerStub.play(packageName, caller);
+        }
+
+        @Override
+        public void playFromMediaId(String packageName, ControllerCallbackLink caller,
+                String mediaId, Bundle extras) {
+            mControllerStub.playFromMediaId(packageName, caller, mediaId, extras);
+        }
+
+        @Override
+        public void playFromSearch(String packageName, ControllerCallbackLink caller,
+                String query, Bundle extras) {
+            mControllerStub.playFromSearch(packageName, caller, query, extras);
+        }
+
+        @Override
+        public void playFromUri(String packageName, ControllerCallbackLink caller,
+                Uri uri, Bundle extras) {
+            mControllerStub.playFromUri(packageName, caller, uri, extras);
+        }
+
+        @Override
+        public void skipToQueueItem(String packageName, ControllerCallbackLink caller, long id) {
+            mControllerStub.skipToQueueItem(packageName, caller, id);
+        }
+
+        @Override
+        public void pause(String packageName, ControllerCallbackLink caller) {
+            mControllerStub.pause(packageName, caller);
+        }
+
+        @Override
+        public void stop(String packageName, ControllerCallbackLink caller) {
+            mControllerStub.stop(packageName, caller);
+        }
+
+        @Override
+        public void next(String packageName, ControllerCallbackLink caller) {
+            mControllerStub.next(packageName, caller);
+        }
+
+        @Override
+        public void previous(String packageName, ControllerCallbackLink caller) {
+            mControllerStub.previous(packageName, caller);
+        }
+
+        @Override
+        public void fastForward(String packageName, ControllerCallbackLink caller) {
+            mControllerStub.fastForward(packageName, caller);
+        }
+
+        @Override
+        public void rewind(String packageName, ControllerCallbackLink caller) {
+            mControllerStub.rewind(packageName, caller);
+        }
+
+        @Override
+        public void seekTo(String packageName, ControllerCallbackLink caller, long pos) {
+            mControllerStub.seekTo(packageName, caller, pos);
+        }
+
+        @Override
+        public void rate(String packageName, ControllerCallbackLink caller, Rating rating) {
+            mControllerStub.rate(packageName, caller, rating);
+        }
+
+        @Override
+        public void sendCustomAction(String packageName, ControllerCallbackLink caller,
+                String action, Bundle args) {
+            mControllerStub.sendCustomAction(packageName, caller, action, args);
+        }
+
+        @Override
+        public MediaMetadata getMetadata() {
+            return mControllerStub.getMetadata();
+        }
+
+        @Override
+        public PlaybackState getPlaybackState() {
+            return mControllerStub.getPlaybackState();
+        }
+
+        @Override
+        public List<MediaSession.QueueItem> getQueue() {
+            return mControllerStub.getQueue();
+        }
+
+        @Override
+        public CharSequence getQueueTitle() {
+            return mControllerStub.getQueueTitle();
+        }
+
+        @Override
+        public Bundle getExtras() {
+            return mControllerStub.getExtras();
+        }
+
+        @Override
+        public int getRatingType() {
+            return mControllerStub.getRatingType();
+        }
+    }
+}
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index 1524ad9..9b4e2bc 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -18,7 +18,7 @@
 import android.app.PendingIntent;
 import android.media.AudioAttributes;
 import android.media.MediaMetadata;
-import android.media.session.ISessionController;
+import android.media.session.ControllerLink;
 import android.media.session.PlaybackState;
 import android.media.session.MediaSession;
 import android.os.Bundle;
@@ -30,12 +30,12 @@
  */
 interface ISession {
     void sendEvent(String event, in Bundle data);
-    ISessionController getController();
+    ControllerLink getController();
     void setFlags(int flags);
     void setActive(boolean active);
     void setMediaButtonReceiver(in PendingIntent mbr);
     void setLaunchPendingIntent(in PendingIntent pi);
-    void destroy();
+    void destroySession();
 
     // These commands are for the TransportPerformer
     void setMetadata(in MediaMetadata metadata, long duration, String metadataDescription);
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index 2ba09fd..787cb77 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -39,9 +39,8 @@
             String command, in Bundle args, in ResultReceiver cb);
     boolean sendMediaButton(String packageName, in ControllerCallbackLink caller,
             boolean asSystemService, in KeyEvent mediaButton);
-    void registerCallbackListener(String packageName, in ControllerCallbackLink cb);
-    void unregisterCallbackListener(in ControllerCallbackLink cb);
-    boolean isTransportControlEnabled();
+    void registerCallback(String packageName, in ControllerCallbackLink cb);
+    void unregisterCallback(in ControllerCallbackLink cb);
     String getPackageName();
     String getTag();
     PendingIntent getLaunchPendingIntent();
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 46516e0..ed16250 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -18,13 +18,14 @@
 import android.content.ComponentName;
 import android.media.IRemoteVolumeController;
 import android.media.Session2Token;
+import android.media.session.ControllerLink;
 import android.media.session.IActiveSessionsListener;
 import android.media.session.ICallback;
 import android.media.session.IOnMediaKeyListener;
 import android.media.session.IOnVolumeKeyLongPressListener;
-import android.media.session.ISession;
 import android.media.session.ISession2TokensListener;
 import android.media.session.SessionCallbackLink;
+import android.media.session.SessionLink;
 import android.os.Bundle;
 import android.view.KeyEvent;
 
@@ -33,9 +34,10 @@
  * @hide
  */
 interface ISessionManager {
-    ISession createSession(String packageName, in SessionCallbackLink cb, String tag, int userId);
+    SessionLink createSession(String packageName, in SessionCallbackLink sessionCb, String tag,
+            int userId);
     void notifySession2Created(in Session2Token sessionToken);
-    List<IBinder> getSessions(in ComponentName compName, int userId);
+    List<ControllerLink> getSessions(in ComponentName compName, int userId);
     List<Session2Token> getSession2Tokens(int userId);
     void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent,
             boolean needWakeLock);
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index a1b8170..9d537c8 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -34,7 +34,6 @@
 import android.os.Message;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.text.TextUtils;
 import android.util.Log;
@@ -68,12 +67,11 @@
     private static final int MSG_UPDATE_EXTRAS = 7;
     private static final int MSG_DESTROYED = 8;
 
-    private final ISessionController mSessionBinder;
+    private final ControllerLink mSessionBinder;
 
     private final MediaSession.Token mToken;
     private final Context mContext;
-    private final ControllerCallbackLink mCbStub =
-            new ControllerCallbackLink(new CallbackStub(this));
+    private final ControllerCallbackLink mCbStub;
     private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>();
     private final Object mLock = new Object();
 
@@ -84,12 +82,11 @@
     private final TransportControls mTransportControls;
 
     /**
-     * Call for creating a MediaController directly from a binder. Should only
+     * Call for creating a MediaController directly from a controller link. Should only
      * be used by framework code.
-     *
      * @hide
      */
-    public MediaController(Context context, ISessionController sessionBinder) {
+    public MediaController(Context context, ControllerLink sessionBinder) {
         if (sessionBinder == null) {
             throw new IllegalArgumentException("Session token cannot be null");
         }
@@ -100,6 +97,17 @@
         mTransportControls = new TransportControls();
         mToken = new MediaSession.Token(sessionBinder);
         mContext = context;
+        mCbStub = new ControllerCallbackLink(context, new CallbackStub(this));
+    }
+
+    /**
+     * Call for creating a MediaController directly from a binder. Should only
+     * be used by framework code.
+     * @hide
+     * TODO: remove this constructor
+     */
+    public MediaController(Context context, ISessionController sessionBinder) {
+        this(context, new ControllerLink(sessionBinder.asBinder()));
     }
 
     /**
@@ -158,7 +166,7 @@
         try {
             return mSessionBinder.sendMediaButton(mContext.getPackageName(), mCbStub,
                     asSystemService, keyEvent);
-        } catch (RemoteException e) {
+        } catch (RuntimeException e) {
             // System is dead. =(
         }
         return false;
@@ -195,7 +203,7 @@
                     mSessionBinder.adjustVolume(mContext.getPackageName(),
                             mContext.getOpPackageName(), mCbStub, true, direction,
                             AudioManager.FLAG_SHOW_UI);
-                } catch (RemoteException e) {
+                } catch (RuntimeException e) {
                     Log.wtf(TAG, "Error calling adjustVolumeBy", e);
                 }
             }
@@ -209,7 +217,7 @@
                     //       AppOpsManager usages.
                     mSessionBinder.adjustVolume(mContext.getPackageName(),
                             mContext.getOpPackageName(), mCbStub, true, 0, flags);
-                } catch (RemoteException e) {
+                } catch (RuntimeException e) {
                     Log.wtf(TAG, "Error calling adjustVolumeBy", e);
                 }
             }
@@ -224,7 +232,7 @@
     public @Nullable PlaybackState getPlaybackState() {
         try {
             return mSessionBinder.getPlaybackState();
-        } catch (RemoteException e) {
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Error calling getPlaybackState.", e);
             return null;
         }
@@ -238,7 +246,7 @@
     public @Nullable MediaMetadata getMetadata() {
         try {
             return mSessionBinder.getMetadata();
-        } catch (RemoteException e) {
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Error calling getMetadata.", e);
             return null;
         }
@@ -253,7 +261,7 @@
     public @Nullable List<MediaSession.QueueItem> getQueue() {
         try {
             return mSessionBinder.getQueue();
-        } catch (RemoteException e) {
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Error calling getQueue.", e);
         }
         return null;
@@ -265,7 +273,7 @@
     public @Nullable CharSequence getQueueTitle() {
         try {
             return mSessionBinder.getQueueTitle();
-        } catch (RemoteException e) {
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Error calling getQueueTitle", e);
         }
         return null;
@@ -277,7 +285,7 @@
     public @Nullable Bundle getExtras() {
         try {
             return mSessionBinder.getExtras();
-        } catch (RemoteException e) {
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Error calling getExtras", e);
         }
         return null;
@@ -300,7 +308,7 @@
     public int getRatingType() {
         try {
             return mSessionBinder.getRatingType();
-        } catch (RemoteException e) {
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Error calling getRatingType.", e);
             return Rating.RATING_NONE;
         }
@@ -314,7 +322,7 @@
     public @MediaSession.SessionFlags long getFlags() {
         try {
             return mSessionBinder.getFlags();
-        } catch (RemoteException e) {
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Error calling getFlags.", e);
         }
         return 0;
@@ -328,7 +336,7 @@
     public @Nullable PlaybackInfo getPlaybackInfo() {
         try {
             return mSessionBinder.getVolumeAttributes();
-        } catch (RemoteException e) {
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Error calling getAudioInfo.", e);
         }
         return null;
@@ -343,7 +351,7 @@
     public @Nullable PendingIntent getSessionActivity() {
         try {
             return mSessionBinder.getLaunchPendingIntent();
-        } catch (RemoteException e) {
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Error calling getPendingIntent.", e);
         }
         return null;
@@ -376,7 +384,7 @@
             //       AppOpsManager usages.
             mSessionBinder.setVolumeTo(mContext.getPackageName(), mContext.getOpPackageName(),
                     mCbStub, value, flags);
-        } catch (RemoteException e) {
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Error calling setVolumeTo.", e);
         }
     }
@@ -401,7 +409,7 @@
             //       AppOpsManager usages.
             mSessionBinder.adjustVolume(mContext.getPackageName(), mContext.getOpPackageName(),
                     mCbStub, false, direction, flags);
-        } catch (RemoteException e) {
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Error calling adjustVolumeBy.", e);
         }
     }
@@ -467,7 +475,7 @@
         }
         try {
             mSessionBinder.sendCommand(mContext.getPackageName(), mCbStub, command, args, cb);
-        } catch (RemoteException e) {
+        } catch (RuntimeException e) {
             Log.d(TAG, "Dead object in sendCommand.", e);
         }
     }
@@ -481,7 +489,7 @@
         if (mPackageName == null) {
             try {
                 mPackageName = mSessionBinder.getPackageName();
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.d(TAG, "Dead object in getPackageName.", e);
             }
         }
@@ -498,7 +506,7 @@
         if (mTag == null) {
             try {
                 mTag = mSessionBinder.getTag();
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.d(TAG, "Dead object in getTag.", e);
             }
         }
@@ -508,7 +516,7 @@
     /*
      * @hide
      */
-    ISessionController getSessionBinder() {
+    ControllerLink getSessionBinder() {
         return mSessionBinder;
     }
 
@@ -518,7 +526,7 @@
     @UnsupportedAppUsage
     public boolean controlsSameSession(MediaController other) {
         if (other == null) return false;
-        return mSessionBinder.asBinder() == other.getSessionBinder().asBinder();
+        return mSessionBinder.getBinder() == other.getSessionBinder().getBinder();
     }
 
     private void addCallbackLocked(Callback cb, Handler handler) {
@@ -532,9 +540,9 @@
 
         if (!mCbRegistered) {
             try {
-                mSessionBinder.registerCallbackListener(mContext.getPackageName(), mCbStub);
+                mSessionBinder.registerCallback(mContext.getPackageName(), mCbStub);
                 mCbRegistered = true;
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.e(TAG, "Dead object in registerCallback", e);
             }
         }
@@ -552,8 +560,8 @@
         }
         if (mCbRegistered && mCallbacks.size() == 0) {
             try {
-                mSessionBinder.unregisterCallbackListener(mCbStub);
-            } catch (RemoteException e) {
+                mSessionBinder.unregisterCallback(mCbStub);
+            } catch (RuntimeException e) {
                 Log.e(TAG, "Dead object in removeCallbackLocked");
             }
             mCbRegistered = false;
@@ -680,7 +688,7 @@
         public void prepare() {
             try {
                 mSessionBinder.prepare(mContext.getPackageName(), mCbStub);
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.wtf(TAG, "Error calling prepare.", e);
             }
         }
@@ -705,7 +713,7 @@
             try {
                 mSessionBinder.prepareFromMediaId(mContext.getPackageName(), mCbStub, mediaId,
                         extras);
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.wtf(TAG, "Error calling prepare(" + mediaId + ").", e);
             }
         }
@@ -732,7 +740,7 @@
             try {
                 mSessionBinder.prepareFromSearch(mContext.getPackageName(), mCbStub, query,
                         extras);
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.wtf(TAG, "Error calling prepare(" + query + ").", e);
             }
         }
@@ -756,7 +764,7 @@
             }
             try {
                 mSessionBinder.prepareFromUri(mContext.getPackageName(), mCbStub, uri, extras);
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.wtf(TAG, "Error calling prepare(" + uri + ").", e);
             }
         }
@@ -767,7 +775,7 @@
         public void play() {
             try {
                 mSessionBinder.play(mContext.getPackageName(), mCbStub);
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.wtf(TAG, "Error calling play.", e);
             }
         }
@@ -787,7 +795,7 @@
             try {
                 mSessionBinder.playFromMediaId(mContext.getPackageName(), mCbStub, mediaId,
                         extras);
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.wtf(TAG, "Error calling play(" + mediaId + ").", e);
             }
         }
@@ -809,7 +817,7 @@
             }
             try {
                 mSessionBinder.playFromSearch(mContext.getPackageName(), mCbStub, query, extras);
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.wtf(TAG, "Error calling play(" + query + ").", e);
             }
         }
@@ -828,7 +836,7 @@
             }
             try {
                 mSessionBinder.playFromUri(mContext.getPackageName(), mCbStub, uri, extras);
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.wtf(TAG, "Error calling play(" + uri + ").", e);
             }
         }
@@ -840,7 +848,7 @@
         public void skipToQueueItem(long id) {
             try {
                 mSessionBinder.skipToQueueItem(mContext.getPackageName(), mCbStub, id);
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.wtf(TAG, "Error calling skipToItem(" + id + ").", e);
             }
         }
@@ -852,7 +860,7 @@
         public void pause() {
             try {
                 mSessionBinder.pause(mContext.getPackageName(), mCbStub);
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.wtf(TAG, "Error calling pause.", e);
             }
         }
@@ -864,7 +872,7 @@
         public void stop() {
             try {
                 mSessionBinder.stop(mContext.getPackageName(), mCbStub);
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.wtf(TAG, "Error calling stop.", e);
             }
         }
@@ -877,7 +885,7 @@
         public void seekTo(long pos) {
             try {
                 mSessionBinder.seekTo(mContext.getPackageName(), mCbStub, pos);
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.wtf(TAG, "Error calling seekTo.", e);
             }
         }
@@ -889,7 +897,7 @@
         public void fastForward() {
             try {
                 mSessionBinder.fastForward(mContext.getPackageName(), mCbStub);
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.wtf(TAG, "Error calling fastForward.", e);
             }
         }
@@ -900,7 +908,7 @@
         public void skipToNext() {
             try {
                 mSessionBinder.next(mContext.getPackageName(), mCbStub);
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.wtf(TAG, "Error calling next.", e);
             }
         }
@@ -912,7 +920,7 @@
         public void rewind() {
             try {
                 mSessionBinder.rewind(mContext.getPackageName(), mCbStub);
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.wtf(TAG, "Error calling rewind.", e);
             }
         }
@@ -923,7 +931,7 @@
         public void skipToPrevious() {
             try {
                 mSessionBinder.previous(mContext.getPackageName(), mCbStub);
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.wtf(TAG, "Error calling previous.", e);
             }
         }
@@ -938,7 +946,7 @@
         public void setRating(Rating rating) {
             try {
                 mSessionBinder.rate(mContext.getPackageName(), mCbStub, rating);
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.wtf(TAG, "Error calling rate.", e);
             }
         }
@@ -973,7 +981,7 @@
             }
             try {
                 mSessionBinder.sendCustomAction(mContext.getPackageName(), mCbStub, action, args);
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Log.d(TAG, "Dead object in sendCustomAction.", e);
             }
         }
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index e07cf15..881e6ee 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -37,9 +37,7 @@
 import android.os.Message;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.RemoteException;
 import android.os.ResultReceiver;
-import android.os.UserHandle;
 import android.service.media.MediaBrowserService;
 import android.text.TextUtils;
 import android.util.Log;
@@ -128,7 +126,7 @@
 
     private final MediaSession.Token mSessionToken;
     private final MediaController mController;
-    private final ISession mBinder;
+    private final SessionLink mSessionLink;
     private final SessionCallbackLink mCbStub;
 
     // Do not change the name of mCallback. Support lib accesses this by using reflection.
@@ -149,21 +147,6 @@
      * @param tag A short name for debugging purposes.
      */
     public MediaSession(@NonNull Context context, @NonNull String tag) {
-        this(context, tag, UserHandle.myUserId());
-    }
-
-    /**
-     * Creates a new session as the specified user. To create a session as a
-     * user other than your own you must hold the
-     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
-     * permission.
-     *
-     * @param context The context to use to create the session.
-     * @param tag A short name for debugging purposes.
-     * @param userId The user id to create the session as.
-     * @hide
-     */
-    public MediaSession(@NonNull Context context, @NonNull String tag, int userId) {
         if (context == null) {
             throw new IllegalArgumentException("context cannot be null.");
         }
@@ -172,14 +155,14 @@
         }
         mMaxBitmapSize = context.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize);
-        mCbStub = new SessionCallbackLink(new CallbackStub(this));
+        mCbStub = new SessionCallbackLink(context, new CallbackStub(this));
         MediaSessionManager manager = (MediaSessionManager) context
                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
         try {
-            mBinder = manager.createSession(mCbStub, tag, userId);
-            mSessionToken = new Token(mBinder.getController());
+            mSessionLink = manager.createSession(mCbStub, tag);
+            mSessionToken = new Token(mSessionLink.getController());
             mController = new MediaController(context, mSessionToken);
-        } catch (RemoteException e) {
+        } catch (RuntimeException e) {
             throw new RuntimeException("Remote error creating session.", e);
         }
     }
@@ -236,8 +219,8 @@
      */
     public void setSessionActivity(@Nullable PendingIntent pi) {
         try {
-            mBinder.setLaunchPendingIntent(pi);
-        } catch (RemoteException e) {
+            mSessionLink.setLaunchPendingIntent(pi);
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Failure in setLaunchPendingIntent.", e);
         }
     }
@@ -252,8 +235,8 @@
      */
     public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
         try {
-            mBinder.setMediaButtonReceiver(mbr);
-        } catch (RemoteException e) {
+            mSessionLink.setMediaButtonReceiver(mbr);
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
         }
     }
@@ -265,8 +248,8 @@
      */
     public void setFlags(@SessionFlags int flags) {
         try {
-            mBinder.setFlags(flags);
-        } catch (RemoteException e) {
+            mSessionLink.setFlags(flags);
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Failure in setFlags.", e);
         }
     }
@@ -287,8 +270,8 @@
             throw new IllegalArgumentException("Attributes cannot be null for local playback.");
         }
         try {
-            mBinder.setPlaybackToLocal(attributes);
-        } catch (RemoteException e) {
+            mSessionLink.setPlaybackToLocal(attributes);
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
         }
     }
@@ -319,10 +302,10 @@
         });
 
         try {
-            mBinder.setPlaybackToRemote(volumeProvider.getVolumeControl(),
+            mSessionLink.setPlaybackToRemote(volumeProvider.getVolumeControl(),
                     volumeProvider.getMaxVolume());
-            mBinder.setCurrentVolume(volumeProvider.getCurrentVolume());
-        } catch (RemoteException e) {
+            mSessionLink.setCurrentVolume(volumeProvider.getCurrentVolume());
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
         }
     }
@@ -340,9 +323,9 @@
             return;
         }
         try {
-            mBinder.setActive(active);
+            mSessionLink.setActive(active);
             mActive = active;
-        } catch (RemoteException e) {
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Failure in setActive.", e);
         }
     }
@@ -369,8 +352,8 @@
             throw new IllegalArgumentException("event cannot be null or empty");
         }
         try {
-            mBinder.sendEvent(event, extras);
-        } catch (RemoteException e) {
+            mSessionLink.sendEvent(event, extras);
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Error sending event", e);
         }
     }
@@ -382,8 +365,8 @@
      */
     public void release() {
         try {
-            mBinder.destroy();
-        } catch (RemoteException e) {
+            mSessionLink.destroySession();
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Error releasing session: ", e);
         }
     }
@@ -418,8 +401,8 @@
     public void setPlaybackState(@Nullable PlaybackState state) {
         mPlaybackState = state;
         try {
-            mBinder.setPlaybackState(state);
-        } catch (RemoteException e) {
+            mSessionLink.setPlaybackState(state);
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Dead object in setPlaybackState.", e);
         }
     }
@@ -447,8 +430,8 @@
         String metadataDescription = "size=" + fields + ", description=" + description;
 
         try {
-            mBinder.setMetadata(metadata, duration, metadataDescription);
-        } catch (RemoteException e) {
+            mSessionLink.setMetadata(metadata, duration, metadataDescription);
+        } catch (RuntimeException e) {
             Log.wtf(TAG, "Dead object in setPlaybackState.", e);
         }
     }
@@ -466,8 +449,8 @@
      */
     public void setQueue(@Nullable List<QueueItem> queue) {
         try {
-            mBinder.setQueue(queue);
-        } catch (RemoteException e) {
+            mSessionLink.setQueue(queue);
+        } catch (RuntimeException e) {
             Log.wtf("Dead object in setQueue.", e);
         }
     }
@@ -481,8 +464,8 @@
      */
     public void setQueueTitle(@Nullable CharSequence title) {
         try {
-            mBinder.setQueueTitle(title);
-        } catch (RemoteException e) {
+            mSessionLink.setQueueTitle(title);
+        } catch (RuntimeException e) {
             Log.wtf("Dead object in setQueueTitle.", e);
         }
     }
@@ -502,8 +485,8 @@
      */
     public void setRatingType(@Rating.Style int type) {
         try {
-            mBinder.setRatingType(type);
-        } catch (RemoteException e) {
+            mSessionLink.setRatingType(type);
+        } catch (RuntimeException e) {
             Log.e(TAG, "Error in setRatingType.", e);
         }
     }
@@ -517,8 +500,8 @@
      */
     public void setExtras(@Nullable Bundle extras) {
         try {
-            mBinder.setExtras(extras);
-        } catch (RemoteException e) {
+            mSessionLink.setExtras(extras);
+        } catch (RuntimeException e) {
             Log.wtf("Dead object in setExtras.", e);
         }
     }
@@ -553,8 +536,8 @@
             }
         }
         try {
-            mBinder.setCurrentVolume(provider.getCurrentVolume());
-        } catch (RemoteException e) {
+            mSessionLink.setCurrentVolume(provider.getCurrentVolume());
+        } catch (RuntimeException e) {
             Log.e(TAG, "Error in notifyVolumeChanged", e);
         }
     }
@@ -709,12 +692,12 @@
      */
     public static final class Token implements Parcelable {
 
-        private ISessionController mBinder;
+        private ControllerLink mBinder;
 
         /**
          * @hide
          */
-        public Token(ISessionController binder) {
+        public Token(ControllerLink binder) {
             mBinder = binder;
         }
 
@@ -725,14 +708,14 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeStrongBinder(mBinder.asBinder());
+            dest.writeParcelable(mBinder, flags);
         }
 
         @Override
         public int hashCode() {
             final int prime = 31;
             int result = 1;
-            result = prime * result + ((mBinder == null) ? 0 : mBinder.asBinder().hashCode());
+            result = prime * result + ((mBinder == null) ? 0 : mBinder.getBinder().hashCode());
             return result;
         }
 
@@ -748,20 +731,22 @@
             if (mBinder == null) {
                 if (other.mBinder != null)
                     return false;
-            } else if (!mBinder.asBinder().equals(other.mBinder.asBinder()))
+            } else if (!mBinder.getBinder().equals(other.mBinder.getBinder())) {
                 return false;
+            }
             return true;
         }
 
-        ISessionController getBinder() {
+        ControllerLink getBinder() {
             return mBinder;
         }
 
-        public static final Parcelable.Creator<Token> CREATOR
-                = new Parcelable.Creator<Token>() {
+        public static final Parcelable.Creator<Token> CREATOR =
+                new Parcelable.Creator<Token>() {
             @Override
             public Token createFromParcel(Parcel in) {
-                return new Token(ISessionController.Stub.asInterface(in.readStrongBinder()));
+                ControllerLink link = in.readParcelable(null);
+                return new Token(link);
             }
 
             @Override
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 4596c22..77e758fc 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -104,9 +104,14 @@
      * @return The binder object from the system
      * @hide
      */
-    public @NonNull ISession createSession(@NonNull SessionCallbackLink cbStub,
-            @NonNull String tag, int userId) throws RemoteException {
-        return mService.createSession(mContext.getPackageName(), cbStub, tag, userId);
+    @NonNull
+    public SessionLink createSession(@NonNull SessionCallbackLink cbStub, @NonNull String tag) {
+        try {
+            return mService.createSession(mContext.getPackageName(), cbStub, tag,
+                    UserHandle.myUserId());
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
     }
 
     /**
@@ -171,11 +176,10 @@
             @Nullable ComponentName notificationListener, int userId) {
         ArrayList<MediaController> controllers = new ArrayList<MediaController>();
         try {
-            List<IBinder> binders = mService.getSessions(notificationListener, userId);
+            List<ControllerLink> binders = mService.getSessions(notificationListener, userId);
             int size = binders.size();
             for (int i = 0; i < size; i++) {
-                MediaController controller = new MediaController(mContext, ISessionController.Stub
-                        .asInterface(binders.get(i)));
+                MediaController controller = new MediaController(mContext, binders.get(i));
                 controllers.add(controller);
             }
         } catch (RemoteException e) {
diff --git a/media/java/android/media/session/SessionCallbackLink.java b/media/java/android/media/session/SessionCallbackLink.java
index 7547bff..0265687b 100644
--- a/media/java/android/media/session/SessionCallbackLink.java
+++ b/media/java/android/media/session/SessionCallbackLink.java
@@ -16,31 +16,40 @@
 
 package android.media.session;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.media.Rating;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 
 /**
  * Handles incoming commands to {@link MediaSession.Callback}.
- * <p>
- * This API is not generally intended for third party application developers.
+ * @hide
  */
+@SystemApi
 public final class SessionCallbackLink implements Parcelable {
+    final Context mContext;
     final CallbackStub mCallbackStub;
     final ISessionCallback mISessionCallback;
 
     /**
      * Constructor for stub (Callee)
      */
-    SessionCallbackLink(@NonNull CallbackStub callbackStub) {
+    SessionCallbackLink(@NonNull Context context, @NonNull CallbackStub callbackStub) {
+        mContext = context;
         mCallbackStub = callbackStub;
         mISessionCallback = new CallbackStubProxy();
     }
@@ -48,9 +57,10 @@
     /**
      * Constructor for interface (Caller)
      */
-    SessionCallbackLink(Parcel in) {
+    public SessionCallbackLink(IBinder binder) {
+        mContext = null;
         mCallbackStub = null;
-        mISessionCallback = ISessionCallback.Stub.asInterface(in.readStrongBinder());
+        mISessionCallback = ISessionCallback.Stub.asInterface(binder);
     }
 
     /**
@@ -64,6 +74,7 @@
      * @param args the arguments included with the command
      * @param cb the result receiver for getting the result of the command
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyCommand(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller, @NonNull String command,
             @Nullable Bundle args, @Nullable ResultReceiver cb) {
@@ -84,6 +95,7 @@
      * @param sequenceNumber the sequence number of this call
      * @param cb the result receiver for getting the result of the command
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyMediaButton(@NonNull String packageName, int pid, int uid,
             @NonNull Intent mediaButtonIntent, int sequenceNumber,
             @Nullable ResultReceiver cb) {
@@ -104,6 +116,7 @@
      * @param caller the {@link ControllerCallbackLink} of the controller
      * @param mediaButtonIntent the media button intent
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyMediaButtonFromController(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller, @NonNull Intent mediaButtonIntent) {
         try {
@@ -122,6 +135,7 @@
      * @param uid the uid of the controller
      * @param caller the {@link ControllerCallbackLink} of the controller
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyPrepare(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller) {
         try {
@@ -141,6 +155,7 @@
      * @param mediaId the ID of the media
      * @param extras the extras included with this request.
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyPrepareFromMediaId(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
             @Nullable Bundle extras) {
@@ -162,6 +177,7 @@
      * @param query the search query
      * @param extras the extras included with this request.
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyPrepareFromSearch(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller, @NonNull String query,
             @Nullable Bundle extras) {
@@ -182,6 +198,7 @@
      * @param uri the uri of the media
      * @param extras the extras included with this request.
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyPrepareFromUri(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) {
         try {
@@ -199,6 +216,7 @@
      * @param uid the uid of the controller
      * @param caller the {@link ControllerCallbackLink} of the controller
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyPlay(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller) {
         try {
@@ -218,6 +236,7 @@
      * @param mediaId the ID of the media
      * @param extras the extras included with this request.
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyPlayFromMediaId(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
             @Nullable Bundle extras) {
@@ -238,6 +257,7 @@
      * @param query the search query
      * @param extras the extras included with this request.
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyPlayFromSearch(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller, @NonNull String query,
             @Nullable Bundle extras) {
@@ -258,6 +278,7 @@
      * @param uri the uri of the media
      * @param extras the extras included with this request.
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyPlayFromUri(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) {
         try {
@@ -276,6 +297,7 @@
      * @param caller the {@link ControllerCallbackLink} of the controller
      * @param id the queue id of the item
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifySkipToTrack(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller, long id) {
         try {
@@ -293,6 +315,7 @@
      * @param uid the uid of the controller
      * @param caller the {@link ControllerCallbackLink} of the controller
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyPause(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller) {
         try {
@@ -310,6 +333,7 @@
      * @param uid the uid of the controller
      * @param caller the {@link ControllerCallbackLink} of the controller
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyStop(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller) {
         try {
@@ -327,6 +351,7 @@
      * @param uid the uid of the controller
      * @param caller the {@link ControllerCallbackLink} of the controller
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyNext(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller) {
         try {
@@ -344,6 +369,7 @@
      * @param uid the uid of the controller
      * @param caller the {@link ControllerCallbackLink} of the controller
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyPrevious(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller) {
         try {
@@ -361,6 +387,7 @@
      * @param uid the uid of the controller
      * @param caller the {@link ControllerCallbackLink} of the controller
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyFastForward(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller) {
         try {
@@ -378,6 +405,7 @@
      * @param uid the uid of the controller
      * @param caller the {@link ControllerCallbackLink} of the controller
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyRewind(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller) {
         try {
@@ -396,6 +424,7 @@
      * @param caller the {@link ControllerCallbackLink} of the controller
      * @param pos the position to move to, in milliseconds
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifySeekTo(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller, long pos) {
         try {
@@ -414,6 +443,7 @@
      * @param caller the {@link ControllerCallbackLink} of the controller
      * @param rating the rating of the current media
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyRate(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller, @NonNull Rating rating) {
         try {
@@ -433,6 +463,7 @@
      * @param action the name of the action
      * @param args the arguments included with this action
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyCustomAction(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller, @NonNull String action, @Nullable Bundle args) {
         try {
@@ -451,6 +482,7 @@
      * @param caller the {@link ControllerCallbackLink} of the controller
      * @param direction the direction of the volume change.
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyAdjustVolume(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller, int direction) {
         try {
@@ -469,6 +501,7 @@
      * @param caller the {@link ControllerCallbackLink} of the controller
      * @param value the volume value to set
      */
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifySetVolumeTo(@NonNull String packageName, int pid, int uid,
             @NonNull ControllerCallbackLink caller, int value) {
         try {
@@ -498,7 +531,7 @@
             new Parcelable.Creator<SessionCallbackLink>() {
                 @Override
                 public SessionCallbackLink createFromParcel(Parcel in) {
-                    return new SessionCallbackLink(in);
+                    return new SessionCallbackLink(in.readStrongBinder());
                 }
 
                 @Override
@@ -636,141 +669,296 @@
         @Override
         public void notifyCommand(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
-            mCallbackStub.onCommand(packageName, pid, uid, caller, command, args, cb);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onCommand(packageName, pid, uid, caller, command, args, cb);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyMediaButton(String packageName, int pid, int uid,
                 Intent mediaButtonIntent, int sequenceNumber, ResultReceiver cb) {
-            mCallbackStub.onMediaButton(packageName, pid, uid, mediaButtonIntent, sequenceNumber,
-                    cb);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onMediaButton(packageName, pid, uid, mediaButtonIntent,
+                        sequenceNumber, cb);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyMediaButtonFromController(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, Intent mediaButtonIntent) {
-            mCallbackStub.onMediaButtonFromController(packageName, pid, uid, caller,
-                    mediaButtonIntent);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onMediaButtonFromController(packageName, pid, uid, caller,
+                        mediaButtonIntent);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyPrepare(String packageName, int pid, int uid,
                 ControllerCallbackLink caller) {
-            mCallbackStub.onPrepare(packageName, pid, uid, caller);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onPrepare(packageName, pid, uid, caller);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyPrepareFromMediaId(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, String mediaId, Bundle extras) {
-            mCallbackStub.onPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyPrepareFromSearch(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, String query, Bundle extras) {
-            mCallbackStub.onPrepareFromSearch(packageName, pid, uid, caller, query, extras);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onPrepareFromSearch(packageName, pid, uid, caller, query, extras);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyPrepareFromUri(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, Uri uri, Bundle extras) {
-            mCallbackStub.onPrepareFromUri(packageName, pid, uid, caller, uri, extras);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onPrepareFromUri(packageName, pid, uid, caller, uri, extras);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyPlay(String packageName, int pid, int uid,
                 ControllerCallbackLink caller) {
-            mCallbackStub.onPlay(packageName, pid, uid, caller);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onPlay(packageName, pid, uid, caller);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyPlayFromMediaId(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, String mediaId, Bundle extras) {
-            mCallbackStub.onPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyPlayFromSearch(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, String query, Bundle extras) {
-            mCallbackStub.onPlayFromSearch(packageName, pid, uid, caller, query, extras);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onPlayFromSearch(packageName, pid, uid, caller, query, extras);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyPlayFromUri(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, Uri uri, Bundle extras) {
-            mCallbackStub.onPlayFromUri(packageName, pid, uid, caller, uri, extras);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onPlayFromUri(packageName, pid, uid, caller, uri, extras);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifySkipToTrack(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, long id) {
-            mCallbackStub.onSkipToTrack(packageName, pid, uid, caller, id);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onSkipToTrack(packageName, pid, uid, caller, id);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyPause(String packageName, int pid, int uid,
                 ControllerCallbackLink caller) {
-            mCallbackStub.onPause(packageName, pid, uid, caller);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onPause(packageName, pid, uid, caller);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyStop(String packageName, int pid, int uid,
                 ControllerCallbackLink caller) {
-            mCallbackStub.onStop(packageName, pid, uid, caller);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onStop(packageName, pid, uid, caller);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyNext(String packageName, int pid, int uid,
                 ControllerCallbackLink caller) {
-            mCallbackStub.onNext(packageName, pid, uid, caller);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onNext(packageName, pid, uid, caller);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyPrevious(String packageName, int pid, int uid,
                 ControllerCallbackLink caller) {
-            mCallbackStub.onPrevious(packageName, pid, uid, caller);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onPrevious(packageName, pid, uid, caller);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyFastForward(String packageName, int pid, int uid,
                 ControllerCallbackLink caller) {
-            mCallbackStub.onFastForward(packageName, pid, uid, caller);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onFastForward(packageName, pid, uid, caller);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyRewind(String packageName, int pid, int uid,
                 ControllerCallbackLink caller) {
-            mCallbackStub.onRewind(packageName, pid, uid, caller);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onRewind(packageName, pid, uid, caller);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifySeekTo(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, long pos) {
-            mCallbackStub.onSeekTo(packageName, pid, uid, caller, pos);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onSeekTo(packageName, pid, uid, caller, pos);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
                 Rating rating) {
-            mCallbackStub.onRate(packageName, pid, uid, caller, rating);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onRate(packageName, pid, uid, caller, rating);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
-        @Override
         public void notifyCustomAction(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, String action, Bundle args) {
-            mCallbackStub.onCustomAction(packageName, pid, uid, caller, action, args);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onCustomAction(packageName, pid, uid, caller, action, args);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifyAdjustVolume(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, int direction) {
-            mCallbackStub.onAdjustVolume(packageName, pid, uid, caller, direction);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onAdjustVolume(packageName, pid, uid, caller, direction);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
         public void notifySetVolumeTo(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, int value) {
-            mCallbackStub.onSetVolumeTo(packageName, pid, uid, caller, value);
+            ensureMediasControlPermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallbackStub.onSetVolumeTo(packageName, pid, uid, caller, value);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        private void ensureMediasControlPermission() {
+            // Allow API calls from the System UI
+            if (mContext.checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
+                    == PackageManager.PERMISSION_GRANTED) {
+                return;
+            }
+
+            // Check if it's system server or has MEDIA_CONTENT_CONTROL.
+            // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
+            // check here.
+            if (getCallingUid() == Process.SYSTEM_UID || mContext.checkCallingPermission(
+                    android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+                    == PackageManager.PERMISSION_GRANTED) {
+                return;
+            }
+            throw new SecurityException("Must hold the MEDIA_CONTENT_CONTROL permission.");
         }
     }
 }
diff --git a/media/java/android/media/session/SessionLink.aidl b/media/java/android/media/session/SessionLink.aidl
new file mode 100644
index 0000000..c3be23e
--- /dev/null
+++ b/media/java/android/media/session/SessionLink.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2019 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.session;
+
+parcelable SessionLink;
diff --git a/media/java/android/media/session/SessionLink.java b/media/java/android/media/session/SessionLink.java
new file mode 100644
index 0000000..466077e
--- /dev/null
+++ b/media/java/android/media/session/SessionLink.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright 2019 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.session;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.media.AudioAttributes;
+import android.media.MediaMetadata;
+import android.media.Rating;
+import android.media.VolumeProvider;
+import android.media.session.MediaSession.QueueItem;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import java.util.List;
+
+/**
+ * Handles incoming commands from {@link MediaSession}.
+ * @hide
+ */
+@SystemApi
+public final class SessionLink implements Parcelable {
+    public static final Parcelable.Creator<SessionLink> CREATOR =
+            new Parcelable.Creator<SessionLink>() {
+                @Override
+                public SessionLink createFromParcel(Parcel in) {
+                    return new SessionLink(in.readStrongBinder());
+                }
+
+                @Override
+                public SessionLink[] newArray(int size) {
+                    return new SessionLink[size];
+                }
+            };
+
+    final SessionStub mSessionStub;
+    final ISession mISession;
+
+    /**
+     * Constructor for stub (Callee)
+     */
+    public SessionLink(@NonNull SessionStub sessionStub) {
+        mSessionStub = sessionStub;
+        mISession = new StubProxy();
+    }
+
+    /**
+     * Constructor for interface (Caller)
+     */
+    public SessionLink(IBinder binder) {
+        mSessionStub = null;
+        mISession = ISession.Stub.asInterface(binder);
+    }
+
+    /**
+     * Tell system that the session sends an event to all the connected controllers.
+     *
+     * @param event the name of the event
+     * @param extras the extras included with the event
+     */
+    void sendEvent(@NonNull String event, @Nullable Bundle extras) {
+        try {
+            mISession.sendEvent(event, extras);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Gets the controller link from the system.
+     */
+    @NonNull
+    ControllerLink getController() {
+        try {
+            return mISession.getController();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that the session sets the flags.
+     *
+     * @param flags the new session flags
+     */
+    void setFlags(int flags) {
+        try {
+            mISession.setFlags(flags);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that the session is (in)active.
+     *
+     * @param active the new activeness state
+     */
+    void setActive(boolean active) {
+        try {
+            mISession.setActive(active);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that the session sets the media button receiver.
+     *
+     * @param mbr the pending intent for media button receiver
+     */
+    void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
+        try {
+            mISession.setMediaButtonReceiver(mbr);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that the session sets the pending intent for launching UI.
+     *
+     * @param pi the pending intent for launching UI
+     */
+    void setLaunchPendingIntent(@Nullable PendingIntent pi) {
+        try {
+            mISession.setLaunchPendingIntent(pi);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that the session is destroyed.
+     */
+    void destroySession() {
+        try {
+            mISession.destroySession();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that the session sets the new metadata.
+     *
+     * @param metadata the new metadata
+     * @param duration the duration of the media in milliseconds
+     * @param metadataDescription the description of the metadata
+     */
+    void setMetadata(@Nullable MediaMetadata metadata, long duration,
+            @Nullable String metadataDescription) {
+        try {
+            mISession.setMetadata(metadata, duration, metadataDescription);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that the session sets the new playback state.
+     *
+     * @param state the new playback state
+     */
+    void setPlaybackState(@Nullable PlaybackState state) {
+        try {
+            mISession.setPlaybackState(state);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that the session sets the new queue.
+     *
+     * @param queue the new queue
+     */
+    void setQueue(@Nullable List<QueueItem> queue) {
+        try {
+            mISession.setQueue(queue);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that the session sets the new queue title.
+     *
+     * @param title the new queue title
+     */
+    void setQueueTitle(@Nullable CharSequence title) {
+        try {
+            mISession.setQueueTitle(title);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that the session sets the new extras.
+     *
+     * @param extras the new extras
+     */
+    void setExtras(@Nullable Bundle extras) {
+        try {
+            mISession.setExtras(extras);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that the session sets the new rating type of the current media.
+     *
+     * @param type the rating type.
+     */
+    void setRatingType(@Rating.Style int type) {
+        try {
+            mISession.setRatingType(type);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that the session represents a local playback.
+     *
+     * @param attributes the audio attributes of the local playback.
+     */
+    void setPlaybackToLocal(@NonNull AudioAttributes attributes) {
+        try {
+            mISession.setPlaybackToLocal(attributes);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that the session represents a remote playback.
+     *
+     * @param control the volume control type
+     * @param max the max volume
+     */
+    void setPlaybackToRemote(@VolumeProvider.ControlType int control, int max) {
+        try {
+            mISession.setPlaybackToRemote(control, max);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Tell system that the session sets the new current volume.
+     *
+     * @param currentVolume the new current volume
+     */
+    void setCurrentVolume(int currentVolume) {
+        try {
+            mISession.setCurrentVolume(currentVolume);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /** Gets the binder */
+    @NonNull
+    public IBinder getBinder() {
+        return mISession.asBinder();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongBinder(mISession.asBinder());
+    }
+
+    /**
+     * Class for Stub implementation
+     */
+    public abstract static class SessionStub {
+        /** Stub method for ISession.sendEvent */
+        public void sendEvent(@NonNull String event, @Nullable Bundle data) {
+        }
+
+        /** Stub method for ISession.getController */
+        @NonNull
+        public ControllerLink getController() {
+            return null;
+        }
+
+        /** Stub method for ISession.setFlags */
+        public void setFlags(int flags) {
+        }
+
+        /** Stub method for ISession.setActive */
+        public void setActive(boolean active) {
+        }
+
+        /** Stub method for ISession.setMediaButtonReceiver */
+        public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
+        }
+
+        /** Stub method for ISession.setLaunchPendingIntent */
+        public void setLaunchPendingIntent(@Nullable PendingIntent pi) {
+        }
+
+        /** Stub method for ISession.destroySession */
+        public void destroySession() {
+        }
+
+        /** Stub method for ISession.setMetadata */
+        public void setMetadata(@Nullable MediaMetadata metadata, long duration,
+                @Nullable String metadataDescription) {
+        }
+
+        /** Stub method for ISession.setPlaybackState */
+        public void setPlaybackState(@Nullable PlaybackState state) {
+        }
+
+        /** Stub method for ISession.setQueue */
+        public void setQueue(@Nullable List<QueueItem> queue) {
+        }
+
+        /** Stub method for ISession.setQueueTitle */
+        public void setQueueTitle(@Nullable CharSequence title) {
+        }
+
+        /** Stub method for ISession.setExtras */
+        public void setExtras(@Nullable Bundle extras) {
+        }
+
+        /** Stub method for ISession.setRatingType */
+        public void setRatingType(int type) {
+        }
+
+        /** Stub method for ISession.setPlaybackToLocal */
+        public void setPlaybackToLocal(@NonNull AudioAttributes attributes) {
+        }
+
+        /** Stub method for ISession.setPlaybackToRemote */
+        public void setPlaybackToRemote(int control, int max) {
+        }
+
+        /** Stub method for ISession.setCurrentVolume */
+        public void setCurrentVolume(int currentVolume) {
+        }
+    }
+
+    private class StubProxy extends ISession.Stub {
+        @Override
+        public void sendEvent(String event, Bundle data) {
+            mSessionStub.sendEvent(event, data);
+        }
+
+        @Override
+        public ControllerLink getController() {
+            return mSessionStub.getController();
+        }
+
+        @Override
+        public void setFlags(int flags) {
+            mSessionStub.setFlags(flags);
+        }
+
+        @Override
+        public void setActive(boolean active) {
+            mSessionStub.setActive(active);
+        }
+
+        @Override
+        public void setMediaButtonReceiver(PendingIntent mbr) {
+            mSessionStub.setMediaButtonReceiver(mbr);
+        }
+
+        @Override
+        public void setLaunchPendingIntent(PendingIntent pi) {
+            mSessionStub.setLaunchPendingIntent(pi);
+        }
+
+        @Override
+        public void destroySession() {
+            mSessionStub.destroySession();
+        }
+
+        @Override
+        public void setMetadata(MediaMetadata metadata, long duration, String metadataDescription) {
+            mSessionStub.setMetadata(metadata, duration, metadataDescription);
+        }
+
+        @Override
+        public void setPlaybackState(PlaybackState state) {
+            mSessionStub.setPlaybackState(state);
+        }
+
+        @Override
+        public void setQueue(List<QueueItem> queue) {
+            mSessionStub.setQueue(queue);
+        }
+
+        @Override
+        public void setQueueTitle(CharSequence title) {
+            mSessionStub.setQueueTitle(title);
+        }
+
+        @Override
+        public void setExtras(Bundle extras) {
+            mSessionStub.setExtras(extras);
+        }
+
+        @Override
+        public void setRatingType(int type) {
+            mSessionStub.setRatingType(type);
+        }
+
+        @Override
+        public void setPlaybackToLocal(AudioAttributes attributes) {
+            mSessionStub.setPlaybackToLocal(attributes);
+        }
+
+        @Override
+        public void setPlaybackToRemote(int control, int max) {
+            mSessionStub.setPlaybackToRemote(control, max);
+        }
+
+        @Override
+        public void setCurrentVolume(int currentVolume) {
+            mSessionStub.setCurrentVolume(currentVolume);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 7fffe8e..d8c2432 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -27,14 +27,14 @@
 import android.media.Rating;
 import android.media.VolumeProvider;
 import android.media.session.ControllerCallbackLink;
-import android.media.session.ISession;
-import android.media.session.ISessionController;
+import android.media.session.ControllerLink;
 import android.media.session.MediaController;
 import android.media.session.MediaController.PlaybackInfo;
 import android.media.session.MediaSession;
 import android.media.session.MediaSession.QueueItem;
 import android.media.session.PlaybackState;
 import android.media.session.SessionCallbackLink;
+import android.media.session.SessionLink;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -77,8 +77,8 @@
     private final int mUserId;
     private final String mPackageName;
     private final String mTag;
-    private final ControllerStub mController;
-    private final SessionStub mSession;
+    private final ControllerLink mController;
+    private final SessionLink mSession;
     private final SessionCb mSessionCb;
     private final MediaSessionService.ServiceImpl mService;
     private final Context mContext;
@@ -127,8 +127,8 @@
         mUserId = userId;
         mPackageName = ownerPackageName;
         mTag = tag;
-        mController = new ControllerStub();
-        mSession = new SessionStub();
+        mController = new ControllerLink(new ControllerStub());
+        mSession = new SessionLink(new SessionStub());
         mSessionCb = new SessionCb(cb);
         mService = service;
         mContext = mService.getContext();
@@ -139,20 +139,20 @@
     }
 
     /**
-     * Get the binder for the {@link MediaSession}.
+     * Get the session link for the {@link MediaSession}.
      *
-     * @return The session binder apps talk to.
+     * @return The session link apps talk to.
      */
-    public ISession getSessionBinder() {
+    public SessionLink getSessionBinder() {
         return mSession;
     }
 
     /**
-     * Get the binder for the {@link MediaController}.
+     * Get the controller link for the {@link MediaController}.
      *
-     * @return The controller binder apps talk to.
+     * @return The controller link apps talk to.
      */
-    public ISessionController getControllerBinder() {
+    public ControllerLink getControllerLink() {
         return mController;
     }
 
@@ -642,7 +642,7 @@
             if (mDestroyed) {
                 return;
             }
-            PlaybackInfo info = mController.getVolumeAttributes();
+            PlaybackInfo info = getVolumeAttributes();
             for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
                 ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
                 try {
@@ -750,6 +750,25 @@
         return -1;
     }
 
+    private PlaybackInfo getVolumeAttributes() {
+        int volumeType;
+        AudioAttributes attributes;
+        synchronized (mLock) {
+            if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
+                int current = mOptimisticVolume != -1 ? mOptimisticVolume : mCurrentVolume;
+                return new PlaybackInfo(mVolumeType, mVolumeControlType, mMaxVolume, current,
+                        mAudioAttrs);
+            }
+            volumeType = mVolumeType;
+            attributes = mAudioAttrs;
+        }
+        int stream = AudioAttributes.toLegacyStreamType(attributes);
+        int max = mAudioManager.getStreamMaxVolume(stream);
+        int current = mAudioManager.getStreamVolume(stream);
+        return new PlaybackInfo(volumeType, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max,
+                current, attributes);
+    }
+
     private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
         @Override
         public void run() {
@@ -761,9 +780,9 @@
         }
     };
 
-    private final class SessionStub extends ISession.Stub {
+    private final class SessionStub extends SessionLink.SessionStub {
         @Override
-        public void destroy() {
+        public void destroySession() {
             final long token = Binder.clearCallingIdentity();
             try {
                 mService.destroySession(MediaSessionRecord.this);
@@ -779,7 +798,7 @@
         }
 
         @Override
-        public ISessionController getController() {
+        public ControllerLink getController() {
             return mController;
         }
 
@@ -798,8 +817,8 @@
         @Override
         public void setFlags(int flags) {
             if ((flags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
-                int pid = getCallingPid();
-                int uid = getCallingUid();
+                int pid = Binder.getCallingPid();
+                int uid = Binder.getCallingUid();
                 mService.enforcePhoneStatePermission(pid, uid);
             }
             mFlags = flags;
@@ -1183,7 +1202,7 @@
         }
     }
 
-    class ControllerStub extends ISessionController.Stub {
+    class ControllerStub extends ControllerLink.ControllerStub {
         @Override
         public void sendCommand(String packageName, ControllerCallbackLink caller,
                 String command, Bundle args, ResultReceiver cb) {
@@ -1199,7 +1218,7 @@
         }
 
         @Override
-        public void registerCallbackListener(String packageName, ControllerCallbackLink cb) {
+        public void registerCallback(String packageName, ControllerCallbackLink cb) {
             synchronized (mLock) {
                 // If this session is already destroyed tell the caller and
                 // don't add them.
@@ -1223,7 +1242,7 @@
         }
 
         @Override
-        public void unregisterCallbackListener(ControllerCallbackLink cb) {
+        public void unregisterCallback(ControllerCallbackLink cb) {
             synchronized (mLock) {
                 int index = getControllerHolderIndexForCb(cb);
                 if (index != -1) {
@@ -1257,22 +1276,7 @@
 
         @Override
         public PlaybackInfo getVolumeAttributes() {
-            int volumeType;
-            AudioAttributes attributes;
-            synchronized (mLock) {
-                if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
-                    int current = mOptimisticVolume != -1 ? mOptimisticVolume : mCurrentVolume;
-                    return new PlaybackInfo(mVolumeType, mVolumeControlType, mMaxVolume, current,
-                            mAudioAttrs);
-                }
-                volumeType = mVolumeType;
-                attributes = mAudioAttrs;
-            }
-            int stream = AudioAttributes.toLegacyStreamType(attributes);
-            int max = mAudioManager.getStreamMaxVolume(stream);
-            int current = mAudioManager.getStreamVolume(stream);
-            return new PlaybackInfo(volumeType, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max,
-                    current, attributes);
+            return MediaSessionRecord.this.getVolumeAttributes();
         }
 
         @Override
@@ -1449,11 +1453,6 @@
         public int getRatingType() {
             return mRatingType;
         }
-
-        @Override
-        public boolean isTransportControlEnabled() {
-            return MediaSessionRecord.this.isTransportControlEnabled();
-        }
     }
 
     private class ControllerCallbackLinkHolder {
diff --git a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
index f374c6d..e3ae8a7 100644
--- a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
@@ -45,16 +45,18 @@
 import android.media.MediaController2;
 import android.media.Session2CommandGroup;
 import android.media.Session2Token;
+import android.media.session.ControllerLink;
 import android.media.session.IActiveSessionsListener;
 import android.media.session.ICallback;
 import android.media.session.IOnMediaKeyListener;
 import android.media.session.IOnVolumeKeyLongPressListener;
-import android.media.session.ISession;
 import android.media.session.ISession2TokensListener;
+import android.media.session.ISessionController;
 import android.media.session.ISessionManager;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
 import android.media.session.SessionCallbackLink;
+import android.media.session.SessionLink;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -288,7 +290,9 @@
             return;
         }
         try {
-            mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
+            mRvc.remoteVolumeChanged(
+                    ISessionController.Stub.asInterface(session.getControllerLink().getBinder()),
+                    flags);
         } catch (Exception e) {
             Log.wtf(TAG, "Error sending volume change to system UI.", e);
         }
@@ -614,7 +618,7 @@
             int size = records.size();
             ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
             for (int i = 0; i < size; i++) {
-                tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
+                tokens.add(new MediaSession.Token(records.get(i).getControllerLink()));
             }
             pushRemoteVolumeUpdateLocked(userId);
             for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
@@ -641,7 +645,9 @@
                     return;
                 }
                 MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId);
-                mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
+                mRvc.updateRemoteController(record == null ? null
+                        : ISessionController.Stub.asInterface(
+                                record.getControllerLink().getBinder()));
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
             }
@@ -858,7 +864,7 @@
                 MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
                 if (mediaButtonSession != null) {
                     mCallback.onAddressedPlayerChangedToMediaSession(
-                            new MediaSession.Token(mediaButtonSession.getControllerBinder()));
+                            new MediaSession.Token(mediaButtonSession.getControllerLink()));
                 } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
                     mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
                             mCurrentFullUserRecord.mLastMediaButtonReceiver
@@ -978,7 +984,7 @@
         private boolean mVoiceButtonHandled = false;
 
         @Override
-        public ISession createSession(String packageName, SessionCallbackLink cb, String tag,
+        public SessionLink createSession(String packageName, SessionCallbackLink cb, String tag,
                 int userId) throws RemoteException {
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
@@ -1023,18 +1029,18 @@
         }
 
         @Override
-        public List<IBinder> getSessions(ComponentName componentName, int userId) {
+        public List<ControllerLink> getSessions(ComponentName componentName, int userId) {
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
             final long token = Binder.clearCallingIdentity();
 
             try {
                 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
-                ArrayList<IBinder> binders = new ArrayList<IBinder>();
+                ArrayList<ControllerLink> binders = new ArrayList<>();
                 synchronized (mLock) {
                     List<MediaSessionRecord> records = getActiveSessionsLocked(resolvedUserId);
                     for (MediaSessionRecord record : records) {
-                        binders.add(record.getControllerBinder().asBinder());
+                        binders.add(record.getControllerLink());
                     }
                 }
                 return binders;
@@ -1798,7 +1804,7 @@
                 if (mCurrentFullUserRecord.mCallback != null) {
                     try {
                         mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession(
-                                keyEvent, new MediaSession.Token(session.getControllerBinder()));
+                                keyEvent, new MediaSession.Token(session.getControllerLink()));
                     } catch (RemoteException e) {
                         Log.w(TAG, "Failed to send callback", e);
                     }