Merge "Add RouteProviders to the new Media APIs"
diff --git a/Android.mk b/Android.mk
index adc9ef1..c2910fd 100644
--- a/Android.mk
+++ b/Android.mk
@@ -288,11 +288,14 @@
 	media/java/android/media/IRemoteDisplayProvider.aidl \
 	media/java/android/media/IRemoteVolumeObserver.aidl \
 	media/java/android/media/IRingtonePlayer.aidl \
-	media/java/android/media/session/IMediaController.aidl \
-	media/java/android/media/session/IMediaControllerCallback.aidl \
-	media/java/android/media/session/IMediaSession.aidl \
-	media/java/android/media/session/IMediaSessionCallback.aidl \
-	media/java/android/media/session/IMediaSessionManager.aidl \
+	media/java/android/media/routeprovider/IRouteConnection.aidl \
+	media/java/android/media/routeprovider/IRouteProvider.aidl \
+	media/java/android/media/routeprovider/IRouteProviderCallback.aidl \
+	media/java/android/media/session/ISessionController.aidl \
+	media/java/android/media/session/ISessionControllerCallback.aidl \
+	media/java/android/media/session/ISession.aidl \
+	media/java/android/media/session/ISessionCallback.aidl \
+	media/java/android/media/session/ISessionManager.aidl \
 	telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \
 	telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \
 	telephony/java/com/android/internal/telephony/ITelephony.aidl \
diff --git a/api/current.txt b/api/current.txt
index e13b3c2..8e24874 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -27,6 +27,7 @@
     field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
     field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
     field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
+    field public static final java.lang.String BIND_ROUTE_PROVIDER = "android.permission.BIND_ROUTE_PROVIDER";
     field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
     field public static final java.lang.String BIND_TRUST_AGENT_SERVICE = "android.permission.BIND_TRUST_AGENT_SERVICE";
     field public static final java.lang.String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT";
@@ -14957,24 +14958,68 @@
 
 }
 
+package android.media.routeprovider {
+
+  public final class RouteConnection {
+    ctor public RouteConnection(android.media.routeprovider.RouteProviderService, android.media.session.RouteInfo);
+    method public android.media.routeprovider.RouteInterfaceHandler addRouteInterface(java.lang.String);
+    method public android.media.routeprovider.RouteInterfaceHandler getRouteInterface(java.lang.String);
+    method public void shutDown();
+  }
+
+  public final class RouteInterfaceHandler {
+    method public void addListener(android.media.routeprovider.RouteInterfaceHandler.CommandListener, android.os.Handler);
+    method public java.lang.String getName();
+    method public void removeListener(android.media.routeprovider.RouteInterfaceHandler.CommandListener);
+    method public void sendEvent(java.lang.String, android.os.Bundle);
+    method public static void sendResult(android.os.ResultReceiver, int, android.os.Bundle);
+  }
+
+  public static abstract class RouteInterfaceHandler.CommandListener {
+    ctor public RouteInterfaceHandler.CommandListener();
+    method public abstract boolean onCommand(android.media.routeprovider.RouteInterfaceHandler, java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+  }
+
+  public final class RoutePlaybackControlsHandler {
+    method public void addListener(android.media.routeprovider.RoutePlaybackControlsHandler.Listener);
+    method public void addListener(android.media.routeprovider.RoutePlaybackControlsHandler.Listener, android.os.Handler);
+    method public static android.media.routeprovider.RoutePlaybackControlsHandler addTo(android.media.routeprovider.RouteConnection);
+    method public void removeListener(android.media.routeprovider.RoutePlaybackControlsHandler.Listener);
+    method public void sendPlaybackChangeEvent(int);
+  }
+
+  public static abstract class RoutePlaybackControlsHandler.Listener extends android.media.routeprovider.RouteInterfaceHandler.CommandListener {
+    ctor public RoutePlaybackControlsHandler.Listener();
+    method public boolean fastForward();
+    method public long getCapabilities();
+    method public long getCurrentPosition();
+    method public final boolean onCommand(android.media.routeprovider.RouteInterfaceHandler, java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+    method public boolean pause();
+    method public void playNow(java.lang.String, android.os.ResultReceiver);
+    method public boolean resume();
+  }
+
+  public abstract class RouteProviderService extends android.app.Service {
+    ctor public RouteProviderService();
+    method public abstract android.media.routeprovider.RouteConnection connect(android.media.session.RouteInfo, android.media.routeprovider.RouteRequest);
+    method public abstract java.util.List<android.media.session.RouteInfo> getMatchingRoutes(java.util.List<android.media.routeprovider.RouteRequest>);
+    method public android.os.IBinder onBind(android.content.Intent);
+    method public void updateDiscoveryRequests(java.util.List<android.media.routeprovider.RouteRequest>);
+    field public static final java.lang.String SERVICE_INTERFACE = "com.android.media.session.MediaRouteProvider";
+  }
+
+  public final class RouteRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.media.session.RouteOptions getConnectionOptions();
+    method public android.media.session.SessionInfo getSessionInfo();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
+}
+
 package android.media.session {
 
-  public final class MediaController {
-    method public void addCallback(android.media.session.MediaController.Callback);
-    method public void addCallback(android.media.session.MediaController.Callback, android.os.Handler);
-    method public static android.media.session.MediaController fromToken(android.media.session.MediaSessionToken);
-    method public android.media.session.TransportController getTransportController();
-    method public void removeCallback(android.media.session.MediaController.Callback);
-    method public void sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
-    method public void sendMediaButton(int);
-  }
-
-  public static abstract class MediaController.Callback {
-    ctor public MediaController.Callback();
-    method public void onEvent(java.lang.String, android.os.Bundle);
-    method public void onRouteChanged(android.os.Bundle);
-  }
-
   public final class MediaMetadata implements android.os.Parcelable {
     method public int describeContents();
     method public android.graphics.Bitmap getBitmap(java.lang.String);
@@ -15015,36 +15060,6 @@
     method public android.media.session.MediaMetadata.Builder putString(java.lang.String, java.lang.String);
   }
 
-  public final class MediaSession {
-    method public void addCallback(android.media.session.MediaSession.Callback);
-    method public void addCallback(android.media.session.MediaSession.Callback, android.os.Handler);
-    method public android.media.session.MediaSessionToken getSessionToken();
-    method public android.media.session.TransportPerformer getTransportPerformer();
-    method public void publish();
-    method public void release();
-    method public void removeCallback(android.media.session.MediaSession.Callback);
-    method public void sendEvent(java.lang.String, android.os.Bundle);
-    method public android.media.session.TransportPerformer setTransportPerformerEnabled();
-  }
-
-  public static abstract class MediaSession.Callback {
-    ctor public MediaSession.Callback();
-    method public void onCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
-    method public void onMediaButton(android.content.Intent);
-    method public void onRequestRouteChange(android.os.Bundle);
-  }
-
-  public final class MediaSessionManager {
-    method public android.media.session.MediaSession createSession(java.lang.String);
-    method public java.util.List<android.media.session.MediaController> getActiveSessions();
-  }
-
-  public class MediaSessionToken implements android.os.Parcelable {
-    method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator CREATOR;
-  }
-
   public final class PlaybackState implements android.os.Parcelable {
     ctor public PlaybackState();
     ctor public PlaybackState(android.media.session.PlaybackState);
@@ -15073,6 +15088,7 @@
     field public static final long ACTION_STOP = 1L; // 0x1L
     field public static final android.os.Parcelable.Creator CREATOR;
     field public static final int PLAYSTATE_BUFFERING = 6; // 0x6
+    field public static final int PLAYSTATE_CONNECTING = 8; // 0x8
     field public static final int PLAYSTATE_ERROR = 7; // 0x7
     field public static final int PLAYSTATE_FAST_FORWARDING = 4; // 0x4
     field public static final int PLAYSTATE_NONE = 0; // 0x0
@@ -15082,11 +15098,44 @@
     field public static final int PLAYSTATE_STOPPED = 1; // 0x1
   }
 
+  public final class Route {
+    method public android.media.session.RouteInterface getInterface(java.lang.String);
+    method public android.media.session.RouteOptions getOptions();
+    method public android.media.session.RouteInfo getRouteInfo();
+  }
+
+  public final class RouteInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.util.List<android.media.session.RouteOptions> getConnectionMethods();
+    method public java.lang.String getId();
+    method public java.lang.String getName();
+    method public java.lang.String getProvider();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
+  public static final class RouteInfo.Builder {
+    ctor public RouteInfo.Builder(android.media.session.RouteInfo);
+    ctor public RouteInfo.Builder();
+    method public android.media.session.RouteInfo.Builder addRouteOptions(android.media.session.RouteOptions);
+    method public android.media.session.RouteInfo build();
+    method public android.media.session.RouteInfo.Builder clearRouteOptions();
+    method public int getOptionsSize();
+    method public android.media.session.RouteInfo.Builder setId(java.lang.String);
+    method public android.media.session.RouteInfo.Builder setName(java.lang.String);
+  }
+
   public final class RouteInterface {
     method public void addListener(android.media.session.RouteInterface.EventListener);
     method public void addListener(android.media.session.RouteInterface.EventListener, android.os.Handler);
     method public void removeListener(android.media.session.RouteInterface.EventListener);
-    method public void sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+    method public boolean sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+    field public static final int RESULT_COMMAND_NOT_SUPPORTED = -3; // 0xfffffffd
+    field public static final int RESULT_ERROR = -1; // 0xffffffff
+    field public static final int RESULT_INTERFACE_NOT_SUPPORTED = -2; // 0xfffffffe
+    field public static final int RESULT_NOT_CONNECTED = -5; // 0xfffffffb
+    field public static final int RESULT_ROUTE_IS_STALE = -4; // 0xfffffffc
+    field public static final int RESULT_SUCCESS = 1; // 0x1
   }
 
   public static abstract class RouteInterface.EventListener {
@@ -15094,40 +15143,100 @@
     method public abstract void onEvent(java.lang.String, android.os.Bundle);
   }
 
-  public static abstract class RouteInterface.Stub {
-    ctor public RouteInterface.Stub();
-    method public abstract java.lang.String getName();
-    method public abstract void onCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
-    method public final void sendEvent(android.media.session.MediaSession, java.lang.String, android.os.Bundle);
+  public final class RouteOptions implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.os.Bundle getConnectionParams();
+    method public java.util.List<java.lang.String> getInterfaceNames();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
   }
 
-  public final class RouteTransportControls {
-    method public void addListener(android.media.session.RouteTransportControls.Listener);
-    method public void addListener(android.media.session.RouteTransportControls.Listener, android.os.Handler);
-    method public void fastForward(float);
-    method public static android.media.session.RouteTransportControls from(android.media.session.MediaController);
+  public static final class RouteOptions.Builder {
+    ctor public RouteOptions.Builder();
+    method public android.media.session.RouteOptions.Builder addInterface(java.lang.String);
+    method public android.media.session.RouteOptions build();
+    method public android.media.session.RouteOptions.Builder setParameters(android.os.Bundle);
+  }
+
+  public final class RoutePlaybackControls {
+    method public void addListener(android.media.session.RoutePlaybackControls.Listener);
+    method public void addListener(android.media.session.RoutePlaybackControls.Listener, android.os.Handler);
+    method public void fastForward();
+    method public static android.media.session.RoutePlaybackControls from(android.media.session.Route);
     method public void getCapabilities(android.os.ResultReceiver);
     method public void getCurrentPosition(android.os.ResultReceiver);
     method public void pause();
-    method public void play();
-    method public void removeListener(android.media.session.RouteTransportControls.Listener);
-    field public static final java.lang.String NAME = "android.media.session.RouteTransportControls";
+    method public void playNow(java.lang.String);
+    method public void removeListener(android.media.session.RoutePlaybackControls.Listener);
+    method public void resume();
+    field public static final java.lang.String NAME = "android.media.session.RoutePlaybackControls";
   }
 
-  public static abstract class RouteTransportControls.Listener {
-    ctor public RouteTransportControls.Listener();
-    method public void onMetadataUpdate(android.os.Bundle);
+  public static abstract class RoutePlaybackControls.Listener extends android.media.session.RouteInterface.EventListener {
+    ctor public RoutePlaybackControls.Listener();
+    method public final void onEvent(java.lang.String, android.os.Bundle);
+    method public void onMetadataUpdate(android.media.session.MediaMetadata);
     method public void onPlaybackStateChange(int);
   }
 
-  public static abstract class RouteTransportControls.Stub extends android.media.session.RouteInterface.Stub {
-    ctor public RouteTransportControls.Stub(android.media.session.MediaSession);
-    method public void fastForward(float);
-    method public long getCapabilities();
-    method public long getCurrentPosition();
-    method public java.lang.String getName();
+  public final class Session {
+    method public void addCallback(android.media.session.Session.Callback);
+    method public void addCallback(android.media.session.Session.Callback, android.os.Handler);
+    method public void connect(android.media.session.RouteInfo, android.media.session.RouteOptions);
+    method public void disconnect(android.media.session.RouteInfo);
+    method public android.media.session.SessionToken getSessionToken();
+    method public android.media.session.TransportPerformer getTransportPerformer();
+    method public void publish();
+    method public void release();
+    method public void removeCallback(android.media.session.Session.Callback);
+    method public void sendEvent(java.lang.String, android.os.Bundle);
+    method public void setRouteOptions(java.util.List<android.media.session.RouteOptions>);
+    method public android.media.session.TransportPerformer setTransportPerformerEnabled();
+  }
+
+  public static abstract class Session.Callback {
+    ctor public Session.Callback();
     method public void onCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
-    method public final void updatePlaybackState(int);
+    method public void onMediaButton(android.content.Intent);
+    method public void onRequestRouteChange(android.media.session.RouteInfo);
+    method public void onRouteConnected(android.media.session.Route);
+    method public void onRouteDisconnected(android.media.session.Route, int);
+  }
+
+  public final class SessionController {
+    method public void addCallback(android.media.session.SessionController.Callback);
+    method public void addCallback(android.media.session.SessionController.Callback, android.os.Handler);
+    method public static android.media.session.SessionController fromToken(android.media.session.SessionToken);
+    method public android.media.session.TransportController getTransportController();
+    method public void removeCallback(android.media.session.SessionController.Callback);
+    method public void sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+    method public void sendMediaButton(int);
+    method public void showRoutePicker();
+  }
+
+  public static abstract class SessionController.Callback {
+    ctor public SessionController.Callback();
+    method public void onEvent(java.lang.String, android.os.Bundle);
+    method public void onRouteChanged(android.media.session.RouteInfo);
+  }
+
+  public final class SessionInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.lang.String getId();
+    method public java.lang.String getPackageName();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
+  public final class SessionManager {
+    method public android.media.session.Session createSession(java.lang.String);
+    method public java.util.List<android.media.session.SessionController> getActiveSessions();
+  }
+
+  public class SessionToken implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
   }
 
   public final class TransportController {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 77b5485..f1ce54a 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -67,7 +67,7 @@
 import android.location.LocationManager;
 import android.media.AudioManager;
 import android.media.MediaRouter;
-import android.media.session.MediaSessionManager;
+import android.media.session.SessionManager;
 import android.net.ConnectivityManager;
 import android.net.IConnectivityManager;
 import android.net.INetworkPolicyManager;
@@ -639,7 +639,7 @@
 
         registerService(MEDIA_SESSION_SERVICE, new ServiceFetcher() {
             public Object createService(ContextImpl ctx) {
-                return new MediaSessionManager(ctx);
+                return new SessionManager(ctx);
             }
         });
         registerService(TRUST_SERVICE, new ServiceFetcher() {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index ff92d82..906484a 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2387,10 +2387,10 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a
-     * {@link android.media.session.MediaSessionManager} for managing media Sessions.
+     * {@link android.media.session.SessionManager} for managing media Sessions.
      *
      * @see #getSystemService
-     * @see android.media.session.MediaSessionManager
+     * @see android.media.session.SessionManager
      */
     public static final String MEDIA_SESSION_SERVICE = "media_session";
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b8fe0ff..57e845f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2071,6 +2071,14 @@
         android:description="@string/permdesc_bindTvInput"
         android:protectionLevel="signature|system" />
 
+    <!-- Must be required by a {@link android.media.routeprovider.RouteProviderService}
+         to ensure that only the system can interact with it.
+         -->
+    <permission android:name="android.permission.BIND_ROUTE_PROVIDER"
+        android:label="@string/permlab_bindRouteProvider"
+        android:description="@string/permdesc_bindRouteProvider"
+        android:protectionLevel="signature" />
+
     <!-- Must be required by device administration receiver, to ensure that only the
          system can interact with it. -->
     <permission android:name="android.permission.BIND_DEVICE_ADMIN"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b0e1150..cacb41f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1077,6 +1077,12 @@
         interface of a widget service. Should never be needed for normal apps.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_bindRouteProvider">bind to a route provider service</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_bindRouteProvider">Allows the holder to bind to any registered
+        route providers. Should never be needed for normal apps.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_bindDeviceAdmin">interact with a device admin</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_bindDeviceAdmin">Allows the holder to send intents to
diff --git a/media/java/android/media/session/IMediaSessionManager.aidl b/media/java/android/media/routeprovider/IRouteConnection.aidl
similarity index 65%
copy from media/java/android/media/session/IMediaSessionManager.aidl
copy to media/java/android/media/routeprovider/IRouteConnection.aidl
index 0b4328e..15c8039 100644
--- a/media/java/android/media/session/IMediaSessionManager.aidl
+++ b/media/java/android/media/routeprovider/IRouteConnection.aidl
@@ -13,16 +13,16 @@
  * limitations under the License.
  */
 
-package android.media.session;
+package android.media.routeprovider;
 
-import android.media.session.IMediaSession;
-import android.media.session.IMediaSessionCallback;
-import android.os.Bundle;
+import android.media.session.RouteCommand;
+import android.os.ResultReceiver;
 
 /**
- * Interface to the MediaSessionManagerService
+ * Interface for a specific connected route.
  * @hide
  */
-interface IMediaSessionManager {
-    IMediaSession createSession(String packageName, in IMediaSessionCallback cb, String tag);
+oneway interface IRouteConnection {
+    void onCommand(in RouteCommand command, in ResultReceiver cb);
+    void disconnect();
 }
\ No newline at end of file
diff --git a/media/java/android/media/routeprovider/IRouteProvider.aidl b/media/java/android/media/routeprovider/IRouteProvider.aidl
new file mode 100644
index 0000000..c36f6a7
--- /dev/null
+++ b/media/java/android/media/routeprovider/IRouteProvider.aidl
@@ -0,0 +1,36 @@
+/* Copyright (C) 2014 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.routeprovider;
+
+import android.media.routeprovider.IRouteConnection;
+import android.media.routeprovider.IRouteProviderCallback;
+import android.media.routeprovider.RouteRequest;
+import android.media.session.RouteInfo;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+/**
+ * Interface to an app's RouteProviderService.
+ * @hide
+ */
+oneway interface IRouteProvider {
+    void registerCallback(in IRouteProviderCallback cb);
+    void unregisterCallback(in IRouteProviderCallback cb);
+    void updateDiscoveryRequests(in List<RouteRequest> requests);
+
+    void getAvailableRoutes(in List<RouteRequest> requests, in ResultReceiver cb);
+    void connect(in RouteInfo route, in RouteRequest request, in ResultReceiver cb);
+}
\ No newline at end of file
diff --git a/media/java/android/media/routeprovider/IRouteProviderCallback.aidl b/media/java/android/media/routeprovider/IRouteProviderCallback.aidl
new file mode 100644
index 0000000..9185347
--- /dev/null
+++ b/media/java/android/media/routeprovider/IRouteProviderCallback.aidl
@@ -0,0 +1,32 @@
+/* Copyright (C) 2014 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.routeprovider;
+
+import android.media.routeprovider.IRouteConnection;
+import android.media.session.RouteEvent;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+/**
+ * System's provider callback interface.
+ * @hide
+ */
+oneway interface IRouteProviderCallback {
+    void onRoutesChanged();
+    void onConnectionStateChanged(in IRouteConnection connection, int state);
+    void onConnectionTerminated(in IRouteConnection connection);
+    void onRouteEvent(in RouteEvent event);
+}
\ No newline at end of file
diff --git a/media/java/android/media/routeprovider/RouteConnection.java b/media/java/android/media/routeprovider/RouteConnection.java
new file mode 100644
index 0000000..9214ff8
--- /dev/null
+++ b/media/java/android/media/routeprovider/RouteConnection.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2014 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.routeprovider;
+
+import android.media.routeprovider.IRouteConnection;
+import android.media.session.RouteCommand;
+import android.media.session.RouteEvent;
+import android.media.session.RouteInfo;
+import android.media.session.RouteInterface;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents an ongoing connection between an application and a media route
+ * offered by a media route provider.
+ * <p>
+ * The media route provider should add interfaces to the connection before
+ * returning it to the system in order to receive commands from clients on those
+ * interfaces. Use {@link #addRouteInterface(String)} to add an interface and
+ * {@link #getRouteInterface(String)} to retrieve the interface's handle anytime
+ * after it has been added.
+ */
+public final class RouteConnection {
+    private static final String TAG = "RouteConnection";
+    private final ConnectionStub mBinder;
+    private final ArrayList<String> mIfaceNames = new ArrayList<String>();
+    private final ArrayMap<String, RouteInterfaceHandler> mIfaces
+            = new ArrayMap<String, RouteInterfaceHandler>();
+    private final RouteProviderService mProvider;
+    private final RouteInfo mRoute;
+
+    private boolean mPublished;
+
+    /**
+     * Create a new connection for the given Provider and Route.
+     *
+     * @param provider The provider this route is associated with.
+     * @param route The route this is a connection to.
+     */
+    public RouteConnection(RouteProviderService provider, RouteInfo route) {
+        if (provider == null) {
+            throw new IllegalArgumentException("provider may not be null.");
+        }
+        if (route == null) {
+            throw new IllegalArgumentException("route may not be null.");
+        }
+        mBinder = new ConnectionStub(this);
+        mProvider = provider;
+        mRoute = route;
+    }
+
+    /**
+     * Add an interface to this route connection. All interfaces must be added
+     * to the connection before the connection is returned to the system.
+     *
+     * @param ifaceName The name of the interface to add
+     * @return The route interface that was registered
+     */
+    public RouteInterfaceHandler addRouteInterface(String ifaceName) {
+        if (TextUtils.isEmpty(ifaceName)) {
+            throw new IllegalArgumentException("The interface's name may not be empty");
+        }
+        if (mPublished) {
+            throw new IllegalStateException(
+                    "Connection has already been published to the system.");
+        }
+        RouteInterfaceHandler iface = mIfaces.get(ifaceName);
+        if (iface == null) {
+            iface = new RouteInterfaceHandler(this, ifaceName);
+            mIfaceNames.add(ifaceName);
+            mIfaces.put(ifaceName, iface);
+        } else {
+            Log.w(TAG, "Attempted to add an interface that already exists");
+        }
+        return iface;
+    }
+
+    /**
+     * Get the interface instance for the specified interface name. If the
+     * interface was not added to this connection null will be returned.
+     *
+     * @param ifaceName The name of the interface to get.
+     * @return The route interface with that name or null.
+     */
+    public RouteInterfaceHandler getRouteInterface(String ifaceName) {
+        return mIfaces.get(ifaceName);
+    }
+
+    /**
+     * Close the connection and inform the system that it may no longer be used.
+     */
+    public void shutDown() {
+        mProvider.disconnect(this);
+    }
+
+    /**
+     * @hide
+     */
+    public void sendEvent(String iface, String event, Bundle extras) {
+        RouteEvent e = new RouteEvent(mBinder, iface, event, extras);
+        mProvider.sendRouteEvent(e);
+    }
+
+    /**
+     * @hide
+     */
+    IRouteConnection.Stub getBinder() {
+        return mBinder;
+    }
+
+    /**
+     * @hide
+     */
+    void publish() {
+        mPublished = true;
+    }
+
+    private static class ConnectionStub extends IRouteConnection.Stub {
+        private final WeakReference<RouteConnection> mConnection;
+
+        public ConnectionStub(RouteConnection connection) {
+            mConnection = new WeakReference<RouteConnection>(connection);
+        }
+
+        @Override
+        public void onCommand(RouteCommand command, ResultReceiver cb) {
+            RouteConnection connection = mConnection.get();
+            if (connection != null) {
+                RouteInterfaceHandler iface = connection.mIfaces.get(command.getIface());
+                if (iface != null) {
+                    iface.onCommand(command.getEvent(), command.getExtras(), cb);
+                } else if (cb != null) {
+                    cb.send(RouteInterface.RESULT_INTERFACE_NOT_SUPPORTED, null);
+                }
+            }
+        }
+
+        @Override
+        public void disconnect() {
+            // TODO
+        }
+    }
+}
diff --git a/media/java/android/media/routeprovider/RouteInterfaceHandler.java b/media/java/android/media/routeprovider/RouteInterfaceHandler.java
new file mode 100644
index 0000000..9693dc6
--- /dev/null
+++ b/media/java/android/media/routeprovider/RouteInterfaceHandler.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2014 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.routeprovider;
+
+import android.media.session.Route;
+import android.media.session.Session;
+import android.media.session.RouteInterface;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ResultReceiver;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Represents an interface that an application may use to send requests to a
+ * connected media route.
+ * <p>
+ * A {@link RouteProviderService} may expose multiple interfaces on a
+ * {@link RouteConnection} for a {@link Session} to interact with. A
+ * provider creates an interface with
+ * {@link RouteConnection#addRouteInterface(String)} to allow messages to be
+ * routed appropriately. Events are then sent through a specific interface and
+ * all commands being sent on the interface will be sent to any registered
+ * {@link CommandListener}s.
+ * <p>
+ * An interface instance can only be registered on one {@link RouteConnection}.
+ * To use the same interface on multiple connections a new instance must be
+ * created for each connection.
+ * <p>
+ * It is recommended you wrap this interface with a standard implementation to
+ * avoid errors, but for simple interfaces this class may be used directly. TODO
+ * add link to sample code.
+ */
+public final class RouteInterfaceHandler {
+    private static final String TAG = "RouteInterfaceHandler";
+
+    private final Object mLock = new Object();
+    private final RouteConnection mConnection;
+    private final String mName;
+
+    private ArrayList<MessageHandler> mListeners = new ArrayList<MessageHandler>();
+
+    /**
+     * Create a new RouteInterface for a given connection. This can be used to
+     * send events on the given interface and register listeners for commands
+     * from the connected session.
+     *
+     * @param connection The connection this interface sends events on
+     * @param ifaceName The name of this interface
+     * @hide
+     */
+    public RouteInterfaceHandler(RouteConnection connection, String ifaceName) {
+        if (connection == null) {
+            throw new IllegalArgumentException("connection may not be null");
+        }
+        if (TextUtils.isEmpty(ifaceName)) {
+            throw new IllegalArgumentException("ifaceName can not be empty");
+        }
+        mConnection = connection;
+        mName = ifaceName;
+    }
+
+    /**
+     * Send an event on this interface to the connected session.
+     *
+     * @param event The event to send
+     * @param extras Any extras for the event
+     */
+    public void sendEvent(String event, Bundle extras) {
+        mConnection.sendEvent(mName, event, extras);
+    }
+
+    /**
+     * Send a result from a command to the specified callback. The result codes
+     * in {@link RouteInterface} must be used. More information
+     * about the result, whether successful or an error, should be included in
+     * the extras.
+     *
+     * @param cb The callback to send the result to
+     * @param resultCode The result code for the call
+     * @param extras Any extras to include
+     */
+    public static void sendResult(ResultReceiver cb, int resultCode, Bundle extras) {
+        if (cb != null) {
+            cb.send(resultCode, extras);
+        }
+    }
+
+    /**
+     * Add a listener for this interface. If a handler is specified callbacks
+     * will be performed on the handler's thread, otherwise the callers thread
+     * will be used.
+     *
+     * @param listener The listener to receive calls on.
+     * @param handler The handler whose thread to post calls on or null.
+     */
+    public void addListener(CommandListener listener, Handler handler) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener may not be null");
+        }
+        Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
+        synchronized (mLock) {
+            if (findIndexOfListenerLocked(listener) != -1) {
+                Log.d(TAG, "Listener is already added, ignoring");
+                return;
+            }
+            mListeners.add(new MessageHandler(looper, listener));
+        }
+    }
+
+    /**
+     * Remove a listener from this interface.
+     *
+     * @param listener The listener to stop receiving commands on.
+     */
+    public void removeListener(CommandListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener may not be null");
+        }
+        synchronized (mLock) {
+            int index = findIndexOfListenerLocked(listener);
+            if (index != -1) {
+                mListeners.remove(index);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public void onCommand(String command, Bundle args, ResultReceiver cb) {
+        synchronized (mLock) {
+            Command cmd = new Command(command, args, cb);
+            for (int i = mListeners.size() - 1; i >= 0; i--) {
+                mListeners.get(i).post(MessageHandler.MSG_COMMAND, cmd);
+            }
+        }
+    }
+
+    /**
+     * Get the interface name.
+     *
+     * @return The name of this interface
+     */
+    public String getName() {
+        return mName;
+    }
+
+    private int findIndexOfListenerLocked(CommandListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("Callback cannot be null");
+        }
+        for (int i = mListeners.size() - 1; i >= 0; i--) {
+            MessageHandler handler = mListeners.get(i);
+            if (listener == handler.mListener) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Handles commands sent to the interface.
+     * <p>
+     * Register an InterfaceListener using {@link #addListener}.
+     */
+    public abstract static class CommandListener {
+        /**
+         * This is called when a command is received that matches this
+         * interface. Commands are sent by a {@link Session} that is
+         * connected to the route this interface is registered with.
+         *
+         * @param iface The interface the command was received on.
+         * @param command The command or method to invoke.
+         * @param args Any args that were included with the command. May be
+         *            null.
+         * @param cb The callback provided to send a response on. May be null.
+         * @return true if the command was handled, false otherwise. If the
+         *         command was not handled an error will be sent automatically.
+         *         true may be returned if the command will be handled
+         *         asynchronously.
+         * @see Route
+         * @see Session
+         */
+        public abstract boolean onCommand(RouteInterfaceHandler iface, String command, Bundle args,
+                ResultReceiver cb);
+    }
+
+    private class MessageHandler extends Handler {
+        private static final int MSG_COMMAND = 1;
+
+        private final CommandListener mListener;
+
+        public MessageHandler(Looper looper, CommandListener listener) {
+            super(looper, null, true /* async */);
+            mListener = listener;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_COMMAND:
+                    Command cmd = (Command) msg.obj;
+                    if (!mListener.onCommand(RouteInterfaceHandler.this, cmd.command, cmd.args, cmd.cb)) {
+                        sendResult(cmd.cb, RouteInterface.RESULT_COMMAND_NOT_SUPPORTED,
+                                null);
+                    }
+                    break;
+            }
+        }
+
+        public void post(int what, Object obj) {
+            obtainMessage(what, obj).sendToTarget();
+        }
+    }
+
+    private final static class Command {
+        public final String command;
+        public final Bundle args;
+        public final ResultReceiver cb;
+
+        public Command(String command, Bundle args, ResultReceiver cb) {
+            this.command = command;
+            this.args = args;
+            this.cb = cb;
+        }
+    }
+}
diff --git a/media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java b/media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java
new file mode 100644
index 0000000..dcef79a
--- /dev/null
+++ b/media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2014 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.routeprovider;
+
+import android.media.session.RoutePlaybackControls;
+import android.media.session.RouteInterface;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * Standard wrapper for using playback controls over a {@link RouteInterfaceHandler}.
+ * This is the provider half of the interface. Sessions should use
+ * {@link RoutePlaybackControls} to interact with this interface.
+ */
+public final class RoutePlaybackControlsHandler {
+    private static final String TAG = "RoutePlaybackControls";
+
+    private final RouteInterfaceHandler mIface;
+
+    private RoutePlaybackControlsHandler(RouteInterfaceHandler iface) {
+        mIface = iface;
+    }
+
+    /**
+     * Add this interface to the specified route and return a handle for
+     * communicating on the interface.
+     *
+     * @param connection The connection to register this interface on.
+     * @return A handle for communicating on this interface.
+     */
+    public static RoutePlaybackControlsHandler addTo(RouteConnection connection) {
+        if (connection == null) {
+            throw new IllegalArgumentException("connection may not be null");
+        }
+        RouteInterfaceHandler iface = connection
+                .addRouteInterface(RoutePlaybackControls.NAME);
+
+        return new RoutePlaybackControlsHandler(iface);
+    }
+
+    /**
+     * Add a {@link Listener} to this interface. The listener will receive
+     * commands on the caller's thread.
+     *
+     * @param listener The listener to send commands to.
+     */
+    public void addListener(Listener listener) {
+        addListener(listener, null);
+    }
+
+    /**
+     * Add a {@link Listener} to this interface. The listener will receive
+     * updates on the handler's thread. If no handler is specified the caller's
+     * thread will be used instead.
+     *
+     * @param listener The listener to send commands to.
+     * @param handler The handler whose thread calls should be posted on. May be
+     *            null.
+     */
+    public void addListener(Listener listener, Handler handler) {
+        mIface.addListener(listener, handler);
+    }
+
+    /**
+     * Remove a {@link Listener} from this interface.
+     *
+     * @param listener The Listener to remove.
+     */
+    public void removeListener(Listener listener) {
+        mIface.removeListener(listener);
+    }
+
+    /**
+     * Publish the current playback state to the system and any controllers.
+     * Valid values are defined in {@link PlaybackState}. TODO create
+     * RoutePlaybackState.
+     *
+     * @param state
+     */
+    public void sendPlaybackChangeEvent(int state) {
+        Bundle extras = new Bundle();
+        extras.putInt(RoutePlaybackControls.KEY_VALUE1, state);
+        mIface.sendEvent(RoutePlaybackControls.EVENT_PLAYSTATE_CHANGE, extras);
+    }
+
+    /**
+     * Command handler for the RoutePlaybackControls interface. You can add a
+     * Listener to the interface using {@link #addListener}.
+     */
+    public static abstract class Listener extends RouteInterfaceHandler.CommandListener {
+
+        @Override
+        public final boolean onCommand(RouteInterfaceHandler iface, String method, Bundle extras,
+                ResultReceiver cb) {
+            if (RoutePlaybackControls.CMD_FAST_FORWARD.equals(method)) {
+                boolean success = fastForward();
+                // TODO specify type of error
+                RouteInterfaceHandler.sendResult(cb, success
+                        ? RouteInterface.RESULT_SUCCESS
+                        : RouteInterface.RESULT_ERROR, null);
+                return true;
+            } else if (RoutePlaybackControls.CMD_GET_CURRENT_POSITION.equals(method)) {
+                Bundle result = new Bundle();
+                result.putLong(RoutePlaybackControls.KEY_VALUE1, getCurrentPosition());
+                RouteInterfaceHandler.sendResult(cb, RouteInterface.RESULT_SUCCESS,
+                        result);
+                return true;
+            } else if (RoutePlaybackControls.CMD_GET_CAPABILITIES.equals(method)) {
+                Bundle result = new Bundle();
+                result.putLong(RoutePlaybackControls.KEY_VALUE1, getCapabilities());
+                RouteInterfaceHandler.sendResult(cb, RouteInterface.RESULT_SUCCESS,
+                        result);
+                return true;
+            } else if (RoutePlaybackControls.CMD_PLAY_NOW.equals(method)) {
+                playNow(extras.getString(RoutePlaybackControls.KEY_VALUE1, null), cb);
+                return true;
+            } else if (RoutePlaybackControls.CMD_RESUME.equals(method)) {
+                boolean success = resume();
+                RouteInterfaceHandler.sendResult(cb, success
+                        ? RouteInterface.RESULT_SUCCESS
+                        : RouteInterface.RESULT_ERROR, null);
+                return true;
+            } else if (RoutePlaybackControls.CMD_PAUSE.equals(method)) {
+                boolean success = pause();
+                RouteInterfaceHandler.sendResult(cb, success
+                        ? RouteInterface.RESULT_SUCCESS
+                        : RouteInterface.RESULT_ERROR, null);
+                return true;
+            } else {
+                // The command wasn't recognized
+            }
+            return false;
+        }
+
+        /**
+         * Override to handle fast forwarding.
+         *
+         * @return true if the request succeeded, false otherwise
+         */
+        public boolean fastForward() {
+            Log.w(TAG, "fastForward is not supported.");
+            return false;
+        }
+
+        /**
+         * Override to handle getting the current position of playback in
+         * millis.
+         *
+         * @return The current position in millis or -1
+         */
+        public long getCurrentPosition() {
+            Log.w(TAG, "getCurrentPosition is not supported");
+            return -1;
+        }
+
+        /**
+         * Override to handle getting the set of capabilities currently
+         * available.
+         *
+         * @return A bit mask of the supported capabilities
+         */
+        public long getCapabilities() {
+            Log.w(TAG, "getCapabilities is not supported");
+            return 0;
+        }
+
+        /**
+         * Override to handle play now requests.
+         *
+         * @param content The uri of the item to play.
+         * @param cb The callback to send the result to.
+         */
+        public void playNow(String content, ResultReceiver cb) {
+            Log.w(TAG, "playNow is not supported");
+            if (cb != null) {
+                // We do this directly since we don't have a reference to the
+                // iface
+                cb.send(RouteInterface.RESULT_COMMAND_NOT_SUPPORTED, null);
+            }
+        }
+
+        /**
+         * Override to handle resume requests. Return true if the call was
+         * handled, even if it was a no-op.
+         *
+         * @return true if the call was handled.
+         */
+        public boolean resume() {
+            Log.w(TAG, "resume is not supported");
+            return false;
+        }
+
+        /**
+         * Override to handle pause requests. Return true if the call was
+         * handled, even if it was a no-op.
+         *
+         * @return true if the call was handled.
+         */
+        public boolean pause() {
+            Log.w(TAG, "pause is not supported");
+            return false;
+        }
+    }
+}
diff --git a/media/java/android/media/routeprovider/RouteProviderService.java b/media/java/android/media/routeprovider/RouteProviderService.java
new file mode 100644
index 0000000..6ebfb5b
--- /dev/null
+++ b/media/java/android/media/routeprovider/RouteProviderService.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2014 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.routeprovider;
+
+import android.app.Service;
+import android.content.Intent;
+import android.media.routeprovider.IRouteProvider;
+import android.media.routeprovider.IRouteProviderCallback;
+import android.media.session.RouteEvent;
+import android.media.session.RouteInfo;
+import android.media.session.RouteOptions;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Base class for defining a route provider service.
+ * <p>
+ * A route provider offers media routes which represent destinations to which
+ * applications may connect, control, and send content. This provides a means
+ * for Android applications to interact with a variety of media streaming
+ * devices such as speakers or television sets.
+ * <p>
+ * The system will bind to your provider when an active app is interested in
+ * routes that may be discovered through your provider. After binding, the
+ * system will send updates on which routes to discover through
+ * {@link #updateDiscoveryRequests(List)}. The system will call
+ * {@link #getMatchingRoutes(List)} with a subset of filters when a route is
+ * needed for a specific app.
+ * <p>
+ * TODO add documentation for how the sytem knows an app is interested. Maybe
+ * interface declarations in the manifest.
+ * <p>
+ * The system will only start a provider when an app may discover routes through
+ * it. If your service needs to run at other times you are responsible for
+ * managing its lifecycle.
+ * <p>
+ * Declare your route provider service in your application manifest like this:
+ * <p>
+ *
+ * <pre>
+ *   &lt;service android:name=".MyRouteProviderService"
+ *           android:label="@string/my_route_provider_service">
+ *       &lt;intent-filter>
+ *           &lt;action android:name="com.android.media.session.MediaRouteProvider" />
+ *       &lt;/intent-filter>
+ *   &lt;/service>
+ * </pre>
+ */
+public abstract class RouteProviderService extends Service {
+    private static final String TAG = "RouteProvider";
+    /**
+     * A service that implements a RouteProvider must declare that it handles
+     * this action in its AndroidManifest.
+     */
+    public static final String SERVICE_INTERFACE =
+            "com.android.media.session.MediaRouteProvider";
+
+    /**
+     * @hide
+     */
+    public static final String KEY_ROUTES = "routes";
+    /**
+     * @hide
+     */
+    public static final String KEY_CONNECTION = "connection";
+    /**
+     * @hide
+     */
+    public static final int RESULT_FAILURE = -1;
+    /**
+     * @hide
+     */
+    public static final int RESULT_SUCCESS = 0;
+
+    // The system's callback once it has bound to the service
+    private IRouteProviderCallback mCb;
+
+    /**
+     * If your service overrides onBind it must return super.onBind() in
+     * response to the {@link #SERVICE_INTERFACE} action.
+     */
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (intent != null && RouteProviderService.SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mBinder;
+        }
+        return null;
+    }
+
+    /**
+     * Disconnect the specified RouteConnection. The system will stop sending
+     * commands to this connection.
+     *
+     * @param connection The connection to disconnect.
+     * @hide
+     */
+    public final void disconnect(RouteConnection connection) {
+        if (mCb != null) {
+            try {
+                mCb.onConnectionTerminated(connection.getBinder());
+            } catch (RemoteException e) {
+                Log.wtf(TAG, "Error in disconnect.", e);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public final void sendRouteEvent(RouteEvent event) {
+        if (mCb != null) {
+            try {
+                mCb.onRouteEvent(event);
+            } catch (RemoteException e) {
+                Log.wtf(TAG, "Unable to send MediaRouteEvent to system", e);
+            }
+        }
+    }
+
+    /**
+     * Override to handle updates to the routes that are of interest. Each
+     * {@link RouteRequest} will specify if it is an active or passive request.
+     * Route discovery may perform more aggressive discovery on behalf of active
+     * requests but should use low power discovery methods otherwise.
+     * <p>
+     * A single app may have more than one request. Your provider is responsible
+     * for deciding the set of features that are important for discovery given
+     * the set of requests. If your provider only has one method of discovery it
+     * may simply verify that one or more requests are valid before starting
+     * discovery.
+     *
+     * @param requests The route requests that are currently relevant.
+     */
+    public void updateDiscoveryRequests(List<RouteRequest> requests) {
+    }
+
+    /**
+     * Return a list of matching routes for the given set of requests. Returning
+     * null or an empty list indicates there are no matches. A route is
+     * considered matching if it supports one or more of the
+     * {@link RouteOptions} specified. Each returned {@link RouteInfo}
+     * should include all the requested connections that it supports.
+     *
+     * @param options The set of requests for routes
+     * @return The routes that this caller may connect to using one or more of
+     *         the route options.
+     */
+    public abstract List<RouteInfo> getMatchingRoutes(List<RouteRequest> options);
+
+    /**
+     * Handle a request to connect to a specific route with a specific request.
+     * The {@link RouteConnection} must be fully defined before being returned,
+     * though the actual connection to the route may be performed in the
+     * background.
+     *
+     * @param route The route to connect to
+     * @param request The connection request parameters
+     * @return A MediaRouteConnection representing the connection to the route
+     */
+    public abstract RouteConnection connect(RouteInfo route, RouteRequest request);
+
+    private IRouteProvider.Stub mBinder = new IRouteProvider.Stub() {
+
+        @Override
+        public void registerCallback(IRouteProviderCallback cb) throws RemoteException {
+            mCb = cb;
+        }
+
+        @Override
+        public void unregisterCallback(IRouteProviderCallback cb) throws RemoteException {
+            mCb = null;
+        }
+
+        @Override
+        public void updateDiscoveryRequests(List<RouteRequest> requests)
+                throws RemoteException {
+            RouteProviderService.this.updateDiscoveryRequests(requests);
+        }
+
+        @Override
+        public void getAvailableRoutes(List<RouteRequest> requests, ResultReceiver cb)
+                throws RemoteException {
+            List<RouteInfo> routes = RouteProviderService.this.getMatchingRoutes(requests);
+            ArrayList<RouteInfo> routesArray;
+            if (routes instanceof ArrayList) {
+                routesArray = (ArrayList<RouteInfo>) routes;
+            } else {
+                routesArray = new ArrayList<RouteInfo>(routes);
+            }
+            Bundle resultData = new Bundle();
+            resultData.putParcelableArrayList(KEY_ROUTES, routesArray);
+            cb.send(routes == null ? RESULT_FAILURE : RESULT_SUCCESS, resultData);
+        }
+
+        @Override
+        public void connect(RouteInfo route, RouteRequest request, ResultReceiver cb)
+                throws RemoteException {
+            RouteConnection connection = RouteProviderService.this.connect(route, request);
+            Bundle resultData = new Bundle();
+            if (connection != null) {
+                connection.publish();
+                resultData.putBinder(KEY_CONNECTION, connection.getBinder());
+            }
+
+            cb.send(connection == null ? RESULT_FAILURE : RESULT_SUCCESS, resultData);
+        }
+    };
+}
diff --git a/media/java/android/media/session/MediaSessionToken.aidl b/media/java/android/media/routeprovider/RouteRequest.aidl
similarity index 90%
copy from media/java/android/media/session/MediaSessionToken.aidl
copy to media/java/android/media/routeprovider/RouteRequest.aidl
index 5812682..7bc5722 100644
--- a/media/java/android/media/session/MediaSessionToken.aidl
+++ b/media/java/android/media/routeprovider/RouteRequest.aidl
@@ -13,6 +13,6 @@
 ** limitations under the License.
 */
 
-package android.media.session;
+package android.media.routeprovider;
 
-parcelable MediaSessionToken;
+parcelable RouteRequest;
diff --git a/media/java/android/media/routeprovider/RouteRequest.java b/media/java/android/media/routeprovider/RouteRequest.java
new file mode 100644
index 0000000..9913566
--- /dev/null
+++ b/media/java/android/media/routeprovider/RouteRequest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2014 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.routeprovider;
+
+import android.media.session.RouteOptions;
+import android.media.session.SessionInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A request to connect or discover routes with certain capabilities.
+ * <p>
+ * Passed to a {@link RouteProviderService} when a request for discovery or to
+ * connect to a route is made. This identifies the app making the request and
+ * provides the full set of connection parameters they would like to use for a
+ * connection. An app that can connect in multiple ways will be represented by
+ * multiple requests.
+ */
+public final class RouteRequest implements Parcelable {
+    private final SessionInfo mSessionInfo;
+    private final RouteOptions mOptions;
+    private final boolean mActive;
+
+    /**
+     * @hide
+     */
+    public RouteRequest(SessionInfo info, RouteOptions connRequest,
+            boolean active) {
+        mSessionInfo = info;
+        mOptions = connRequest;
+        mActive = active;
+    }
+
+    private RouteRequest(Parcel in) {
+        mSessionInfo = SessionInfo.CREATOR.createFromParcel(in);
+        mOptions = RouteOptions.CREATOR.createFromParcel(in);
+        mActive = in.readInt() != 0;
+    }
+
+    /**
+     * Get information about the session making the request.
+     *
+     * @return Info on the session making the request
+     */
+    public SessionInfo getSessionInfo() {
+        return mSessionInfo;
+    }
+
+    /**
+     * Get the connection options, which includes the interfaces and other
+     * connection params the session wants to use with a route.
+     *
+     * @return The connection options
+     */
+    public RouteOptions getConnectionOptions() {
+        return mOptions;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        mSessionInfo.writeToParcel(dest, flags);
+        mOptions.writeToParcel(dest, flags);
+        dest.writeInt(mActive ? 1 : 0);
+    }
+
+    public static final Parcelable.Creator<RouteRequest> CREATOR
+            = new Parcelable.Creator<RouteRequest>() {
+        @Override
+        public RouteRequest createFromParcel(Parcel in) {
+            return new RouteRequest(in);
+        }
+
+        @Override
+        public RouteRequest[] newArray(int size) {
+            return new RouteRequest[size];
+        }
+    };
+}
diff --git a/media/java/android/media/session/IMediaSession.aidl b/media/java/android/media/session/ISession.aidl
similarity index 64%
rename from media/java/android/media/session/IMediaSession.aidl
rename to media/java/android/media/session/ISession.aidl
index aed7641..ca77f04 100644
--- a/media/java/android/media/session/IMediaSession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -15,25 +15,33 @@
 
 package android.media.session;
 
-import android.media.session.IMediaController;
+import android.media.session.ISessionController;
 import android.media.session.MediaMetadata;
+import android.media.session.RouteOptions;
+import android.media.session.RouteCommand;
+import android.media.session.RouteInfo;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
+import android.os.ResultReceiver;
 
 /**
  * Interface to a MediaSession in the system.
  * @hide
  */
-interface IMediaSession {
+interface ISession {
     void sendEvent(String event, in Bundle data);
-    IMediaController getMediaController();
+    ISessionController getController();
     void setTransportPerformerEnabled();
-    void setRouteState(in Bundle routeState);
-    void setRoute(in Bundle mediaRouteDescriptor);
-    List<String> getSupportedInterfaces();
     void publish();
     void destroy();
 
+    // These commands are for setting up and communicating with routes
+    // Returns true if the route was set for this session
+    boolean setRoute(in RouteInfo route);
+    void setRouteOptions(in List<RouteOptions> options);
+    void connectToRoute(in RouteInfo route, in RouteOptions options);
+    void sendRouteCommand(in RouteCommand event, in ResultReceiver cb);
+
     // These commands are for the TransportPerformer
     void setMetadata(in MediaMetadata metadata);
     void setPlaybackState(in PlaybackState state);
diff --git a/media/java/android/media/session/IMediaSessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
similarity index 73%
rename from media/java/android/media/session/IMediaSessionCallback.aidl
rename to media/java/android/media/session/ISessionCallback.aidl
index 7c183e0..f04cbcc 100644
--- a/media/java/android/media/session/IMediaSessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -16,6 +16,9 @@
 package android.media.session;
 
 import android.media.Rating;
+import android.media.session.RouteEvent;
+import android.media.session.RouteInfo;
+import android.media.session.RouteOptions;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.ResultReceiver;
@@ -23,10 +26,13 @@
 /**
  * @hide
  */
-oneway interface IMediaSessionCallback {
+oneway interface ISessionCallback {
     void onCommand(String command, in Bundle extras, in ResultReceiver cb);
-    void onMediaButton(in Intent mediaRequestIntent);
-    void onRequestRouteChange(in Bundle route);
+    void onMediaButton(in Intent mediaButtonIntent);
+    void onRequestRouteChange(in RouteInfo route);
+    void onRouteConnected(in RouteInfo route, in RouteOptions options);
+    void onRouteStateChange(int state);
+    void onRouteEvent(in RouteEvent event);
 
     // These callbacks are for the TransportPerformer
     void onPlay();
diff --git a/media/java/android/media/session/IMediaController.aidl b/media/java/android/media/session/ISessionController.aidl
similarity index 85%
rename from media/java/android/media/session/IMediaController.aidl
rename to media/java/android/media/session/ISessionController.aidl
index d34e973..e2e046f 100644
--- a/media/java/android/media/session/IMediaController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -17,7 +17,7 @@
 
 import android.content.Intent;
 import android.media.Rating;
-import android.media.session.IMediaControllerCallback;
+import android.media.session.ISessionControllerCallback;
 import android.media.session.MediaMetadata;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
@@ -28,12 +28,13 @@
  * Interface to a MediaSession in the system.
  * @hide
  */
-interface IMediaController {
+interface ISessionController {
     void sendCommand(String command, in Bundle extras, in ResultReceiver cb);
     void sendMediaButton(in KeyEvent mediaButton);
-    void registerCallbackListener(in IMediaControllerCallback cb);
-    void unregisterCallbackListener(in IMediaControllerCallback cb);
+    void registerCallbackListener(in ISessionControllerCallback cb);
+    void unregisterCallbackListener(in ISessionControllerCallback cb);
     boolean isTransportControlEnabled();
+    void showRoutePicker();
 
     // These commands are for the TransportController
     void play();
diff --git a/media/java/android/media/session/IMediaControllerCallback.aidl b/media/java/android/media/session/ISessionControllerCallback.aidl
similarity index 88%
rename from media/java/android/media/session/IMediaControllerCallback.aidl
rename to media/java/android/media/session/ISessionControllerCallback.aidl
index 3651f1b..bc1ae05 100644
--- a/media/java/android/media/session/IMediaControllerCallback.aidl
+++ b/media/java/android/media/session/ISessionControllerCallback.aidl
@@ -16,15 +16,16 @@
 package android.media.session;
 
 import android.media.session.MediaMetadata;
+import android.media.session.RouteInfo;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
 
 /**
  * @hide
  */
-oneway interface IMediaControllerCallback {
+oneway interface ISessionControllerCallback {
     void onEvent(String event, in Bundle extras);
-    void onRouteChanged(in Bundle route);
+    void onRouteChanged(in RouteInfo route);
 
     // These callbacks are for the TransportController
     void onPlaybackStateChanged(in PlaybackState state);
diff --git a/media/java/android/media/session/IMediaSessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
similarity index 77%
rename from media/java/android/media/session/IMediaSessionManager.aidl
rename to media/java/android/media/session/ISessionManager.aidl
index 0b4328e..84b9a0f 100644
--- a/media/java/android/media/session/IMediaSessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -15,14 +15,14 @@
 
 package android.media.session;
 
-import android.media.session.IMediaSession;
-import android.media.session.IMediaSessionCallback;
+import android.media.session.ISession;
+import android.media.session.ISessionCallback;
 import android.os.Bundle;
 
 /**
  * Interface to the MediaSessionManagerService
  * @hide
  */
-interface IMediaSessionManager {
-    IMediaSession createSession(String packageName, in IMediaSessionCallback cb, String tag);
+interface ISessionManager {
+    ISession createSession(String packageName, in ISessionCallback cb, String tag);
 }
\ No newline at end of file
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index b3506b3..14d9fb1 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -15,12 +15,11 @@
  */
 package android.media.session;
 
-import android.media.RemoteControlClient;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 /**
- * Playback state for a {@link MediaSession}. This includes a state like
+ * Playback state for a {@link Session}. This includes a state like
  * {@link PlaybackState#PLAYSTATE_PLAYING}, the current playback position,
  * and the current control capabilities.
  */
@@ -147,6 +146,14 @@
      */
     public final static int PLAYSTATE_ERROR = 7;
 
+    /**
+     * State indicating the class doing playback is currently connecting to a
+     * route. Depending on the implementation you may return to the previous
+     * state when the connection finishes or enter {@link #PLAYSTATE_NONE}. If
+     * the connection failed {@link #PLAYSTATE_ERROR} should be used.
+     */
+    public final static int PLAYSTATE_CONNECTING = 8;
+
     private int mState;
     private long mPosition;
     private long mBufferPosition;
diff --git a/media/java/android/media/session/Route.java b/media/java/android/media/session/Route.java
new file mode 100644
index 0000000..c9530a6
--- /dev/null
+++ b/media/java/android/media/session/Route.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2014 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.text.TextUtils;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Represents a destination which an application has connected to and may send
+ * media content.
+ * <p>
+ * This allows a session owner to interact with a route it has been connected
+ * to. The MediaRoute must be used to get {@link RouteInterface}
+ * instances which can be used to communicate over a specific interface on the
+ * route.
+ */
+public final class Route {
+    private static final String TAG = "Route";
+    private final RouteInfo mInfo;
+    private final Session mSession;
+    private final RouteOptions mOptions;
+
+    /**
+     * @hide
+     */
+    public Route(RouteInfo info, RouteOptions options, Session session) {
+        if (info == null || options == null) {
+            throw new IllegalStateException("Route info was not valid!");
+        }
+        mInfo = info;
+        mOptions = options;
+        mSession = session;
+    }
+
+    /**
+     * Get the {@link RouteInfo} for this route.
+     *
+     * @return The info for this route.
+     */
+    public RouteInfo getRouteInfo() {
+        return mInfo;
+    }
+
+    /**
+     * Get the {@link RouteOptions} that were used to connect this route.
+     *
+     * @return The options used to connect to this route.
+     */
+    public RouteOptions getOptions() {
+        return mOptions;
+    }
+
+    /**
+     * Gets an interface provided by this route. If the interface is not
+     * supported by the route, returns null.
+     *
+     * @see RouteInterface
+     * @param iface The name of the interface to create
+     * @return A {@link RouteInterface} or null if the interface is
+     *         not supported.
+     */
+    public RouteInterface getInterface(String iface) {
+        if (TextUtils.isEmpty(iface)) {
+            throw new IllegalArgumentException("iface may not be empty.");
+        }
+        List<String> ifaces = mOptions.getInterfaceNames();
+        if (ifaces != null) {
+            for (int i = ifaces.size() - 1; i >= 0; i--) {
+                if (iface.equals(ifaces.get(i))) {
+                    return new RouteInterface(this, iface, mSession);
+                }
+            }
+        }
+        Log.e(TAG, "Interface not supported by route");
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    Session getSession() {
+        return mSession;
+    }
+}
diff --git a/media/java/android/media/session/MediaSessionToken.aidl b/media/java/android/media/session/RouteCommand.aidl
similarity index 95%
rename from media/java/android/media/session/MediaSessionToken.aidl
rename to media/java/android/media/session/RouteCommand.aidl
index 5812682..725b308 100644
--- a/media/java/android/media/session/MediaSessionToken.aidl
+++ b/media/java/android/media/session/RouteCommand.aidl
@@ -15,4 +15,4 @@
 
 package android.media.session;
 
-parcelable MediaSessionToken;
+parcelable RouteCommand;
diff --git a/media/java/android/media/session/RouteCommand.java b/media/java/android/media/session/RouteCommand.java
new file mode 100644
index 0000000..358bc0a
--- /dev/null
+++ b/media/java/android/media/session/RouteCommand.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2014 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.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents a command that an application may send to a route.
+ * <p>
+ * Commands are associated with a specific route and interface supported by that
+ * route and sent through the session. This class isn't used directly by apps.
+ *
+ * @hide
+ */
+public final class RouteCommand implements Parcelable {
+    private final String mRoute;
+    private final String mIface;
+    private final String mEvent;
+    private final Bundle mExtras;
+
+    /**
+     * @param route The id of the route this event is being sent on
+     * @param iface The interface the sender used
+     * @param event The event or command
+     * @param extras Any extras included with the event
+     */
+    public RouteCommand(String route, String iface, String event, Bundle extras) {
+        mRoute = route;
+        mIface = iface;
+        mEvent = event;
+        mExtras = extras;
+    }
+
+    private RouteCommand(Parcel in) {
+        mRoute = in.readString();
+        mIface = in.readString();
+        mEvent = in.readString();
+        mExtras = in.readBundle();
+    }
+
+    /**
+     * Get the id for the route this event was sent on.
+     *
+     * @return The route id this event is using
+     */
+    public String getRouteInfo() {
+        return mRoute;
+    }
+
+    /**
+     * Get the interface this event was sent from
+     *
+     * @return The interface for this event
+     */
+    public String getIface() {
+        return mIface;
+    }
+
+    /**
+     * Get the action/name of the event.
+     *
+     * @return The name of event/command.
+     */
+    public String getEvent() {
+        return mEvent;
+    }
+
+    /**
+     * Get any extras included with the event.
+     *
+     * @return The bundle included with the event or null
+     */
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mRoute);
+        dest.writeString(mIface);
+        dest.writeString(mEvent);
+        dest.writeBundle(mExtras);
+    }
+
+    public static final Parcelable.Creator<RouteCommand> CREATOR
+            = new Parcelable.Creator<RouteCommand>() {
+        @Override
+        public RouteCommand createFromParcel(Parcel in) {
+            return new RouteCommand(in);
+        }
+
+        @Override
+        public RouteCommand[] newArray(int size) {
+            return new RouteCommand[size];
+        }
+    };
+}
diff --git a/media/java/android/media/session/MediaSessionToken.aidl b/media/java/android/media/session/RouteEvent.aidl
similarity index 95%
copy from media/java/android/media/session/MediaSessionToken.aidl
copy to media/java/android/media/session/RouteEvent.aidl
index 5812682..6966207 100644
--- a/media/java/android/media/session/MediaSessionToken.aidl
+++ b/media/java/android/media/session/RouteEvent.aidl
@@ -15,4 +15,4 @@
 
 package android.media.session;
 
-parcelable MediaSessionToken;
+parcelable RouteEvent;
diff --git a/media/java/android/media/session/RouteEvent.java b/media/java/android/media/session/RouteEvent.java
new file mode 100644
index 0000000..918e410
--- /dev/null
+++ b/media/java/android/media/session/RouteEvent.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2014 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.media.routeprovider.RouteConnection;
+import android.media.routeprovider.RouteProviderService;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents an event that a route provider is sending to a particular
+ * {@link RouteConnection}. Events are associated with a specific interface
+ * supported by the connection and sent through the {@link RouteProviderService}.
+ * This class isn't used directly by apps.
+ *
+ * @hide
+ */
+public class RouteEvent implements Parcelable {
+    private final IBinder mConnection;
+    private final String mIface;
+    private final String mEvent;
+    private final Bundle mExtras;
+
+    /**
+     * @param connection The connection that this event is for
+     * @param iface The interface the sender used
+     * @param event The event or command
+     * @param extras Any extras included with the event
+     */
+    public RouteEvent(IBinder connection, String iface, String event, Bundle extras) {
+        mConnection = connection;
+        mIface = iface;
+        mEvent = event;
+        mExtras = extras;
+    }
+
+    private RouteEvent(Parcel in) {
+        mConnection = in.readStrongBinder();
+        mIface = in.readString();
+        mEvent = in.readString();
+        mExtras = in.readBundle();
+    }
+
+    /**
+     * Get the connection this event was sent on.
+     *
+     * @return The connection this event is using
+     */
+    public IBinder getConnection() {
+        return mConnection;
+    }
+
+    /**
+     * Get the interface this event was sent from
+     *
+     * @return The interface for this event
+     */
+    public String getIface() {
+        return mIface;
+    }
+
+    /**
+     * Get the action/name of the event.
+     *
+     * @return The name of event/command.
+     */
+    public String getEvent() {
+        return mEvent;
+    }
+
+    /**
+     * Get any extras included with the event.
+     *
+     * @return The bundle included with the event or null
+     */
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongBinder(mConnection);
+        dest.writeString(mIface);
+        dest.writeString(mEvent);
+        dest.writeBundle(mExtras);
+    }
+
+    public static final Parcelable.Creator<RouteEvent> CREATOR
+            = new Parcelable.Creator<RouteEvent>() {
+        @Override
+        public RouteEvent createFromParcel(Parcel in) {
+            return new RouteEvent(in);
+        }
+
+        @Override
+        public RouteEvent[] newArray(int size) {
+            return new RouteEvent[size];
+        }
+    };
+}
diff --git a/media/java/android/media/session/MediaSessionToken.aidl b/media/java/android/media/session/RouteInfo.aidl
similarity index 95%
copy from media/java/android/media/session/MediaSessionToken.aidl
copy to media/java/android/media/session/RouteInfo.aidl
index 5812682..c5f50c8 100644
--- a/media/java/android/media/session/MediaSessionToken.aidl
+++ b/media/java/android/media/session/RouteInfo.aidl
@@ -15,4 +15,4 @@
 
 package android.media.session;
 
-parcelable MediaSessionToken;
+parcelable RouteInfo;
diff --git a/media/java/android/media/session/RouteInfo.java b/media/java/android/media/session/RouteInfo.java
new file mode 100644
index 0000000..17df969
--- /dev/null
+++ b/media/java/android/media/session/RouteInfo.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2014 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.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Information about a route, including its display name, a way to identify it,
+ * and the ways it can be connected to.
+ */
+public final class RouteInfo implements Parcelable {
+    private final String mName;
+    private final String mId;
+    private final String mProviderId;
+    private final List<RouteOptions> mOptions;
+
+    private RouteInfo(String id, String name, String providerId,
+            List<RouteOptions> connRequests) {
+        mId = id;
+        mName = name;
+        mProviderId = providerId;
+        mOptions = connRequests;
+    }
+
+    private RouteInfo(Parcel in) {
+        mId = in.readString();
+        mName = in.readString();
+        mProviderId = in.readString();
+        mOptions = new ArrayList<RouteOptions>();
+        in.readTypedList(mOptions, RouteOptions.CREATOR);
+    }
+
+    /**
+     * Get the displayable name of this route.
+     *
+     * @return A short, user readable name for this route
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Get the unique id for this route.
+     *
+     * @return A unique route id.
+     */
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * Get the package name of this route's provider.
+     *
+     * @return The package name of this route's provider.
+     */
+    public String getProvider() {
+        return mProviderId;
+    }
+
+    /**
+     * Get the set of connections that may be used with this route.
+     *
+     * @return An array of connection requests that may be used to connect
+     */
+    public List<RouteOptions> getConnectionMethods() {
+        return mOptions;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mId);
+        dest.writeString(mName);
+        dest.writeString(mProviderId);
+        dest.writeTypedList(mOptions);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder bob = new StringBuilder();
+        bob.append("RouteInfo: id=").append(mId).append(", name=").append(mName)
+                .append(", provider=").append(mProviderId).append(", options={");
+        for (int i = 0; i < mOptions.size(); i++) {
+            if (i != 0) {
+                bob.append(", ");
+            }
+            bob.append(mOptions.get(i).toString());
+        }
+        bob.append("}");
+        return bob.toString();
+    }
+
+    public static final Parcelable.Creator<RouteInfo> CREATOR
+            = new Parcelable.Creator<RouteInfo>() {
+        @Override
+        public RouteInfo createFromParcel(Parcel in) {
+            return new RouteInfo(in);
+        }
+
+        @Override
+        public RouteInfo[] newArray(int size) {
+            return new RouteInfo[size];
+        }
+    };
+
+    /**
+     * Helper for creating MediaRouteInfos. A route must have a name and an id.
+     * While options are not strictly required the route cannot be connected to
+     * without at least one set of options.
+     */
+    public static final class Builder {
+        private String mName;
+        private String mId;
+        private String mProviderPackage;
+        private ArrayList<RouteOptions> mOptions;
+
+        /**
+         * Copies an existing route info object. TODO Remove once we have
+         * helpers for creating route infos.
+         *
+         * @param from The existing info to copy.
+         */
+        public Builder(RouteInfo from) {
+            mOptions = new ArrayList<RouteOptions>(from.getConnectionMethods());
+            mName = from.mName;
+            mId = from.mId;
+            mProviderPackage = from.mProviderId;
+        }
+
+        public Builder() {
+            mOptions = new ArrayList<RouteOptions>();
+        }
+
+        /**
+         * Set the user visible name for this route.
+         *
+         * @param name The name of the route
+         * @return The builder for easy chaining.
+         */
+        public Builder setName(String name) {
+            mName = name;
+            return this;
+        }
+
+        /**
+         * Set the id of the route. This should be unique to the provider.
+         *
+         * @param id The unique id of the route.
+         * @return The builder for easy chaining.
+         */
+        public Builder setId(String id) {
+            mId = id;
+            return this;
+        }
+
+        /**
+         * @hide
+         */
+        public Builder setProviderId(String packageName) {
+            mProviderPackage = packageName;
+            return this;
+        }
+
+        /**
+         * Add a set of {@link RouteOptions} to the route. Multiple options
+         * may be added to the same route.
+         *
+         * @param options The options to add to this route.
+         * @return The builder for easy chaining.
+         */
+        public Builder addRouteOptions(RouteOptions options) {
+            mOptions.add(options);
+            return this;
+        }
+
+        /**
+         * Clear the set of {@link RouteOptions} on the route.
+         *
+         * @return The builder for easy chaining
+         */
+        public Builder clearRouteOptions() {
+            mOptions.clear();
+            return this;
+        }
+
+        /**
+         * Build a new MediaRouteInfo.
+         *
+         * @return A new MediaRouteInfo with the values that were set.
+         */
+        public RouteInfo build() {
+            if (TextUtils.isEmpty(mName)) {
+                throw new IllegalArgumentException("Must set a name before building");
+            }
+            if (TextUtils.isEmpty(mId)) {
+                throw new IllegalArgumentException("Must set an id before building");
+            }
+            return new RouteInfo(mId, mName, mProviderPackage, mOptions);
+        }
+
+        /**
+         * Get the current number of options that have been added to this
+         * builder.
+         *
+         * @return The number of options that have been added.
+         */
+        public int getOptionsSize() {
+            return mOptions.size();
+        }
+    }
+}
diff --git a/media/java/android/media/session/RouteInterface.java b/media/java/android/media/session/RouteInterface.java
index 2391f27..e9c9fd3 100644
--- a/media/java/android/media/session/RouteInterface.java
+++ b/media/java/android/media/session/RouteInterface.java
@@ -17,136 +17,161 @@
 
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Parcelable;
 import android.os.ResultReceiver;
+import android.util.Log;
+
+import java.util.ArrayList;
 
 /**
- * Routes can support multiple interfaces for MediaSessions to interact with. To
- * add a standard interface you should implement that interface's RouteInterface
- * Stub and register it with the session. The set of supported commands is
- * dependent on the specific interface's implementation.
- * <p>
- * A MediaInterface can be registered by calling TODO. Once added an interface
- * will be used by Sessions to decide how they communicate with a session and
- * cannot be removed, so all interfaces that you plan to support should be added
- * when the route is created.
+ * A route can support multiple interfaces for a {@link Session} to
+ * interact with. To use a specific interface with a route a
+ * MediaSessionRouteInterface needs to be retrieved from the route. An
+ * implementation of the specific interface, like
+ * {@link RoutePlaybackControls}, should be used to simplify communication
+ * and reduce errors on that interface.
  *
- * @see RouteTransportControls
+ * @see RoutePlaybackControls for an example
  */
 public final class RouteInterface {
-    private static final String TAG = "MediaInterface";
+    private static final String TAG = "RouteInterface";
 
-    private static final String KEY_RESULT = "result";
+    /**
+     * Error indicating the route is currently not connected.
+     */
+    public static final int RESULT_NOT_CONNECTED = -5;
+    /**
+     * Error indicating the session is no longer using the route this command
+     * was sent to.
+     */
+    public static final int RESULT_ROUTE_IS_STALE = -4;
+    /**
+     * Error indicating that the interface does not support the command.
+     */
+    public static final int RESULT_COMMAND_NOT_SUPPORTED = -3;
+    /**
+     * Error indicating that the route does not support the interface.
+     */
+    public static final int RESULT_INTERFACE_NOT_SUPPORTED = -2;
+    /**
+     * Generic error. Extra information about the error may be included in the
+     * result bundle.
+     */
+    public static final int RESULT_ERROR = -1;
+    /**
+     * The command was successful. Extra information may be included in the
+     * result bundle.
+     */
+    public static final int RESULT_SUCCESS = 1;
 
-    private final MediaController mController;
+    private final Route mRoute;
     private final String mIface;
+    private final Session mSession;
+
+    private final Object mLock = new Object();
+    private final ArrayList<EventHandler> mListeners = new ArrayList<EventHandler>();
 
     /**
      * @hide
      */
-    RouteInterface(MediaController controller, String iface) {
-        mController = controller;
+    RouteInterface(Route route, String iface, Session session) {
+        mRoute = route;
         mIface = iface;
+        mSession = session;
+        mSession.addInterfaceListener(iface, mEventListener);
     }
 
-    public void sendCommand(String command, Bundle params, ResultReceiver cb) {
-        // TODO
+    /**
+     * Send a command using this interface.
+     *
+     * @param command The command to send.
+     * @param extras Any extras to include with the command.
+     * @param cb The callback to receive the result on.
+     * @return true if the command was sent, false otherwise.
+     */
+    public boolean sendCommand(String command, Bundle extras, ResultReceiver cb) {
+        RouteCommand cmd = new RouteCommand(mRoute.getRouteInfo().getId(), mIface,
+                command, extras);
+        return mSession.sendRouteCommand(cmd, cb);
     }
 
+    /**
+     * Add a listener to this interface. Events will be sent on the caller's
+     * thread.
+     *
+     * @param listener The listener to receive events on.
+     */
     public void addListener(EventListener listener) {
         addListener(listener, null);
     }
 
+    /**
+     * Add a listener for this interface. If a handler is specified events will
+     * be performed on the handler's thread, otherwise the caller's thread will
+     * be used.
+     *
+     * @param listener The listener to receive events on
+     * @param handler The handler whose thread to post calls on
+     */
     public void addListener(EventListener listener, Handler handler) {
-        // TODO See MediaController for add/remove pattern
+        if (listener == null) {
+            throw new IllegalArgumentException("listener may not be null");
+        }
+        if (handler == null) {
+            handler = new Handler();
+        }
+        synchronized (mLock) {
+            if (findIndexOfListenerLocked(listener) != -1) {
+                Log.d(TAG, "Listener is already added, ignoring");
+                return;
+            }
+            mListeners.add(new EventHandler(handler.getLooper(), listener));
+        }
     }
 
+    /**
+     * Remove a listener from this interface.
+     *
+     * @param listener The listener to stop receiving events on.
+     */
     public void removeListener(EventListener listener) {
-        // TODO
-    }
-
-    // TODO decide on list of supported types
-    private static Bundle writeResultToBundle(Object v) {
-        Bundle b = new Bundle();
-        if (v == null) {
-            // Don't send anything if null
-        } else if (v instanceof String) {
-            b.putString(KEY_RESULT, (String) v);
-        } else if (v instanceof Integer) {
-            b.putInt(KEY_RESULT, (Integer) v);
-        } else if (v instanceof Bundle) {
-            // Must be before Parcelable
-            b.putBundle(KEY_RESULT, (Bundle) v);
-        } else if (v instanceof Parcelable) {
-            b.putParcelable(KEY_RESULT, (Parcelable) v);
-        } else if (v instanceof Short) {
-            b.putShort(KEY_RESULT, (Short) v);
-        } else if (v instanceof Long) {
-            b.putLong(KEY_RESULT, (Long) v);
-        } else if (v instanceof Float) {
-            b.putFloat(KEY_RESULT, (Float) v);
-        } else if (v instanceof Double) {
-            b.putDouble(KEY_RESULT, (Double) v);
-        } else if (v instanceof Boolean) {
-            b.putBoolean(KEY_RESULT, (Boolean) v);
-        } else if (v instanceof CharSequence) {
-            // Must be after String
-            b.putCharSequence(KEY_RESULT, (CharSequence) v);
-        } else if (v instanceof boolean[]) {
-            b.putBooleanArray(KEY_RESULT, (boolean[]) v);
-        } else if (v instanceof byte[]) {
-            b.putByteArray(KEY_RESULT, (byte[]) v);
-        } else if (v instanceof String[]) {
-            b.putStringArray(KEY_RESULT, (String[]) v);
-        } else if (v instanceof CharSequence[]) {
-            // Must be after String[] and before Object[]
-            b.putCharSequenceArray(KEY_RESULT, (CharSequence[]) v);
-        } else if (v instanceof IBinder) {
-            b.putBinder(KEY_RESULT, (IBinder) v);
-        } else if (v instanceof Parcelable[]) {
-            b.putParcelableArray(KEY_RESULT, (Parcelable[]) v);
-        } else if (v instanceof int[]) {
-            b.putIntArray(KEY_RESULT, (int[]) v);
-        } else if (v instanceof long[]) {
-            b.putLongArray(KEY_RESULT, (long[]) v);
-        } else if (v instanceof Byte) {
-            b.putByte(KEY_RESULT, (Byte) v);
+        if (listener == null) {
+            throw new IllegalArgumentException("listener may not be null");
         }
-        return b;
-    }
-
-    public abstract static class Stub {
-
-        /**
-         * The name of an interface should be a fully qualified name to prevent
-         * namespace collisions. Example: "com.myproject.MyPlaybackInterface"
-         *
-         * @return The name of this interface
-         */
-        public abstract String getName();
-
-        /**
-         * This is called when a command is received that matches the interface
-         * you registered. Commands can come from any app with a MediaController
-         * reference to the session.
-         *
-         * @see MediaController
-         * @see MediaSession
-         * @param command The command or method to invoke.
-         * @param args Any args that were included with the command. May be
-         *            null.
-         * @param cb The callback provided to send a response on. May be null.
-         */
-        public abstract void onCommand(String command, Bundle args, ResultReceiver cb);
-
-        public final void sendEvent(MediaSession session, String event, Bundle extras) {
-            // TODO
+        synchronized (mLock) {
+            int index = findIndexOfListenerLocked(listener);
+            if (index != -1) {
+                mListeners.remove(index);
+            }
         }
     }
 
+    private int findIndexOfListenerLocked(EventListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("Callback cannot be null");
+        }
+        for (int i = mListeners.size() - 1; i >= 0; i--) {
+            EventHandler handler = mListeners.get(i);
+            if (listener == handler.mListener) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private EventListener mEventListener = new EventListener() {
+            @Override
+        public void onEvent(String event, Bundle args) {
+            synchronized (mLock) {
+                for (int i = mListeners.size() - 1; i >= 0; i--) {
+                    mListeners.get(i).postEvent(event, args);
+                }
+            }
+        }
+
+    };
+
     /**
      * An EventListener can be registered by an app with TODO to handle events
      * sent by the session on a specific interface.
@@ -166,9 +191,9 @@
 
     private static final class EventHandler extends Handler {
 
-        private final RouteInterface.EventListener mListener;
+        private final EventListener mListener;
 
-        public EventHandler(Looper looper, RouteInterface.EventListener cb) {
+        public EventHandler(Looper looper, EventListener cb) {
             super(looper, null, true);
             mListener = cb;
         }
diff --git a/media/java/android/media/session/MediaSessionToken.aidl b/media/java/android/media/session/RouteOptions.aidl
similarity index 95%
copy from media/java/android/media/session/MediaSessionToken.aidl
copy to media/java/android/media/session/RouteOptions.aidl
index 5812682..feaf517 100644
--- a/media/java/android/media/session/MediaSessionToken.aidl
+++ b/media/java/android/media/session/RouteOptions.aidl
@@ -15,4 +15,4 @@
 
 package android.media.session;
 
-parcelable MediaSessionToken;
+parcelable RouteOptions;
diff --git a/media/java/android/media/session/RouteOptions.java b/media/java/android/media/session/RouteOptions.java
new file mode 100644
index 0000000..5105867
--- /dev/null
+++ b/media/java/android/media/session/RouteOptions.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2014 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.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Specifies options that an application might use when connecting to a route.
+ * This includes things like interfaces, connection parameters, and required
+ * features.
+ * <p>
+ * An application may create several different route options that describe
+ * alternative sets of capabilities that it can use and choose the most
+ * appropriate route options when it is ready to connect to the route. Each
+ * route options instance must specify a complete set of capabilities to request
+ * when the connection is established.
+ */
+public final class RouteOptions implements Parcelable {
+    private static final String TAG = "RouteOptions";
+
+    private final ArrayList<String> mIfaces;
+    private final Bundle mConnectionParams;
+
+    private RouteOptions(List<String> ifaces, Bundle params) {
+        mIfaces = new ArrayList<String>(ifaces);
+        mConnectionParams = params;
+    }
+
+    private RouteOptions(Parcel in) {
+        mIfaces = new ArrayList<String>();
+        in.readStringList(mIfaces);
+        mConnectionParams = in.readBundle();
+    }
+
+    /**
+     * Get the interfaces this connection wants to use.
+     *
+     * @return The interfaces for this connection
+     */
+    public List<String> getInterfaceNames() {
+        return mIfaces;
+    }
+
+    /**
+     * Get the parameters that will be used for connecting.
+     *
+     * @return The set of connection parameters this connections uses
+     */
+    public Bundle getConnectionParams() {
+        return mConnectionParams;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStringList(mIfaces);
+        dest.writeBundle(mConnectionParams);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder bob = new StringBuilder();
+        bob.append("Options: interfaces={");
+        for (int i = 0; i < mIfaces.size(); i++) {
+            if (i != 0) {
+                bob.append(", ");
+            }
+            bob.append(mIfaces.get(i));
+        }
+        bob.append("}");
+        bob.append(", parameters=");
+        bob.append(mConnectionParams == null ? "null" : mConnectionParams.toString());
+        return bob.toString();
+    }
+
+    public static final Parcelable.Creator<RouteOptions> CREATOR
+            = new Parcelable.Creator<RouteOptions>() {
+        @Override
+        public RouteOptions createFromParcel(Parcel in) {
+            return new RouteOptions(in);
+        }
+
+        @Override
+        public RouteOptions[] newArray(int size) {
+            return new RouteOptions[size];
+        }
+    };
+
+    /**
+     * Builder for creating {@link RouteOptions}.
+     */
+    public final static class Builder {
+        private ArrayList<String> mIfaces = new ArrayList<String>();
+        private Bundle mConnectionParams;
+
+        public Builder() {
+        }
+
+        /**
+         * Add a required interface to the options.
+         *
+         * @param interfaceName The name of the interface to add.
+         * @return The builder to allow chaining commands.
+         */
+        public Builder addInterface(String interfaceName) {
+            if (TextUtils.isEmpty(interfaceName)) {
+                throw new IllegalArgumentException("interfaceName cannot be empty");
+            }
+            if (!mIfaces.contains(interfaceName)) {
+                mIfaces.add(interfaceName);
+            } else {
+                Log.w(TAG, "Attempted to add interface that is already added");
+            }
+            return this;
+        }
+
+        /**
+         * Set the connection parameters to use with the options. TODO replace
+         * with more specific calls once we decide on the standard way to
+         * express parameters.
+         *
+         * @param parameters The parameters to use.
+         * @return The builder to allow chaining commands.
+         */
+        public Builder setParameters(Bundle parameters) {
+            mConnectionParams = parameters;
+            return this;
+        }
+
+        /**
+         * Generate a set of options.
+         *
+         * @return The options with the specified components.
+         */
+        public RouteOptions build() {
+            return new RouteOptions(mIfaces, mConnectionParams);
+        }
+    }
+}
diff --git a/media/java/android/media/session/RoutePlaybackControls.java b/media/java/android/media/session/RoutePlaybackControls.java
new file mode 100644
index 0000000..a3ffb58
--- /dev/null
+++ b/media/java/android/media/session/RoutePlaybackControls.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2014 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.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+
+/**
+ * A standard media control interface for Routes that support queueing and
+ * transport controls. Routes may support multiple interfaces for MediaSessions
+ * to interact with.
+ */
+public final class RoutePlaybackControls {
+    private static final String TAG = "RoutePlaybackControls";
+    public static final String NAME = "android.media.session.RoutePlaybackControls";
+
+    /** @hide */
+    public static final String KEY_VALUE1 = "value1";
+
+    /** @hide */
+    public static final String CMD_FAST_FORWARD = "fastForward";
+    /** @hide */
+    public static final String CMD_GET_CURRENT_POSITION = "getCurrentPosition";
+    /** @hide */
+    public static final String CMD_GET_CAPABILITIES = "getCapabilities";
+    /** @hide */
+    public static final String CMD_PLAY_NOW = "playNow";
+    /** @hide */
+    public static final String CMD_RESUME = "resume";
+    /** @hide */
+    public static final String CMD_PAUSE = "pause";
+
+    /** @hide */
+    public static final String EVENT_PLAYSTATE_CHANGE = "playstateChange";
+    /** @hide */
+    public static final String EVENT_METADATA_CHANGE = "metadataChange";
+
+    private final RouteInterface mIface;
+
+    private RoutePlaybackControls(RouteInterface iface) {
+        mIface = iface;
+    }
+
+    /**
+     * Get a new MediaRoutePlaybackControls instance for sending commands using
+     * this interface. If the provided route doesn't support this interface null
+     * will be returned.
+     *
+     * @param route The route to send commands to.
+     * @return A MediaRoutePlaybackControls instance or null if not supported.
+     */
+    public static RoutePlaybackControls from(Route route) {
+        RouteInterface iface = route.getInterface(NAME);
+        if (iface != null) {
+            return new RoutePlaybackControls(iface);
+        }
+        return null;
+    }
+
+    /**
+     * Send a resume command to the route.
+     */
+    public void resume() {
+        mIface.sendCommand(CMD_RESUME, null, null);
+    }
+
+    /**
+     * Send a pause command to the route.
+     */
+    public void pause() {
+        mIface.sendCommand(CMD_PAUSE, null, null);
+    }
+
+    /**
+     * Send a fast forward command.
+     */
+    public void fastForward() {
+        Bundle b = new Bundle();
+        mIface.sendCommand(CMD_FAST_FORWARD, b, null);
+    }
+
+    /**
+     * Retrieves the current playback position.
+     *
+     * @param cb The callback to receive the result on.
+     */
+    public void getCurrentPosition(ResultReceiver cb) {
+        mIface.sendCommand(CMD_GET_CURRENT_POSITION, null, cb);
+    }
+
+    public void getCapabilities(ResultReceiver cb) {
+        mIface.sendCommand(CMD_GET_CAPABILITIES, null, cb);
+    }
+
+    public void addListener(Listener listener) {
+        mIface.addListener(listener);
+    }
+
+    public void addListener(Listener listener, Handler handler) {
+        mIface.addListener(listener, handler);
+    }
+
+    public void removeListener(Listener listener) {
+        mIface.removeListener(listener);
+    }
+
+    public void playNow(String content) {
+        Bundle bundle = new Bundle();
+        bundle.putString(KEY_VALUE1, content);
+        mIface.sendCommand(CMD_PLAY_NOW, bundle, null);
+    }
+
+    /**
+     * Register this event listener using {@link #addListener} to receive
+     * RoutePlaybackControl events from a session.
+     */
+    public static abstract class Listener extends RouteInterface.EventListener {
+        @Override
+        public final void onEvent(String event, Bundle args) {
+            if (EVENT_PLAYSTATE_CHANGE.equals(event)) {
+                onPlaybackStateChange(args.getInt(KEY_VALUE1, 0));
+            } else if (EVENT_METADATA_CHANGE.equals(event)) {
+                onMetadataUpdate((MediaMetadata) args.getParcelable(KEY_VALUE1));
+            }
+        }
+
+        /**
+         * Override to handle updates to the playback state. Valid values are in
+         * {@link TransportPerformer}. TODO put playstate values somewhere more
+         * generic.
+         *
+         * @param state
+         */
+        public void onPlaybackStateChange(int state) {
+        }
+
+        /**
+         * Override to handle metadata changes for this session's media. The
+         * default supported fields are those in {@link MediaMetadata}.
+         *
+         * @param metadata
+         */
+        public void onMetadataUpdate(MediaMetadata metadata) {
+        }
+    }
+
+}
diff --git a/media/java/android/media/session/RouteTransportControls.java b/media/java/android/media/session/RouteTransportControls.java
deleted file mode 100644
index 665fd10..0000000
--- a/media/java/android/media/session/RouteTransportControls.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 2014 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.media.RemoteControlClient;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.ResultReceiver;
-import android.text.TextUtils;
-import android.util.Log;
-
-/**
- * A standard media control interface for Routes. Routes can support multiple
- * interfaces for MediaSessions to interact with. TODO rewrite for routes
- */
-public final class RouteTransportControls {
-    private static final String TAG = "RouteTransportControls";
-    public static final String NAME = "android.media.session.RouteTransportControls";
-
-    private static final String KEY_VALUE1 = "value1";
-
-    private static final String METHOD_FAST_FORWARD = "fastForward";
-    private static final String METHOD_GET_CURRENT_POSITION = "getCurrentPosition";
-    private static final String METHOD_GET_CAPABILITIES = "getCapabilities";
-
-    private static final String EVENT_PLAYSTATE_CHANGE = "playstateChange";
-    private static final String EVENT_METADATA_CHANGE = "metadataChange";
-
-    private final MediaController mController;
-    private final RouteInterface mIface;
-
-    private RouteTransportControls(RouteInterface iface, MediaController controller) {
-        mIface = iface;
-        mController = controller;
-    }
-
-    public static RouteTransportControls from(MediaController controller) {
-//        MediaInterface iface = controller.getInterface(NAME);
-//        if (iface != null) {
-//            return new RouteTransportControls(iface, controller);
-//        }
-        return null;
-    }
-
-    /**
-     * Send a play command to the route. TODO rename resume() and use messaging
-     * protocol, not KeyEvent
-     */
-    public void play() {
-        // TODO
-    }
-
-    /**
-     * Send a pause command to the session.
-     */
-    public void pause() {
-        // TODO
-    }
-
-    /**
-     * Set the rate at which to fastforward. Valid values are in the range [0,1]
-     * with actual rates depending on the implementation.
-     *
-     * @param rate
-     */
-    public void fastForward(float rate) {
-        if (rate < 0 || rate > 1) {
-            throw new IllegalArgumentException("Rate must be between 0 and 1 inclusive");
-        }
-        Bundle b = new Bundle();
-        b.putFloat(KEY_VALUE1, rate);
-        mIface.sendCommand(METHOD_FAST_FORWARD, b, null);
-    }
-
-    public void getCurrentPosition(ResultReceiver cb) {
-        mIface.sendCommand(METHOD_GET_CURRENT_POSITION, null, cb);
-    }
-
-    public void getCapabilities(ResultReceiver cb) {
-        mIface.sendCommand(METHOD_GET_CAPABILITIES, null, cb);
-    }
-
-    public void addListener(Listener listener) {
-        mIface.addListener(listener.mListener);
-    }
-
-    public void addListener(Listener listener, Handler handler) {
-        mIface.addListener(listener.mListener, handler);
-    }
-
-    public void removeListener(Listener listener) {
-        mIface.removeListener(listener.mListener);
-    }
-
-    public static abstract class Stub extends RouteInterface.Stub {
-        private final MediaSession mSession;
-
-        public Stub(MediaSession session) {
-            mSession = session;
-        }
-
-        @Override
-        public String getName() {
-            return NAME;
-        }
-
-        @Override
-        public void onCommand(String method, Bundle extras, ResultReceiver cb) {
-            if (TextUtils.isEmpty(method)) {
-                return;
-            }
-            Bundle result;
-            if (METHOD_FAST_FORWARD.equals(method)) {
-                fastForward(extras.getFloat(KEY_VALUE1, -1));
-            } else if (METHOD_GET_CURRENT_POSITION.equals(method)) {
-                if (cb != null) {
-                    result = new Bundle();
-                    result.putLong(KEY_VALUE1, getCurrentPosition());
-                    cb.send(0, result);
-                }
-            } else if (METHOD_GET_CAPABILITIES.equals(method)) {
-                if (cb != null) {
-                    result = new Bundle();
-                    result.putLong(KEY_VALUE1, getCapabilities());
-                    cb.send(0, result);
-                }
-            }
-        }
-
-        /**
-         * Override to handle fast forwarding. Valid values are [0,1] inclusive.
-         * The interpretation of the rate is up to the implementation. If no
-         * rate was included with the command a rate of -1 will be used by
-         * default.
-         *
-         * @param rate The rate at which to fast forward as a multiplier
-         */
-        public void fastForward(float rate) {
-            Log.w(TAG, "fastForward is not supported.");
-        }
-
-        /**
-         * Override to handle getting the current position of playback in
-         * millis.
-         *
-         * @return The current position in millis or -1
-         */
-        public long getCurrentPosition() {
-            Log.w(TAG, "getCurrentPosition is not supported");
-            return -1;
-        }
-
-        /**
-         * Override to handle getting the set of capabilities currently
-         * available.
-         *
-         * @return A bit mask of the supported capabilities
-         */
-        public long getCapabilities() {
-            Log.w(TAG, "getCapabilities is not supported");
-            return 0;
-        }
-
-        /**
-         * Publish the current playback state to the system and any controllers.
-         * Valid values are defined in {@link RemoteControlClient}. TODO move
-         * play states somewhere else.
-         *
-         * @param state
-         */
-        public final void updatePlaybackState(int state) {
-            Bundle extras = new Bundle();
-            extras.putInt(KEY_VALUE1, state);
-            sendEvent(mSession, EVENT_PLAYSTATE_CHANGE, extras);
-        }
-    }
-
-    /**
-     * Register this event listener using TODO to receive
-     * TransportControlInterface events from a session.
-     *
-     * @see RouteInterface.EventListener
-     */
-    public static abstract class Listener {
-
-        private RouteInterface.EventListener mListener = new RouteInterface.EventListener() {
-            @Override
-            public final void onEvent(String event, Bundle args) {
-                if (EVENT_PLAYSTATE_CHANGE.equals(event)) {
-                    onPlaybackStateChange(args.getInt(KEY_VALUE1));
-                } else if (EVENT_METADATA_CHANGE.equals(event)) {
-                    onMetadataUpdate(args);
-                }
-            }
-        };
-
-        /**
-         * Override to handle updates to the playback state. Valid values are in
-         * {@link TransportPerformer}. TODO put playstate values somewhere more
-         * generic.
-         *
-         * @param state
-         */
-        public void onPlaybackStateChange(int state) {
-        }
-
-        /**
-         * Override to handle metadata changes for this session's media. The
-         * default supported fields are those in {@link MediaMetadata}.
-         *
-         * @param metadata
-         */
-        public void onMetadataUpdate(Bundle metadata) {
-        }
-    }
-
-}
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/Session.java
similarity index 66%
rename from media/java/android/media/session/MediaSession.java
rename to media/java/android/media/session/Session.java
index 23c3035..8ccd788 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/Session.java
@@ -18,9 +18,9 @@
 
 import android.content.Intent;
 import android.media.Rating;
-import android.media.session.IMediaController;
-import android.media.session.IMediaSession;
-import android.media.session.IMediaSessionCallback;
+import android.media.session.ISessionController;
+import android.media.session.ISession;
+import android.media.session.ISessionCallback;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -33,6 +33,7 @@
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Allows interaction with media controllers, media routes, volume keys, media
@@ -44,11 +45,11 @@
  * media to multiple routes or to provide finer grain controls of media.
  * <p>
  * A MediaSession is created by calling
- * {@link MediaSessionManager#createSession(String)}. Once a session is created
+ * {@link SessionManager#createSession(String)}. Once a session is created
  * apps that have the MEDIA_CONTENT_CONTROL permission can interact with the
- * session through {@link MediaSessionManager#getActiveSessions()}. The owner of
+ * session through {@link SessionManager#getActiveSessions()}. The owner of
  * the session may also use {@link #getSessionToken()} to allow apps without
- * this permission to create a {@link MediaController} to interact with this
+ * this permission to create a {@link SessionController} to interact with this
  * session.
  * <p>
  * To receive commands, media keys, and other events a Callback must be set with
@@ -59,12 +60,13 @@
  * <p>
  * MediaSession objects are thread safe
  */
-public final class MediaSession {
-    private static final String TAG = "MediaSession";
+public final class Session {
+    private static final String TAG = "Session";
 
     private static final int MSG_MEDIA_BUTTON = 1;
     private static final int MSG_COMMAND = 2;
     private static final int MSG_ROUTE_CHANGE = 3;
+    private static final int MSG_ROUTE_CONNECTED = 4;
 
     private static final String KEY_COMMAND = "command";
     private static final String KEY_EXTRAS = "extras";
@@ -72,32 +74,33 @@
 
     private final Object mLock = new Object();
 
-    private final MediaSessionToken mSessionToken;
-    private final IMediaSession mBinder;
+    private final SessionToken mSessionToken;
+    private final ISession mBinder;
     private final CallbackStub mCbStub;
 
     private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>();
     // TODO route interfaces
-    private final ArrayMap<String, RouteInterface.Stub> mInterfaces
-            = new ArrayMap<String, RouteInterface.Stub>();
+    private final ArrayMap<String, RouteInterface.EventListener> mInterfaceListeners
+            = new ArrayMap<String, RouteInterface.EventListener>();
 
     private TransportPerformer mPerformer;
+    private Route mRoute;
 
     private boolean mPublished = false;;
 
     /**
      * @hide
      */
-    public MediaSession(IMediaSession binder, CallbackStub cbStub) {
+    public Session(ISession binder, CallbackStub cbStub) {
         mBinder = binder;
         mCbStub = cbStub;
-        IMediaController controllerBinder = null;
+        ISessionController controllerBinder = null;
         try {
-            controllerBinder = mBinder.getMediaController();
+            controllerBinder = mBinder.getController();
         } catch (RemoteException e) {
             throw new RuntimeException("Dead object in MediaSessionController constructor: ", e);
         }
-        mSessionToken = new MediaSessionToken(controllerBinder);
+        mSessionToken = new SessionToken(controllerBinder);
     }
 
     /**
@@ -109,6 +112,13 @@
         addCallback(callback, null);
     }
 
+    /**
+     * Add a callback to receive updates for the MediaSession. This includes
+     * events like route updates, media buttons, and focus changes.
+     *
+     * @param callback The callback to receive updates on.
+     * @param handler The handler that events should be posted on.
+     */
     public void addCallback(Callback callback, Handler handler) {
         if (callback == null) {
             throw new IllegalArgumentException("Callback cannot be null");
@@ -126,6 +136,11 @@
         }
     }
 
+    /**
+     * Remove a callback. It will no longer receive updates.
+     *
+     * @param callback The callback to remove.
+     */
     public void removeCallback(Callback callback) {
         synchronized (mLock) {
             removeCallbackLocked(callback);
@@ -186,30 +201,6 @@
     }
 
     /**
-     * Add an interface that can be used by MediaSessions. TODO make this a
-     * route provider api
-     *
-     * @see RouteInterface
-     * @param iface The interface to add
-     * @hide
-     */
-    public void addInterface(RouteInterface.Stub iface) {
-        if (iface == null) {
-            throw new IllegalArgumentException("Stub cannot be null");
-        }
-        String name = iface.getName();
-        if (TextUtils.isEmpty(name)) {
-            throw new IllegalArgumentException("Stub must return a valid name");
-        }
-        if (mInterfaces.containsKey(iface)) {
-            throw new IllegalArgumentException("Interface is already added");
-        }
-        synchronized (mLock) {
-            mInterfaces.put(iface.getName(), iface);
-        }
-    }
-
-    /**
      * Send a proprietary event to all MediaControllers listening to this
      * Session. It's up to the Controller/Session owner to determine the meaning
      * of any events.
@@ -243,16 +234,92 @@
 
     /**
      * Retrieve a token object that can be used by apps to create a
-     * {@link MediaController} for interacting with this session. The owner of
+     * {@link SessionController} for interacting with this session. The owner of
      * the session is responsible for deciding how to distribute these tokens.
      *
      * @return A token that can be used to create a MediaController for this
      *         session
      */
-    public MediaSessionToken getSessionToken() {
+    public SessionToken getSessionToken() {
         return mSessionToken;
     }
 
+    /**
+     * Connect to the current route using the specified request.
+     * <p>
+     * Connection updates will be sent to the callback's
+     * {@link Callback#onRouteConnected(Route)} and
+     * {@link Callback#onRouteDisconnected(Route, int)} methods. If the
+     * connection fails {@link Callback#onRouteDisconnected(Route, int)}
+     * will be called.
+     * <p>
+     * If you already have a connection to this route it will be disconnected
+     * before the new connection is established. TODO add an easy way to compare
+     * MediaRouteOptions.
+     *
+     * @param route The route the app is trying to connect to.
+     * @param request The connection request to use.
+     */
+    public void connect(RouteInfo route, RouteOptions request) {
+        if (route == null) {
+            throw new IllegalArgumentException("Must specify the route");
+        }
+        if (request == null) {
+            throw new IllegalArgumentException("Must specify the connection request");
+        }
+        try {
+            mBinder.connectToRoute(route, request);
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Error starting connection to route", e);
+        }
+    }
+
+    /**
+     * Disconnect from the current route. After calling you will be switched
+     * back to the default route.
+     *
+     * @param route The route to disconnect from.
+     */
+    public void disconnect(RouteInfo route) {
+        // TODO
+    }
+
+    /**
+     * Set the list of route options your app is interested in connecting to. It
+     * will be used for picking valid routes.
+     *
+     * @param options The set of route options your app may use to connect.
+     */
+    public void setRouteOptions(List<RouteOptions> options) {
+        try {
+            mBinder.setRouteOptions(options);
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Error setting route options.", e);
+        }
+    }
+
+    /**
+     * @hide
+     * TODO allow multiple listeners for the same interface, allow removal
+     */
+    public void addInterfaceListener(String iface,
+            RouteInterface.EventListener listener) {
+        mInterfaceListeners.put(iface, listener);
+    }
+
+    /**
+     * @hide
+     */
+    public boolean sendRouteCommand(RouteCommand command, ResultReceiver cb) {
+        try {
+            mBinder.sendRouteCommand(command, cb);
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Error sending command to route.", e);
+            return false;
+        }
+        return true;
+    }
+
     private MessageHandler getHandlerForCallbackLocked(Callback cb) {
         if (cb == null) {
             throw new IllegalArgumentException("Callback cannot be null");
@@ -297,10 +364,19 @@
         }
     }
 
-    private void postRequestRouteChange(Bundle mediaRouteDescriptor) {
+    private void postRequestRouteChange(RouteInfo route) {
         synchronized (mLock) {
             for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-                mCallbacks.get(i).post(MSG_ROUTE_CHANGE, mediaRouteDescriptor);
+                mCallbacks.get(i).post(MSG_ROUTE_CHANGE, route);
+            }
+        }
+    }
+
+    private void postRouteConnected(RouteInfo route, RouteOptions options) {
+        synchronized (mLock) {
+            mRoute = new Route(route, options, this);
+            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+                mCallbacks.get(i).post(MSG_ROUTE_CONNECTED, mRoute);
             }
         }
     }
@@ -346,26 +422,49 @@
          * The app is responsible for connecting to the new route and migrating
          * ongoing playback if necessary.
          *
-         * @param descriptor
+         * @param route
          */
-        public void onRequestRouteChange(Bundle descriptor) {
+        public void onRequestRouteChange(RouteInfo route) {
+        }
+
+        /**
+         * Called when a route has successfully connected. Calls to the route
+         * are now valid.
+         *
+         * @param route The route that was connected
+         */
+        public void onRouteConnected(Route route) {
+        }
+
+        /**
+         * Called when a route was disconnected. Further calls to the route will
+         * fail. If available a reason for being disconnected will be provided.
+         * <p>
+         * Valid reasons are:
+         * <ul>
+         * </ul>
+         *
+         * @param route The route that disconnected
+         * @param reason The reason for the disconnect
+         */
+        public void onRouteDisconnected(Route route, int reason) {
         }
     }
 
     /**
      * @hide
      */
-    public static class CallbackStub extends IMediaSessionCallback.Stub {
-        private WeakReference<MediaSession> mMediaSession;
+    public static class CallbackStub extends ISessionCallback.Stub {
+        private WeakReference<Session> mMediaSession;
 
-        public void setMediaSession(MediaSession session) {
-            mMediaSession = new WeakReference<MediaSession>(session);
+        public void setMediaSession(Session session) {
+            mMediaSession = new WeakReference<Session>(session);
         }
 
         @Override
         public void onCommand(String command, Bundle extras, ResultReceiver cb)
                 throws RemoteException {
-            MediaSession session = mMediaSession.get();
+            Session session = mMediaSession.get();
             if (session != null) {
                 session.postCommand(command, extras, cb);
             }
@@ -373,23 +472,31 @@
 
         @Override
         public void onMediaButton(Intent mediaButtonIntent) throws RemoteException {
-            MediaSession session = mMediaSession.get();
+            Session session = mMediaSession.get();
             if (session != null) {
                 session.postMediaButton(mediaButtonIntent);
             }
         }
 
         @Override
-        public void onRequestRouteChange(Bundle mediaRouteDescriptor) throws RemoteException {
-            MediaSession session = mMediaSession.get();
+        public void onRequestRouteChange(RouteInfo route) throws RemoteException {
+            Session session = mMediaSession.get();
             if (session != null) {
-                session.postRequestRouteChange(mediaRouteDescriptor);
+                session.postRequestRouteChange(route);
+            }
+        }
+
+        @Override
+        public void onRouteConnected(RouteInfo route, RouteOptions options) {
+            Session session = mMediaSession.get();
+            if (session != null) {
+                session.postRouteConnected(route, options);
             }
         }
 
         @Override
         public void onPlay() throws RemoteException {
-            MediaSession session = mMediaSession.get();
+            Session session = mMediaSession.get();
             if (session != null) {
                 TransportPerformer tp = session.getTransportPerformer();
                 if (tp != null) {
@@ -400,7 +507,7 @@
 
         @Override
         public void onPause() throws RemoteException {
-            MediaSession session = mMediaSession.get();
+            Session session = mMediaSession.get();
             if (session != null) {
                 TransportPerformer tp = session.getTransportPerformer();
                 if (tp != null) {
@@ -411,7 +518,7 @@
 
         @Override
         public void onStop() throws RemoteException {
-            MediaSession session = mMediaSession.get();
+            Session session = mMediaSession.get();
             if (session != null) {
                 TransportPerformer tp = session.getTransportPerformer();
                 if (tp != null) {
@@ -422,7 +529,7 @@
 
         @Override
         public void onNext() throws RemoteException {
-            MediaSession session = mMediaSession.get();
+            Session session = mMediaSession.get();
             if (session != null) {
                 TransportPerformer tp = session.getTransportPerformer();
                 if (tp != null) {
@@ -433,7 +540,7 @@
 
         @Override
         public void onPrevious() throws RemoteException {
-            MediaSession session = mMediaSession.get();
+            Session session = mMediaSession.get();
             if (session != null) {
                 TransportPerformer tp = session.getTransportPerformer();
                 if (tp != null) {
@@ -444,7 +551,7 @@
 
         @Override
         public void onFastForward() throws RemoteException {
-            MediaSession session = mMediaSession.get();
+            Session session = mMediaSession.get();
             if (session != null) {
                 TransportPerformer tp = session.getTransportPerformer();
                 if (tp != null) {
@@ -455,7 +562,7 @@
 
         @Override
         public void onRewind() throws RemoteException {
-            MediaSession session = mMediaSession.get();
+            Session session = mMediaSession.get();
             if (session != null) {
                 TransportPerformer tp = session.getTransportPerformer();
                 if (tp != null) {
@@ -466,7 +573,7 @@
 
         @Override
         public void onSeekTo(long pos) throws RemoteException {
-            MediaSession session = mMediaSession.get();
+            Session session = mMediaSession.get();
             if (session != null) {
                 TransportPerformer tp = session.getTransportPerformer();
                 if (tp != null) {
@@ -477,7 +584,7 @@
 
         @Override
         public void onRate(Rating rating) throws RemoteException {
-            MediaSession session = mMediaSession.get();
+            Session session = mMediaSession.get();
             if (session != null) {
                 TransportPerformer tp = session.getTransportPerformer();
                 if (tp != null) {
@@ -486,12 +593,32 @@
             }
         }
 
+        @Override
+        public void onRouteEvent(RouteEvent event) throws RemoteException {
+            Session session = mMediaSession.get();
+            if (session != null) {
+                RouteInterface.EventListener iface
+                        = session.mInterfaceListeners.get(event.getIface());
+                Log.d(TAG, "Received route event on iface " + event.getIface() + ". Listener is "
+                        + iface);
+                if (iface != null) {
+                    iface.onEvent(event.getEvent(), event.getExtras());
+                }
+            }
+        }
+
+        @Override
+        public void onRouteStateChange(int state) throws RemoteException {
+            // TODO
+
+        }
+
     }
 
     private class MessageHandler extends Handler {
-        private MediaSession.Callback mCallback;
+        private Session.Callback mCallback;
 
-        public MessageHandler(Looper looper, MediaSession.Callback callback) {
+        public MessageHandler(Looper looper, Session.Callback callback) {
             super(looper, null, true);
             mCallback = callback;
         }
@@ -511,11 +638,13 @@
                         mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
                         break;
                     case MSG_ROUTE_CHANGE:
-                        mCallback.onRequestRouteChange((Bundle) msg.obj);
+                        mCallback.onRequestRouteChange((RouteInfo) msg.obj);
+                        break;
+                    case MSG_ROUTE_CONNECTED:
+                        mCallback.onRouteConnected((Route) msg.obj);
                         break;
                 }
             }
-            msg.recycle();
         }
 
         public void post(int what, Object obj) {
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/SessionController.java
similarity index 83%
rename from media/java/android/media/session/MediaController.java
rename to media/java/android/media/session/SessionController.java
index afd8b11..dc4f7d9 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/SessionController.java
@@ -34,21 +34,21 @@
  * other commands can be sent to the session. A callback may be registered to
  * receive updates from the session, such as metadata and play state changes.
  * <p>
- * A MediaController can be created through {@link MediaSessionManager} if you
+ * A MediaController can be created through {@link SessionManager} if you
  * hold the "android.permission.MEDIA_CONTENT_CONTROL" permission or directly if
- * you have a {@link MediaSessionToken} from the session owner.
+ * you have a {@link SessionToken} from the session owner.
  * <p>
  * MediaController objects are thread-safe.
  */
-public final class MediaController {
-    private static final String TAG = "MediaController";
+public final class SessionController {
+    private static final String TAG = "SessionController";
 
     private static final int MSG_EVENT = 1;
     private static final int MESSAGE_PLAYBACK_STATE = 2;
     private static final int MESSAGE_METADATA = 3;
     private static final int MSG_ROUTE = 4;
 
-    private final IMediaController mSessionBinder;
+    private final ISessionController mSessionBinder;
 
     private final CallbackStub mCbStub = new CallbackStub(this);
     private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>();
@@ -58,15 +58,15 @@
 
     private TransportController mTransportController;
 
-    private MediaController(IMediaController sessionBinder) {
+    private SessionController(ISessionController sessionBinder) {
         mSessionBinder = sessionBinder;
     }
 
     /**
      * @hide
      */
-    public static MediaController fromBinder(IMediaController sessionBinder) {
-        MediaController controller = new MediaController(sessionBinder);
+    public static SessionController fromBinder(ISessionController sessionBinder) {
+        SessionController controller = new SessionController(sessionBinder);
         try {
             controller.mSessionBinder.registerCallbackListener(controller.mCbStub);
             if (controller.mSessionBinder.isTransportControlEnabled()) {
@@ -87,7 +87,7 @@
      * @param token The session token to use
      * @return A controller for the session or null
      */
-    public static MediaController fromToken(MediaSessionToken token) {
+    public static SessionController fromToken(SessionToken token) {
         return fromBinder(token.getBinder());
     }
 
@@ -181,10 +181,22 @@
         }
     }
 
+    /**
+     * Request that the route picker be shown for this session. This should
+     * generally be called in response to a user action.
+     */
+    public void showRoutePicker() {
+        try {
+            mSessionBinder.showRoutePicker();
+        } catch (RemoteException e) {
+            Log.d(TAG, "Dead object in showRoutePicker", e);
+        }
+    }
+
     /*
      * @hide
      */
-    IMediaController getSessionBinder() {
+    ISessionController getSessionBinder() {
         return mSessionBinder;
     }
 
@@ -247,10 +259,10 @@
         }
     }
 
-    private void postRouteChanged(Bundle routeDescriptor) {
+    private void postRouteChanged(RouteInfo route) {
         synchronized (mLock) {
             for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-                mCallbacks.get(i).post(MSG_ROUTE, null, routeDescriptor);
+                mCallbacks.get(i).post(MSG_ROUTE, route, null);
             }
         }
     }
@@ -275,36 +287,36 @@
          *
          * @param route
          */
-        public void onRouteChanged(Bundle route) {
+        public void onRouteChanged(RouteInfo route) {
         }
     }
 
-    private final static class CallbackStub extends IMediaControllerCallback.Stub {
-        private final WeakReference<MediaController> mController;
+    private final static class CallbackStub extends ISessionControllerCallback.Stub {
+        private final WeakReference<SessionController> mController;
 
-        public CallbackStub(MediaController controller) {
-            mController = new WeakReference<MediaController>(controller);
+        public CallbackStub(SessionController controller) {
+            mController = new WeakReference<SessionController>(controller);
         }
 
         @Override
         public void onEvent(String event, Bundle extras) {
-            MediaController controller = mController.get();
+            SessionController controller = mController.get();
             if (controller != null) {
                 controller.postEvent(event, extras);
             }
         }
 
         @Override
-        public void onRouteChanged(Bundle mediaRouteDescriptor) {
-            MediaController controller = mController.get();
+        public void onRouteChanged(RouteInfo route) {
+            SessionController controller = mController.get();
             if (controller != null) {
-                controller.postRouteChanged(mediaRouteDescriptor);
+                controller.postRouteChanged(route);
             }
         }
 
         @Override
         public void onPlaybackStateChanged(PlaybackState state) {
-            MediaController controller = mController.get();
+            SessionController controller = mController.get();
             if (controller != null) {
                 TransportController tc = controller.getTransportController();
                 if (tc != null) {
@@ -315,7 +327,7 @@
 
         @Override
         public void onMetadataChanged(MediaMetadata metadata) {
-            MediaController controller = mController.get();
+            SessionController controller = mController.get();
             if (controller != null) {
                 TransportController tc = controller.getTransportController();
                 if (tc != null) {
@@ -327,9 +339,9 @@
     }
 
     private final static class MessageHandler extends Handler {
-        private final MediaController.Callback mCallback;
+        private final SessionController.Callback mCallback;
 
-        public MessageHandler(Looper looper, MediaController.Callback cb) {
+        public MessageHandler(Looper looper, SessionController.Callback cb) {
             super(looper, null, true);
             mCallback = cb;
         }
@@ -341,7 +353,7 @@
                     mCallback.onEvent((String) msg.obj, msg.getData());
                     break;
                 case MSG_ROUTE:
-                    mCallback.onRouteChanged(msg.getData());
+                    mCallback.onRouteChanged((RouteInfo) msg.obj);
             }
         }
 
diff --git a/media/java/android/media/session/SessionInfo.java b/media/java/android/media/session/SessionInfo.java
new file mode 100644
index 0000000..22d8ab1
--- /dev/null
+++ b/media/java/android/media/session/SessionInfo.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2014 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information about a media session, including the owner's package name.
+ */
+public final class SessionInfo implements Parcelable {
+    private final String mId;
+    private final String mPackageName;
+
+    /**
+     * @hide
+     */
+    public SessionInfo(String id, String packageName) {
+        mId = id;
+        mPackageName = packageName;
+    }
+
+    private SessionInfo(Parcel in) {
+        mId = in.readString();
+        mPackageName = in.readString();
+    }
+
+    /**
+     * Get the package name of the owner of this session.
+     *
+     * @return The owner's package name
+     */
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Get the unique id for this session.
+     *
+     * @return The id for the session.
+     */
+    public String getId() {
+        return mId;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mId);
+        dest.writeString(mPackageName);
+    }
+
+    public static final Parcelable.Creator<SessionInfo> CREATOR
+            = new Parcelable.Creator<SessionInfo>() {
+        @Override
+        public SessionInfo createFromParcel(Parcel in) {
+            return new SessionInfo(in);
+        }
+
+        @Override
+        public SessionInfo[] newArray(int size) {
+            return new SessionInfo[size];
+        }
+    };
+}
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/SessionManager.java
similarity index 75%
rename from media/java/android/media/session/MediaSessionManager.java
rename to media/java/android/media/session/SessionManager.java
index e3f2d9c..15bf0e3 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/SessionManager.java
@@ -17,7 +17,7 @@
 package android.media.session;
 
 import android.content.Context;
-import android.media.session.IMediaSessionManager;
+import android.media.session.ISessionManager;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -35,37 +35,37 @@
  * get an instance of this class.
  * <p>
  *
- * @see MediaSession
- * @see MediaController
+ * @see Session
+ * @see SessionController
  */
-public final class MediaSessionManager {
-    private static final String TAG = "MediaSessionManager";
+public final class SessionManager {
+    private static final String TAG = "SessionManager";
 
-    private final IMediaSessionManager mService;
+    private final ISessionManager mService;
 
     private Context mContext;
 
     /**
      * @hide
      */
-    public MediaSessionManager(Context context) {
+    public SessionManager(Context context) {
         // Consider rewriting like DisplayManagerGlobal
         // Decide if we need context
         mContext = context;
         IBinder b = ServiceManager.getService(Context.MEDIA_SESSION_SERVICE);
-        mService = IMediaSessionManager.Stub.asInterface(b);
+        mService = ISessionManager.Stub.asInterface(b);
     }
 
     /**
      * Creates a new session.
      *
      * @param tag A short name for debugging purposes
-     * @return a {@link MediaSession} for the new session
+     * @return a {@link Session} for the new session
      */
-    public MediaSession createSession(String tag) {
+    public Session createSession(String tag) {
         try {
-            MediaSession.CallbackStub cbStub = new MediaSession.CallbackStub();
-            MediaSession session = new MediaSession(mService
+            Session.CallbackStub cbStub = new Session.CallbackStub();
+            Session session = new Session(mService
                     .createSession(mContext.getPackageName(), cbStub, tag), cbStub);
             cbStub.setMediaSession(session);
 
@@ -83,8 +83,8 @@
      *
      * @return a list of controllers for ongoing sessions
      */
-    public List<MediaController> getActiveSessions() {
+    public List<SessionController> getActiveSessions() {
         // TODO
-        return new ArrayList<MediaController>();
+        return new ArrayList<SessionController>();
     }
 }
diff --git a/media/java/android/media/session/MediaSessionToken.aidl b/media/java/android/media/session/SessionToken.aidl
similarity index 95%
copy from media/java/android/media/session/MediaSessionToken.aidl
copy to media/java/android/media/session/SessionToken.aidl
index 5812682..db35f85 100644
--- a/media/java/android/media/session/MediaSessionToken.aidl
+++ b/media/java/android/media/session/SessionToken.aidl
@@ -15,4 +15,4 @@
 
 package android.media.session;
 
-parcelable MediaSessionToken;
+parcelable SessionToken;
diff --git a/media/java/android/media/session/MediaSessionToken.java b/media/java/android/media/session/SessionToken.java
similarity index 62%
rename from media/java/android/media/session/MediaSessionToken.java
rename to media/java/android/media/session/SessionToken.java
index dbb4964..59486f6 100644
--- a/media/java/android/media/session/MediaSessionToken.java
+++ b/media/java/android/media/session/SessionToken.java
@@ -16,28 +16,28 @@
 
 package android.media.session;
 
-import android.media.session.IMediaController;
+import android.media.session.ISessionController;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-public class MediaSessionToken implements Parcelable {
-    private IMediaController mBinder;
+public class SessionToken implements Parcelable {
+    private ISessionController mBinder;
 
     /**
      * @hide
      */
-    MediaSessionToken(IMediaController binder) {
+    SessionToken(ISessionController binder) {
         mBinder = binder;
     }
 
-    private MediaSessionToken(Parcel in) {
-        mBinder = IMediaController.Stub.asInterface(in.readStrongBinder());
+    private SessionToken(Parcel in) {
+        mBinder = ISessionController.Stub.asInterface(in.readStrongBinder());
     }
 
     /**
      * @hide
      */
-    IMediaController getBinder() {
+    ISessionController getBinder() {
         return mBinder;
     }
 
@@ -51,16 +51,16 @@
         dest.writeStrongBinder(mBinder.asBinder());
     }
 
-    public static final Parcelable.Creator<MediaSessionToken> CREATOR
-            = new Parcelable.Creator<MediaSessionToken>() {
+    public static final Parcelable.Creator<SessionToken> CREATOR
+            = new Parcelable.Creator<SessionToken>() {
         @Override
-        public MediaSessionToken createFromParcel(Parcel in) {
-            return new MediaSessionToken(in);
+        public SessionToken createFromParcel(Parcel in) {
+            return new SessionToken(in);
         }
 
         @Override
-        public MediaSessionToken[] newArray(int size) {
-            return new MediaSessionToken[size];
+        public SessionToken[] newArray(int size) {
+            return new SessionToken[size];
         }
     };
 }
diff --git a/media/java/android/media/session/TransportController.java b/media/java/android/media/session/TransportController.java
index 15b11f3..9574df6 100644
--- a/media/java/android/media/session/TransportController.java
+++ b/media/java/android/media/session/TransportController.java
@@ -34,12 +34,12 @@
 
     private final Object mLock = new Object();
     private final ArrayList<MessageHandler> mListeners = new ArrayList<MessageHandler>();
-    private final IMediaController mBinder;
+    private final ISessionController mBinder;
 
     /**
      * @hide
      */
-    public TransportController(IMediaController binder) {
+    public TransportController(ISessionController binder) {
         mBinder = binder;
     }
 
diff --git a/media/java/android/media/session/TransportPerformer.java b/media/java/android/media/session/TransportPerformer.java
index b96db20..eddffd1 100644
--- a/media/java/android/media/session/TransportPerformer.java
+++ b/media/java/android/media/session/TransportPerformer.java
@@ -34,12 +34,12 @@
     private final Object mLock = new Object();
     private final ArrayList<MessageHandler> mListeners = new ArrayList<MessageHandler>();
 
-    private IMediaSession mBinder;
+    private ISession mBinder;
 
     /**
      * @hide
      */
-    public TransportPerformer(IMediaSession binder) {
+    public TransportPerformer(ISession binder) {
         mBinder = binder;
     }
 
diff --git a/services/core/java/com/android/server/media/MediaRouteProviderProxy.java b/services/core/java/com/android/server/media/MediaRouteProviderProxy.java
new file mode 100644
index 0000000..d314ea7
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaRouteProviderProxy.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2014 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 com.android.server.media;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.media.routeprovider.IRouteConnection;
+import android.media.routeprovider.IRouteProvider;
+import android.media.routeprovider.IRouteProviderCallback;
+import android.media.routeprovider.RouteProviderService;
+import android.media.routeprovider.RouteRequest;
+import android.media.session.RouteEvent;
+import android.media.session.RouteInfo;
+import android.media.session.Session;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * System representation and interface to a MediaRouteProvider. This class is
+ * not thread safe so all calls should be made on the main thread.
+ */
+public class MediaRouteProviderProxy {
+    private static final String TAG = "MRPProxy";
+    private static final boolean DEBUG = true;
+
+    private static final int MAX_RETRIES = 3;
+
+    private final Object mLock = new Object();
+    private final Context mContext;
+    private final String mId;
+    private final ComponentName mComponentName;
+    private final int mUserId;
+
+    private Intent mBindIntent;
+    // Interfaces declared in the manifest
+    private ArrayList<String> mInterfaces;
+    private ArrayList<RouteConnectionRecord> mConnections = new ArrayList<RouteConnectionRecord>();
+    private Handler mHandler = new Handler();
+
+    private IRouteProvider mBinder;
+    private boolean mRunning;
+    private boolean mInterested;
+    private boolean mBound;
+    private int mRetryCount;
+
+    private RoutesListener mRouteListener;
+
+    public MediaRouteProviderProxy(Context context, String id, ComponentName component, int uid,
+            ArrayList<String> interfaces) {
+        mContext = context;
+        mId = id;
+        mComponentName = component;
+        mUserId = uid;
+        mInterfaces = interfaces;
+        mBindIntent = new Intent(RouteProviderService.SERVICE_INTERFACE);
+        mBindIntent.setComponent(mComponentName);
+    }
+
+    /**
+     * Send any cleanup messages and unbind from the media route provider
+     */
+    public void stop() {
+        if (mRunning) {
+            mRunning = false;
+            mRetryCount = 0;
+            updateBinding();
+        }
+    }
+
+    /**
+     * Bind to the media route provider and perform any setup needed
+     */
+    public void start() {
+        if (!mRunning) {
+            mRunning = true;
+            updateBinding();
+        }
+    }
+
+    /**
+     * Set whether or not this provider is currently interesting to the system.
+     * In the future this may take a list of interfaces instead.
+     *
+     * @param interested True if we want to connect to this provider
+     */
+    public void setInterested(boolean interested) {
+        mInterested = interested;
+        updateBinding();
+    }
+
+    /**
+     * Set a listener to get route updates on.
+     *
+     * @param listener The listener to receive updates on.
+     */
+    public void setRoutesListener(RoutesListener listener) {
+        mRouteListener = listener;
+    }
+
+    /**
+     * Send a request to the Provider to get all the routes that the session can
+     * use.
+     *
+     * @param record The session to get routes for.
+     * @param requestId An id to identify this request.
+     */
+    public void getRoutes(MediaSessionRecord record, final int requestId) {
+        // TODO change routes to have a system global id and maintain a mapping
+        // to the original route
+        if (mBinder == null) {
+            Log.wtf(TAG, "Attempted to call getRoutes without a binder connection");
+            return;
+        }
+        List<RouteRequest> requests = record.getRouteRequests();
+        final String sessionId = record.getSessionInfo().getId();
+        try {
+            mBinder.getAvailableRoutes(requests, new ResultReceiver(mHandler) {
+                @Override
+                protected void onReceiveResult(int resultCode, Bundle resultData) {
+                    if (resultCode != RouteProviderService.RESULT_SUCCESS) {
+                        // ignore failures, just means no routes were generated
+                        return;
+                    }
+                    ArrayList<RouteInfo> routes
+                            = resultData.getParcelableArrayList(RouteProviderService.KEY_ROUTES);
+                    ArrayList<RouteInfo> sysRoutes = new ArrayList<RouteInfo>();
+                    for (int i = 0; i < routes.size(); i++) {
+                        RouteInfo route = routes.get(i);
+                        RouteInfo.Builder bob = new RouteInfo.Builder(route);
+                        bob.setProviderId(mId);
+                        sysRoutes.add(bob.build());
+                    }
+                    if (mRouteListener != null) {
+                        mRouteListener.onRoutesUpdated(sessionId, sysRoutes, requestId);
+                    }
+                }
+            });
+        } catch (RemoteException e) {
+            Log.d(TAG, "Error in getRoutes", e);
+        }
+    }
+
+    /**
+     * Try connecting again if we've been disconnected.
+     */
+    public void rebindIfDisconnected() {
+        if (mBinder == null && shouldBind()) {
+            unbind();
+            bind();
+        }
+    }
+
+    /**
+     * Send a request to connect to a route.
+     *
+     * @param session The session that is trying to connect.
+     * @param route The route it is connecting to.
+     * @param request The request with the connection parameters.
+     * @return true if the request was sent, false otherwise.
+     */
+    public boolean connectToRoute(MediaSessionRecord session, final RouteInfo route,
+            final RouteRequest request) {
+        final String sessionId = session.getSessionInfo().getId();
+        try {
+            mBinder.connect(route, request, new ResultReceiver(mHandler) {
+                @Override
+                protected void onReceiveResult(int resultCode, Bundle resultData) {
+                    if (resultCode != RouteProviderService.RESULT_SUCCESS) {
+                        // TODO handle connection failure
+                        return;
+                    }
+                    IBinder binder = resultData.getBinder(RouteProviderService.KEY_CONNECTION);
+                    IRouteConnection connection = null;
+                    if (binder != null) {
+                        connection = IRouteConnection.Stub.asInterface(binder);
+                    }
+
+                    if (connection != null) {
+                        RouteConnectionRecord record = new RouteConnectionRecord(
+                                connection);
+                        mConnections.add(record);
+                        if (mRouteListener != null) {
+                            mRouteListener.onRouteConnected(sessionId, route, request, record);
+                        }
+                    }
+                }
+            });
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error connecting to route.", e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Check if this is the provider you're looking for.
+     */
+    public boolean hasComponentName(String packageName, String className) {
+        return mComponentName.getPackageName().equals(packageName)
+                && mComponentName.getClassName().equals(className);
+    }
+
+    /**
+     * Get the unique id for this provider.
+     *
+     * @return The provider's id.
+     */
+    public String getId() {
+        return mId;
+    }
+
+    private void updateBinding() {
+        if (shouldBind()) {
+            bind();
+        } else {
+            unbind();
+        }
+    }
+
+    private boolean shouldBind() {
+        return mRunning && mInterested;
+    }
+
+    private void bind() {
+        if (!mBound) {
+            if (DEBUG) {
+                Slog.d(TAG, this + ": Binding");
+            }
+
+            try {
+                mBound = mContext.bindServiceAsUser(mBindIntent, mServiceConn,
+                        Context.BIND_AUTO_CREATE, new UserHandle(mUserId));
+                if (!mBound && DEBUG) {
+                    Slog.d(TAG, this + ": Bind failed");
+                }
+            } catch (SecurityException ex) {
+                if (DEBUG) {
+                    Slog.d(TAG, this + ": Bind failed", ex);
+                }
+            }
+        }
+    }
+
+    private void unbind() {
+        if (mBound) {
+            if (DEBUG) {
+                Slog.d(TAG, this + ": Unbinding");
+            }
+
+            mBound = false;
+            mContext.unbindService(mServiceConn);
+        }
+    }
+
+    private RouteConnectionRecord getConnectionLocked(IBinder binder) {
+        for (int i = mConnections.size() - 1; i >= 0; i--) {
+            RouteConnectionRecord record = mConnections.get(i);
+            if (record.isConnection(binder)) {
+                return record;
+            }
+        }
+        return null;
+    }
+
+    private ServiceConnection mServiceConn = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mBinder = IRouteProvider.Stub.asInterface(service);
+            if (DEBUG) {
+                Slog.d(TAG, "Connected to route provider");
+            }
+            try {
+                mBinder.registerCallback(mCbStub);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error registering callback on route provider. Retry count: "
+                        + mRetryCount, e);
+                if (mRetryCount < MAX_RETRIES) {
+                    mRetryCount++;
+                    rebindIfDisconnected();
+                }
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            mBinder = null;
+            if (DEBUG) {
+                Slog.d(TAG, "Disconnected from route provider");
+            }
+        }
+
+    };
+
+    private IRouteProviderCallback.Stub mCbStub = new IRouteProviderCallback.Stub() {
+        @Override
+        public void onConnectionStateChanged(IRouteConnection connection, int state)
+                throws RemoteException {
+            // TODO
+        }
+
+        @Override
+        public void onRouteEvent(RouteEvent event) throws RemoteException {
+            synchronized (mLock) {
+                RouteConnectionRecord record = getConnectionLocked(event.getConnection());
+                Log.d(TAG, "Received route event for record " + record);
+                if (record != null) {
+                    record.sendEvent(event);
+                }
+            }
+        }
+
+        @Override
+        public void onConnectionTerminated(IRouteConnection connection) throws RemoteException {
+            synchronized (mLock) {
+                RouteConnectionRecord record = getConnectionLocked(connection.asBinder());
+                if (record != null) {
+                    record.disconnect();
+                    mConnections.remove(record);
+                }
+            }
+        }
+
+        @Override
+        public void onRoutesChanged() throws RemoteException {
+            // TODO
+        }
+    };
+
+    /**
+     * Listener for receiving responses to route requests on the provider.
+     */
+    public interface RoutesListener {
+        /**
+         * Called when routes have been returned from a request to getRoutes.
+         *
+         * @param record The session that the routes were requested for.
+         * @param routes The matching routes returned by the provider.
+         * @param reqId The request id this is responding to.
+         */
+        public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
+                int reqId);
+
+        /**
+         * Called when a route has successfully connected.
+         *
+         * @param session The session that was connected.
+         * @param route The route it connected to.
+         * @param options The options that were used for the connection.
+         * @param connection The connection instance that was created.
+         */
+        public void onRouteConnected(String sessionId, RouteInfo route,
+                RouteRequest options, RouteConnectionRecord connection);
+    }
+}
diff --git a/services/core/java/com/android/server/media/MediaRouteProviderWatcher.java b/services/core/java/com/android/server/media/MediaRouteProviderWatcher.java
new file mode 100644
index 0000000..cf1d95ab
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaRouteProviderWatcher.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2014 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 com.android.server.media;
+
+import android.Manifest;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.media.routeprovider.RouteProviderService;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.UUID;
+
+/**
+ * Watches for media route provider services to be installed. Adds a provider to
+ * the media session service for each registered service. For now just run all
+ * providers. In the future define a policy for when to run providers.
+ */
+public class MediaRouteProviderWatcher {
+    private static final String TAG = "MRPWatcher";
+    private static final boolean DEBUG = true; // Log.isLoggable(TAG,
+                                               // Log.DEBUG);
+
+    private final Context mContext;
+    private final Callback mCallback;
+    private final Handler mHandler;
+    private final int mUserId;
+    private final PackageManager mPackageManager;
+
+    private final ArrayList<MediaRouteProviderProxy> mProviders =
+            new ArrayList<MediaRouteProviderProxy>();
+    private boolean mRunning;
+
+    public MediaRouteProviderWatcher(Context context, Callback callback, Handler handler,
+            int userId) {
+        mContext = context;
+        mCallback = callback;
+        mHandler = handler;
+        mUserId = userId;
+        mPackageManager = context.getPackageManager();
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + " mUserId=" + mUserId);
+        pw.println(prefix + " mRunning=" + mRunning);
+        pw.println(prefix + " mProviders.size()=" + mProviders.size());
+    }
+
+    public void start() {
+        if (!mRunning) {
+            mRunning = true;
+
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+            filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+            filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+            filter.addDataScheme("package");
+            mContext.registerReceiverAsUser(mScanPackagesReceiver,
+                    new UserHandle(mUserId), filter, null, mHandler);
+
+            // Scan packages.
+            // Also has the side-effect of restarting providers if needed.
+            mHandler.post(mScanPackagesRunnable);
+        }
+    }
+
+    public void stop() {
+        if (mRunning) {
+            mRunning = false;
+
+            mContext.unregisterReceiver(mScanPackagesReceiver);
+            mHandler.removeCallbacks(mScanPackagesRunnable);
+
+            // Stop all providers.
+            for (int i = mProviders.size() - 1; i >= 0; i--) {
+                mProviders.get(i).stop();
+            }
+        }
+    }
+
+    public ArrayList<MediaRouteProviderProxy> getProviders() {
+        return mProviders;
+    }
+
+    public MediaRouteProviderProxy getProvider(String id) {
+        int providerIndex = findProvider(id);
+        if (providerIndex != -1) {
+            return mProviders.get(providerIndex);
+        }
+        return null;
+    }
+
+    private void scanPackages() {
+        if (!mRunning) {
+            return;
+        }
+
+        // Add providers for all new services.
+        // Reorder the list so that providers left at the end will be the ones
+        // to remove.
+        int targetIndex = 0;
+        Intent intent = new Intent(RouteProviderService.SERVICE_INTERFACE);
+        for (ResolveInfo resolveInfo : mPackageManager.queryIntentServicesAsUser(
+                intent, 0, mUserId)) {
+            ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+            if (DEBUG) {
+                Slog.d(TAG, "Checking service " + (serviceInfo == null ? null : serviceInfo.name));
+            }
+            if (serviceInfo != null && verifyServiceTrusted(serviceInfo)) {
+                int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
+                if (sourceIndex < 0) {
+                    // TODO get declared interfaces from manifest
+                    if (DEBUG) {
+                        Slog.d(TAG, "Creating new provider proxy for service");
+                    }
+                    MediaRouteProviderProxy provider =
+                            new MediaRouteProviderProxy(mContext, UUID.randomUUID().toString(),
+                                    new ComponentName(serviceInfo.packageName, serviceInfo.name),
+                                    mUserId, null);
+                    provider.start();
+                    mProviders.add(targetIndex++, provider);
+                    mCallback.addProvider(provider);
+                } else if (sourceIndex >= targetIndex) {
+                    MediaRouteProviderProxy provider = mProviders.get(sourceIndex);
+                    provider.start(); // restart the provider if needed
+                    provider.rebindIfDisconnected();
+                    Collections.swap(mProviders, sourceIndex, targetIndex++);
+                }
+            }
+        }
+
+        // Remove providers for missing services.
+        if (targetIndex < mProviders.size()) {
+            for (int i = mProviders.size() - 1; i >= targetIndex; i--) {
+                MediaRouteProviderProxy provider = mProviders.get(i);
+                mCallback.removeProvider(provider);
+                mProviders.remove(provider);
+                provider.stop();
+            }
+        }
+    }
+
+    private boolean verifyServiceTrusted(ServiceInfo serviceInfo) {
+        if (serviceInfo.permission == null || !serviceInfo.permission.equals(
+                Manifest.permission.BIND_ROUTE_PROVIDER)) {
+            // If the service does not require this permission then any app
+            // could potentially bind to it and mess with their routes. So we
+            // only want to trust providers that require the
+            // correct permissions.
+            Slog.w(TAG, "Ignoring route provider service because it did not "
+                    + "require the BIND_ROUTE_PROVIDER permission in its manifest: "
+                    + serviceInfo.packageName + "/" + serviceInfo.name);
+            return false;
+        }
+        // Looks good.
+        return true;
+    }
+
+    private int findProvider(String id) {
+        int count = mProviders.size();
+        for (int i = 0; i < count; i++) {
+            MediaRouteProviderProxy provider = mProviders.get(i);
+            if (TextUtils.equals(id, provider.getId())) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private int findProvider(String packageName, String className) {
+        int count = mProviders.size();
+        for (int i = 0; i < count; i++) {
+            MediaRouteProviderProxy provider = mProviders.get(i);
+            if (provider.hasComponentName(packageName, className)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() {
+            @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DEBUG) {
+                Slog.d(TAG, "Received package manager broadcast: " + intent);
+            }
+            scanPackages();
+        }
+    };
+
+    private final Runnable mScanPackagesRunnable = new Runnable() {
+            @Override
+        public void run() {
+            scanPackages();
+        }
+    };
+
+    public interface Callback {
+        void addProvider(MediaRouteProviderProxy provider);
+
+        void removeProvider(MediaRouteProviderProxy provider);
+    }
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 1ff925c..ac7f4f3 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -17,11 +17,20 @@
 package com.android.server.media;
 
 import android.content.Intent;
-import android.media.session.IMediaController;
-import android.media.session.IMediaControllerCallback;
-import android.media.session.IMediaSession;
-import android.media.session.IMediaSessionCallback;
+import android.media.routeprovider.RouteRequest;
+import android.media.session.ISessionController;
+import android.media.session.ISessionControllerCallback;
+import android.media.session.ISession;
+import android.media.session.ISessionCallback;
+import android.media.session.SessionController;
 import android.media.session.MediaMetadata;
+import android.media.session.RouteCommand;
+import android.media.session.RouteInfo;
+import android.media.session.RouteOptions;
+import android.media.session.RouteEvent;
+import android.media.session.Session;
+import android.media.session.SessionInfo;
+import android.media.session.RouteInterface;
 import android.media.session.PlaybackState;
 import android.media.Rating;
 import android.os.Bundle;
@@ -31,37 +40,44 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 import android.view.KeyEvent;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.UUID;
 
 /**
  * This is the system implementation of a Session. Apps will interact with the
  * MediaSession wrapper class instead.
  */
 public class MediaSessionRecord implements IBinder.DeathRecipient {
-    private static final String TAG = "MediaSessionImpl";
+    private static final String TAG = "MediaSessionRecord";
 
     private final MessageHandler mHandler;
 
     private final int mPid;
-    private final String mPackageName;
+    private final SessionInfo mSessionInfo;
     private final String mTag;
     private final ControllerStub mController;
     private final SessionStub mSession;
     private final SessionCb mSessionCb;
     private final MediaSessionService mService;
 
-    private final Object mControllerLock = new Object();
-    private final ArrayList<IMediaControllerCallback> mControllerCallbacks =
-            new ArrayList<IMediaControllerCallback>();
-    private final ArrayList<String> mInterfaces = new ArrayList<String>();
+    private final Object mLock = new Object();
+    private final ArrayList<ISessionControllerCallback> mControllerCallbacks =
+            new ArrayList<ISessionControllerCallback>();
+    private final ArrayList<RouteRequest> mRequests = new ArrayList<RouteRequest>();
 
     private boolean mTransportPerformerEnabled = false;
-    private Bundle mRoute;
+    private RouteInfo mRoute;
+    private RouteOptions mRequest;
+    private RouteConnectionRecord mConnection;
+    // TODO define a RouteState class with relevant info
+    private int mRouteState;
 
     // TransportPerformer fields
 
@@ -72,10 +88,10 @@
 
     private boolean mIsPublished = false;
 
-    public MediaSessionRecord(int pid, String packageName, IMediaSessionCallback cb, String tag,
+    public MediaSessionRecord(int pid, String packageName, ISessionCallback cb, String tag,
             MediaSessionService service, Handler handler) {
         mPid = pid;
-        mPackageName = packageName;
+        mSessionInfo = new SessionInfo(UUID.randomUUID().toString(), packageName);
         mTag = tag;
         mController = new ControllerStub();
         mSession = new SessionStub();
@@ -84,31 +100,140 @@
         mHandler = new MessageHandler(handler.getLooper());
     }
 
-    public IMediaSession getSessionBinder() {
+    /**
+     * Get the binder for the {@link Session}.
+     *
+     * @return The session binder apps talk to.
+     */
+    public ISession getSessionBinder() {
         return mSession;
     }
 
-    public IMediaController getControllerBinder() {
+    /**
+     * Get the binder for the {@link SessionController}.
+     *
+     * @return The controller binder apps talk to.
+     */
+    public ISessionController getControllerBinder() {
         return mController;
     }
 
+    /**
+     * Get the set of route requests this session is interested in.
+     *
+     * @return The list of RouteRequests
+     */
+    public List<RouteRequest> getRouteRequests() {
+        return mRequests;
+    }
+
+    /**
+     * Get the route this session is currently on.
+     *
+     * @return The route the session is on.
+     */
+    public RouteInfo getRoute() {
+        return mRoute;
+    }
+
+    /**
+     * Get the info for this session.
+     *
+     * @return Info that identifies this session.
+     */
+    public SessionInfo getSessionInfo() {
+        return mSessionInfo;
+    }
+
+    /**
+     * Set the selected route. This does not connect to the route, just notifies
+     * the app that a new route has been selected.
+     *
+     * @param route The route that was selected.
+     */
+    public void selectRoute(RouteInfo route) {
+        synchronized (mLock) {
+            if (route != mRoute) {
+                if (mConnection != null) {
+                    mConnection.disconnect();
+                    mConnection = null;
+                }
+            }
+            mRoute = route;
+        }
+        mSessionCb.sendRouteChange(route);
+    }
+
+    /**
+     * Update the state of the route this session is using and notify the
+     * session.
+     *
+     * @param state The new state of the route.
+     */
+    public void setRouteState(int state) {
+        mSessionCb.sendRouteStateChange(state);
+    }
+
+    /**
+     * Send an event to this session from the route it is using.
+     *
+     * @param event The event to send.
+     */
+    public void sendRouteEvent(RouteEvent event) {
+        mSessionCb.sendRouteEvent(event);
+    }
+
+    /**
+     * Set the connection to use for the selected route and notify the app it is
+     * now connected.
+     *
+     * @param route The route the connection is to.
+     * @param request The request that was used to connect.
+     * @param connection The connection to the route.
+     * @return True if this connection is still valid, false if it is stale.
+     */
+    public boolean setRouteConnected(RouteInfo route, RouteOptions request,
+            RouteConnectionRecord connection) {
+        synchronized (mLock) {
+            if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) {
+                Log.w(TAG, "setRouteConnected: connected route is stale");
+                // TODO figure out disconnection path
+                return false;
+            }
+            if (request != mRequest) {
+                Log.w(TAG, "setRouteConnected: connection request is stale");
+                // TODO figure out disconnection path
+                return false;
+            }
+            mConnection = connection;
+            mConnection.setListener(mConnectionListener);
+            mSessionCb.sendRouteConnected();
+        }
+        return true;
+    }
+
+    /**
+     * Check if this session has been published by the app yet.
+     *
+     * @return True if it has been published, false otherwise.
+     */
+    public boolean isPublished() {
+        return mIsPublished;
+    }
+
     @Override
     public void binderDied() {
         mService.sessionDied(this);
     }
 
-    public boolean isPublished() {
-        return mIsPublished;
-    }
-
     private void onDestroy() {
         mService.destroySession(this);
     }
 
     private void pushPlaybackStateUpdate() {
-        synchronized (mControllerLock) {
+        synchronized (mLock) {
             for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
-                IMediaControllerCallback cb = mControllerCallbacks.get(i);
+                ISessionControllerCallback cb = mControllerCallbacks.get(i);
                 try {
                     cb.onPlaybackStateChanged(mPlaybackState);
                 } catch (RemoteException e) {
@@ -120,9 +245,9 @@
     }
 
     private void pushMetadataUpdate() {
-        synchronized (mControllerLock) {
+        synchronized (mLock) {
             for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
-                IMediaControllerCallback cb = mControllerCallbacks.get(i);
+                ISessionControllerCallback cb = mControllerCallbacks.get(i);
                 try {
                     cb.onMetadataChanged(mMetadata);
                 } catch (RemoteException e) {
@@ -134,9 +259,9 @@
     }
 
     private void pushRouteUpdate() {
-        synchronized (mControllerLock) {
+        synchronized (mLock) {
             for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
-                IMediaControllerCallback cb = mControllerCallbacks.get(i);
+                ISessionControllerCallback cb = mControllerCallbacks.get(i);
                 try {
                     cb.onRouteChanged(mRoute);
                 } catch (RemoteException e) {
@@ -148,21 +273,50 @@
     }
 
     private void pushEvent(String event, Bundle data) {
-        synchronized (mControllerLock) {
+        synchronized (mLock) {
             for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
-                IMediaControllerCallback cb = mControllerCallbacks.get(i);
+                ISessionControllerCallback cb = mControllerCallbacks.get(i);
                 try {
                     cb.onEvent(event, data);
                 } catch (RemoteException e) {
-                    Log.w(TAG, "Removing dead callback in pushRouteUpdate.", e);
-                    mControllerCallbacks.remove(i);
+                    Log.w(TAG, "Error with callback in pushEvent.", e);
                 }
             }
         }
     }
 
-    private final class SessionStub extends IMediaSession.Stub {
+    private void pushRouteCommand(RouteCommand command, ResultReceiver cb) {
+        synchronized (mLock) {
+            if (mRoute == null || !TextUtils.equals(command.getRouteInfo(), mRoute.getId())) {
+                if (cb != null) {
+                    cb.send(RouteInterface.RESULT_ROUTE_IS_STALE, null);
+                    return;
+                }
+            }
+            if (mConnection != null) {
+                mConnection.sendCommand(command, cb);
+            } else if (cb != null) {
+                cb.send(RouteInterface.RESULT_NOT_CONNECTED, null);
+            }
+        }
+    }
 
+    private final RouteConnectionRecord.Listener mConnectionListener
+            = new RouteConnectionRecord.Listener() {
+        @Override
+        public void onEvent(RouteEvent event) {
+            RouteEvent eventForSession = new RouteEvent(null, event.getIface(),
+                    event.getEvent(), event.getExtras());
+            mSessionCb.sendRouteEvent(eventForSession);
+        }
+
+        @Override
+        public void disconnect() {
+            // TODO
+        }
+    };
+
+    private final class SessionStub extends ISession.Stub {
         @Override
         public void destroy() {
             onDestroy();
@@ -174,21 +328,11 @@
         }
 
         @Override
-        public IMediaController getMediaController() {
+        public ISessionController getController() {
             return mController;
         }
 
         @Override
-        public void setRouteState(Bundle routeState) {
-        }
-
-        @Override
-        public void setRoute(Bundle mediaRouteDescriptor) {
-            mRoute = mediaRouteDescriptor;
-            mHandler.post(MessageHandler.MSG_UPDATE_ROUTE);
-        }
-
-        @Override
         public void publish() {
             mIsPublished = true; // TODO push update to service
         }
@@ -198,11 +342,6 @@
         }
 
         @Override
-        public List<String> getSupportedInterfaces() {
-            return mInterfaces;
-        }
-
-        @Override
         public void setMetadata(MediaMetadata metadata) {
             mMetadata = metadata;
             mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
@@ -218,12 +357,44 @@
         public void setRatingType(int type) {
             mRatingType = type;
         }
+
+        @Override
+        public void sendRouteCommand(RouteCommand command, ResultReceiver cb) {
+            mHandler.post(MessageHandler.MSG_SEND_COMMAND,
+                    new Pair<RouteCommand, ResultReceiver>(command, cb));
+        }
+
+        @Override
+        public boolean setRoute(RouteInfo route) throws RemoteException {
+            // TODO decide if allowed to set route and if the route exists
+            return false;
+        }
+
+        @Override
+        public void connectToRoute(RouteInfo route, RouteOptions request)
+                throws RemoteException {
+            if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) {
+                throw new RemoteException("RouteInfo does not match current route");
+            }
+            mService.connectToRoute(MediaSessionRecord.this, route, request);
+            mRequest = request;
+        }
+
+        @Override
+        public void setRouteOptions(List<RouteOptions> options) throws RemoteException {
+            mRequests.clear();
+            for (int i = options.size() - 1; i >= 0; i--) {
+                RouteRequest request = new RouteRequest(mSessionInfo, options.get(i),
+                        false);
+                mRequests.add(request);
+            }
+        }
     }
 
     class SessionCb {
-        private final IMediaSessionCallback mCb;
+        private final ISessionCallback mCb;
 
-        public SessionCb(IMediaSessionCallback cb) {
+        public SessionCb(ISessionCallback cb) {
             mCb = cb;
         }
 
@@ -245,6 +416,38 @@
             }
         }
 
+        public void sendRouteChange(RouteInfo route) {
+            try {
+                mCb.onRequestRouteChange(route);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote failure in sendRouteChange.", e);
+            }
+        }
+
+        public void sendRouteStateChange(int state) {
+            try {
+                mCb.onRouteStateChange(state);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote failure in sendRouteStateChange.", e);
+            }
+        }
+
+        public void sendRouteEvent(RouteEvent event) {
+            try {
+                mCb.onRouteEvent(event);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote failure in sendRouteEvent.", e);
+            }
+        }
+
+        public void sendRouteConnected() {
+            try {
+                mCb.onRouteConnected(mRoute, mRequest);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote failure in sendRouteStateChange.", e);
+            }
+        }
+
         public void play() {
             try {
                 mCb.onPlay();
@@ -318,7 +521,7 @@
         }
     }
 
-    class ControllerStub extends IMediaController.Stub {
+    class ControllerStub extends ISessionController.Stub {
         @Override
         public void sendCommand(String command, Bundle extras, ResultReceiver cb)
                 throws RemoteException {
@@ -331,8 +534,8 @@
         }
 
         @Override
-        public void registerCallbackListener(IMediaControllerCallback cb) {
-            synchronized (mControllerLock) {
+        public void registerCallbackListener(ISessionControllerCallback cb) {
+            synchronized (mLock) {
                 if (!mControllerCallbacks.contains(cb)) {
                     mControllerCallbacks.add(cb);
                 }
@@ -340,9 +543,9 @@
         }
 
         @Override
-        public void unregisterCallbackListener(IMediaControllerCallback cb)
+        public void unregisterCallbackListener(ISessionControllerCallback cb)
                 throws RemoteException {
-            synchronized (mControllerLock) {
+            synchronized (mLock) {
                 mControllerCallbacks.remove(cb);
             }
         }
@@ -409,9 +612,14 @@
         }
 
         @Override
-        public boolean isTransportControlEnabled() throws RemoteException {
+        public boolean isTransportControlEnabled() {
             return mTransportPerformerEnabled;
         }
+
+        @Override
+        public void showRoutePicker() {
+            mService.showRoutePickerForSession(MediaSessionRecord.this);
+        }
     }
 
     private class MessageHandler extends Handler {
@@ -419,6 +627,8 @@
         private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
         private static final int MSG_UPDATE_ROUTE = 3;
         private static final int MSG_SEND_EVENT = 4;
+        private static final int MSG_UPDATE_ROUTE_FILTERS = 5;
+        private static final int MSG_SEND_COMMAND = 6;
 
         public MessageHandler(Looper looper) {
             super(looper);
@@ -438,6 +648,11 @@
                 case MSG_SEND_EVENT:
                     pushEvent((String) msg.obj, msg.getData());
                     break;
+                case MSG_SEND_COMMAND:
+                    Pair<RouteCommand, ResultReceiver> cmd =
+                            (Pair<RouteCommand, ResultReceiver>) msg.obj;
+                    pushRouteCommand(cmd.first, cmd.second);
+                    break;
             }
         }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 8fe6055..bc91370 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -17,9 +17,12 @@
 package com.android.server.media;
 
 import android.content.Context;
-import android.media.session.IMediaSession;
-import android.media.session.IMediaSessionCallback;
-import android.media.session.IMediaSessionManager;
+import android.media.routeprovider.RouteRequest;
+import android.media.session.ISession;
+import android.media.session.ISessionCallback;
+import android.media.session.ISessionManager;
+import android.media.session.RouteInfo;
+import android.media.session.RouteOptions;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -38,21 +41,77 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final SessionManagerImpl mSessionManagerImpl;
+    private final MediaRouteProviderWatcher mRouteProviderWatcher;
 
     private final ArrayList<MediaSessionRecord> mSessions
             = new ArrayList<MediaSessionRecord>();
+    private final ArrayList<MediaRouteProviderProxy> mProviders
+            = new ArrayList<MediaRouteProviderProxy>();
     private final Object mLock = new Object();
     // TODO do we want a separate thread for handling mediasession messages?
     private final Handler mHandler = new Handler();
 
+    // Used to keep track of the current request to show routes for a specific
+    // session so we drop late callbacks properly.
+    private int mShowRoutesRequestId = 0;
+
+    // TODO refactor to have per user state. See MediaRouterService for an
+    // example
+
     public MediaSessionService(Context context) {
         super(context);
         mSessionManagerImpl = new SessionManagerImpl();
+        mRouteProviderWatcher = new MediaRouteProviderWatcher(context, mProviderWatcherCallback,
+                mHandler, context.getUserId());
     }
 
     @Override
     public void onStart() {
         publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
+        mRouteProviderWatcher.start();
+    }
+
+    /**
+     * Should trigger showing the Media route picker dialog. Right now it just
+     * kicks off a query to all the providers to get routes.
+     *
+     * @param record The session to show the picker for.
+     */
+    public void showRoutePickerForSession(MediaSessionRecord record) {
+        // TODO for now just toggle the route to test (we will only have one
+        // match for now)
+        if (record.getRoute() != null) {
+            // For now send null to mean the local route
+            record.selectRoute(null);
+            return;
+        }
+        mShowRoutesRequestId++;
+        ArrayList<MediaRouteProviderProxy> providers = mRouteProviderWatcher.getProviders();
+        for (int i = providers.size() - 1; i >= 0; i--) {
+            MediaRouteProviderProxy provider = providers.get(i);
+            provider.getRoutes(record, mShowRoutesRequestId);
+        }
+    }
+
+    /**
+     * Connect a session to the given route.
+     *
+     * @param session The session to connect.
+     * @param route The route to connect to.
+     * @param options The options to use for the connection.
+     */
+    public void connectToRoute(MediaSessionRecord session, RouteInfo route,
+            RouteOptions options) {
+        synchronized (mLock) {
+            MediaRouteProviderProxy proxy = getProviderLocked(route.getProvider());
+            if (proxy == null) {
+                Log.w(TAG, "Provider for route " + route.getName() + " does not exist.");
+                return;
+            }
+            RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true);
+            // TODO make connect an async call to a ThreadPoolExecutor
+            proxy.connectToRoute(session, route, request);
+        }
     }
 
     void sessionDied(MediaSessionRecord session) {
@@ -86,14 +145,14 @@
     }
 
     private MediaSessionRecord createSessionInternal(int pid, String packageName,
-            IMediaSessionCallback cb, String tag) {
+            ISessionCallback cb, String tag) {
         synchronized (mLock) {
             return createSessionLocked(pid, packageName, cb, tag);
         }
     }
 
     private MediaSessionRecord createSessionLocked(int pid, String packageName,
-            IMediaSessionCallback cb, String tag) {
+            ISessionCallback cb, String tag) {
         final MediaSessionRecord session = new MediaSessionRecord(pid, packageName, cb, tag, this,
                 mHandler);
         try {
@@ -110,9 +169,82 @@
         return session;
     }
 
-    class SessionManagerImpl extends IMediaSessionManager.Stub {
+    private MediaRouteProviderProxy getProviderLocked(String providerId) {
+        for (int i = mProviders.size() - 1; i >= 0; i--) {
+            MediaRouteProviderProxy provider = mProviders.get(i);
+            if (TextUtils.equals(providerId, provider.getId())) {
+                return provider;
+            }
+        }
+        return null;
+    }
+
+    private int findIndexOfSessionForIdLocked(String sessionId) {
+        for (int i = mSessions.size() - 1; i >= 0; i--) {
+            MediaSessionRecord session = mSessions.get(i);
+            if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private MediaRouteProviderWatcher.Callback mProviderWatcherCallback
+            = new MediaRouteProviderWatcher.Callback() {
         @Override
-        public IMediaSession createSession(String packageName, IMediaSessionCallback cb, String tag)
+        public void removeProvider(MediaRouteProviderProxy provider) {
+            synchronized (mLock) {
+                mProviders.remove(provider);
+                provider.setRoutesListener(null);
+                provider.setInterested(false);
+            }
+        }
+
+        @Override
+        public void addProvider(MediaRouteProviderProxy provider) {
+            synchronized (mLock) {
+                mProviders.add(provider);
+                provider.setRoutesListener(mRoutesCallback);
+                provider.setInterested(true);
+            }
+        }
+    };
+
+    private MediaRouteProviderProxy.RoutesListener mRoutesCallback
+            = new MediaRouteProviderProxy.RoutesListener() {
+        @Override
+        public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
+                int reqId) {
+            // TODO for now select the first route to test, eventually add the
+            // new routes to the dialog if it is still open
+            synchronized (mLock) {
+                int index = findIndexOfSessionForIdLocked(sessionId);
+                if (index != -1 && routes != null && routes.size() > 0) {
+                    MediaSessionRecord record = mSessions.get(index);
+                    record.selectRoute(routes.get(0));
+                }
+            }
+        }
+
+        @Override
+        public void onRouteConnected(String sessionId, RouteInfo route,
+                RouteRequest options, RouteConnectionRecord connection) {
+            synchronized (mLock) {
+                int index = findIndexOfSessionForIdLocked(sessionId);
+                if (index != -1) {
+                    MediaSessionRecord session = mSessions.get(index);
+                    session.setRouteConnected(route, options.getConnectionOptions(), connection);
+                }
+            }
+        }
+    };
+
+    class SessionManagerImpl extends ISessionManager.Stub {
+        // TODO add createSessionAsUser, pass user-id to
+        // ActivityManagerNative.handleIncomingUser and stash result for use
+        // when starting services on that session's behalf.
+        @Override
+        public ISession createSession(String packageName, ISessionCallback cb, String tag)
                 throws RemoteException {
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/media/RouteConnectionRecord.java b/services/core/java/com/android/server/media/RouteConnectionRecord.java
new file mode 100644
index 0000000..8da0f95
--- /dev/null
+++ b/services/core/java/com/android/server/media/RouteConnectionRecord.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2014 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 com.android.server.media;
+
+import android.media.routeprovider.IRouteConnection;
+import android.media.session.RouteCommand;
+import android.media.session.RouteEvent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+/**
+ * A connection between a Session and a Route.
+ */
+public class RouteConnectionRecord {
+    private static final String TAG = "RouteConnRecord";
+    private final IRouteConnection mBinder;
+    private Listener mListener;
+
+    public RouteConnectionRecord(IRouteConnection binder) {
+        mBinder = binder;
+    }
+
+    /**
+     * Add a listener to get route events on.
+     *
+     * @param listener The listener to get events on.
+     */
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Check if this connection matches the token given.
+     *
+     * @param binder The token to check
+     * @return True if this is the connection you're looking for, false
+     *         otherwise.
+     */
+    public boolean isConnection(IBinder binder) {
+        return binder != null && binder.equals(mBinder.asBinder());
+    }
+
+    /**
+     * Send an event from this connection.
+     *
+     * @param event The event to send.
+     */
+    public void sendEvent(RouteEvent event) {
+        if (mListener != null) {
+            mListener.onEvent(event);
+        }
+    }
+
+    /**
+     * Send a command to this connection.
+     *
+     * @param command The command to send.
+     * @param cb The receiver to get a result on.
+     */
+    public void sendCommand(RouteCommand command, ResultReceiver cb) {
+        try {
+            mBinder.onCommand(command, cb);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error in sendCommand", e);
+        }
+    }
+
+    /**
+     * Tell the session that the provider has disconnected it.
+     */
+    public void disconnect() {
+        if (mListener != null) {
+            mListener.disconnect();
+        }
+    }
+
+    /**
+     * Listener to receive updates from the provider for this connection.
+     */
+    public static interface Listener {
+        /**
+         * Called when an event is sent on this connection.
+         *
+         * @param event The event that was sent.
+         */
+        public void onEvent(RouteEvent event);
+
+        /**
+         * Called when the provider has disconnected the route.
+         */
+        public void disconnect();
+    }
+}
\ No newline at end of file
diff --git a/tests/OneMedia/AndroidManifest.xml b/tests/OneMedia/AndroidManifest.xml
index 7d6ba1d..504d471 100644
--- a/tests/OneMedia/AndroidManifest.xml
+++ b/tests/OneMedia/AndroidManifest.xml
@@ -25,6 +25,15 @@
             android:name="com.android.onemedia.OnePlayerService"
             android:exported="false"
             android:process="com.android.onemedia.service" />
+        <service
+            android:name=".provider.OneMediaRouteProvider"
+            android:permission="android.permission.BIND_ROUTE_PROVIDER"
+            android:exported="true"
+            android:process="com.android.onemedia.provider">
+            <intent-filter>
+                <action android:name="com.android.media.session.MediaRouteProvider" />
+            </intent-filter>
+          </service>
     </application>
 
 </manifest>
diff --git a/tests/OneMedia/res/layout/activity_one_player.xml b/tests/OneMedia/res/layout/activity_one_player.xml
index 4208355..516562f 100644
--- a/tests/OneMedia/res/layout/activity_one_player.xml
+++ b/tests/OneMedia/res/layout/activity_one_player.xml
@@ -53,6 +53,12 @@
                 android:layout_weight="1"
                 android:text="@string/play_button" />
     </LinearLayout>
+    <Button
+            android:id="@+id/route_button"
+            style="@style/BottomBarButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/route_button" />
     <TextView
             android:id="@+id/status"
             android:layout_width="match_parent"
diff --git a/tests/OneMedia/res/values/strings.xml b/tests/OneMedia/res/values/strings.xml
index 1b0cebb..3735c8d 100644
--- a/tests/OneMedia/res/values/strings.xml
+++ b/tests/OneMedia/res/values/strings.xml
@@ -7,6 +7,7 @@
 
     <string name="start_button">Start</string>
     <string name="play_button">Play</string>
+    <string name="route_button">Change route</string>
     <string name="media_content_hint">Content</string>
     <string name="media_next_hint">Next content</string>
     <string name="has_video">Is video</string>
diff --git a/tests/OneMedia/src/com/android/onemedia/IPlayerCallback.aidl b/tests/OneMedia/src/com/android/onemedia/IPlayerCallback.aidl
index 2b14384..189fa6a 100644
--- a/tests/OneMedia/src/com/android/onemedia/IPlayerCallback.aidl
+++ b/tests/OneMedia/src/com/android/onemedia/IPlayerCallback.aidl
@@ -15,8 +15,8 @@
 
 package com.android.onemedia;
 
-import android.media.session.MediaSessionToken;
+import android.media.session.SessionToken;
 
 interface IPlayerCallback {
-    void onSessionChanged(in MediaSessionToken session);
+    void onSessionChanged(in SessionToken session);
 }
\ No newline at end of file
diff --git a/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl b/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl
index efdbe9a..15ea25f 100644
--- a/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl
+++ b/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl
@@ -15,14 +15,14 @@
 
 package com.android.onemedia;
 
-import android.media.session.MediaSessionToken;
+import android.media.session.SessionToken;
 import android.os.Bundle;
 
 import com.android.onemedia.IPlayerCallback;
 import com.android.onemedia.playback.IRequestCallback;
 
 interface IPlayerService {
-    MediaSessionToken getSessionToken();
+    SessionToken getSessionToken();
     void registerCallback(in IPlayerCallback cb);
     void unregisterCallback(in IPlayerCallback cb);
     void sendRequest(String action, in Bundle params, in IRequestCallback cb);
diff --git a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
index 3114ca9..b9a6470 100644
--- a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
+++ b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
@@ -37,6 +37,7 @@
 
     private Button mStartButton;
     private Button mPlayButton;
+    private Button mRouteButton;
     private TextView mStatusView;
 
     private EditText mContentText;
@@ -54,6 +55,7 @@
 
         mStartButton = (Button) findViewById(R.id.start_button);
         mPlayButton = (Button) findViewById(R.id.play_button);
+        mRouteButton = (Button) findViewById(R.id.route_button);
         mStatusView = (TextView) findViewById(R.id.status);
         mContentText = (EditText) findViewById(R.id.content);
         mNextContentText = (EditText) findViewById(R.id.next_content);
@@ -61,6 +63,7 @@
 
         mStartButton.setOnClickListener(mButtonListener);
         mPlayButton.setOnClickListener(mButtonListener);
+        mRouteButton.setOnClickListener(mButtonListener);
 
     }
 
@@ -107,6 +110,9 @@
                     Log.d(TAG, "Start button pressed, in state " + mPlaybackState);
                     mPlayer.setContent(mContentText.getText().toString());
                     break;
+                case R.id.route_button:
+                    mPlayer.showRoutePicker();
+                    break;
             }
 
         }
@@ -117,6 +123,7 @@
         public void onPlaybackStateChange(PlaybackState state) {
             mPlaybackState = state.getState();
             boolean enablePlay = false;
+            boolean enableControls = true;
             StringBuilder statusBuilder = new StringBuilder();
             switch (mPlaybackState) {
                 case PlaybackState.PLAYSTATE_PLAYING:
@@ -143,12 +150,17 @@
                 case PlaybackState.PLAYSTATE_NONE:
                     statusBuilder.append("none");
                     break;
+                case PlaybackState.PLAYSTATE_CONNECTING:
+                    statusBuilder.append("connecting");
+                    enableControls = false;
+                    break;
                 default:
                     statusBuilder.append(mPlaybackState);
             }
             statusBuilder.append(" -- At position: ").append(state.getPosition());
             mStatusView.setText(statusBuilder.toString());
             mPlayButton.setEnabled(enablePlay);
+            setControlsEnabled(enableControls);
         }
 
         @Override
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerController.java b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
index e831ec6..e3f5c0c 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerController.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
@@ -16,9 +16,10 @@
  */
 package com.android.onemedia;
 
-import android.media.session.MediaController;
+import android.media.session.SessionController;
 import android.media.session.MediaMetadata;
-import android.media.session.MediaSessionManager;
+import android.media.session.RouteInfo;
+import android.media.session.SessionManager;
 import android.media.session.PlaybackState;
 import android.media.session.TransportController;
 import android.os.Bundle;
@@ -39,7 +40,7 @@
     public static final int STATE_DISCONNECTED = 0;
     public static final int STATE_CONNECTED = 1;
 
-    protected MediaController mController;
+    protected SessionController mController;
     protected IPlayerService mBinder;
     protected TransportController mTransportControls;
 
@@ -48,7 +49,7 @@
     private Listener mListener;
     private TransportListener mTransportListener = new TransportListener();
     private SessionCallback mControllerCb;
-    private MediaSessionManager mManager;
+    private SessionManager mManager;
     private Handler mHandler = new Handler();
 
     private boolean mResumed;
@@ -61,7 +62,7 @@
             mServiceIntent = serviceIntent;
         }
         mControllerCb = new SessionCallback();
-        mManager = (MediaSessionManager) context
+        mManager = (SessionManager) context
                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
 
         mResumed = false;
@@ -121,6 +122,10 @@
         }
     }
 
+    public void showRoutePicker() {
+        mController.showRoutePicker();
+    }
+
     private void unbindFromService() {
         mContext.unbindService(mServiceConnection);
     }
@@ -150,7 +155,7 @@
             mBinder = IPlayerService.Stub.asInterface(service);
             Log.d(TAG, "service is " + service + " binder is " + mBinder);
             try {
-                mController = MediaController.fromToken(mBinder.getSessionToken());
+                mController = SessionController.fromToken(mBinder.getSessionToken());
             } catch (RemoteException e) {
                 Log.e(TAG, "Error getting session", e);
                 return;
@@ -171,9 +176,9 @@
         }
     };
 
-    private class SessionCallback extends MediaController.Callback {
+    private class SessionCallback extends SessionController.Callback {
         @Override
-        public void onRouteChanged(Bundle route) {
+        public void onRouteChanged(RouteInfo route) {
             // TODO
         }
     }
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerService.java b/tests/OneMedia/src/com/android/onemedia/PlayerService.java
index 0ad6dd1..8b53ddf 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerService.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerService.java
@@ -17,7 +17,7 @@
 
 import android.app.Service;
 import android.content.Intent;
-import android.media.session.MediaSessionToken;
+import android.media.session.SessionToken;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -149,7 +149,7 @@
         }
 
         @Override
-        public MediaSessionToken getSessionToken() throws RemoteException {
+        public SessionToken getSessionToken() throws RemoteException {
             return mSession.getSessionToken();
         }
     }
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
index a2d7897..5dc3904 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
@@ -17,9 +17,13 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.media.session.MediaSession;
-import android.media.session.MediaSessionManager;
-import android.media.session.MediaSessionToken;
+import android.media.session.Route;
+import android.media.session.RouteInfo;
+import android.media.session.RouteOptions;
+import android.media.session.RoutePlaybackControls;
+import android.media.session.Session;
+import android.media.session.SessionManager;
+import android.media.session.SessionToken;
 import android.media.session.PlaybackState;
 import android.media.session.TransportPerformer;
 import android.os.Bundle;
@@ -27,41 +31,55 @@
 import android.view.KeyEvent;
 
 import com.android.onemedia.playback.LocalRenderer;
+import com.android.onemedia.playback.OneMRPRenderer;
 import com.android.onemedia.playback.Renderer;
-import com.android.onemedia.playback.RendererFactory;
+import com.android.onemedia.playback.RequestUtils;
+
+import java.util.ArrayList;
 
 public class PlayerSession {
     private static final String TAG = "PlayerSession";
 
-    protected MediaSession mSession;
+    protected Session mSession;
     protected Context mContext;
-    protected RendererFactory mRendererFactory;
-    protected LocalRenderer mRenderer;
-    protected MediaSession.Callback mCallback;
+    protected Renderer mRenderer;
+    protected Session.Callback mCallback;
     protected Renderer.Listener mRenderListener;
     protected TransportPerformer mPerformer;
 
     protected PlaybackState mPlaybackState;
     protected Listener mListener;
+    protected ArrayList<RouteOptions> mRouteOptions;
+    protected Route mRoute;
+    protected RoutePlaybackControls mRouteControls;
+    protected RouteListener mRouteListener;
+
+    private String mContent;
 
     public PlayerSession(Context context) {
         mContext = context;
-        mRendererFactory = new RendererFactory();
         mRenderer = new LocalRenderer(context, null);
-        mCallback = new ControllerCb();
+        mCallback = new SessionCb();
         mRenderListener = new RenderListener();
         mPlaybackState = new PlaybackState();
         mPlaybackState.setActions(PlaybackState.ACTION_PAUSE
                 | PlaybackState.ACTION_PLAY);
 
         mRenderer.registerListener(mRenderListener);
+
+        // TODO need an easier way to build route options
+        mRouteOptions = new ArrayList<RouteOptions>();
+        RouteOptions.Builder bob = new RouteOptions.Builder();
+        bob.addInterface(RoutePlaybackControls.NAME);
+        mRouteOptions.add(bob.build());
+        mRouteListener = new RouteListener();
     }
 
     public void createSession() {
         if (mSession != null) {
             mSession.release();
         }
-        MediaSessionManager man = (MediaSessionManager) mContext
+        SessionManager man = (SessionManager) mContext
                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
         Log.d(TAG, "Creating session for package " + mContext.getBasePackageName());
         mSession = man.createSession("OneMedia");
@@ -69,6 +87,7 @@
         mPerformer = mSession.setTransportPerformerEnabled();
         mPerformer.addListener(new TransportListener());
         mPerformer.setPlaybackState(mPlaybackState);
+        mSession.setRouteOptions(mRouteOptions);
         mSession.publish();
     }
 
@@ -86,18 +105,24 @@
         mListener = listener;
     }
 
-    public MediaSessionToken getSessionToken() {
+    public SessionToken getSessionToken() {
         return mSession.getSessionToken();
     }
 
     public void setContent(Bundle request) {
         mRenderer.setContent(request);
+        mContent = request.getString(RequestUtils.EXTRA_KEY_SOURCE);
     }
 
     public void setNextContent(Bundle request) {
         mRenderer.setNextContent(request);
     }
 
+    private void updateState(int newState) {
+        mPlaybackState.setState(newState);
+        mPerformer.setPlaybackState(mPlaybackState);
+    }
+
     public interface Listener {
         public void onPlayStateChanged(PlaybackState state);
     }
@@ -145,7 +170,11 @@
                     mPlaybackState.setErrorMessage("unkown state");
                     break;
             }
-            mPlaybackState.setPosition(mRenderer.getSeekPosition());
+            if (mRenderer != null) {
+                mPlaybackState.setPosition(mRenderer.getSeekPosition());
+            } else {
+                mPlaybackState.setPosition(-1);
+            }
             mPerformer.setPlaybackState(mPlaybackState);
             if (mListener != null) {
                 mListener.onPlayStateChanged(mPlaybackState);
@@ -173,8 +202,7 @@
 
     }
 
-    private class ControllerCb extends MediaSession.Callback {
-
+    private class SessionCb extends Session.Callback {
         @Override
         public void onMediaButton(Intent mediaRequestIntent) {
             if (Intent.ACTION_MEDIA_BUTTON.equals(mediaRequestIntent.getAction())) {
@@ -192,6 +220,40 @@
                 }
             }
         }
+
+        @Override
+        public void onRequestRouteChange(RouteInfo route) {
+            if (mRenderer != null) {
+                mRenderer.onStop();
+            }
+            if (route == null) {
+                // Use local route
+                mRoute = null;
+                mRenderer = new LocalRenderer(mContext, null);
+                mRenderer.registerListener(mRenderListener);
+                updateState(PlaybackState.PLAYSTATE_NONE);
+            } else {
+                // Use remote route
+                mSession.connect(route, mRouteOptions.get(0));
+                mRenderer = null;
+                updateState(PlaybackState.PLAYSTATE_CONNECTING);
+            }
+        }
+
+        @Override
+        public void onRouteConnected(Route route) {
+            mRoute = route;
+            mRouteControls = RoutePlaybackControls.from(route);
+            mRouteControls.addListener(mRouteListener);
+            Log.d(TAG, "Connected to route, registering listener");
+            mRenderer = new OneMRPRenderer(mRouteControls);
+            updateState(PlaybackState.PLAYSTATE_NONE);
+        }
+
+        @Override
+        public void onRouteDisconnected(Route route, int reason) {
+
+        }
     }
 
     private class TransportListener extends TransportPerformer.Listener {
@@ -206,4 +268,12 @@
         }
     }
 
+    private class RouteListener extends RoutePlaybackControls.Listener {
+        @Override
+        public void onPlaybackStateChange(int state) {
+            Log.d(TAG, "Updating state to " + state);
+            updateState(state);
+        }
+    }
+
 }
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/LocalRenderer.java b/tests/OneMedia/src/com/android/onemedia/playback/LocalRenderer.java
index 7f62f66..c8a8d6c 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/LocalRenderer.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/LocalRenderer.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2014 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 com.android.onemedia.playback;
 
 import org.apache.http.Header;
@@ -370,6 +385,8 @@
      * Prepares the player for the given playback request. If the holder is null
      * it is assumed this is an audio only source. If playOnReady is set to true
      * the media will begin playing as soon as it can.
+     *
+     * @see RequestUtils for the set of valid keys.
      */
     public void setContent(Bundle request, SurfaceHolder holder) {
         String source = request.getString(RequestUtils.EXTRA_KEY_SOURCE);
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java b/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java
index f9e6794..05516d2 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2014 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 com.android.onemedia.playback;
 
 import android.os.Bundle;
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java b/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java
new file mode 100644
index 0000000..9b0a2b2
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java
@@ -0,0 +1,44 @@
+package com.android.onemedia.playback;
+
+import android.media.session.RoutePlaybackControls;
+import android.os.Bundle;
+
+/**
+ * Renderer for communicating with the OneMRP route
+ */
+public class OneMRPRenderer extends Renderer {
+    private final RoutePlaybackControls mControls;
+
+    public OneMRPRenderer(RoutePlaybackControls controls) {
+        super(null, null);
+        mControls = controls;
+    }
+
+    @Override
+    public void setContent(Bundle request) {
+        mControls.playNow(request.getString(RequestUtils.EXTRA_KEY_SOURCE));
+    }
+
+    @Override
+    public boolean onStop() {
+        mControls.pause();
+        return true;
+    }
+
+    @Override
+    public boolean onPlay() {
+        mControls.resume();
+        return true;
+    }
+
+    @Override
+    public boolean onPause() {
+        mControls.pause();
+        return true;
+    }
+
+    @Override
+    public long getSeekPosition() {
+        return -1;
+    }
+}
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/PlaybackError.java b/tests/OneMedia/src/com/android/onemedia/playback/PlaybackError.java
index 72d936c..ac9da23 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/PlaybackError.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/PlaybackError.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2014 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 com.android.onemedia.playback;
 
 import android.os.Bundle;
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/Renderer.java b/tests/OneMedia/src/com/android/onemedia/playback/Renderer.java
index 2451bdf..09debcf 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/Renderer.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/Renderer.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2014 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 com.android.onemedia.playback;
 
 import android.content.Context;
@@ -77,39 +92,54 @@
     }
 
     public boolean onPlay() {
-        throw new UnsupportedOperationException("play is not supported.");
+        // TODO consider making these log warnings instead of crashes (or
+        // Log.wtf)
+        // throw new UnsupportedOperationException("play is not supported.");
+        return false;
     }
 
     public boolean onPause() {
-        throw new UnsupportedOperationException("pause is not supported.");
+        // throw new UnsupportedOperationException("pause is not supported.");
+        return false;
     }
 
     public boolean onNext() {
-        throw new UnsupportedOperationException("next is not supported.");
+        // throw new UnsupportedOperationException("next is not supported.");
+        return false;
     }
 
     public boolean onPrevious() {
-        throw new UnsupportedOperationException("previous is not supported.");
+        // throw new
+        // UnsupportedOperationException("previous is not supported.");
+        return false;
     }
 
     public boolean onStop() {
-        throw new UnsupportedOperationException("stop is not supported.");
+        // throw new UnsupportedOperationException("stop is not supported.");
+        return false;
     }
 
     public boolean onSeekTo(int time) {
-        throw new UnsupportedOperationException("seekTo is not supported.");
+        // throw new UnsupportedOperationException("seekTo is not supported.");
+        return false;
     }
 
     public long getSeekPosition() {
-        throw new UnsupportedOperationException("getSeekPosition is not supported.");
+        // throw new
+        // UnsupportedOperationException("getSeekPosition is not supported.");
+        return -1;
     }
 
     public long getDuration() {
-        throw new UnsupportedOperationException("getDuration is not supported.");
+        // throw new
+        // UnsupportedOperationException("getDuration is not supported.");
+        return -1;
     }
 
     public int getPlayState() {
-        throw new UnsupportedOperationException("getPlayState is not supported.");
+        // throw new
+        // UnsupportedOperationException("getPlayState is not supported.");
+        return 0;
     }
 
     public void onDestroy() {
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/RendererFactory.java b/tests/OneMedia/src/com/android/onemedia/playback/RendererFactory.java
deleted file mode 100644
index f333fce..0000000
--- a/tests/OneMedia/src/com/android/onemedia/playback/RendererFactory.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.android.onemedia.playback;
-
-import android.content.Context;
-import android.media.MediaRouter;
-import android.os.Bundle;
-import android.util.Log;
-
-/**
- * TODO: Insert description here.
- */
-public class RendererFactory {
-    private static final String TAG = "RendererFactory";
-
-    public Renderer createRenderer(MediaRouter.RouteInfo route, Context context, Bundle params) {
-        if (route.getPlaybackType() == MediaRouter.RouteInfo.PLAYBACK_TYPE_LOCAL) {
-            return new LocalRenderer(context, params);
-        }
-        Log.e(TAG, "Unable to create renderer for route of playback type "
-                + route.getPlaybackType());
-        return null;
-    }
-}
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java b/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
index 9b50dad..dd0d982 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2014 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 com.android.onemedia.playback;
 
 import android.os.Bundle;
diff --git a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
new file mode 100644
index 0000000..6edcd7d
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2014 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 com.android.onemedia.provider;
+
+import android.media.routeprovider.RouteConnection;
+import android.media.routeprovider.RouteInterfaceHandler;
+import android.media.routeprovider.RoutePlaybackControlsHandler;
+import android.media.routeprovider.RouteProviderService;
+import android.media.routeprovider.RouteRequest;
+import android.media.session.RouteInfo;
+import android.media.session.RoutePlaybackControls;
+import android.media.session.RouteInterface;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import com.android.onemedia.playback.LocalRenderer;
+import com.android.onemedia.playback.Renderer;
+import com.android.onemedia.playback.RequestUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Test of MediaRouteProvider. Show a dummy provider with a simple interface for
+ * playing music.
+ */
+public class OneMediaRouteProvider extends RouteProviderService {
+    private static final String TAG = "OneMRP";
+    private static final boolean DEBUG = true;
+
+    private Renderer mRenderer;
+    private RenderListener mRenderListener;
+    private PlaybackState mPlaybackState;
+    private RouteConnection mConnection;
+    private RoutePlaybackControlsHandler mControls;
+    private String mRouteId;
+    private Handler mHandler;
+
+    @Override
+    public void onCreate() {
+        mHandler = new Handler();
+        mRouteId = UUID.randomUUID().toString();
+        mRenderer = new LocalRenderer(this, null);
+        mRenderListener = new RenderListener();
+        mPlaybackState = new PlaybackState();
+        mPlaybackState.setActions(PlaybackState.ACTION_PAUSE
+                | PlaybackState.ACTION_PLAY);
+
+        mRenderer.registerListener(mRenderListener);
+
+        if (DEBUG) {
+            Log.d(TAG, "onCreate, routeId is " + mRouteId);
+        }
+    }
+
+    @Override
+    public List<RouteInfo> getMatchingRoutes(List<RouteRequest> requests) {
+        RouteInfo.Builder bob = new RouteInfo.Builder();
+        bob.setName("OneMedia").setId(mRouteId);
+        // TODO add a helper library for generating route info with the correct
+        // options
+        Log.d(TAG, "Requests:");
+        for (RouteRequest request : requests) {
+            List<String> ifaces = request.getConnectionOptions().getInterfaceNames();
+            Log.d(TAG, "  request ifaces:" + ifaces.toString());
+            if (ifaces != null && ifaces.size() == 1
+                    && RoutePlaybackControls.NAME.equals(ifaces.get(0))) {
+                bob.addRouteOptions(request.getConnectionOptions());
+            }
+        }
+        ArrayList<RouteInfo> result = new ArrayList<RouteInfo>();
+        if (bob.getOptionsSize() > 0) {
+            RouteInfo info = bob.build();
+            result.add(info);
+        }
+        if (DEBUG) {
+            Log.d(TAG, "getRoutes returning " + result.toString());
+        }
+        return result;
+    }
+
+    @Override
+    public RouteConnection connect(RouteInfo route, RouteRequest request) {
+        if (mConnection != null) {
+            disconnect(mConnection);
+        }
+        RouteConnection connection = new RouteConnection(this, route);
+        mControls = RoutePlaybackControlsHandler.addTo(connection);
+        mControls.addListener(new PlayHandler(mRouteId), mHandler);
+        if (DEBUG) {
+            Log.d(TAG, "Connected to route");
+        }
+        return connection;
+    }
+
+    private class PlayHandler extends RoutePlaybackControlsHandler.Listener {
+        private final String mRouteId;
+
+        public PlayHandler(String routeId) {
+            mRouteId = routeId;
+        }
+
+        @Override
+        public void playNow(String content, ResultReceiver cb) {
+            if (DEBUG) {
+                Log.d(TAG, "Attempting to play " + content);
+            }
+            // look up the route and send a play command to it
+            Bundle bundle = new Bundle();
+            bundle.putString(RequestUtils.EXTRA_KEY_SOURCE, content);
+            mRenderer.setContent(bundle);
+            RouteInterfaceHandler.sendResult(cb, RouteInterface.RESULT_SUCCESS, null);
+        }
+
+        @Override
+        public boolean resume() {
+            mRenderer.onPlay();
+            return true;
+        }
+
+        @Override
+        public boolean pause() {
+            mRenderer.onPause();
+            return true;
+        }
+    }
+
+    private class RenderListener implements Renderer.Listener {
+
+        @Override
+        public void onError(int type, int extra, Bundle extras, Throwable error) {
+            Log.d(TAG, "Sending onError with type " + type + " and extra " + extra);
+            if (mControls != null) {
+                mControls.sendPlaybackChangeEvent(PlaybackState.PLAYSTATE_ERROR);
+            }
+        }
+
+        @Override
+        public void onStateChanged(int newState) {
+            if (newState != Renderer.STATE_ERROR) {
+                mPlaybackState.setErrorMessage(null);
+            }
+            switch (newState) {
+                case Renderer.STATE_ENDED:
+                case Renderer.STATE_STOPPED:
+                    mPlaybackState.setState(PlaybackState.PLAYSTATE_STOPPED);
+                    break;
+                case Renderer.STATE_INIT:
+                case Renderer.STATE_PREPARING:
+                    mPlaybackState.setState(PlaybackState.PLAYSTATE_BUFFERING);
+                    break;
+                case Renderer.STATE_ERROR:
+                    mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR);
+                    break;
+                case Renderer.STATE_PAUSED:
+                    mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED);
+                    break;
+                case Renderer.STATE_PLAYING:
+                    mPlaybackState.setState(PlaybackState.PLAYSTATE_PLAYING);
+                    break;
+                default:
+                    mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR);
+                    mPlaybackState.setErrorMessage("unkown state");
+                    break;
+            }
+            mPlaybackState.setPosition(mRenderer.getSeekPosition());
+
+            mControls.sendPlaybackChangeEvent(mPlaybackState.getState());
+        }
+
+        @Override
+        public void onBufferingUpdate(int percent) {
+        }
+
+        @Override
+        public void onFocusLost() {
+            Log.d(TAG, "Focus lost, changing state to " + Renderer.STATE_PAUSED);
+            mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED);
+            mPlaybackState.setPosition(mRenderer.getSeekPosition());
+        }
+
+        @Override
+        public void onNextStarted() {
+        }
+    }
+}