Merge "Add TRANSCRIPTION column to Calls table" into lmp-dev
diff --git a/Android.mk b/Android.mk
index 23c4753..96b2b17 100644
--- a/Android.mk
+++ b/Android.mk
@@ -320,9 +320,12 @@
 	media/java/android/media/IRemoteVolumeObserver.aidl \
 	media/java/android/media/IRingtonePlayer.aidl \
 	media/java/android/media/IVolumeController.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/routing/IMediaRouteService.aidl \
+	media/java/android/media/routing/IMediaRouteClientCallback.aidl \
+	media/java/android/media/routing/IMediaRouter.aidl \
+	media/java/android/media/routing/IMediaRouterDelegate.aidl \
+	media/java/android/media/routing/IMediaRouterRoutingCallback.aidl \
+	media/java/android/media/routing/IMediaRouterStateCallback.aidl \
 	media/java/android/media/session/IActiveSessionsListener.aidl \
 	media/java/android/media/session/ISessionController.aidl \
 	media/java/android/media/session/ISessionControllerCallback.aidl \
@@ -527,6 +530,7 @@
 	frameworks/base/location/java/com/android/internal/location/ProviderRequest.aidl \
 	frameworks/base/media/java/android/media/MediaMetadata.aidl \
 	frameworks/base/media/java/android/media/Rating.aidl \
+	frameworks/base/media/java/android/media/routing/MediaRouteSelector.aidl \
 	frameworks/base/media/java/android/media/session/MediaSession.aidl \
 	frameworks/base/media/java/android/media/session/PlaybackState.aidl \
 	frameworks/base/telephony/java/android/telephony/ServiceState.aidl \
diff --git a/api/current.txt b/api/current.txt
index 588d558..ea21786 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -23,6 +23,7 @@
     field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
     field public static final java.lang.String BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE";
     field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
+    field public static final java.lang.String BIND_MEDIA_ROUTE_SERVICE = "android.permission.BIND_MEDIA_ROUTE_SERVICE";
     field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
     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";
@@ -11857,7 +11858,7 @@
 
 package android.hardware {
 
-  public class Camera {
+  public deprecated class Camera {
     method public final void addCallbackBuffer(byte[]);
     method public final void autoFocus(android.hardware.Camera.AutoFocusCallback);
     method public final void cancelAutoFocus();
@@ -11896,21 +11897,21 @@
     field public static final int CAMERA_ERROR_UNKNOWN = 1; // 0x1
   }
 
-  public static class Camera.Area {
+  public static deprecated class Camera.Area {
     ctor public Camera.Area(android.graphics.Rect, int);
     field public android.graphics.Rect rect;
     field public int weight;
   }
 
-  public static abstract interface Camera.AutoFocusCallback {
+  public static abstract deprecated interface Camera.AutoFocusCallback {
     method public abstract void onAutoFocus(boolean, android.hardware.Camera);
   }
 
-  public static abstract interface Camera.AutoFocusMoveCallback {
+  public static abstract deprecated interface Camera.AutoFocusMoveCallback {
     method public abstract void onAutoFocusMoving(boolean, android.hardware.Camera);
   }
 
-  public static class Camera.CameraInfo {
+  public static deprecated class Camera.CameraInfo {
     ctor public Camera.CameraInfo();
     field public static final int CAMERA_FACING_BACK = 0; // 0x0
     field public static final int CAMERA_FACING_FRONT = 1; // 0x1
@@ -11919,11 +11920,11 @@
     field public int orientation;
   }
 
-  public static abstract interface Camera.ErrorCallback {
+  public static abstract deprecated interface Camera.ErrorCallback {
     method public abstract void onError(int, android.hardware.Camera);
   }
 
-  public static class Camera.Face {
+  public static deprecated class Camera.Face {
     ctor public Camera.Face();
     field public int id;
     field public android.graphics.Point leftEye;
@@ -11933,15 +11934,15 @@
     field public int score;
   }
 
-  public static abstract interface Camera.FaceDetectionListener {
+  public static abstract deprecated interface Camera.FaceDetectionListener {
     method public abstract void onFaceDetection(android.hardware.Camera.Face[], android.hardware.Camera);
   }
 
-  public static abstract interface Camera.OnZoomChangeListener {
+  public static abstract deprecated interface Camera.OnZoomChangeListener {
     method public abstract void onZoomChange(int, boolean, android.hardware.Camera);
   }
 
-  public class Camera.Parameters {
+  public deprecated class Camera.Parameters {
     method public java.lang.String flatten();
     method public java.lang.String get(java.lang.String);
     method public java.lang.String getAntibanding();
@@ -12091,19 +12092,19 @@
     field public static final java.lang.String WHITE_BALANCE_WARM_FLUORESCENT = "warm-fluorescent";
   }
 
-  public static abstract interface Camera.PictureCallback {
+  public static abstract deprecated interface Camera.PictureCallback {
     method public abstract void onPictureTaken(byte[], android.hardware.Camera);
   }
 
-  public static abstract interface Camera.PreviewCallback {
+  public static abstract deprecated interface Camera.PreviewCallback {
     method public abstract void onPreviewFrame(byte[], android.hardware.Camera);
   }
 
-  public static abstract interface Camera.ShutterCallback {
+  public static abstract deprecated interface Camera.ShutterCallback {
     method public abstract void onShutter();
   }
 
-  public class Camera.Size {
+  public deprecated class Camera.Size {
     ctor public Camera.Size(int, int);
     field public int height;
     field public int width;
@@ -14970,7 +14971,7 @@
     method public void setAudioEncodingBitRate(int);
     method public void setAudioSamplingRate(int);
     method public void setAudioSource(int) throws java.lang.IllegalStateException;
-    method public void setCamera(android.hardware.Camera);
+    method public deprecated void setCamera(android.hardware.Camera);
     method public void setCaptureRate(double);
     method public void setLocation(float, float);
     method public void setMaxDuration(int) throws java.lang.IllegalArgumentException;
@@ -15871,12 +15872,249 @@
 
 }
 
+package android.media.routing {
+
+  public final class MediaRouteSelector implements android.os.Parcelable {
+    method public boolean containsProtocol(java.lang.Class<?>);
+    method public boolean containsProtocol(java.lang.String);
+    method public int describeContents();
+    method public android.os.Bundle getExtras();
+    method public int getOptionalFeatures();
+    method public java.util.List<java.lang.String> getOptionalProtocols();
+    method public int getRequiredFeatures();
+    method public java.util.List<java.lang.String> getRequiredProtocols();
+    method public java.lang.String getServicePackageName();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
+  public static final class MediaRouteSelector.Builder {
+    ctor public MediaRouteSelector.Builder();
+    method public android.media.routing.MediaRouteSelector.Builder addOptionalProtocol(java.lang.Class<?>);
+    method public android.media.routing.MediaRouteSelector.Builder addOptionalProtocol(java.lang.String);
+    method public android.media.routing.MediaRouteSelector.Builder addRequiredProtocol(java.lang.Class<?>);
+    method public android.media.routing.MediaRouteSelector.Builder addRequiredProtocol(java.lang.String);
+    method public android.media.routing.MediaRouteSelector build();
+    method public android.media.routing.MediaRouteSelector.Builder setExtras(android.os.Bundle);
+    method public android.media.routing.MediaRouteSelector.Builder setOptionalFeatures(int);
+    method public android.media.routing.MediaRouteSelector.Builder setRequiredFeatures(int);
+    method public android.media.routing.MediaRouteSelector.Builder setServicePackageName(java.lang.String);
+  }
+
+  public abstract class MediaRouteService extends android.app.Service {
+    ctor public MediaRouteService();
+    method public android.media.routing.MediaRouter.ServiceMetadata getServiceMetadata();
+    method public android.os.IBinder onBind(android.content.Intent);
+    method public abstract android.media.routing.MediaRouteService.ClientSession onCreateClientSession(android.media.routing.MediaRouteService.ClientInfo);
+    field public static final java.lang.String SERVICE_INTERFACE = "android.media.routing.MediaRouteService";
+  }
+
+  public static final class MediaRouteService.ClientInfo {
+    method public java.lang.String getPackageName();
+    method public int getUid();
+  }
+
+  public static abstract class MediaRouteService.ClientSession {
+    ctor public MediaRouteService.ClientSession();
+    method public abstract boolean onConnect(android.media.routing.MediaRouter.ConnectionRequest, android.media.routing.MediaRouteService.ConnectionCallback);
+    method public abstract void onDisconnect();
+    method public void onPauseStream();
+    method public void onRelease();
+    method public void onResumeStream();
+    method public abstract boolean onStartDiscovery(android.media.routing.MediaRouter.DiscoveryRequest, android.media.routing.MediaRouteService.DiscoveryCallback);
+    method public abstract void onStopDiscovery();
+  }
+
+  public final class MediaRouteService.ConnectionCallback {
+    method public void onConnected(android.media.routing.MediaRouter.ConnectionInfo);
+    method public void onConnectionFailed(int, java.lang.CharSequence, android.os.Bundle);
+    method public void onDisconnected();
+  }
+
+  public final class MediaRouteService.DiscoveryCallback {
+    method public void onDestinationFound(android.media.routing.MediaRouter.DestinationInfo, java.util.List<android.media.routing.MediaRouter.RouteInfo>);
+    method public void onDestinationLost(android.media.routing.MediaRouter.DestinationInfo);
+    method public void onDiscoveryFailed(int, java.lang.CharSequence, android.os.Bundle);
+  }
+
+  public final class MediaRouter {
+    ctor public MediaRouter(android.content.Context);
+    method public void addSelector(android.media.routing.MediaRouteSelector);
+    method public void clearSelectors();
+    method public android.media.routing.MediaRouter.Delegate createDelegate();
+    method public android.media.routing.MediaRouter.ConnectionInfo getConnection();
+    method public int getConnectionState();
+    method public java.util.List<android.media.routing.MediaRouter.DestinationInfo> getDiscoveredDestinations();
+    method public java.util.List<android.media.routing.MediaRouter.RouteInfo> getDiscoveredRoutes(android.media.routing.MediaRouter.DestinationInfo);
+    method public int getDiscoveryState();
+    method public android.media.AudioAttributes getPreferredAudioAttributes();
+    method public android.view.Display getPreferredPresentationDisplay();
+    method public android.media.VolumeProvider getPreferredVolumeProvider();
+    method public android.media.routing.MediaRouter.DestinationInfo getSelectedDestination();
+    method public android.media.routing.MediaRouter.RouteInfo getSelectedRoute();
+    method public java.util.List<android.media.routing.MediaRouteSelector> getSelectors();
+    method public boolean isReleased();
+    method public void pauseStream();
+    method public void release();
+    method public void removeSelector(android.media.routing.MediaRouteSelector);
+    method public void resumeStream();
+    method public void setRoutingCallback(android.media.routing.MediaRouter.RoutingCallback, android.os.Handler);
+    field public static final int CONNECTION_ERROR_ABORTED = 1; // 0x1
+    field public static final int CONNECTION_ERROR_BARGED = 7; // 0x7
+    field public static final int CONNECTION_ERROR_BROKEN = 6; // 0x6
+    field public static final int CONNECTION_ERROR_BUSY = 4; // 0x4
+    field public static final int CONNECTION_ERROR_TIMEOUT = 5; // 0x5
+    field public static final int CONNECTION_ERROR_UNAUTHORIZED = 2; // 0x2
+    field public static final int CONNECTION_ERROR_UNKNOWN = 0; // 0x0
+    field public static final int CONNECTION_ERROR_UNREACHABLE = 3; // 0x3
+    field public static final int CONNECTION_FLAG_BARGE = 1; // 0x1
+    field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
+    field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
+    field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+    field public static final int DISCONNECTION_REASON_APPLICATION_REQUEST = 0; // 0x0
+    field public static final int DISCONNECTION_REASON_ERROR = 2; // 0x2
+    field public static final int DISCONNECTION_REASON_USER_REQUEST = 1; // 0x1
+    field public static final int DISCOVERY_ERROR_ABORTED = 1; // 0x1
+    field public static final int DISCOVERY_ERROR_NO_CONNECTIVITY = 2; // 0x2
+    field public static final int DISCOVERY_ERROR_UNKNOWN = 0; // 0x0
+    field public static final int DISCOVERY_FLAG_BACKGROUND = 1; // 0x1
+    field public static final int DISCOVERY_STATE_STARTED = 1; // 0x1
+    field public static final int DISCOVERY_STATE_STOPPED = 0; // 0x0
+    field public static final int ROUTE_FEATURE_LIVE_AUDIO = 1; // 0x1
+    field public static final int ROUTE_FEATURE_LIVE_VIDEO = 2; // 0x2
+  }
+
+  public static final class MediaRouter.ConnectionInfo {
+    method public android.media.AudioAttributes getAudioAttributes();
+    method public android.os.Bundle getExtras();
+    method public int getFeatures();
+    method public android.view.Display getPresentationDisplay();
+    method public android.os.IBinder getProtocolBinder(java.lang.String);
+    method public android.os.IBinder getProtocolBinder(int);
+    method public T getProtocolObject(java.lang.Class<T>);
+    method public java.util.List<java.lang.String> getProtocols();
+    method public android.media.routing.MediaRouter.RouteInfo getRoute();
+    method public android.media.VolumeProvider getVolumeProvider();
+  }
+
+  public static final class MediaRouter.ConnectionInfo.Builder {
+    ctor public MediaRouter.ConnectionInfo.Builder(android.media.routing.MediaRouter.RouteInfo);
+    method public android.media.routing.MediaRouter.ConnectionInfo build();
+    method public android.media.routing.MediaRouter.ConnectionInfo.Builder setAudioAttributes(android.media.AudioAttributes);
+    method public android.media.routing.MediaRouter.ConnectionInfo.Builder setExtras(android.os.Bundle);
+    method public android.media.routing.MediaRouter.ConnectionInfo.Builder setPresentationDisplay(android.view.Display);
+    method public android.media.routing.MediaRouter.ConnectionInfo.Builder setProtocolBinder(java.lang.String, android.os.IBinder);
+    method public android.media.routing.MediaRouter.ConnectionInfo.Builder setProtocolStub(java.lang.Class<?>, android.os.IInterface);
+    method public android.media.routing.MediaRouter.ConnectionInfo.Builder setVolumeProvider(android.media.VolumeProvider);
+  }
+
+  public static final class MediaRouter.ConnectionRequest {
+    method public android.os.Bundle getExtras();
+    method public int getFlags();
+    method public android.media.routing.MediaRouter.RouteInfo getRoute();
+    method public void setExtras(android.os.Bundle);
+    method public void setFlags(int);
+    method public void setRoute(android.media.routing.MediaRouter.RouteInfo);
+  }
+
+  public static final class MediaRouter.Delegate {
+    ctor public MediaRouter.Delegate();
+    method public void addStateCallback(android.media.routing.MediaRouter.StateCallback, android.os.Handler);
+    method public void connect(android.media.routing.MediaRouter.DestinationInfo, int);
+    method public void disconnect(int);
+    method public int getConnectionState();
+    method public java.util.List<android.media.routing.MediaRouter.DestinationInfo> getDiscoveredDestinations();
+    method public int getDiscoveryState();
+    method public android.media.routing.MediaRouter.DestinationInfo getSelectedDestination();
+    method public boolean isReleased();
+    method public void removeStateCallback(android.media.routing.MediaRouter.StateCallback);
+    method public void startDiscovery(int);
+    method public void stopDiscovery();
+  }
+
+  public static final class MediaRouter.DestinationInfo {
+    method public java.lang.CharSequence getDescription();
+    method public android.os.Bundle getExtras();
+    method public int getIconResourceId();
+    method public java.lang.String getId();
+    method public java.lang.CharSequence getName();
+    method public android.media.routing.MediaRouter.ServiceMetadata getServiceMetadata();
+    method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
+  }
+
+  public static final class MediaRouter.DestinationInfo.Builder {
+    ctor public MediaRouter.DestinationInfo.Builder(java.lang.String, android.media.routing.MediaRouter.ServiceMetadata, java.lang.CharSequence);
+    method public android.media.routing.MediaRouter.DestinationInfo build();
+    method public android.media.routing.MediaRouter.DestinationInfo.Builder setDescription(java.lang.CharSequence);
+    method public android.media.routing.MediaRouter.DestinationInfo.Builder setExtras(android.os.Bundle);
+    method public android.media.routing.MediaRouter.DestinationInfo.Builder setIconResourceId(int);
+  }
+
+  public static final class MediaRouter.DiscoveryRequest {
+    method public int getFlags();
+    method public java.util.List<android.media.routing.MediaRouteSelector> getSelectors();
+    method public void setFlags(int);
+    method public void setSelectors(java.util.List<android.media.routing.MediaRouteSelector>);
+  }
+
+  public static final class MediaRouter.RouteInfo {
+    method public android.media.routing.MediaRouter.DestinationInfo getDestination();
+    method public android.os.Bundle getExtras();
+    method public int getFeatures();
+    method public java.lang.String getId();
+    method public java.util.List<java.lang.String> getProtocols();
+    method public android.media.routing.MediaRouteSelector getSelector();
+  }
+
+  public static final class MediaRouter.RouteInfo.Builder {
+    ctor public MediaRouter.RouteInfo.Builder(java.lang.String, android.media.routing.MediaRouter.DestinationInfo, android.media.routing.MediaRouteSelector);
+    method public android.media.routing.MediaRouter.RouteInfo.Builder addProtocol(java.lang.Class<T>);
+    method public android.media.routing.MediaRouter.RouteInfo.Builder addProtocol(java.lang.String);
+    method public android.media.routing.MediaRouter.RouteInfo build();
+    method public android.media.routing.MediaRouter.RouteInfo.Builder setExtras(android.os.Bundle);
+    method public android.media.routing.MediaRouter.RouteInfo.Builder setFeatures(int);
+  }
+
+  public static abstract class MediaRouter.RoutingCallback extends android.media.routing.MediaRouter.StateCallback {
+    ctor public MediaRouter.RoutingCallback();
+    method public boolean onPrepareConnectionRequest(android.media.routing.MediaRouter.ConnectionRequest, android.media.routing.MediaRouter.DestinationInfo, java.util.List<android.media.routing.MediaRouter.RouteInfo>);
+    method public boolean onPrepareDiscoveryRequest(android.media.routing.MediaRouter.DiscoveryRequest, java.util.List<android.media.routing.MediaRouteSelector>);
+  }
+
+  public static final class MediaRouter.ServiceMetadata {
+    method public android.content.ComponentName getComponentName();
+    method public android.graphics.drawable.Drawable getIcon(android.content.pm.PackageManager);
+    method public java.lang.CharSequence getLabel(android.content.pm.PackageManager);
+    method public java.lang.String getPackageName();
+    method public android.content.pm.ServiceInfo getService();
+  }
+
+  public static abstract class MediaRouter.StateCallback {
+    ctor public MediaRouter.StateCallback();
+    method public void onConnected();
+    method public void onConnecting();
+    method public void onConnectionFailed(int, java.lang.CharSequence, android.os.Bundle);
+    method public void onConnectionStateChanged(int);
+    method public void onDestinationFound(android.media.routing.MediaRouter.DestinationInfo);
+    method public void onDestinationLost(android.media.routing.MediaRouter.DestinationInfo);
+    method public void onDisconnected();
+    method public void onDiscoveryFailed(int, java.lang.CharSequence, android.os.Bundle);
+    method public void onDiscoveryStarted();
+    method public void onDiscoveryStateChanged(int);
+    method public void onDiscoveryStopped();
+    method public void onReleased();
+    method public void onSelectedDestinationChanged(android.media.routing.MediaRouter.DestinationInfo);
+  }
+
+}
+
 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 void adjustVolumeBy(int, int);
+    method public android.media.routing.MediaRouter.Delegate createMediaRouterDelegate();
     method public boolean dispatchMediaButtonEvent(android.view.KeyEvent);
     method public static android.media.session.MediaController fromToken(android.media.session.MediaSession.Token);
     method public android.media.MediaMetadata getMetadata();
@@ -15931,6 +16169,7 @@
     method public void setActive(boolean);
     method public void setFlags(int);
     method public void setLaunchPendingIntent(android.app.PendingIntent);
+    method public void setMediaRouter(android.media.routing.MediaRouter);
     method public void setMetadata(android.media.MediaMetadata);
     method public void setPlaybackState(android.media.session.PlaybackState);
     method public void setPlaybackToLocal(int);
@@ -16005,6 +16244,7 @@
     field public static final android.os.Parcelable.Creator CREATOR;
     field public static final long PLAYBACK_POSITION_UNKNOWN = -1L; // 0xffffffffffffffffL
     field public static final int STATE_BUFFERING = 6; // 0x6
+    field public static final int STATE_CONNECTING = 8; // 0x8
     field public static final int STATE_ERROR = 7; // 0x7
     field public static final int STATE_FAST_FORWARDING = 4; // 0x4
     field public static final int STATE_NONE = 0; // 0x0
@@ -24659,6 +24899,7 @@
     field public static final java.lang.String ACTION_APPLICATION_SETTINGS = "android.settings.APPLICATION_SETTINGS";
     field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
     field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
+    field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
     field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
     field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
     field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java
index 4e91361..800b925 100644
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ b/cmds/media/src/com/android/commands/media/Media.java
@@ -25,7 +25,6 @@
 import android.media.session.MediaController;
 import android.media.session.MediaSessionInfo;
 import android.media.session.PlaybackState;
-import android.media.session.RouteInfo;
 import android.os.Bundle;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -181,11 +180,6 @@
         }
 
         @Override
-        public void onRouteChanged(RouteInfo route) {
-            System.out.println("onRouteChanged " + route);
-        }
-
-        @Override
         public void onPlaybackStateChanged(PlaybackState state) {
             System.out.println("onPlaybackStateChanged " + state);
         }
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index f1a2576..d20a5dc 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -3751,6 +3751,48 @@
     /**
      * @hide Implement to provide correct calling token.
      */
+    public void startActivityForResultAsUser(Intent intent, int requestCode, UserHandle user) {
+        startActivityForResultAsUser(intent, requestCode, null, user);
+    }
+
+    /**
+     * @hide Implement to provide correct calling token.
+     */
+    public void startActivityForResultAsUser(Intent intent, int requestCode,
+            @Nullable Bundle options, UserHandle user) {
+        if (options != null) {
+            mActivityTransitionState.startExitOutTransition(this, options);
+        }
+        if (mParent != null) {
+            throw new RuntimeException("Can't be called from a child");
+        }
+        Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(
+                this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode,
+                options, user);
+        if (ar != null) {
+            mMainThread.sendActivityResult(
+                mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData());
+        }
+        if (requestCode >= 0) {
+            // If this start is requesting a result, we can avoid making
+            // the activity visible until the result is received.  Setting
+            // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
+            // activity hidden during this time, to avoid flickering.
+            // This can only be done when a result is requested because
+            // that guarantees we will get information back when the
+            // activity is finished, no matter what happens to it.
+            mStartedActivity = true;
+        }
+
+        final View decor = mWindow != null ? mWindow.peekDecorView() : null;
+        if (decor != null) {
+            decor.cancelPendingInputEvents();
+        }
+    }
+
+    /**
+     * @hide Implement to provide correct calling token.
+     */
     public void startActivityAsUser(Intent intent, UserHandle user) {
         startActivityAsUser(intent, null, user);
     }
@@ -3760,7 +3802,7 @@
      */
     public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
         if (mParent != null) {
-            throw new RuntimeException("Called be called from a child");
+            throw new RuntimeException("Can't be called from a child");
         }
         Instrumentation.ActivityResult ar =
                 mInstrumentation.execStartActivity(
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index cf462cd..4c73e6a 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -133,7 +133,11 @@
  * <p>For more information about using cameras, read the
  * <a href="{@docRoot}guide/topics/media/camera.html">Camera</a> developer guide.</p>
  * </div>
+ *
+ * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+ *             applications.
  */
+@Deprecated
 public class Camera {
     private static final String TAG = "Camera";
 
@@ -247,7 +251,11 @@
 
     /**
      * Information about a camera
+     *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public static class CameraInfo {
         /**
          * The facing of the camera is opposite to that of the screen.
@@ -675,7 +683,11 @@
      * @see #setOneShotPreviewCallback(Camera.PreviewCallback)
      * @see #setPreviewCallbackWithBuffer(Camera.PreviewCallback)
      * @see #startPreview()
+     *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public interface PreviewCallback
     {
         /**
@@ -1175,7 +1187,10 @@
      * manifest element.</p>
      *
      * @see #autoFocus(AutoFocusCallback)
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public interface AutoFocusCallback
     {
         /**
@@ -1286,7 +1301,11 @@
      * Parameters#FOCUS_MODE_CONTINUOUS_VIDEO} and {@link
      * Parameters#FOCUS_MODE_CONTINUOUS_PICTURE}. Applications can show
      * autofocus animation based on this.</p>
+     *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public interface AutoFocusMoveCallback
     {
         /**
@@ -1314,7 +1333,11 @@
      * Callback interface used to signal the moment of actual image capture.
      *
      * @see #takePicture(ShutterCallback, PictureCallback, PictureCallback, PictureCallback)
+     *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public interface ShutterCallback
     {
         /**
@@ -1331,7 +1354,11 @@
      * Callback interface used to supply image data from a photo capture.
      *
      * @see #takePicture(ShutterCallback, PictureCallback, PictureCallback, PictureCallback)
+     *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public interface PictureCallback {
         /**
          * Called when image data is available after a picture is taken.
@@ -1538,7 +1565,11 @@
      *
      * @see #setZoomChangeListener(OnZoomChangeListener)
      * @see #startSmoothZoom(int)
+     *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public interface OnZoomChangeListener
     {
         /**
@@ -1568,7 +1599,10 @@
     /**
      * Callback interface for face detected in the preview frame.
      *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public interface FaceDetectionListener
     {
         /**
@@ -1652,7 +1686,10 @@
      * list of face objects for use in focusing and metering.</p>
      *
      * @see FaceDetectionListener
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public static class Face {
         /**
          * Create an empty face.
@@ -1766,7 +1803,11 @@
      * Callback interface for camera error notification.
      *
      * @see #setErrorCallback(ErrorCallback)
+     *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public interface ErrorCallback
     {
         /**
@@ -1864,7 +1905,10 @@
 
     /**
      * Image size (width and height dimensions).
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public class Size {
         /**
          * Sets the dimensions for pictures.
@@ -1931,7 +1975,11 @@
      * @see Parameters#setMeteringAreas(List)
      * @see Parameters#getMeteringAreas()
      * @see Parameters#getMaxNumMeteringAreas()
+     *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public static class Area {
         /**
          * Create an area with specified rectangle and weight.
@@ -2005,7 +2053,11 @@
      * calling {@link Camera.Parameters#setColorEffect(String)}. If the
      * camera does not support color effects,
      * {@link Camera.Parameters#getSupportedColorEffects()} will return null.
+     *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public class Parameters {
         // Parameter keys to communicate with the camera driver.
         private static final String KEY_PREVIEW_SIZE = "preview-size";
@@ -2960,7 +3012,6 @@
             case ImageFormat.YV12:      return PIXEL_FORMAT_YUV420P;
             case ImageFormat.RGB_565:   return PIXEL_FORMAT_RGB565;
             case ImageFormat.JPEG:      return PIXEL_FORMAT_JPEG;
-            case ImageFormat.BAYER_RGGB: return PIXEL_FORMAT_BAYER_RGGB;
             default:                    return null;
             }
         }
diff --git a/core/java/android/hardware/camera2/package.html b/core/java/android/hardware/camera2/package.html
index ef0d7bd..719c2f6 100644
--- a/core/java/android/hardware/camera2/package.html
+++ b/core/java/android/hardware/camera2/package.html
@@ -58,16 +58,16 @@
 <p>Generally, camera preview images are sent to {@link
 android.view.SurfaceView} or {@link android.view.TextureView} (via its
 {@link android.graphics.SurfaceTexture}). Capture of JPEG images or
-RAW buffers for {@link android.hardware.camera2.DngCreator} can be done
-with {@link android.media.ImageReader} with the
-{android.graphics.ImageFormat#JPEG} and
-{android.graphics.ImageFormat#RAW_SENSOR} formats.  Application-driven
+RAW buffers for {@link android.hardware.camera2.DngCreator} can be
+done with {@link android.media.ImageReader} with the {@link
+android.graphics.ImageFormat#JPEG} and {@link
+android.graphics.ImageFormat#RAW_SENSOR} formats.  Application-driven
 processing of camera data in RenderScript, OpenGL ES, or directly in
 managed or native code is best done through {@link
 android.renderscript.Allocation} with a YUV {@link
 android.renderscript.Type}, {@link android.graphics.SurfaceTexture},
-and {@link android.media.ImageReader} with a
-{android.graphics.ImageFormat#YUV_420_888} format, respectively.</p>
+and {@link android.media.ImageReader} with a {@link
+android.graphics.ImageFormat#YUV_420_888} format, respectively.</p>
 
 <p>The application then needs to construct a {@link
 android.hardware.camera2.CaptureRequest}, which defines all the
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 07397973..2687bcc 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -266,6 +266,21 @@
             "android.settings.WIFI_DISPLAY_SETTINGS";
 
     /**
+     * Activity Action: Show settings to allow configuration of
+     * {@link android.media.routing.MediaRouteService media route providers}.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CAST_SETTINGS =
+            "android.settings.CAST_SETTINGS";
+
+    /**
      * Activity Action: Show settings to allow configuration of date and time.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4725cfb..350660b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2149,7 +2149,7 @@
         android:description="@string/permdesc_bindTvInput"
         android:protectionLevel="signature|system" />
 
-    <!-- Must be required by a {@link android.media.routeprovider.RouteProviderService}
+    <!-- Must be required by a {@link android.media.routing.MediaRouteService}
          to ensure that only the system can interact with it.
          @hide -->
     <permission android:name="android.permission.BIND_ROUTE_PROVIDER"
@@ -2720,6 +2720,13 @@
         android:description="@string/permdesc_bindConditionProviderService"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by a {@link android.media.routing.MediaRouteService},
+         to ensure that only the system can bind to it. -->
+    <permission android:name="android.permission.BIND_MEDIA_ROUTE_SERVICE"
+        android:label="@string/permlab_bindMediaRouteService"
+        android:description="@string/permdesc_bindMediaRouteService"
+        android:protectionLevel="signature" />
+
     <!-- Must be required by an {@link android.service.dreams.DreamService},
          to ensure that only the system can bind to it. -->
     <permission android:name="android.permission.BIND_DREAM_SERVICE"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 5d57262..193d3c0 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2124,6 +2124,11 @@
     <string name="permdesc_bindConditionProviderService">Allows the holder to bind to the top-level interface of a condition provider 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_bindMediaRouteService">bind to a media route 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_bindMediaRouteService">Allows the holder to bind to the top-level interface of a media route 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_bindDreamService">bind to a dream 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_bindDreamService">Allows the holder to bind to the top-level interface of a dream service. Should never be needed for normal apps.</string>
diff --git a/docs/html/guide/topics/media/camera.jd b/docs/html/guide/topics/media/camera.jd
index 56ef624..8b79b23 100644
--- a/docs/html/guide/topics/media/camera.jd
+++ b/docs/html/guide/topics/media/camera.jd
@@ -86,14 +86,17 @@
 
 <h2 id="basics">The Basics</h2>
 <p>The Android framework supports capturing images and video through the
-{@link android.hardware.Camera} API or camera {@link android.content.Intent}. Here are the relevant
+{@link android.hardware.camera2} API or camera {@link android.content.Intent}. Here are the relevant
 classes:</p>
 
 <dl>
-  <dt>{@link android.hardware.Camera}</dt>
-  <dd>This class is the primary API for controlling device cameras. This class is used to take
+  <dt>{@link android.hardware.camera2}</dt>
+  <dd>This package is the primary API for controlling device cameras. It can be used to take
 pictures or videos when you are building a camera application.</dd>
 
+  <dt>{@link android.hardware.Camera}</dt>
+  <dd>This class is the older deprecated API for controlling device cameras.</dd>
+
   <dt>{@link android.view.SurfaceView}</dt>
   <dd>This class is used to present a live camera preview to the user.</dd>
 
@@ -354,6 +357,10 @@
 code than <a href="#intents">using an intent</a>, but it can provide a more compelling experience
 for your users.</p>
 
+<p><strong> Note: The following guide is for the older, deprecated {@link android.hardware.Camera}
+API. For new or advanced camera applications, the newer {@link android.hardware.camera2} API is
+recommended.</strong></p>
+
 <p>The general steps for creating a custom camera interface for your application are as follows:</p>
 
 <ul>
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 28fd7ba..ab4258d 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -54,9 +54,12 @@
      * cr_offset = y_size
      * cb_offset = y_size + c_size</pre>
      *
-     * <p>This format is guaranteed to be supported for camera preview images since
-     * API level 12; for earlier API versions, check
-     * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
+     * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is
+     * recommended for YUV output instead.</p>
+     *
+     * <p>For the older camera API, this format is guaranteed to be supported for
+     * {@link android.hardware.Camera} preview images since API level 12; for earlier API versions,
+     * check {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
      *
      * <p>Note that for camera preview callback use (see
      * {@link android.hardware.Camera#setPreviewCallback}), the
@@ -133,29 +136,47 @@
     public static final int Y16 = 0x20363159;
 
     /**
-     * YCbCr format, used for video. Whether this format is supported by the
-     * camera hardware can be determined by
-     * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
+     * YCbCr format, used for video.
+     *
+     * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is
+     * recommended for YUV output instead.</p>
+     *
+     * <p>Whether this format is supported by the old camera API can be determined by
+     * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.</p>
+     *
      */
     public static final int NV16 = 0x10;
 
     /**
-     * YCrCb format used for images, which uses the NV21 encoding format. This
-     * is the default format for camera preview images, when not otherwise set
-     * with {@link android.hardware.Camera.Parameters#setPreviewFormat(int)}.
+     * YCrCb format used for images, which uses the NV21 encoding format.
+     *
+     * <p>This is the default format
+     * for {@link android.hardware.Camera} preview images, when not otherwise set with
+     * {@link android.hardware.Camera.Parameters#setPreviewFormat(int)}.</p>
+     *
+     * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is
+     * recommended for YUV output instead.</p>
      */
     public static final int NV21 = 0x11;
 
     /**
      * YCbCr format used for images, which uses YUYV (YUY2) encoding format.
-     * This is an alternative format for camera preview images. Whether this
-     * format is supported by the camera hardware can be determined by
-     * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
+     *
+     * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is
+     * recommended for YUV output instead.</p>
+     *
+     * <p>This is an alternative format for {@link android.hardware.Camera} preview images. Whether
+     * this format is supported by the camera hardware can be determined by
+     * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.</p>
      */
     public static final int YUY2 = 0x14;
 
     /**
-     * Encoded formats. These are not necessarily supported by the hardware.
+     * Compressed JPEG format.
+     *
+     * <p>This format is always supported as an output format for the
+     * {@link android.hardware.camera2} API, and as a picture format for the older
+     * {@link android.hardware.Camera} API</p>
      */
     public static final int JPEG = 0x100;
 
@@ -332,16 +353,6 @@
     public static final int RAW10 = 0x25;
 
     /**
-     * Raw bayer format used for images, which is 10 bit precision samples
-     * stored in 16 bit words. The filter pattern is RGGB. Whether this format
-     * is supported by the camera hardware can be determined by
-     * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
-     *
-     * @hide
-     */
-    public static final int BAYER_RGGB = 0x200;
-
-    /**
      * Use this function to retrieve the number of bits per pixel of an
      * ImageFormat.
      *
@@ -369,8 +380,6 @@
                 return 12;
             case RAW_SENSOR:
                 return 16;
-            case BAYER_RGGB:
-                return 16;
             case RAW10:
                 return 10;
         }
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 17795f3..f52c661 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -27,14 +27,18 @@
 /**
  * Captures frames from an image stream as an OpenGL ES texture.
  *
- * <p>The image stream may come from either camera preview or video decode.  A SurfaceTexture
- * may be used in place of a SurfaceHolder when specifying the output destination of a
- * {@link android.hardware.Camera} or {@link android.media.MediaPlayer}
- * object.  Doing so will cause all the frames from the image stream to be sent to the
- * SurfaceTexture object rather than to the device's display.  When {@link #updateTexImage} is
- * called, the contents of the texture object specified when the SurfaceTexture was created are
- * updated to contain the most recent image from the image stream.  This may cause some frames of
- * the stream to be skipped.
+ * <p>The image stream may come from either camera preview or video decode. A
+ * {@link android.view.Surface} created from a SurfaceTexture can be used as an output
+ * destination for the {@link android.hardware.camera2}, {@link android.media.MediaCodec},
+ * {@link android.media.MediaPlayer}, and {@link android.renderscript.Allocation} APIs.
+ * When {@link #updateTexImage} is called, the contents of the texture object specified
+ * when the SurfaceTexture was created are updated to contain the most recent image from the image
+ * stream.  This may cause some frames of the stream to be skipped.
+ *
+ * <p>A SurfaceTexture may also be used in place of a SurfaceHolder when specifying the output
+ * destination of the older {@link android.hardware.Camera} API. Doing so will cause all the
+ * frames from the image stream to be sent to the SurfaceTexture object rather than to the device's
+ * display.
  *
  * <p>When sampling from the texture one should first transform the texture coordinates using the
  * matrix queried via {@link #getTransformMatrix(float[])}.  The transform matrix may change each
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index eb6bf2c..c65961d 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2912,6 +2912,20 @@
     }
 
     /**
+     * Notify audio manager about volume controller visibility changes.
+     * Currently limited to SystemUI.
+     *
+     * @hide
+     */
+    public void notifyVolumeControllerVisible(IVolumeController controller, boolean visible) {
+        try {
+            getService().notifyVolumeControllerVisible(controller, visible);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error notifying about volume controller visibility", e);
+        }
+    }
+
+    /**
      * Only useful for volume controllers.
      * @hide
      */
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index b08d631..ab63145 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -61,6 +61,7 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.Vibrator;
@@ -810,6 +811,9 @@
 
         // Restore the default media button receiver from the system settings
         mMediaFocusControl.restoreMediaButtonReceiver();
+
+        // Load settings for the volume controller
+        mVolumeController.loadSettings(cr);
     }
 
     private int rescaleIndex(int index, int srcStream, int dstStream) {
@@ -851,14 +855,23 @@
         } else {
             streamType = getActiveStreamType(suggestedStreamType);
         }
+        final int resolvedStream = mStreamVolumeAlias[streamType];
 
         // Play sounds on STREAM_RING and STREAM_REMOTE_MUSIC only.
         if ((streamType != STREAM_REMOTE_MUSIC) &&
                 (flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&
-                (mStreamVolumeAlias[streamType] != AudioSystem.STREAM_RING)) {
+                resolvedStream != AudioSystem.STREAM_RING) {
             flags &= ~AudioManager.FLAG_PLAY_SOUND;
         }
 
+        // For notifications/ring, show the ui before making any adjustments
+        if (mVolumeController.suppressAdjustment(resolvedStream, flags)) {
+            direction = 0;
+            flags &= ~AudioManager.FLAG_PLAY_SOUND;
+            flags &= ~AudioManager.FLAG_VIBRATE;
+            if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");
+        }
+
         if (streamType == STREAM_REMOTE_MUSIC) {
             // TODO bounce it to MediaSessionService to find an appropriate
             // session
@@ -4955,15 +4968,65 @@
             }
         }
         mVolumeController.setController(controller);
+        if (DEBUG_VOL) Log.d(TAG, "Volume controller: " + mVolumeController);
+    }
+
+    @Override
+    public void notifyVolumeControllerVisible(final IVolumeController controller, boolean visible) {
+        enforceSelfOrSystemUI("notify about volume controller visibility");
+
+        // return early if the controller is not current
+        if (!mVolumeController.isSameBinder(controller)) {
+            return;
+        }
+
+        mVolumeController.setVisible(visible);
+        if (DEBUG_VOL) Log.d(TAG, "Volume controller visible: " + visible);
     }
 
     public static class VolumeController {
         private static final String TAG = "VolumeController";
 
         private IVolumeController mController;
+        private boolean mVisible;
+        private long mNextLongPress;
+        private int mLongPressTimeout;
 
         public void setController(IVolumeController controller) {
             mController = controller;
+            mVisible = false;
+        }
+
+        public void loadSettings(ContentResolver cr) {
+            mLongPressTimeout = Settings.Secure.getIntForUser(cr,
+                    Settings.Secure.LONG_PRESS_TIMEOUT, 500, UserHandle.USER_CURRENT);
+        }
+
+        public boolean suppressAdjustment(int resolvedStream, int flags) {
+            boolean suppress = false;
+            if (resolvedStream == AudioSystem.STREAM_RING && mController != null) {
+                final long now = SystemClock.uptimeMillis();
+                if ((flags & AudioManager.FLAG_SHOW_UI) != 0 && !mVisible) {
+                    // ui will become visible
+                    if (mNextLongPress < now) {
+                        mNextLongPress = now + mLongPressTimeout;
+                    }
+                    suppress = true;
+                } else if (mNextLongPress > 0) {  // in a long-press
+                    if (now > mNextLongPress) {
+                        // long press triggered, no more suppression
+                        mNextLongPress = 0;
+                    } else {
+                        // keep suppressing until the long press triggers
+                        suppress = true;
+                    }
+                }
+            }
+            return suppress;
+        }
+
+        public void setVisible(boolean visible) {
+            mVisible = visible;
         }
 
         public boolean isSameBinder(IVolumeController controller) {
@@ -4980,7 +5043,7 @@
 
         @Override
         public String toString() {
-            return "VolumeController(" + asBinder() + ")";
+            return "VolumeController(" + asBinder() + ",mVisible=" + mVisible + ")";
         }
 
         public void postDisplaySafeVolumeWarning(int flags) {
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index e112a65..4f7021e 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -195,6 +195,8 @@
 
     void setVolumeController(in IVolumeController controller);
 
+    void notifyVolumeControllerVisible(in IVolumeController controller, boolean visible);
+
     boolean isStreamAffectedByRingerMode(int streamType);
 
     void disableSafeMediaVolume();
diff --git a/media/java/android/media/MediaActionSound.java b/media/java/android/media/MediaActionSound.java
index 7a520fe..2f4d136 100644
--- a/media/java/android/media/MediaActionSound.java
+++ b/media/java/android/media/MediaActionSound.java
@@ -24,13 +24,15 @@
  * <p>A class for producing sounds that match those produced by various actions
  * taken by the media and camera APIs.  </p>
  *
- * <p>Use this class to play an appropriate camera operation sound when
- * implementing a custom still or video recording mechanism (through the Camera
- * preview callbacks with {@link android.hardware.Camera#setPreviewCallback
- * Camera.setPreviewCallback}, or through GPU processing with {@link
- * android.hardware.Camera#setPreviewTexture Camera.setPreviewTexture}, for
- * example), or when implementing some other camera-like function in your
- * application.</p>
+ * <p>This class is recommended for use with the {@link android.hardware.camera2} API, since the
+ * camera2 API does not play any sounds on its own for any capture or video recording actions.</p>
+ *
+ * <p>With the older {@link android.hardware.Camera} API, use this class to play an appropriate
+ * camera operation sound when implementing a custom still or video recording mechanism (through the
+ * Camera preview callbacks with
+ * {@link android.hardware.Camera#setPreviewCallback Camera.setPreviewCallback}, or through GPU
+ * processing with {@link android.hardware.Camera#setPreviewTexture Camera.setPreviewTexture}, for
+ * example), or when implementing some other camera-like function in your application.</p>
  *
  * <p>There is no need to play sounds when using
  * {@link android.hardware.Camera#takePicture Camera.takePicture} or
@@ -136,10 +138,12 @@
      * {@link android.media.MediaRecorder#start MediaRecorder.start}, and
      * {@link android.media.MediaRecorder#stop MediaRecorder.stop}.</p>
      *
-     * <p>Using this method makes it easy to match the default device sounds
-     * when recording or capturing data through the preview callbacks, or when
-     * implementing custom camera-like features in your
-     * application.</p>
+     * <p>With the {@link android.hardware.camera2 camera2} API, this method can be used to play
+     * standard camera operation sounds with the appropriate system behavior for such sounds.</p>
+
+     * <p>With the older {@link android.hardware.Camera} API, using this method makes it easy to
+     * match the default device sounds when recording or capturing data through the preview
+     * callbacks, or when implementing custom camera-like features in your application.</p>
      *
      * <p>If the sound has not been loaded by {@link #load} before calling play,
      * play will load the sound at the cost of some additional latency before
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index f8c0494..3917bf1 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -114,23 +114,26 @@
     }
 
     /**
-     * Sets a Camera to use for recording. Use this function to switch
-     * quickly between preview and capture mode without a teardown of
-     * the camera object. {@link android.hardware.Camera#unlock()} should be
-     * called before this. Must call before prepare().
+     * Sets a {@link android.hardware.Camera} to use for recording.
+     *
+     * <p>Use this function to switch quickly between preview and capture mode without a teardown of
+     * the camera object. {@link android.hardware.Camera#unlock()} should be called before
+     * this. Must call before {@link #prepare}.</p>
      *
      * @param c the Camera to use for recording
+     * @deprecated Use {@link #getSurface} and the {@link android.hardware.camera2} API instead.
      */
+    @Deprecated
     public native void setCamera(Camera c);
 
     /**
      * Gets the surface to record from when using SURFACE video source.
-     * <p>
-     * Should only be called after prepare(). Frames rendered before start()
-     * will be discarded.
-     * </p>
-     * @throws IllegalStateException if it is called before prepare(), after
-     * stop() or is called when VideoSource is not set to SURFACE.
+     *
+     * <p> May only be called after {@link #prepare}. Frames rendered to the Surface before
+     * {@link #start} will be discarded.</p>
+     *
+     * @throws IllegalStateException if it is called before {@link #prepare}, after
+     * {@link #stop}, or is called when VideoSource is not set to SURFACE.
      * @see android.media.MediaRecorder.VideoSource
      */
     public native Surface getSurface();
@@ -239,7 +242,7 @@
         public static final int DEFAULT = 0;
         /** Camera video source
          * <p>
-         * Using android.hardware.Camera as video source.
+         * Using the {@link android.hardware.Camera} API as video source.
          * </p>
          */
         public static final int CAMERA = 1;
@@ -248,7 +251,7 @@
          * Using a Surface as video source.
          * </p><p>
          * This flag must be used when recording from an
-         * android.hardware.camera2.CameraDevice source.
+         * {@link android.hardware.camera2} API source.
          * </p><p>
          * When using this video source type, use {@link MediaRecorder#getSurface()}
          * to retrieve the surface created by MediaRecorder.
diff --git a/media/java/android/media/routeprovider/IRouteProvider.aidl b/media/java/android/media/routeprovider/IRouteProvider.aidl
deleted file mode 100644
index c36f6a7..0000000
--- a/media/java/android/media/routeprovider/IRouteProvider.aidl
+++ /dev/null
@@ -1,36 +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.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
deleted file mode 100644
index 9185347..0000000
--- a/media/java/android/media/routeprovider/IRouteProviderCallback.aidl
+++ /dev/null
@@ -1,32 +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.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
deleted file mode 100644
index 43692c1..0000000
--- a/media/java/android/media/routeprovider/RouteConnection.java
+++ /dev/null
@@ -1,165 +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.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.
- * @hide
- */
-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
deleted file mode 100644
index e7f8bbf..0000000
--- a/media/java/android/media/routeprovider/RouteInterfaceHandler.java
+++ /dev/null
@@ -1,246 +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.routeprovider;
-
-import android.media.session.Route;
-import android.media.session.MediaSession;
-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 MediaSession} 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.
- * @hide
- */
-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 MediaSession} 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 MediaSession
-         */
-        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
deleted file mode 100644
index f2c40d2..0000000
--- a/media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java
+++ /dev/null
@@ -1,222 +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.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.
- * @hide
- */
-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
deleted file mode 100644
index a6ef0bb..0000000
--- a/media/java/android/media/routeprovider/RouteProviderService.java
+++ /dev/null
@@ -1,228 +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.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>
- * @hide
- */
-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/routeprovider/RouteRequest.aidl b/media/java/android/media/routeprovider/RouteRequest.aidl
deleted file mode 100644
index 7bc5722..0000000
--- a/media/java/android/media/routeprovider/RouteRequest.aidl
+++ /dev/null
@@ -1,18 +0,0 @@
-/* Copyright 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;
-
-parcelable RouteRequest;
diff --git a/media/java/android/media/routeprovider/RouteRequest.java b/media/java/android/media/routeprovider/RouteRequest.java
deleted file mode 100644
index 2ba75de..0000000
--- a/media/java/android/media/routeprovider/RouteRequest.java
+++ /dev/null
@@ -1,110 +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.routeprovider;
-
-import android.media.session.RouteOptions;
-import android.media.session.MediaSessionInfo;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.io.PrintWriter;
-
-/**
- * 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.
- * @hide
- */
-public final class RouteRequest implements Parcelable {
-    private final MediaSessionInfo mSessionInfo;
-    private final RouteOptions mOptions;
-    private final boolean mActive;
-
-    /**
-     * @hide
-     */
-    public RouteRequest(MediaSessionInfo info, RouteOptions connRequest,
-            boolean active) {
-        mSessionInfo = info;
-        mOptions = connRequest;
-        mActive = active;
-    }
-
-    private RouteRequest(Parcel in) {
-        mSessionInfo = MediaSessionInfo.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 MediaSessionInfo 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 String toString() {
-        StringBuilder bob = new StringBuilder();
-        bob.append("RouteRequest {");
-        bob.append("active=").append(mActive);
-        bob.append(", info=").append(mSessionInfo.toString());
-        bob.append(", options=").append(mOptions.toString());
-        bob.append("}");
-        return bob.toString();
-    }
-
-    @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/routing/IMediaRouteClientCallback.aidl b/media/java/android/media/routing/IMediaRouteClientCallback.aidl
new file mode 100644
index 0000000..d90ea3b
--- /dev/null
+++ b/media/java/android/media/routing/IMediaRouteClientCallback.aidl
@@ -0,0 +1,41 @@
+/* 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.routing;
+
+import android.media.routing.MediaRouteSelector;
+import android.media.routing.ParcelableConnectionInfo;
+import android.media.routing.ParcelableDestinationInfo;
+import android.media.routing.ParcelableRouteInfo;
+import android.os.IBinder;
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface IMediaRouteClientCallback {
+    void onDestinationFound(int seq, in ParcelableDestinationInfo destination,
+            in ParcelableRouteInfo[] routes);
+
+    void onDestinationLost(int seq, String id);
+
+    void onDiscoveryFailed(int seq, int error, in CharSequence message, in Bundle extras);
+
+    void onConnected(int seq, in ParcelableConnectionInfo connection);
+
+    void onDisconnected(int seq);
+
+    void onConnectionFailed(int seq, int error, in CharSequence message, in Bundle extras);
+}
diff --git a/media/java/android/media/routing/IMediaRouteService.aidl b/media/java/android/media/routing/IMediaRouteService.aidl
new file mode 100644
index 0000000..493ab6d
--- /dev/null
+++ b/media/java/android/media/routing/IMediaRouteService.aidl
@@ -0,0 +1,46 @@
+/* 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.routing;
+
+import android.media.routing.IMediaRouteClientCallback;
+import android.media.routing.MediaRouteSelector;
+import android.os.Bundle;
+
+/**
+ * Interface to an app's MediaRouteService.
+ * @hide
+ */
+oneway interface IMediaRouteService {
+    void registerClient(int clientUid, String clientPackageName,
+            in IMediaRouteClientCallback callback);
+
+    void unregisterClient(in IMediaRouteClientCallback callback);
+
+    void startDiscovery(in IMediaRouteClientCallback callback, int seq,
+            in List<MediaRouteSelector> selectors, int flags);
+
+    void stopDiscovery(in IMediaRouteClientCallback callback);
+
+    void connect(in IMediaRouteClientCallback callback, int seq,
+            String destinationId, String routeId, int flags, in Bundle extras);
+
+    void disconnect(in IMediaRouteClientCallback callback);
+
+    void pauseStream(in IMediaRouteClientCallback callback);
+
+    void resumeStream(in IMediaRouteClientCallback callback);
+}
+
diff --git a/media/java/android/media/routeprovider/IRouteConnection.aidl b/media/java/android/media/routing/IMediaRouter.aidl
similarity index 66%
copy from media/java/android/media/routeprovider/IRouteConnection.aidl
copy to media/java/android/media/routing/IMediaRouter.aidl
index 15c8039..0abb258 100644
--- a/media/java/android/media/routeprovider/IRouteConnection.aidl
+++ b/media/java/android/media/routing/IMediaRouter.aidl
@@ -13,16 +13,10 @@
  * limitations under the License.
  */
 
-package android.media.routeprovider;
+package android.media.routing;
 
-import android.media.session.RouteCommand;
-import android.os.ResultReceiver;
+/** @hide */
+interface IMediaRouter {
 
-/**
- * Interface for a specific connected route.
- * @hide
- */
-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/IRouteConnection.aidl b/media/java/android/media/routing/IMediaRouterDelegate.aidl
similarity index 66%
copy from media/java/android/media/routeprovider/IRouteConnection.aidl
copy to media/java/android/media/routing/IMediaRouterDelegate.aidl
index 15c8039..35f84c8 100644
--- a/media/java/android/media/routeprovider/IRouteConnection.aidl
+++ b/media/java/android/media/routing/IMediaRouterDelegate.aidl
@@ -13,16 +13,10 @@
  * limitations under the License.
  */
 
-package android.media.routeprovider;
+package android.media.routing;
 
-import android.media.session.RouteCommand;
-import android.os.ResultReceiver;
+/** @hide */
+interface IMediaRouterDelegate {
 
-/**
- * Interface for a specific connected route.
- * @hide
- */
-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/IRouteConnection.aidl b/media/java/android/media/routing/IMediaRouterRoutingCallback.aidl
similarity index 66%
rename from media/java/android/media/routeprovider/IRouteConnection.aidl
rename to media/java/android/media/routing/IMediaRouterRoutingCallback.aidl
index 15c8039..173ae55 100644
--- a/media/java/android/media/routeprovider/IRouteConnection.aidl
+++ b/media/java/android/media/routing/IMediaRouterRoutingCallback.aidl
@@ -13,16 +13,10 @@
  * limitations under the License.
  */
 
-package android.media.routeprovider;
+package android.media.routing;
 
-import android.media.session.RouteCommand;
-import android.os.ResultReceiver;
+/** @hide */
+interface IMediaRouterRoutingCallback {
 
-/**
- * Interface for a specific connected route.
- * @hide
- */
-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/IRouteConnection.aidl b/media/java/android/media/routing/IMediaRouterStateCallback.aidl
similarity index 66%
copy from media/java/android/media/routeprovider/IRouteConnection.aidl
copy to media/java/android/media/routing/IMediaRouterStateCallback.aidl
index 15c8039..0299904 100644
--- a/media/java/android/media/routeprovider/IRouteConnection.aidl
+++ b/media/java/android/media/routing/IMediaRouterStateCallback.aidl
@@ -13,16 +13,10 @@
  * limitations under the License.
  */
 
-package android.media.routeprovider;
+package android.media.routing;
 
-import android.media.session.RouteCommand;
-import android.os.ResultReceiver;
+/** @hide */
+interface IMediaRouterStateCallback {
 
-/**
- * Interface for a specific connected route.
- * @hide
- */
-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/session/RouteCommand.aidl b/media/java/android/media/routing/MediaRouteSelector.aidl
similarity index 90%
copy from media/java/android/media/session/RouteCommand.aidl
copy to media/java/android/media/routing/MediaRouteSelector.aidl
index 725b308..37bfa4a 100644
--- a/media/java/android/media/session/RouteCommand.aidl
+++ b/media/java/android/media/routing/MediaRouteSelector.aidl
@@ -13,6 +13,6 @@
 ** limitations under the License.
 */
 
-package android.media.session;
+package android.media.routing;
 
-parcelable RouteCommand;
+parcelable MediaRouteSelector;
diff --git a/media/java/android/media/routing/MediaRouteSelector.java b/media/java/android/media/routing/MediaRouteSelector.java
new file mode 100644
index 0000000..26a9b1c
--- /dev/null
+++ b/media/java/android/media/routing/MediaRouteSelector.java
@@ -0,0 +1,357 @@
+/*
+ * 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.routing;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.routing.MediaRouter.RouteFeatures;
+import android.os.Bundle;
+import android.os.IInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A media route selector consists of a set of constraints that are used to select
+ * the routes to which an application would like to connect.  The constraints consist
+ * of a set of required or optional features and protocols.  The constraints may also
+ * require the use of a specific media route service package or additional characteristics
+ * that are described by a bundle of extra parameters.
+ * <p>
+ * The application will typically create several different selectors that express
+ * various combinations of characteristics that it would like to use together when
+ * it connects to a destination media device.  For each destination that is discovered,
+ * media route services will publish some number of routes and include information
+ * about which selector each route matches.  The application will then choose among
+ * these routes to determine which best satisfies its desired purpose and connect to it.
+ * </p>
+ */
+public final class MediaRouteSelector implements Parcelable {
+    private final int mRequiredFeatures;
+    private final int mOptionalFeatures;
+    private final List<String> mRequiredProtocols;
+    private final List<String> mOptionalProtocols;
+    private final String mServicePackageName;
+    private final Bundle mExtras;
+
+    MediaRouteSelector(int requiredFeatures, int optionalFeatures,
+            List<String> requiredProtocols, List<String> optionalProtocols,
+            String servicePackageName, Bundle extras) {
+        mRequiredFeatures = requiredFeatures;
+        mOptionalFeatures = optionalFeatures;
+        mRequiredProtocols = requiredProtocols;
+        mOptionalProtocols = optionalProtocols;
+        mServicePackageName = servicePackageName;
+        mExtras = extras;
+    }
+
+    /**
+     * Gets the set of required route features.
+     *
+     * @return A set of required route feature flags.
+     */
+    public @RouteFeatures int getRequiredFeatures() {
+        return mRequiredFeatures;
+    }
+
+    /**
+     * Gets the set of optional route features.
+     *
+     * @return A set of optional route feature flags.
+     */
+    public @RouteFeatures int getOptionalFeatures() {
+        return mOptionalFeatures;
+    }
+
+    /**
+     * Gets the list of route protocols that a route must support in order to be selected.
+     * <p>
+     * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+     * for more information.
+     * </p>
+     *
+     * @return The list of fully qualified route protocol names.
+     */
+    public @NonNull List<String> getRequiredProtocols() {
+        return mRequiredProtocols;
+    }
+
+    /**
+     * Gets the list of optional route protocols that a client may use if they are available.
+     * <p>
+     * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+     * for more information.
+     * </p>
+     *
+     * @return The list of optional fully qualified route protocol names.
+     */
+    public @NonNull List<String> getOptionalProtocols() {
+        return mOptionalProtocols;
+    }
+
+    /**
+     * Returns true if the selector includes a required or optional request for
+     * the specified protocol using its fully qualified class name.
+     * <p>
+     * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+     * for more information.
+     * </p>
+     *
+     * @param clazz The protocol class.
+     * @return True if the protocol was requested.
+     */
+    public boolean containsProtocol(@NonNull Class<?> clazz) {
+        return containsProtocol(clazz.getName());
+    }
+
+    /**
+     * Returns true if the selector includes a required or optional request for
+     * the specified protocol.
+     * <p>
+     * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+     * for more information.
+     * </p>
+     *
+     * @param name The name of the protocol.
+     * @return True if the protocol was requested.
+     */
+    public boolean containsProtocol(@NonNull String name) {
+        return mRequiredProtocols.contains(name)
+                || mOptionalProtocols.contains(name);
+    }
+
+    /**
+     * Gets the package name of a specific media route service that this route selector
+     * requires.
+     *
+     * @return The required media route service package name, or null if none.
+     */
+    public @Nullable String getServicePackageName() {
+        return mServicePackageName;
+    }
+
+    /**
+     * Gets optional extras that may be used to select or configure routes for a
+     * particular purpose.  Some extras may be used by media route services to apply
+     * additional constraints or parameters for the routes to be discovered.
+     *
+     * @return The optional extras, or null if none.
+     */
+    public @Nullable Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public String toString() {
+        return "MediaRouteSelector{ "
+                + ", requiredFeatures=0x" + Integer.toHexString(mRequiredFeatures)
+                + ", optionalFeatures=0x" + Integer.toHexString(mOptionalFeatures)
+                + ", requiredProtocols=" + mRequiredProtocols
+                + ", optionalProtocols=" + mOptionalProtocols
+                + ", servicePackageName=" + mServicePackageName
+                + ", extras=" + mExtras + " }";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mRequiredFeatures);
+        dest.writeInt(mOptionalFeatures);
+        dest.writeStringList(mRequiredProtocols);
+        dest.writeStringList(mOptionalProtocols);
+        dest.writeString(mServicePackageName);
+        dest.writeBundle(mExtras);
+    }
+
+    public static final Parcelable.Creator<MediaRouteSelector> CREATOR =
+            new Parcelable.Creator<MediaRouteSelector>() {
+        @Override
+        public MediaRouteSelector createFromParcel(Parcel source) {
+            int requiredFeatures = source.readInt();
+            int optionalFeatures = source.readInt();
+            ArrayList<String> requiredProtocols = new ArrayList<String>();
+            ArrayList<String> optionalProtocols = new ArrayList<String>();
+            source.readStringList(requiredProtocols);
+            source.readStringList(optionalProtocols);
+            return new MediaRouteSelector(requiredFeatures, optionalFeatures,
+                    requiredProtocols, optionalProtocols,
+                    source.readString(), source.readBundle());
+        }
+
+        @Override
+        public MediaRouteSelector[] newArray(int size) {
+            return new MediaRouteSelector[size];
+        }
+    };
+
+    /**
+     * Builder for {@link MediaRouteSelector} objects.
+     */
+    public static final class Builder {
+        private int mRequiredFeatures;
+        private int mOptionalFeatures;
+        private final ArrayList<String> mRequiredProtocols = new ArrayList<String>();
+        private final ArrayList<String> mOptionalProtocols = new ArrayList<String>();
+        private String mServicePackageName;
+        private Bundle mExtras;
+
+        /**
+         * Creates an initially empty selector builder.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Sets the set of required route features.
+         *
+         * @param features A set of required route feature flags.
+         */
+        public @NonNull Builder setRequiredFeatures(@RouteFeatures int features) {
+            mRequiredFeatures = features;
+            return this;
+        }
+
+        /**
+         * Sets the set of optional route features.
+         *
+         * @param features A set of optional route feature flags.
+         */
+        public @NonNull Builder setOptionalFeatures(@RouteFeatures int features) {
+            mOptionalFeatures = features;
+            return this;
+        }
+
+        /**
+         * Adds a route protocol that a route must support in order to be selected
+         * using its fully qualified class name.
+         * <p>
+         * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+         * for more information.
+         * </p>
+         *
+         * @param clazz The protocol class.
+         * @return this
+         */
+        public @NonNull Builder addRequiredProtocol(@NonNull Class<?> clazz) {
+            if (clazz == null) {
+                throw new IllegalArgumentException("clazz must not be null");
+            }
+            return addRequiredProtocol(clazz.getName());
+        }
+
+        /**
+         * Adds a route protocol that a route must support in order to be selected.
+         * <p>
+         * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+         * for more information.
+         * </p>
+         *
+         * @param name The fully qualified name of the required protocol.
+         * @return this
+         */
+        public @NonNull Builder addRequiredProtocol(@NonNull String name) {
+            if (TextUtils.isEmpty(name)) {
+                throw new IllegalArgumentException("name must not be null or empty");
+            }
+            mRequiredProtocols.add(name);
+            return this;
+        }
+
+        /**
+         * Adds an optional route protocol that a client may use if available
+         * using its fully qualified class name.
+         * <p>
+         * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+         * for more information.
+         * </p>
+         *
+         * @param clazz The protocol class.
+         * @return this
+         */
+        public @NonNull Builder addOptionalProtocol(@NonNull Class<?> clazz) {
+            if (clazz == null) {
+                throw new IllegalArgumentException("clazz must not be null");
+            }
+            return addOptionalProtocol(clazz.getName());
+        }
+
+        /**
+         * Adds an optional route protocol that a client may use if available.
+         * <p>
+         * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+         * for more information.
+         * </p>
+         *
+         * @param name The fully qualified name of the optional protocol.
+         * @return this
+         */
+        public @NonNull Builder addOptionalProtocol(@NonNull String name) {
+            if (TextUtils.isEmpty(name)) {
+                throw new IllegalArgumentException("name must not be null or empty");
+            }
+            mOptionalProtocols.add(name);
+            return this;
+        }
+
+        /**
+         * Sets the package name of the media route service to which this selector
+         * appertains.
+         * <p>
+         * If a package name is specified here then this selector will only be
+         * passed to media route services from that package.  This has the effect
+         * of restricting the set of matching routes to just those that are offered
+         * by that package.
+         * </p>
+         *
+         * @param packageName The required service package name, or null if none.
+         * @return this
+         */
+        public @NonNull Builder setServicePackageName(@Nullable String packageName) {
+            mServicePackageName = packageName;
+            return this;
+        }
+
+        /**
+         * Sets optional extras that may be used to select or configure routes for a
+         * particular purpose.  Some extras may be used by route services to specify
+         * additional constraints or parameters for the routes to be discovered.
+         *
+         * @param extras The optional extras, or null if none.
+         * @return this
+         */
+        public @NonNull Builder setExtras(@Nullable Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Builds the {@link MediaRouteSelector} object.
+         *
+         * @return The new media route selector instance.
+         */
+        public @NonNull MediaRouteSelector build() {
+            return new MediaRouteSelector(mRequiredFeatures, mOptionalFeatures,
+                    mRequiredProtocols, mOptionalProtocols, mServicePackageName, mExtras);
+        }
+    }
+}
diff --git a/media/java/android/media/routing/MediaRouteService.java b/media/java/android/media/routing/MediaRouteService.java
new file mode 100644
index 0000000..4d5a8a9
--- /dev/null
+++ b/media/java/android/media/routing/MediaRouteService.java
@@ -0,0 +1,1023 @@
+/*
+ * 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.routing;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.media.routing.MediaRouter.ConnectionError;
+import android.media.routing.MediaRouter.ConnectionInfo;
+import android.media.routing.MediaRouter.ConnectionRequest;
+import android.media.routing.MediaRouter.DestinationInfo;
+import android.media.routing.MediaRouter.DiscoveryError;
+import android.media.routing.MediaRouter.DiscoveryRequest;
+import android.media.routing.MediaRouter.RouteInfo;
+import android.media.routing.MediaRouter.ServiceMetadata;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Media route services implement strategies for discovering
+ * and establishing connections to media devices and their routes.  These services
+ * are also known as media route providers.
+ * <p>
+ * Each media route service subclass is responsible for enabling applications
+ * and the system to interact with media devices of some kind.
+ * For example, one particular media route service implementation might
+ * offer support for discovering nearby wireless display devices and streaming
+ * video contents to them; another media route service implementation might
+ * offer support for discovering nearby speakers and streaming media appliances
+ * and sending commands to play content on request.
+ * </p><p>
+ * Subclasses must override the {@link #onCreateClientSession} method to return
+ * a {@link ClientSession} object that implements the {@link ClientSession#onStartDiscovery},
+ * {@link ClientSession#onStopDiscovery}, and {@link ClientSession#onConnect} methods
+ * to allow clients to discover and connect to media devices.
+ * </p><p>
+ * This object is not thread-safe.  All callbacks are invoked on the main looper.
+ * </p>
+ *
+ * <h3>Clients</h3>
+ * <p>
+ * The clients of this API are media applications that would like to discover
+ * and connect to media devices.  The client may also be the system, such as
+ * when the user initiates display mirroring via the Cast Screen function.
+ * </p><p>
+ * There may be multiple client sessions active at the same time.  Each client
+ * session can request discovery and connect to routes independently of any
+ * other client.  It is the responsibility of the media route service to maintain
+ * separate state for each client session and to ensure that clients cannot interfere
+ * with one another in harmful ways.
+ * </p><p>
+ * Notwithstanding the requirement to support any number of concurrent client
+ * sessions, the media route service may impose constraints on how many clients
+ * can connect to the same media device in a particular mode at the same time.
+ * In some cases, media devices may support connections from an arbitrary number
+ * of clients simultaneously but often it may be necessary to ensure that only
+ * one client is in control.  When this happens, the media route service should
+ * report a connection error unless the connection request specifies that the
+ * client should take control of the media device (and forcibly disconnect other
+ * clients that may be using it).
+ * </p>
+ *
+ * <h3>Destinations</h3>
+ * <p>
+ * The media devices to which an application may send media content are referred
+ * to in the API as destinations.  Each destination therefore represents a single
+ * independent device such as a speaker or TV set.  Destinations are given meaningful
+ * names and descriptions to help the user associate them with devices in their
+ * environment.
+ * </p><p>
+ * Destinations may be local or remote and may be accessed through various means,
+ * often wirelessly.  The user may install media route services to enable
+ * media applications to connect to a variety of destinations with different
+ * capabilities.
+ * </p>
+ *
+ * <h3>Routes</h3>
+ * <p>
+ * Routes represent possible usages or means of reaching and interacting with
+ * a destination.  Since destinations may support many different features, they may
+ * each offer multiple routes for applications to choose from based on their needs.
+ * For example, one route might express the ability to stream locally rendered audio
+ * and video to the device; another route might express the ability to send a URL for
+ * the destination to download from the network and play all by itself.
+ * </p><p>
+ * Routes are discovered according to the set of capabilities that
+ * an application or the system is seeking to use at a particular time.  For example,
+ * if an application wants to stream music to a destination then it will ask the
+ * {@link MediaRouter} to find routes to destinations can stream music and ignore
+ * all other destinations that cannot.
+ * </p><p>
+ * In general, the application will inspect the set of routes that have been
+ * offered then connect to the most appropriate route for its desired purpose.
+ * </p>
+ *
+ * <h3>Discovery</h3>
+ * <p>
+ * Discovery is the process of finding destinations based on a description of the
+ * kinds of routes that an application or the system would like to use.
+ * </p><p>
+ * Discovery begins when {@link ClientSession#onStartDiscovery} is called and ends when
+ * {@link ClientSession#onStopDiscovery} is called.  There may be multiple simultaneous
+ * discovery requests in progress at the same time from different clients.  It is up to
+ * the media route service to perform these requests in parallel or multiplex them
+ * as required.
+ * </p><p>
+ * Media route services are <em>strongly encouraged</em> to use the information
+ * in the discovery request to optimize discovery and avoid redundant work.
+ * In the case where no media device supported by the media route service
+ * could possibly offer the requested capabilities, the
+ * {@link ClientSession#onStartDiscovery} method should return <code>false</code> to
+ * let the system know that it can unbind from the media route service and
+ * release its resources.
+ * </p>
+ *
+ * <h3>Settings</h3>
+ * <p>
+ * Many kinds of devices can be discovered on demand simply by scanning the local network
+ * or using wireless protocols such as Bluetooth to find them.  However, in some cases
+ * it may be necessary for the user to manually configure destinations before they
+ * can be used (or to adjust settings later).  Actual user configuration of destinations
+ * is beyond the scope of this API but media route services may specify an activity
+ * in their manifest that the user can launch to perform these tasks.
+ * </p><p>
+ * Note that media route services that are installed from the store must be enabled
+ * by the user before they become available for applications to use.
+ * The {@link android.provider.Settings#ACTION_CAST_SETTINGS Settings.ACTION_CAST_SETTINGS}
+ * settings activity provides the ability for the user to configure media route services.
+ * </p>
+ *
+ * <h3>Manifest Declaration</h3>
+ * <p>
+ * Media route services must be declared in the manifest along with meta-data
+ * about the kinds of routes that they are capable of discovering.  The system
+ * uses this information to optimize the set of services to which it binds in
+ * order to satisfy a particular discovery request.
+ * </p><p>
+ * To extend this class, you must declare the service in your manifest file with
+ * the {@link android.Manifest.permission#BIND_MEDIA_ROUTE_SERVICE} permission
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action.  You must
+ * also add meta-data to describe the kinds of routes that your service is capable
+ * of discovering.
+ * </p><p>
+ * For example:
+ * </p><pre>
+ * &lt;service android:name=".MediaRouteProvider"
+ *          android:label="&#64;string/service_name"
+ *          android:permission="android.permission.BIND_MEDIA_ROUTE_SERVICE">
+ *     &lt;intent-filter>
+ *         &lt;action android:name="android.media.routing.MediaRouteService" />
+ *     &lt;/intent-filter>
+ *
+ *     TODO: INSERT METADATA DECLARATIONS HERE
+ *
+ * &lt;/service>
+ * </pre>
+ */
+public abstract class MediaRouteService extends Service {
+    private static final String TAG = "MediaRouteService";
+
+    private static final boolean DEBUG = true;
+
+    private final Handler mHandler;
+    private final BinderService mService;
+    private final ArrayMap<IBinder, ClientRecord> mClientRecords =
+            new ArrayMap<IBinder, ClientRecord>();
+
+    private ServiceMetadata mMetadata;
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE =
+            "android.media.routing.MediaRouteService";
+
+    /**
+     * Creates a media route service.
+     */
+    public MediaRouteService() {
+        mHandler = new Handler(true);
+        mService = new BinderService();
+    }
+
+    @Override
+    public @Nullable IBinder onBind(Intent intent) {
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mService;
+        }
+        return null;
+    }
+
+    /**
+     * Creates a new client session on behalf of a client.
+     * <p>
+     * The implementation should return a {@link ClientSession} for the client
+     * to use.  The media route service must take care to manage the state of
+     * each client session independently from any others that might also be
+     * in use at the same time.
+     * </p>
+     *
+     * @param client Information about the client.
+     * @return The client session object, or null if the client is not allowed
+     * to interact with this media route service.
+     */
+    public abstract @Nullable ClientSession onCreateClientSession(@NonNull ClientInfo client);
+
+    /**
+     * Gets metadata about this service.
+     * <p>
+     * Use this method to obtain a {@link ServiceMetadata} object to provide when creating
+     * a {@link android.media.routing.MediaRouter.DestinationInfo.Builder}.
+     * </p>
+     *
+     * @return Metadata about this service.
+     */
+    public @NonNull ServiceMetadata getServiceMetadata() {
+        if (mMetadata == null) {
+            try {
+                mMetadata = new ServiceMetadata(this);
+            } catch (NameNotFoundException ex) {
+                Log.wtf(TAG, "Could not retrieve own service metadata!");
+            }
+        }
+        return mMetadata;
+    }
+
+    /**
+     * Enables a single client to access the functionality of the media route service.
+     */
+    public static abstract class ClientSession {
+        /**
+         * Starts discovery.
+         * <p>
+         * If the media route service is capable of discovering routes that satisfy
+         * the request then this method should start discovery and return true.
+         * Otherwise, this method should return false.  If false is returned,
+         * then the framework will not call {@link #onStopDiscovery} since discovery
+         * was never actually started.
+         * </p><p>
+         * There may already be other discovery requests in progress at the same time
+         * for other clients; the media route service must keep track of them all.
+         * </p>
+         *
+         * @param req The discovery request to start.
+         * @param callback A callback to receive discovery events related to this
+         * particular request.  The events that the service sends to this callback
+         * will be sent to the client that initiated the discovery request.
+         * @return True if discovery has started.  False if the media route service
+         * is unable to discover routes that satisfy the request.
+         */
+        public abstract boolean onStartDiscovery(@NonNull DiscoveryRequest req,
+                @NonNull DiscoveryCallback callback);
+
+        /**
+         * Stops discovery.
+         * <p>
+         * If {@link #onStartDiscovery} returned true, then this method will eventually
+         * be called when the framework no longer requires this discovery request
+         * to be performed.
+         * </p><p>
+         * There may still be other discovery requests in progress for other clients;
+         * they must keep working until they have each been stopped by their client.
+         * </p>
+         */
+        public abstract void onStopDiscovery();
+
+        /**
+         * Starts connecting to a route.
+         *
+         * @param req The connection request.
+         * @param callback A callback to receive events connection events related
+         * to this particular request.  The events that the service sends to this callback
+         * will be sent to the client that initiated the discovery request.
+         * @return True if the connection is in progress, or false if the client
+         * unable to connect to the requested route.
+         */
+        public abstract boolean onConnect(@NonNull ConnectionRequest req,
+                @NonNull ConnectionCallback callback);
+
+        /**
+         * Called when the client requests to disconnect from the route
+         * or abort a connection attempt in progress.
+         */
+        public abstract void onDisconnect();
+
+        /**
+         * Called when the client requests to pause streaming of content to
+         * live audio/video routes such as when it goes into the background.
+         * <p>
+         * The default implementation does nothing.
+         * </p>
+         */
+        public void onPauseStream() { }
+
+        /**
+         * Called when the application requests to resume streaming of content to
+         * live audio/video routes such as when it returns to the foreground.
+         * <p>
+         * The default implementation does nothing.
+         * </p>
+         */
+        public void onResumeStream() { }
+
+        /**
+         * Called when the client is releasing the session.
+         * <p>
+         * The framework automatically takes care of stopping discovery and
+         * terminating the connection politely before calling this method to release
+         * the session.
+         * </p><p>
+         * The default implementation does nothing.
+         * </p>
+         */
+        public void onRelease() { }
+    }
+
+    /**
+     * Provides events in response to a discovery request.
+     */
+    public final class DiscoveryCallback {
+        private final ClientRecord mRecord;
+
+        DiscoveryCallback(ClientRecord record) {
+            mRecord = record;
+        }
+
+        /**
+         * Called by the service when a destination is found that
+         * offers one or more routes that satisfy the discovery request.
+         * <p>
+         * This method should be called whenever the list of available routes
+         * at a destination changes or whenever the properties of the destination
+         * itself change.
+         * </p>
+         *
+         * @param destination The destination that was found.
+         * @param routes The list of that destination's routes that satisfy the
+         * discovery request.
+         */
+        public void onDestinationFound(final @NonNull DestinationInfo destination,
+                final @NonNull List<RouteInfo> routes) {
+            if (destination == null) {
+                throw new IllegalArgumentException("destination must not be null");
+            }
+            if (routes == null) {
+                throw new IllegalArgumentException("routes must not be null");
+            }
+            for (int i = 0; i < routes.size(); i++) {
+                if (routes.get(i).getDestination() != destination) {
+                    throw new IllegalArgumentException("routes must refer to the "
+                            + "destination");
+                }
+            }
+
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mRecord.dispatchDestinationFound(DiscoveryCallback.this,
+                            destination, routes);
+                }
+            });
+        }
+
+        /**
+         * Called by the service when a destination is no longer
+         * reachable or is no longer offering any routes that satisfy
+         * the discovery request.
+         *
+         * @param destination The destination that went away.
+         */
+        public void onDestinationLost(final @NonNull DestinationInfo destination) {
+            if (destination == null) {
+                throw new IllegalArgumentException("destination must not be null");
+            }
+
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mRecord.dispatchDestinationLost(DiscoveryCallback.this, destination);
+                }
+            });
+        }
+
+        /**
+         * Called by the service when a discovery has failed in a non-recoverable manner.
+         *
+         * @param error The error code: one of
+         * {@link MediaRouter#DISCOVERY_ERROR_UNKNOWN},
+         * {@link MediaRouter#DISCOVERY_ERROR_ABORTED},
+         * or {@link MediaRouter#DISCOVERY_ERROR_NO_CONNECTIVITY}.
+         * @param message The localized error message, or null if none.  This message
+         * may be shown to the user.
+         * @param extras Additional information about the error which a client
+         * may use, or null if none.
+         */
+        public void onDiscoveryFailed(final @DiscoveryError int error,
+                final @Nullable CharSequence message, final @Nullable Bundle extras) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mRecord.dispatchDiscoveryFailed(DiscoveryCallback.this,
+                            error, message, extras);
+                }
+            });
+        }
+    }
+
+    /**
+     * Provides events in response to a connection request.
+     */
+    public final class ConnectionCallback {
+        private final ClientRecord mRecord;
+
+        ConnectionCallback(ClientRecord record) {
+            mRecord = record;
+        }
+
+        /**
+         * Called by the service when the connection succeeds.
+         *
+         * @param connection Immutable information about the connection.
+         */
+        public void onConnected(final @NonNull ConnectionInfo connection) {
+            if (connection == null) {
+                throw new IllegalArgumentException("connection must not be null");
+            }
+
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mRecord.dispatchConnected(ConnectionCallback.this, connection);
+                }
+            });
+        }
+
+        /**
+         * Called by the service when the connection is terminated normally.
+         * <p>
+         * Abnormal termination is reported via {@link #onConnectionFailed}.
+         * </p>
+         */
+        public void onDisconnected() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mRecord.dispatchDisconnected(ConnectionCallback.this);
+                }
+            });
+        }
+
+        /**
+         * Called by the service when a connection attempt or connection in
+         * progress has failed in a non-recoverable manner.
+         *
+         * @param error The error code: one of
+         * {@link MediaRouter#CONNECTION_ERROR_ABORTED},
+         * {@link MediaRouter#CONNECTION_ERROR_UNAUTHORIZED},
+         * {@link MediaRouter#CONNECTION_ERROR_UNREACHABLE},
+         * {@link MediaRouter#CONNECTION_ERROR_BUSY},
+         * {@link MediaRouter#CONNECTION_ERROR_TIMEOUT},
+         * {@link MediaRouter#CONNECTION_ERROR_BROKEN},
+         * or {@link MediaRouter#CONNECTION_ERROR_BARGED}.
+         * @param message The localized error message, or null if none.  This message
+         * may be shown to the user.
+         * @param extras Additional information about the error which a client
+         * may use, or null if none.
+         */
+        public void onConnectionFailed(final @ConnectionError int error,
+                final @Nullable CharSequence message, final @Nullable Bundle extras) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mRecord.dispatchConnectionFailed(ConnectionCallback.this,
+                            error, message, extras);
+                }
+            });
+        }
+    }
+
+    /**
+     * Identifies a client of the media route service.
+     */
+    public static final class ClientInfo {
+        private final int mUid;
+        private final String mPackageName;
+
+        ClientInfo(int uid, String packageName) {
+            mUid = uid;
+            mPackageName = packageName;
+        }
+
+        /**
+         * Gets the UID of the client application.
+         */
+        public int getUid() {
+            return mUid;
+        }
+
+        /**
+         * Gets the package name of the client application.
+         */
+        public @NonNull String getPackageName() {
+            return mPackageName;
+        }
+
+        @Override
+        public @NonNull String toString() {
+            return "ClientInfo{ uid=" + mUid + ", package=" + mPackageName + " }";
+        }
+    }
+
+    private final class BinderService extends IMediaRouteService.Stub {
+        @Override
+        public void registerClient(final int clientUid, final String clientPackageName,
+                final IMediaRouteClientCallback callback) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    ClientInfo client = new ClientInfo(clientUid, clientPackageName);
+                    if (DEBUG) {
+                        Log.d(TAG, "registerClient: client=" + client);
+                    }
+
+                    ClientSession session = onCreateClientSession(client);
+                    if (session == null) {
+                        // request refused by service
+                        Log.w(TAG, "Media route service refused to create session for client: "
+                                + "client=" + client);
+                        return;
+                    }
+
+                    ClientRecord record = new ClientRecord(callback, client, session);
+                    try {
+                        callback.asBinder().linkToDeath(record, 0);
+                    } catch (RemoteException ex) {
+                        // client died prematurely
+                        Log.w(TAG, "Client died prematurely while creating session: "
+                                + "client=" + client);
+                        record.release();
+                        return;
+                    }
+
+                    mClientRecords.put(callback.asBinder(), record);
+                }
+            });
+        }
+
+        @Override
+        public void unregisterClient(IMediaRouteClientCallback callback) {
+            unregisterClient(callback, false);
+        }
+
+        void unregisterClient(final IMediaRouteClientCallback callback,
+                final boolean died) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    ClientRecord record = mClientRecords.remove(callback.asBinder());
+                    if (record == null) {
+                        return; // spurious
+                    }
+
+                    if (DEBUG) {
+                        Log.d(TAG, "unregisterClient: client=" + record.getClientInfo()
+                                + ", died=" + died);
+                    }
+
+                    record.release();
+                    callback.asBinder().unlinkToDeath(record, 0);
+                }
+            });
+        }
+
+        @Override
+        public void startDiscovery(final IMediaRouteClientCallback callback,
+                final int seq, final List<MediaRouteSelector> selectors,
+                final int flags) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    ClientRecord record = mClientRecords.get(callback.asBinder());
+                    if (record == null) {
+                        return; // spurious
+                    }
+
+                    if (DEBUG) {
+                        Log.d(TAG, "startDiscovery: client=" + record.getClientInfo()
+                                + ", seq=" + seq + ", selectors=" + selectors
+                                + ", flags=0x" + Integer.toHexString(flags));
+                    }
+                    record.startDiscovery(seq, selectors, flags);
+                }
+            });
+        }
+
+        @Override
+        public void stopDiscovery(final IMediaRouteClientCallback callback) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    ClientRecord record = mClientRecords.get(callback.asBinder());
+                    if (record == null) {
+                        return; // spurious
+                    }
+
+                    if (DEBUG) {
+                        Log.d(TAG, "stopDiscovery: client=" + record.getClientInfo());
+                    }
+                    record.stopDiscovery();
+                }
+            });
+        }
+
+        @Override
+        public void connect(final IMediaRouteClientCallback callback,
+                final int seq, final String destinationId, final String routeId,
+                final int flags, final Bundle extras) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    ClientRecord record = mClientRecords.get(callback.asBinder());
+                    if (record == null) {
+                        return; // spurious
+                    }
+
+                    if (DEBUG) {
+                        Log.d(TAG, "connect: client=" + record.getClientInfo()
+                                + ", seq=" + seq + ", destinationId=" + destinationId
+                                + ", routeId=" + routeId
+                                + ", flags=0x" + Integer.toHexString(flags)
+                                + ", extras=" + extras);
+                    }
+                    record.connect(seq, destinationId, routeId, flags, extras);
+                }
+            });
+        }
+
+        @Override
+        public void disconnect(final IMediaRouteClientCallback callback) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    ClientRecord record = mClientRecords.get(callback.asBinder());
+                    if (record == null) {
+                        return; // spurious
+                    }
+
+                    if (DEBUG) {
+                        Log.d(TAG, "disconnect: client=" + record.getClientInfo());
+                    }
+                    record.disconnect();
+                }
+            });
+        }
+
+        @Override
+        public void pauseStream(final IMediaRouteClientCallback callback) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    ClientRecord record = mClientRecords.get(callback.asBinder());
+                    if (record == null) {
+                        return; // spurious
+                    }
+
+                    if (DEBUG) {
+                        Log.d(TAG, "pauseStream: client=" + record.getClientInfo());
+                    }
+                    record.pauseStream();
+                }
+            });
+        }
+
+        @Override
+        public void resumeStream(final IMediaRouteClientCallback callback) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    ClientRecord record = mClientRecords.get(callback.asBinder());
+                    if (record == null) {
+                        return; // spurious
+                    }
+
+                    if (DEBUG) {
+                        Log.d(TAG, "resumeStream: client=" + record.getClientInfo());
+                    }
+                    record.resumeStream();
+                }
+            });
+        }
+    }
+
+    // Must be accessed on handler
+    private final class ClientRecord implements IBinder.DeathRecipient {
+        private final IMediaRouteClientCallback mClientCallback;
+        private final ClientInfo mClient;
+        private final ClientSession mSession;
+
+        private int mDiscoverySeq;
+        private DiscoveryRequest mDiscoveryRequest;
+        private DiscoveryCallback mDiscoveryCallback;
+        private final ArrayMap<String, DestinationRecord> mDestinations =
+                new ArrayMap<String, DestinationRecord>();
+
+        private int mConnectionSeq;
+        private ConnectionRequest mConnectionRequest;
+        private ConnectionCallback mConnectionCallback;
+        private ConnectionInfo mConnection;
+        private boolean mConnectionPaused;
+
+        public ClientRecord(IMediaRouteClientCallback callback,
+                ClientInfo client, ClientSession session) {
+            mClientCallback = callback;
+            mClient = client;
+            mSession = session;
+        }
+
+        // Invoked on binder thread unlike all other methods in this class.
+        @Override
+        public void binderDied() {
+            mService.unregisterClient(mClientCallback, true);
+        }
+
+        public ClientInfo getClientInfo() {
+            return mClient;
+        }
+
+        public void release() {
+            stopDiscovery();
+            disconnect();
+        }
+
+        public void startDiscovery(int seq, List<MediaRouteSelector> selectors,
+                int flags) {
+            stopDiscovery();
+
+            mDiscoverySeq = seq;
+            mDiscoveryRequest = new DiscoveryRequest(selectors);
+            mDiscoveryRequest.setFlags(flags);
+            mDiscoveryCallback = new DiscoveryCallback(this);
+            boolean started = mSession.onStartDiscovery(mDiscoveryRequest, mDiscoveryCallback);
+            if (!started) {
+                dispatchDiscoveryFailed(mDiscoveryCallback,
+                        MediaRouter.DISCOVERY_ERROR_ABORTED, null, null);
+                clearDiscovery();
+            }
+        }
+
+        public void stopDiscovery() {
+            if (mDiscoveryRequest != null) {
+                mSession.onStopDiscovery();
+                clearDiscovery();
+            }
+        }
+
+        private void clearDiscovery() {
+            mDestinations.clear();
+            mDiscoveryRequest = null;
+            mDiscoveryCallback = null;
+        }
+
+        public void connect(int seq, String destinationId, String routeId,
+                int flags, Bundle extras) {
+            disconnect();
+
+            mConnectionSeq = seq;
+            mConnectionCallback = new ConnectionCallback(this);
+
+            DestinationRecord destinationRecord = mDestinations.get(destinationId);
+            if (destinationRecord == null) {
+                Log.w(TAG, "Aborting connection to route since no matching destination "
+                        + "was found in the list of known destinations: "
+                        + "destinationId=" + destinationId);
+                dispatchConnectionFailed(mConnectionCallback,
+                        MediaRouter.CONNECTION_ERROR_ABORTED, null, null);
+                clearConnection();
+                return;
+            }
+
+            RouteInfo route = destinationRecord.getRoute(routeId);
+            if (route == null) {
+                Log.w(TAG, "Aborting connection to route since no matching route "
+                        + "was found in the list of known routes: "
+                        + "destination=" + destinationRecord.destination
+                        + ", routeId=" + routeId);
+                dispatchConnectionFailed(mConnectionCallback,
+                        MediaRouter.CONNECTION_ERROR_ABORTED, null, null);
+                clearConnection();
+                return;
+            }
+
+            mConnectionRequest = new ConnectionRequest(route);
+            mConnectionRequest.setFlags(flags);
+            mConnectionRequest.setExtras(extras);
+            boolean started = mSession.onConnect(mConnectionRequest, mConnectionCallback);
+            if (!started) {
+                dispatchConnectionFailed(mConnectionCallback,
+                        MediaRouter.CONNECTION_ERROR_ABORTED, null, null);
+                clearConnection();
+            }
+        }
+
+        public void disconnect() {
+            if (mConnectionRequest != null) {
+                mSession.onDisconnect();
+                clearConnection();
+            }
+        }
+
+        private void clearConnection() {
+            mConnectionRequest = null;
+            mConnectionCallback = null;
+            if (mConnection != null) {
+                mConnection.close();
+                mConnection = null;
+            }
+            mConnectionPaused = false;
+        }
+
+        public void pauseStream() {
+            if (mConnectionRequest != null && !mConnectionPaused) {
+                mConnectionPaused = true;
+                mSession.onPauseStream();
+            }
+        }
+
+        public void resumeStream() {
+            if (mConnectionRequest != null && mConnectionPaused) {
+                mConnectionPaused = false;
+                mSession.onResumeStream();
+            }
+        }
+
+        public void dispatchDestinationFound(DiscoveryCallback callback,
+                DestinationInfo destination, List<RouteInfo> routes) {
+            if (callback == mDiscoveryCallback) {
+                if (DEBUG) {
+                    Log.d(TAG, "destinationFound: destination=" + destination
+                            + ", routes=" + routes);
+                }
+                mDestinations.put(destination.getId(),
+                        new DestinationRecord(destination, routes));
+
+                ParcelableDestinationInfo pdi = new ParcelableDestinationInfo();
+                pdi.id = destination.getId();
+                pdi.name = destination.getName();
+                pdi.description = destination.getDescription();
+                pdi.iconResourceId = destination.getIconResourceId();
+                pdi.extras = destination.getExtras();
+                ArrayList<ParcelableRouteInfo> pris = new ArrayList<ParcelableRouteInfo>();
+                for (RouteInfo route : routes) {
+                    int selectorIndex = mDiscoveryRequest.getSelectors().indexOf(
+                            route.getSelector());
+                    if (selectorIndex < 0) {
+                        Log.w(TAG, "Ignoring route because the selector does not match "
+                                + "any of those that were originally supplied by the "
+                                + "client's discovery request: destination=" + destination
+                                + ", route=" + route);
+                        continue;
+                    }
+
+                    ParcelableRouteInfo pri = new ParcelableRouteInfo();
+                    pri.id = route.getId();
+                    pri.selectorIndex = selectorIndex;
+                    pri.features = route.getFeatures();
+                    pri.protocols = route.getProtocols().toArray(
+                            new String[route.getProtocols().size()]);
+                    pri.extras = route.getExtras();
+                    pris.add(pri);
+                }
+                try {
+                    mClientCallback.onDestinationFound(mDiscoverySeq, pdi,
+                            pris.toArray(new ParcelableRouteInfo[pris.size()]));
+                } catch (RemoteException ex) {
+                    // binder death handled elsewhere
+                }
+            }
+        }
+
+        public void dispatchDestinationLost(DiscoveryCallback callback,
+                DestinationInfo destination) {
+            if (callback == mDiscoveryCallback) {
+                if (DEBUG) {
+                    Log.d(TAG, "destinationLost: destination=" + destination);
+                }
+
+                if (mDestinations.get(destination.getId()).destination == destination) {
+                    mDestinations.remove(destination.getId());
+                    try {
+                        mClientCallback.onDestinationLost(mDiscoverySeq, destination.getId());
+                    } catch (RemoteException ex) {
+                        // binder death handled elsewhere
+                    }
+                }
+            }
+        }
+
+        public void dispatchDiscoveryFailed(DiscoveryCallback callback,
+                int error, CharSequence message, Bundle extras) {
+            if (callback == mDiscoveryCallback) {
+                if (DEBUG) {
+                    Log.d(TAG, "discoveryFailed: error=" + error + ", message=" + message
+                            + ", extras=" + extras);
+                }
+
+                try {
+                    mClientCallback.onDiscoveryFailed(mDiscoverySeq, error, message, extras);
+                } catch (RemoteException ex) {
+                    // binder death handled elsewhere
+                }
+            }
+        }
+
+        public void dispatchConnected(ConnectionCallback callback, ConnectionInfo connection) {
+            if (callback == mConnectionCallback) {
+                if (DEBUG) {
+                    Log.d(TAG, "connected: connection=" + connection);
+                }
+                if (mConnection == null) {
+                    mConnection = connection;
+
+                    ParcelableConnectionInfo pci = new ParcelableConnectionInfo();
+                    pci.audioAttributes = connection.getAudioAttributes();
+                    pci.presentationDisplayId = connection.getPresentationDisplay() != null ?
+                            connection.getPresentationDisplay().getDisplayId() : -1;
+                    pci.protocolBinders = new IBinder[connection.getProtocols().size()];
+                    for (int i = 0; i < pci.protocolBinders.length; i++) {
+                        pci.protocolBinders[i] = connection.getProtocolBinder(i);
+                    }
+                    pci.extras = connection.getExtras();
+                    try {
+                        mClientCallback.onConnected(mConnectionSeq, pci);
+                    } catch (RemoteException ex) {
+                        // binder death handled elsewhere
+                    }
+                } else {
+                    Log.w(TAG, "Media route service called onConnected() while already "
+                            + "connected.");
+                }
+            }
+        }
+
+        public void dispatchDisconnected(ConnectionCallback callback) {
+            if (callback == mConnectionCallback) {
+                if (DEBUG) {
+                    Log.d(TAG, "disconnected");
+                }
+
+                if (mConnection != null) {
+                    mConnection.close();
+                    mConnection = null;
+
+                    try {
+                        mClientCallback.onDisconnected(mConnectionSeq);
+                    } catch (RemoteException ex) {
+                        // binder death handled elsewhere
+                    }
+                }
+            }
+        }
+
+        public void dispatchConnectionFailed(ConnectionCallback callback,
+                int error, CharSequence message, Bundle extras) {
+            if (callback == mConnectionCallback) {
+                if (DEBUG) {
+                    Log.d(TAG, "connectionFailed: error=" + error + ", message=" + message
+                            + ", extras=" + extras);
+                }
+
+                try {
+                    mClientCallback.onConnectionFailed(mConnectionSeq, error, message, extras);
+                } catch (RemoteException ex) {
+                    // binder death handled elsewhere
+                }
+            }
+        }
+    }
+
+    private static final class DestinationRecord {
+        public final DestinationInfo destination;
+        public final List<RouteInfo> routes;
+
+        public DestinationRecord(DestinationInfo destination, List<RouteInfo> routes) {
+            this.destination = destination;
+            this.routes = routes;
+        }
+
+        public RouteInfo getRoute(String routeId) {
+            final int count = routes.size();
+            for (int i = 0; i < count; i++) {
+                RouteInfo route = routes.get(i);
+                if (route.getId().equals(routeId)) {
+                    return route;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/media/java/android/media/routing/MediaRouter.java b/media/java/android/media/routing/MediaRouter.java
new file mode 100644
index 0000000..4f6d324
--- /dev/null
+++ b/media/java/android/media/routing/MediaRouter.java
@@ -0,0 +1,1886 @@
+/*
+ * 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.routing;
+
+import android.annotation.DrawableRes;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Presentation;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Drawable;
+import android.hardware.display.DisplayManager;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.media.VolumeProvider;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.view.Display;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Media router allows applications to discover, connect to, control,
+ * and send content to nearby media devices known as destinations.
+ * <p>
+ * There are generally two participants involved in media routing: an
+ * application that wants to send media content to a destination and a
+ * {@link MediaRouteService media route service} that provides the
+ * service of transporting that content where it needs to go on behalf of the
+ * application.
+ * </p><p>
+ * To send media content to a destination, the application must ask the system
+ * to discover available routes to destinations that provide certain capabilities,
+ * establish a connection to a route, then send messages through the connection to
+ * control the routing of audio and video streams, launch remote applications,
+ * and invoke other functions of the destination.
+ * </p><p>
+ * Media router objects are thread-safe.
+ * </p>
+ *
+ * <h3>Destinations</h3>
+ * <p>
+ * The media devices to which an application may send media content are referred
+ * to in the API as destinations.  Each destination therefore represents a single
+ * independent device such as a speaker or TV set.  Destinations are given meaningful
+ * names and descriptions to help the user associate them with devices in their
+ * environment.
+ * </p><p>
+ * Destinations may be local or remote and may be accessed through various means,
+ * often wirelessly.  The user may install media route services to enable
+ * media applications to connect to a variety of destinations with different
+ * capabilities.
+ * </p>
+ *
+ * <h3>Routes</h3>
+ * <p>
+ * Routes represent possible usages or means of reaching and interacting with
+ * a destination.  Since destinations may support many different features, they may
+ * each offer multiple routes for applications to choose from based on their needs.
+ * For example, one route might express the ability to stream locally rendered audio
+ * and video to the device; another route might express the ability to send a URL for
+ * the destination to download from the network and play all by itself.
+ * </p><p>
+ * Routes are discovered according to the set of capabilities that
+ * an application or the system is seeking to use at a particular time.  For example,
+ * if an application wants to stream music to a destination then it will ask the
+ * {@link MediaRouter} to find routes to destinations can stream music and ignore
+ * all other destinations that cannot.
+ * </p><p>
+ * In general, the application will inspect the set of routes that have been
+ * offered then connect to the most appropriate route for its desired purpose.
+ * </p>
+ *
+ * <h3>Route Selection</h3>
+ * <p>
+ * When the user open the media route chooser activity, the system will display
+ * a list of nearby media destinations which have been discovered.  After the
+ * choice is made the application may connect to one of the routes offered by
+ * this destination and begin communicating with the destination.
+ * </p><p>
+ * Destinations are located through a process called discovery.  During discovery,
+ * the system will start installed {@link MediaRouteService media route services}
+ * to scan the network for nearby devices that offer the kinds of capabilities that the
+ * application is seeking to use.  The application specifies the capabilities it requires by
+ * adding {@link MediaRouteSelector media route selectors} to the media router
+ * using the {@link #addSelector} method.  Only destinations that provide routes
+ * which satisfy at least one of these media route selectors will be discovered.
+ * </p><p>
+ * Once the user has selected a destination, the application will be given a chance
+ * to choose one of the routes to which it would like to connect.  The application
+ * may switch to a different route from the same destination at a later time but
+ * in order to connect to a new destination, the application must once again launch
+ * the media route chooser activity to ask the user to choose a destination.
+ * </p>
+ *
+ * <h3>Route Protocols</h3>
+ * <p>
+ * Route protocols express capabilities offered by routes.  Each media route selector
+ * must specify at least one required protocol by which the routes will be selected.
+ * </p><p>
+ * The framework provides several predefined <code>MediaRouteProtocols</code> which are
+ * defined in the <code>android-support-media-protocols.jar</code> support library.
+ * Applications must statically link this library to make use of these protocols.
+ * </p><p>
+ * The static library approach is used to enable ongoing extension and refinement
+ * of protocols in the SDK and interoperability with the media router implementation
+ * for older platform versions which is offered by the framework support library.
+ * </p><p>
+ * Media route services may also define custom media route protocols of their own
+ * to enable applications to access specialized capabilities of certain destinations
+ * assuming they have linked in the required protocol code.
+ * </p><p>
+ * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> for more information.
+ * </p>
+ *
+ * <h3>Connections</h3>
+ * <p>
+ * After connecting to a media route, the application can send commands to
+ * the route using any of the protocols that it requested.  If the route supports live
+ * audio or video streaming then the application can create an {@link AudioTrack} or
+ * {@link Presentation} to route locally generated content to the destination.
+ * </p>
+ *
+ * <h3>Delegation</h3>
+ * <p>
+ * The creator of the media router is responsible for establishing the policy for
+ * discovering and connecting to destinations.  UI components may observe the state
+ * of the media router by {@link #createDelegate creating} a {@link Delegate}.
+ * </p><p>
+ * The media router should also be attached to the {@link MediaSession media session}
+ * that is handling media playback lifecycle.  This will allow
+ * authorized {@link MediaController media controllers}, possibly running in other
+ * processes, to provide UI to examine and change the media destination by
+ * {@link MediaController#createMediaRouterDelegate creating} a {@link Delegate}
+ * for the media router associated with the session.
+ * </p>
+ */
+public final class MediaRouter {
+    private final DisplayManager mDisplayManager;
+
+    private final Object mLock = new Object();
+
+    private RoutingCallback mRoutingCallback;
+    private Handler mRoutingCallbackHandler;
+
+    private boolean mReleased;
+    private int mDiscoveryState;
+    private int mConnectionState;
+    private final ArrayList<MediaRouteSelector> mSelectors =
+            new ArrayList<MediaRouteSelector>();
+    private final ArrayMap<DestinationInfo, List<RouteInfo>> mDiscoveredDestinations =
+            new ArrayMap<DestinationInfo, List<RouteInfo>>();
+    private RouteInfo mSelectedRoute;
+    private ConnectionInfo mConnection;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = { DISCOVERY_STATE_STOPPED, DISCOVERY_STATE_STARTED })
+    public @interface DiscoveryState { }
+
+    /**
+     * Discovery state: Discovery is not currently in progress.
+     */
+    public static final int DISCOVERY_STATE_STOPPED = 0;
+
+    /**
+     * Discovery state: Discovery is being performed.
+     */
+    public static final int DISCOVERY_STATE_STARTED = 1;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = { DISCOVERY_FLAG_BACKGROUND })
+    public @interface DiscoveryFlags { }
+
+    /**
+     * Discovery flag: Indicates that the client has requested passive discovery in
+     * the background.  The media route service should try to use less power and rely
+     * more on its internal caches to minimize its impact.
+     */
+    public static final int DISCOVERY_FLAG_BACKGROUND = 1 << 0;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = { DISCOVERY_ERROR_UNKNOWN, DISCOVERY_ERROR_ABORTED,
+            DISCOVERY_ERROR_NO_CONNECTIVITY })
+    public @interface DiscoveryError { }
+
+    /**
+     * Discovery error: Unknown error; refer to the error message for details.
+     */
+    public static final int DISCOVERY_ERROR_UNKNOWN = 0;
+
+    /**
+     * Discovery error: The media router or media route service has decided not to
+     * handle the discovery request for some reason.
+     */
+    public static final int DISCOVERY_ERROR_ABORTED = 1;
+
+    /**
+     * Discovery error: The media route service is unable to perform discovery
+     * due to a lack of connectivity such as because the radio is disabled.
+     */
+    public static final int DISCOVERY_ERROR_NO_CONNECTIVITY = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = { CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
+            CONNECTION_STATE_CONNECTED })
+    public @interface ConnectionState { }
+
+    /**
+     * Connection state: No destination has been selected.  Media content should
+     * be sent to the default output.
+     */
+    public static final int CONNECTION_STATE_DISCONNECTED = 0;
+
+    /**
+     * Connection state: The application is in the process of connecting to
+     * a route offered by the selected destination.
+     */
+    public static final int CONNECTION_STATE_CONNECTING = 1;
+
+    /**
+     * Connection state: The application has connected to a route offered by
+     * the selected destination.
+     */
+    public static final int CONNECTION_STATE_CONNECTED = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = { CONNECTION_FLAG_BARGE })
+    public @interface ConnectionFlags { }
+
+    /**
+     * Connection flag: Indicates that the client has requested to barge in and evict
+     * other clients that might have already connected to the destination and that
+     * would otherwise prevent this client from connecting.  When this flag is not
+     * set, the media route service should be polite and report
+     * {@link MediaRouter#CONNECTION_ERROR_BUSY} in case the destination is
+     * already occupied and cannot accept additional connections.
+     */
+    public static final int CONNECTION_FLAG_BARGE = 1 << 0;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = { CONNECTION_ERROR_UNKNOWN, CONNECTION_ERROR_ABORTED,
+            CONNECTION_ERROR_UNAUTHORIZED, CONNECTION_ERROR_UNAUTHORIZED,
+            CONNECTION_ERROR_BUSY, CONNECTION_ERROR_TIMEOUT, CONNECTION_ERROR_BROKEN })
+    public @interface ConnectionError { }
+
+    /**
+     * Connection error: Unknown error; refer to the error message for details.
+     */
+    public static final int CONNECTION_ERROR_UNKNOWN = 0;
+
+    /**
+     * Connection error: The media router or media route service has decided not to
+     * handle the connection request for some reason.
+     */
+    public static final int CONNECTION_ERROR_ABORTED = 1;
+
+    /**
+     * Connection error: The device has refused the connection from this client.
+     * This error should be avoided because the media route service should attempt
+     * to filter out devices that the client cannot access as it performs discovery
+     * on behalf of that client.
+     */
+    public static final int CONNECTION_ERROR_UNAUTHORIZED = 2;
+
+    /**
+     * Connection error: The device is unreachable over the network.
+     */
+    public static final int CONNECTION_ERROR_UNREACHABLE = 3;
+
+    /**
+     * Connection error: The device is already busy serving another client and
+     * the connection request did not ask to barge in.
+     */
+    public static final int CONNECTION_ERROR_BUSY = 4;
+
+    /**
+     * Connection error: A timeout occurred during connection.
+     */
+    public static final int CONNECTION_ERROR_TIMEOUT = 5;
+
+    /**
+     * Connection error: The connection to the device was severed unexpectedly.
+     */
+    public static final int CONNECTION_ERROR_BROKEN = 6;
+
+    /**
+     * Connection error: The connection was terminated because a different client barged
+     * in and took control of the destination.
+     */
+    public static final int CONNECTION_ERROR_BARGED = 7;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = { DISCONNECTION_REASON_APPLICATION_REQUEST,
+            DISCONNECTION_REASON_USER_REQUEST, DISCONNECTION_REASON_ERROR })
+    public @interface DisconnectionReason { }
+
+    /**
+     * Disconnection reason: The application requested disconnection itself.
+     */
+    public static final int DISCONNECTION_REASON_APPLICATION_REQUEST = 0;
+
+    /**
+     * Disconnection reason: The user requested disconnection.
+     */
+    public static final int DISCONNECTION_REASON_USER_REQUEST = 1;
+
+    /**
+     * Disconnection reason: An error occurred.
+     */
+    public static final int DISCONNECTION_REASON_ERROR = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = { ROUTE_FEATURE_LIVE_AUDIO, ROUTE_FEATURE_LIVE_VIDEO })
+    public @interface RouteFeatures { }
+
+    /**
+     * Route feature: Live audio.
+     * <p>
+     * A route that supports live audio streams audio rendered by the application
+     * to the destination.
+     * </p><p>
+     * To take advantage of live audio routing, the application must render its
+     * media using the audio attributes specified by {@link #getPreferredAudioAttributes}.
+     * </p>
+     *
+     * @see #getPreferredAudioAttributes
+     * @see android.media.AudioAttributes
+     */
+    public static final int ROUTE_FEATURE_LIVE_AUDIO = 1 << 0;
+
+    /**
+     * Route feature: Live video.
+     * <p>
+     * A route that supports live video streams video rendered by the application
+     * to the destination.
+     * </p><p>
+     * To take advantage of live video routing, the application must render its
+     * media to a {@link android.app.Presentation presentation window} on the
+     * display specified by {@link #getPreferredPresentationDisplay}.
+     * </p>
+     *
+     * @see #getPreferredPresentationDisplay
+     * @see android.app.Presentation
+     */
+    public static final int ROUTE_FEATURE_LIVE_VIDEO = 1 << 1;
+
+    /**
+     * Creates a media router.
+     *
+     * @param context The context with which the router is associated.
+     */
+    public MediaRouter(@NonNull Context context) {
+        if (context == null) {
+            throw new IllegalArgumentException("context must not be null");
+        }
+
+        mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
+    }
+
+    /** @hide */
+    public IMediaRouter getBinder() {
+        // todo
+        return null;
+    }
+
+    /**
+     * Disconnects from the selected destination and releases the media router.
+     * <p>
+     * This method should be called by the application when it no longer requires
+     * the media router to ensure that all bound resources may be cleaned up.
+     * </p>
+     */
+    public void release() {
+        synchronized (mLock) {
+            mReleased = true;
+            // todo
+        }
+    }
+
+    /**
+     * Returns true if the media router has been released.
+     */
+    public boolean isReleased() {
+        synchronized (mLock) {
+            return mReleased;
+        }
+    }
+
+    /**
+     * Gets the current route discovery state.
+     *
+     * @return The current discovery state: one of {@link #DISCOVERY_STATE_STOPPED},
+     * {@link #DISCOVERY_STATE_STARTED}.
+     */
+    public @DiscoveryState int getDiscoveryState() {
+        synchronized (mLock) {
+            return mDiscoveryState;
+        }
+    }
+
+    /**
+     * Gets the current route connection state.
+     *
+     * @return The current state: one of {@link #CONNECTION_STATE_DISCONNECTED},
+     * {@link #CONNECTION_STATE_CONNECTING} or {@link #CONNECTION_STATE_CONNECTED}.
+     */
+    public @ConnectionState int getConnectionState() {
+        synchronized (mLock) {
+            return mConnectionState;
+        }
+    }
+
+    /**
+     * Creates a media router delegate through which the destination of the media
+     * router may be controlled.
+     * <p>
+     * This is the point of entry for UI code that initiates discovery and
+     * connection to routes.
+     * </p>
+     */
+    public @NonNull Delegate createDelegate() {
+        return null; // todo
+    }
+
+    /**
+     * Sets a callback to participate in route discovery, filtering, and connection
+     * establishment.
+     *
+     * @param callback The callback to set, or null if none.
+     * @param handler The handler to receive callbacks, or null to use the current thread.
+     */
+    public void setRoutingCallback(@Nullable RoutingCallback callback,
+            @Nullable Handler handler) {
+        synchronized (mLock) {
+            if (callback == null) {
+                mRoutingCallback = null;
+                mRoutingCallbackHandler = null;
+            } else {
+                mRoutingCallback = callback;
+                mRoutingCallbackHandler = handler != null ? handler : new Handler();
+            }
+        }
+    }
+
+    /**
+     * Adds a media route selector to use to find destinations that have
+     * routes with the specified capabilities during route discovery.
+     */
+    public void addSelector(@NonNull MediaRouteSelector selector) {
+        if (selector == null) {
+            throw new IllegalArgumentException("selector must not be null");
+        }
+
+        synchronized (mLock) {
+            if (!mSelectors.contains(selector)) {
+                mSelectors.add(selector);
+                // todo
+            }
+        }
+    }
+
+    /**
+     * Removes a media route selector.
+     */
+    public void removeSelector(@NonNull MediaRouteSelector selector) {
+        if (selector == null) {
+            throw new IllegalArgumentException("selector must not be null");
+        }
+
+        synchronized (mLock) {
+            if (mSelectors.remove(selector)) {
+                // todo
+            }
+        }
+    }
+
+    /**
+     * Removes all media route selectors.
+     * <p>
+     * Note that at least one selector must be added in order to perform discovery.
+     * </p>
+     */
+    public void clearSelectors() {
+        synchronized (mLock) {
+            if (!mSelectors.isEmpty()) {
+                mSelectors.clear();
+                // todo
+            }
+        }
+    }
+
+    /**
+     * Gets a list of all media route selectors to consider during discovery.
+     */
+    public @NonNull List<MediaRouteSelector> getSelectors() {
+        synchronized (mLock) {
+            return new ArrayList<MediaRouteSelector>(mSelectors);
+        }
+    }
+
+    /**
+     * Gets the connection to the currently selected route.
+     *
+     * @return The connection to the currently selected route, or null if not connected.
+     */
+    public @NonNull ConnectionInfo getConnection() {
+        synchronized (mLock) {
+            return mConnection;
+        }
+    }
+
+    /**
+     * Gets the list of discovered destinations.
+     * <p>
+     * This list is only valid while discovery is running and is null otherwise.
+     * </p>
+     *
+     * @return The list of discovered destinations, or null if discovery is not running.
+     */
+    public @NonNull List<DestinationInfo> getDiscoveredDestinations() {
+        synchronized (mLock) {
+            if (mDiscoveryState == DISCOVERY_STATE_STARTED) {
+                return new ArrayList<DestinationInfo>(mDiscoveredDestinations.keySet());
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Gets the list of discovered routes for a particular destination.
+     * <p>
+     * This list is only valid while discovery is running and is null otherwise.
+     * </p>
+     *
+     * @param destination The destination for which to get the list of discovered routes.
+     * @return The list of discovered routes for the destination, or null if discovery
+     * is not running.
+     */
+    public @NonNull List<RouteInfo> getDiscoveredRoutes(@NonNull DestinationInfo destination) {
+        if (destination == null) {
+            throw new IllegalArgumentException("destination must not be null");
+        }
+        synchronized (mLock) {
+            if (mDiscoveryState == DISCOVERY_STATE_STARTED) {
+                List<RouteInfo> routes = mDiscoveredDestinations.get(destination);
+                if (routes != null) {
+                    return new ArrayList<RouteInfo>(routes);
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Gets the destination that has been selected.
+     *
+     * @return The selected destination, or null if disconnected.
+     */
+    public @Nullable DestinationInfo getSelectedDestination() {
+        synchronized (mLock) {
+            return mSelectedRoute != null ? mSelectedRoute.getDestination() : null;
+        }
+    }
+
+    /**
+     * Gets the route that has been selected.
+     *
+     * @return The selected destination, or null if disconnected.
+     */
+    public @Nullable RouteInfo getSelectedRoute() {
+        synchronized (mLock) {
+            return mSelectedRoute;
+        }
+    }
+
+    /**
+     * Gets the preferred audio attributes that should be used to stream live audio content
+     * based on the connected route.
+     * <p>
+     * Use an {@link AudioTrack} to send audio content to the destination with these
+     * audio attributes.
+     * </p><p>
+     * The preferred audio attributes may change when a connection is established but it
+     * will remain constant until disconnected.
+     * </p>
+     *
+     * @return The preferred audio attributes to use.  When connected, returns the
+     * route's audio attributes or null if it does not support live audio streaming.
+     * Otherwise returns audio attributes associated with {@link AudioAttributes#USAGE_MEDIA}.
+     */
+    public @Nullable AudioAttributes getPreferredAudioAttributes() {
+        synchronized (mLock) {
+            if (mConnection != null) {
+                return mConnection.getAudioAttributes();
+            }
+            return new AudioAttributes.Builder()
+                    .setLegacyStreamType(AudioManager.STREAM_MUSIC)
+                    .build();
+        }
+    }
+
+    /**
+     * Gets the preferred presentation display that should be used to stream live video content
+     * based on the connected route.
+     * <p>
+     * Use a {@link Presentation} to send video content to the destination with this display.
+     * </p><p>
+     * The preferred presentation display may change when a connection is established but it
+     * will remain constant until disconnected.
+     * </p>
+     *
+     * @return The preferred presentation display to use.  When connected, returns
+     * the route's presentation display or null if it does not support live video
+     * streaming.  Otherwise returns the first available
+     * {@link DisplayManager#DISPLAY_CATEGORY_PRESENTATION presentation display},
+     * such as a mirrored wireless or HDMI display or null if none.
+     */
+    public @Nullable Display getPreferredPresentationDisplay() {
+        synchronized (mLock) {
+            if (mConnection != null) {
+                return mConnection.getPresentationDisplay();
+            }
+            Display[] displays = mDisplayManager.getDisplays(
+                    DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
+            return displays.length != 0 ? displays[0] : null;
+        }
+    }
+
+    /**
+     * Gets the preferred volume provider that should be used to control the volume
+     * of content rendered on the currently selected route.
+     * <p>
+     * The preferred volume provider may change when a connection is established but it
+     * will remain the same until disconnected.
+     * </p>
+     *
+     * @return The preferred volume provider to use, or null if the currently
+     * selected route does not support remote volume adjustment or if the connection
+     * is not yet established.  If no route is selected, returns null to indicate
+     * that system volume control should be used.
+     */
+    public @Nullable VolumeProvider getPreferredVolumeProvider() {
+        synchronized (mLock) {
+            if (mConnection != null) {
+                return mConnection.getVolumeProvider();
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Requests to pause streaming of live audio or video routes.
+     * Should be called when the application is going into the background and is
+     * no longer rendering content locally.
+     * <p>
+     * This method does nothing unless a connection has been established.
+     * </p>
+     */
+    public void pauseStream() {
+        // todo
+    }
+
+    /**
+     * Requests to resume streaming of live audio or video routes.
+     * May be called when the application is returning to the foreground and is
+     * about to resume rendering content locally.
+     * <p>
+     * This method does nothing unless a connection has been established.
+     * </p>
+     */
+    public void resumeStream() {
+        // todo
+    }
+
+    /**
+     * This class is used by UI components to let the user discover and
+     * select a destination to which the media router should connect.
+     * <p>
+     * This API has somewhat more limited functionality than the {@link MediaRouter}
+     * itself because it is designed to allow applications to control
+     * the destination of media router instances that belong to other processes.
+     * </p><p>
+     * To control the destination of your own media router, call
+     * {@link #createDelegate} to obtain a local delegate object.
+     * </p><p>
+     * To control the destination of a media router that belongs to another process,
+     * first obtain a {@link MediaController} that is associated with the media playback
+     * that is occurring in that process, then call
+     * {@link MediaController#createMediaRouterDelegate} to obtain an instance of
+     * its destination controls.  Note that special permissions may be required to
+     * obtain the {@link MediaController} instance in the first place.
+     * </p>
+     */
+    public static final class Delegate {
+        /**
+         * Returns true if the media router has been released.
+         */
+        public boolean isReleased() {
+            // todo
+            return false;
+        }
+
+        /**
+         * Gets the current route discovery state.
+         *
+         * @return The current discovery state: one of {@link #DISCOVERY_STATE_STOPPED},
+         * {@link #DISCOVERY_STATE_STARTED}.
+         */
+        public @DiscoveryState int getDiscoveryState() {
+            // todo
+            return -1;
+        }
+
+        /**
+         * Gets the current route connection state.
+         *
+         * @return The current state: one of {@link #CONNECTION_STATE_DISCONNECTED},
+         * {@link #CONNECTION_STATE_CONNECTING} or {@link #CONNECTION_STATE_CONNECTED}.
+         */
+        public @ConnectionState int getConnectionState() {
+            // todo
+            return -1;
+        }
+
+        /**
+         * Gets the currently selected destination.
+         *
+         * @return The destination information, or null if none.
+         */
+        public @Nullable DestinationInfo getSelectedDestination() {
+            return null;
+        }
+
+        /**
+         * Gets the list of discovered destinations.
+         * <p>
+         * This list is only valid while discovery is running and is null otherwise.
+         * </p>
+         *
+         * @return The list of discovered destinations, or null if discovery is not running.
+         */
+        public @NonNull List<DestinationInfo> getDiscoveredDestinations() {
+            return null;
+        }
+
+        /**
+         * Adds a callback to receive state changes.
+         *
+         * @param callback The callback to set, or null if none.
+         * @param handler The handler to receive callbacks, or null to use the current thread.
+         */
+        public void addStateCallback(@Nullable StateCallback callback,
+                @Nullable Handler handler) {
+            if (callback == null) {
+                throw new IllegalArgumentException("callback must not be null");
+            }
+            if (handler == null) {
+                handler = new Handler();
+            }
+            // todo
+        }
+
+        /**
+         * Removes a callback for state changes.
+         *
+         * @param callback The callback to set, or null if none.
+         */
+        public void removeStateCallback(@Nullable StateCallback callback) {
+            // todo
+        }
+
+        /**
+         * Starts performing discovery.
+         * <p>
+         * Performing discovery is expensive.  Make sure to call {@link #stopDiscovery}
+         * as soon as possible once a new destination has been selected to allow the system
+         * to stop services associated with discovery.
+         * </p>
+         *
+         * @param flags The discovery flags, such as {@link MediaRouter#DISCOVERY_FLAG_BACKGROUND}.
+         */
+        public void startDiscovery(@DiscoveryFlags int flags) {
+            // todo
+        }
+
+        /**
+         * Stops performing discovery.
+         */
+        public void stopDiscovery() {
+            // todo
+        }
+
+        /**
+         * Connects to a destination during route discovery.
+         * <p>
+         * This method may only be called while route discovery is active and the
+         * destination appears in the
+         * {@link #getDiscoveredDestinations list of discovered destinations}.
+         * If the media router is already connected to a route then it will first disconnect
+         * from the current route then connect to the new route.
+         * </p>
+         *
+         * @param destination The destination to which the media router should connect.
+         * @param flags The connection flags, such as {@link MediaRouter#CONNECTION_FLAG_BARGE}.
+         */
+        public void connect(@NonNull DestinationInfo destination, @DiscoveryFlags int flags) {
+            // todo
+        }
+
+        /**
+         * Disconnects from the currently selected destination.
+         * <p>
+         * Does nothing if not currently connected.
+         * </p>
+         *
+         * @param reason The reason for the disconnection: one of
+         * {@link #DISCONNECTION_REASON_APPLICATION_REQUEST},
+         * {@link #DISCONNECTION_REASON_USER_REQUEST}, or {@link #DISCONNECTION_REASON_ERROR}.
+         */
+        public void disconnect(@DisconnectionReason int reason) {
+            // todo
+        }
+    }
+
+    /**
+     * Describes immutable properties of a connection to a route.
+     */
+    public static final class ConnectionInfo {
+        private final RouteInfo mRoute;
+        private final AudioAttributes mAudioAttributes;
+        private final Display mPresentationDisplay;
+        private final VolumeProvider mVolumeProvider;
+        private final IBinder[] mProtocolBinders;
+        private final Object[] mProtocolInstances;
+        private final Bundle mExtras;
+        private final ArrayList<Closeable> mCloseables;
+
+        private static final Class<?>[] MEDIA_ROUTE_PROTOCOL_CTOR_PARAMETERS =
+                new Class<?>[] { IBinder.class };
+
+        ConnectionInfo(RouteInfo route,
+                AudioAttributes audioAttributes, Display display,
+                VolumeProvider volumeProvider, IBinder[] protocolBinders,
+                Bundle extras, ArrayList<Closeable> closeables) {
+            mRoute = route;
+            mAudioAttributes = audioAttributes;
+            mPresentationDisplay = display;
+            mVolumeProvider = volumeProvider;
+            mProtocolBinders = protocolBinders;
+            mProtocolInstances = new Object[mProtocolBinders.length];
+            mExtras = extras;
+            mCloseables = closeables;
+        }
+
+        /**
+         * Gets the route that is connected.
+         */
+        public @NonNull RouteInfo getRoute() {
+            return mRoute;
+        }
+
+        /**
+         * Gets the audio attributes which the client should use to stream audio
+         * to the destination, or null if the route does not support live audio streaming.
+         */
+        public @Nullable AudioAttributes getAudioAttributes() {
+            return mAudioAttributes;
+        }
+
+        /**
+         * Gets the display which the client should use to stream video to the
+         * destination using a {@link Presentation}, or null if the route does not
+         * support live video streaming.
+         */
+        public @Nullable Display getPresentationDisplay() {
+            return mPresentationDisplay;
+        }
+
+        /**
+         * Gets the route's volume provider, or null if none.
+         */
+        public @Nullable VolumeProvider getVolumeProvider() {
+            return mVolumeProvider;
+        }
+
+        /**
+         * Gets the set of supported route features.
+         */
+        public @RouteFeatures int getFeatures() {
+            return mRoute.getFeatures();
+        }
+
+        /**
+         * Gets the list of supported route protocols.
+         * <p>
+         * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+         * for more information.
+         * </p>
+         */
+        public @NonNull List<String> getProtocols() {
+            return mRoute.getProtocols();
+        }
+
+        /**
+         * Gets an instance of a route protocol object that wraps the protocol binder
+         * and provides easy access to the protocol's functionality.
+         * <p>
+         * This is a convenience method which invokes {@link #getProtocolBinder(String)}
+         * using the name of the provided class then passes the resulting {@link IBinder}
+         * to a single-argument constructor of that class.
+         * </p><p>
+         * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+         * for more information.
+         * </p>
+         */
+        @SuppressWarnings("unchecked")
+        public @Nullable <T> T getProtocolObject(Class<T> clazz) {
+            int index = getProtocols().indexOf(clazz.getName());
+            if (index < 0) {
+                return null;
+            }
+            if (mProtocolInstances[index] == null && mProtocolBinders[index] != null) {
+                final Constructor<T> ctor;
+                try {
+                    ctor = clazz.getConstructor(MEDIA_ROUTE_PROTOCOL_CTOR_PARAMETERS);
+                } catch (NoSuchMethodException ex) {
+                    throw new RuntimeException("Could not find public constructor "
+                            + "with IBinder argument in protocol class: " + clazz.getName(), ex);
+                }
+                try {
+                    mProtocolInstances[index] = ctor.newInstance(mProtocolBinders[index]);
+                } catch (InstantiationException | IllegalAccessException
+                        | InvocationTargetException ex) {
+                    throw new RuntimeException("Could create instance of protocol class: "
+                            + clazz.getName(), ex);
+                }
+            }
+            return (T)mProtocolInstances[index];
+        }
+
+        /**
+         * Gets the {@link IBinder} that provides access to the specified route protocol
+         * or null if the protocol is not supported.
+         * <p>
+         * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+         * for more information.
+         * </p>
+         */
+        public @Nullable IBinder getProtocolBinder(@NonNull String name) {
+            int index = getProtocols().indexOf(name);
+            return index >= 0 ? mProtocolBinders[index] : null;
+        }
+
+        /**
+         * Gets the {@link IBinder} that provides access to the specified route protocol
+         * at the given index in the protocol list or null if the protocol is not supported.
+         * <p>
+         * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+         * for more information.
+         * </p>
+         */
+        public @Nullable IBinder getProtocolBinder(int index) {
+            return mProtocolBinders[index];
+        }
+
+        /**
+         * Gets optional extra media route service or protocol specific information about
+         * the connection.  Use the service or protocol name as the prefix for
+         * any extras to avoid namespace collisions.
+         */
+        public @Nullable Bundle getExtras() {
+            return mExtras;
+        }
+
+        /**
+         * Closes all closeables associated with the connection when the connection
+         * is being torn down.
+         */
+        void close() {
+            final int count = mCloseables.size();
+            for (int i = 0; i < count; i++) {
+                try {
+                    mCloseables.get(i).close();
+                } catch (IOException ex) {
+                }
+            }
+        }
+
+        @Override
+        public @NonNull String toString() {
+            return "ConnectionInfo{ route=" + mRoute
+                    + ", audioAttributes=" + mAudioAttributes
+                    + ", presentationDisplay=" + mPresentationDisplay
+                    + ", volumeProvider=" + mVolumeProvider
+                    + ", protocolBinders=" + mProtocolBinders + " }";
+        }
+
+        /**
+         * Builds {@link ConnectionInfo} objects.
+         */
+        public static final class Builder {
+            private final RouteInfo mRoute;
+            private AudioAttributes mAudioAttributes;
+            private Display mPresentationDisplay;
+            private VolumeProvider mVolumeProvider;
+            private final IBinder[] mProtocols;
+            private Bundle mExtras;
+            private final ArrayList<Closeable> mCloseables = new ArrayList<Closeable>();
+
+            /**
+             * Creates a builder for connection information.
+             *
+             * @param route The route that is connected.
+             */
+            public Builder(@NonNull RouteInfo route) {
+                if (route == null) {
+                    throw new IllegalArgumentException("route");
+                }
+                mRoute = route;
+                mProtocols = new IBinder[route.getProtocols().size()];
+            }
+
+            /**
+             * Sets the audio attributes which the client should use to stream audio
+             * to the destination, or null if the route does not support live audio streaming.
+             */
+            public @NonNull Builder setAudioAttributes(
+                    @Nullable AudioAttributes audioAttributes) {
+                mAudioAttributes = audioAttributes;
+                return this;
+            }
+
+            /**
+             * Sets the display which the client should use to stream video to the
+             * destination using a {@link Presentation}, or null if the route does not
+             * support live video streaming.
+             */
+            public @NonNull Builder setPresentationDisplay(@Nullable Display display) {
+                mPresentationDisplay = display;
+                return this;
+            }
+
+            /**
+             * Sets the route's volume provider, or null if none.
+             */
+            public @NonNull Builder setVolumeProvider(@Nullable VolumeProvider provider) {
+                mVolumeProvider = provider;
+                return this;
+            }
+
+            /**
+             * Sets the binder stub of a supported route protocol using
+             * the protocol's fully qualified class name.  The protocol must be one
+             * of those that was indicated as being supported by the route.
+             * <p>
+             * If the stub implements {@link Closeable} then it will automatically
+             * be closed when the client disconnects from the route.
+             * </p><p>
+             * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+             * for more information.
+             * </p>
+             */
+            public @NonNull Builder setProtocolStub(@NonNull Class<?> clazz,
+                    @NonNull IInterface stub) {
+                if (clazz == null) {
+                    throw new IllegalArgumentException("clazz must not be null");
+                }
+                if (stub == null) {
+                    throw new IllegalArgumentException("stub must not be null");
+                }
+                if (stub instanceof Closeable) {
+                    mCloseables.add((Closeable)stub);
+                }
+                return setProtocolBinder(clazz.getName(), stub.asBinder());
+            }
+
+            /**
+             * Sets the binder interface of a supported route protocol by name.
+             * The protocol must be one of those that was indicated as being supported
+             * by the route.
+             * <p>
+             * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+             * for more information.
+             * </p>
+             */
+            public @NonNull Builder setProtocolBinder(@NonNull String name,
+                    @NonNull IBinder binder) {
+                if (TextUtils.isEmpty(name)) {
+                    throw new IllegalArgumentException("name must not be null or empty");
+                }
+                if (binder == null) {
+                    throw new IllegalArgumentException("binder must not be null");
+                }
+                int index = mRoute.getProtocols().indexOf(name);
+                if (index < 0) {
+                    throw new IllegalArgumentException("name must specify a protocol that "
+                            + "the route actually declared that it supports: "
+                            + "name=" + name + ", protocols=" + mRoute.getProtocols());
+                }
+                mProtocols[index] = binder;
+                return this;
+            }
+
+            /**
+             * Sets optional extra media route service or protocol specific information about
+             * the connection.  Use the service or protocol name as the prefix for
+             * any extras to avoid namespace collisions.
+             */
+            public @NonNull Builder setExtras(@Nullable Bundle extras) {
+                mExtras = extras;
+                return this;
+            }
+
+            /**
+             * Builds the {@link ConnectionInfo} object.
+             */
+            public @NonNull ConnectionInfo build() {
+                return new ConnectionInfo(mRoute,
+                        mAudioAttributes, mPresentationDisplay,
+                        mVolumeProvider, mProtocols, mExtras, mCloseables);
+            }
+        }
+    }
+
+    /**
+     * Describes one particular way of routing media content to a destination
+     * according to the capabilities specified by a media route selector on behalf
+     * of an application.
+     */
+    public static final class RouteInfo {
+        private final String mId;
+        private final DestinationInfo mDestination;
+        private final MediaRouteSelector mSelector;
+        private final int mFeatures;
+        private final ArrayList<String> mProtocols;
+        private final Bundle mExtras;
+
+        RouteInfo(String id, DestinationInfo destination, MediaRouteSelector selector,
+                int features, ArrayList<String> protocols, Bundle extras) {
+            mId = id;
+            mDestination = destination;
+            mSelector = selector;
+            mFeatures = features;
+            mProtocols = protocols;
+            mExtras = extras;
+        }
+
+        /**
+         * Gets the route's stable identifier.
+         * <p>
+         * The id is intended to uniquely identify the route among all routes that
+         * are offered by a particular destination in such a way that the client can
+         * refer to it at a later time.
+         * </p>
+         */
+        public @NonNull String getId() {
+            return mId;
+        }
+
+        /**
+         * Gets the destination that is offering this route.
+         */
+        public @NonNull DestinationInfo getDestination() {
+            return mDestination;
+        }
+
+        /**
+         * Gets the media route selector provided by the client for which this
+         * route was created.
+         * <p>
+         * It is implied that this route supports all of the required capabilities
+         * that were expressed in the selector.
+         * </p>
+         */
+        public @NonNull MediaRouteSelector getSelector() {
+            return mSelector;
+        }
+
+        /**
+         * Gets the set of supported route features.
+         */
+        public @RouteFeatures int getFeatures() {
+            return mFeatures;
+        }
+
+        /**
+         * Gets the list of supported route protocols.
+         * <p>
+         * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+         * for more information.
+         * </p>
+         */
+        public @NonNull List<String> getProtocols() {
+            return mProtocols;
+        }
+
+        /**
+         * Gets optional extra information about the route, or null if none.
+         */
+        public @Nullable Bundle getExtras() {
+            return mExtras;
+        }
+
+        @Override
+        public @NonNull String toString() {
+            return "RouteInfo{ id=" + mId + ", destination=" + mDestination
+                    + ", features=0x" + Integer.toHexString(mFeatures)
+                    + ", selector=" + mSelector + ", protocols=" + mProtocols
+                    + ", extras=" + mExtras + " }";
+        }
+
+        /**
+         * Builds {@link RouteInfo} objects.
+         */
+        public static final class Builder {
+            private final DestinationInfo mDestination;
+            private final String mId;
+            private final MediaRouteSelector mSelector;
+            private int mFeatures;
+            private final ArrayList<String> mProtocols = new ArrayList<String>();
+            private Bundle mExtras;
+
+            /**
+             * Creates a builder for route information.
+             *
+             * @param id The route's stable identifier.
+             * @param destination The destination of this route.
+             * @param selector The media route selector provided by the client for which
+             * this route was created.  This must be one of the selectors that was
+             * included in the discovery request.
+             */
+            public Builder(@NonNull String id, @NonNull DestinationInfo destination,
+                    @NonNull MediaRouteSelector selector) {
+                if (TextUtils.isEmpty(id)) {
+                    throw new IllegalArgumentException("id must not be null or empty");
+                }
+                if (destination == null) {
+                    throw new IllegalArgumentException("destination must not be null");
+                }
+                if (selector == null) {
+                    throw new IllegalArgumentException("selector must not be null");
+                }
+                mDestination = destination;
+                mId = id;
+                mSelector = selector;
+            }
+
+            /**
+             * Sets the set of supported route features.
+             */
+            public @NonNull Builder setFeatures(@RouteFeatures int features) {
+                mFeatures = features;
+                return this;
+            }
+
+            /**
+             * Adds a supported route protocol using its fully qualified class name.
+             * <p>
+             * If the protocol was not requested by the client in its selector
+             * then it will be silently discarded.
+             * </p>
+             */
+            public @NonNull <T extends IInterface> Builder addProtocol(@NonNull Class<T> clazz) {
+                if (clazz == null) {
+                    throw new IllegalArgumentException("clazz must not be null");
+                }
+                return addProtocol(clazz.getName());
+            }
+
+            /**
+             * Adds a supported route protocol by name.
+             * <p>
+             * If the protocol was not requested by the client in its selector
+             * then it will be silently discarded.
+             * </p>
+             */
+            public @NonNull Builder addProtocol(@NonNull String name) {
+                if (TextUtils.isEmpty(name)) {
+                    throw new IllegalArgumentException("name must not be null");
+                }
+                if (mSelector.containsProtocol(name)) {
+                    mProtocols.add(name);
+                }
+                return this;
+            }
+
+            /**
+             * Sets optional extra information about the route, or null if none.
+             */
+            public @NonNull Builder setExtras(@Nullable Bundle extras) {
+                mExtras = extras;
+                return this;
+            }
+
+            /**
+             * Builds the {@link RouteInfo} object.
+             * <p>
+             * Ensures that all required protocols have been supplied.
+             * </p>
+             */
+            public @NonNull RouteInfo build() {
+                int missingFeatures = mSelector.getRequiredFeatures() & ~mFeatures;
+                if (missingFeatures != 0) {
+                    throw new IllegalStateException("The media route selector "
+                            + "specified required features which this route does "
+                            + "not appear to support so it should not have been published: "
+                            + "missing 0x" + Integer.toHexString(missingFeatures));
+                }
+                for (String protocol : mSelector.getRequiredProtocols()) {
+                    if (!mProtocols.contains(protocol)) {
+                        throw new IllegalStateException("The media route selector "
+                                + "specified required protocols which this route "
+                                + "does not appear to support so it should not have "
+                                + "been published: missing " + protocol);
+                    }
+                }
+                return new RouteInfo(mId, mDestination, mSelector,
+                        mFeatures, mProtocols, mExtras);
+            }
+        }
+    }
+
+    /**
+     * Describes a destination for media content such as a device,
+     * an individual port on a device, or a group of devices.
+     */
+    public static final class DestinationInfo {
+        private final String mId;
+        private final ServiceMetadata mService;
+        private final CharSequence mName;
+        private final CharSequence mDescription;
+        private final int mIconResourceId;
+        private final Bundle mExtras;
+
+        DestinationInfo(String id, ServiceMetadata service,
+                CharSequence name, CharSequence description,
+                int iconResourceId, Bundle extras) {
+            mId = id;
+            mService = service;
+            mName = name;
+            mDescription = description;
+            mIconResourceId = iconResourceId;
+            mExtras = extras;
+        }
+
+        /**
+         * Gets the destination's stable identifier.
+         * <p>
+         * The id is intended to uniquely identify the destination among all destinations
+         * provided by the media route service in such a way that the client can
+         * refer to it at a later time.  Ideally, the id should be resilient to
+         * user-initiated actions such as changes to the name or description
+         * of the destination.
+         * </p>
+         */
+        public @NonNull String getId() {
+            return mId;
+        }
+
+        /**
+         * Gets metadata about the service that is providing access to this destination.
+         */
+        public @NonNull ServiceMetadata getServiceMetadata() {
+            return mService;
+        }
+
+        /**
+         * Gets the destination's name for display to the user.
+         */
+        public @NonNull CharSequence getName() {
+            return mName;
+        }
+
+        /**
+         * Gets the destination's description for display to the user, or null if none.
+         */
+        public @Nullable CharSequence getDescription() {
+            return mDescription;
+        }
+
+        /**
+         * Gets an icon resource from the service's package which is used
+         * to identify the destination, or -1 if none.
+         */
+        public @DrawableRes int getIconResourceId() {
+            return mIconResourceId;
+        }
+
+        /**
+         * Loads the icon drawable, or null if none.
+         */
+        public @Nullable Drawable loadIcon(@NonNull PackageManager pm) {
+            return mIconResourceId >= 0 ? mService.getDrawable(pm, mIconResourceId) : null;
+        }
+
+        /**
+         * Gets optional extra information about the destination, or null if none.
+         */
+        public @Nullable Bundle getExtras() {
+            return mExtras;
+        }
+
+        @Override
+        public @NonNull String toString() {
+            return "DestinationInfo{ id=" + mId + ", service=" + mService + ", name=" + mName
+                    + ", description=" + mDescription + ", iconResourceId=" + mIconResourceId
+                    + ", extras=" + mExtras + " }";
+        }
+
+        /**
+         * Builds {@link DestinationInfo} objects.
+         */
+        public static final class Builder {
+            private final String mId;
+            private final ServiceMetadata mService;
+            private final CharSequence mName;
+            private CharSequence mDescription;
+            private int mIconResourceId = -1;
+            private Bundle mExtras;
+
+            /**
+             * Creates a builder for destination information.
+             *
+             * @param id The destination's stable identifier.
+             * @param service Metatada about the service that is providing access to
+             * this destination.
+             * @param name The destination's name for display to the user.
+             */
+            public Builder(@NonNull String id, @NonNull ServiceMetadata service,
+                    @NonNull CharSequence name) {
+                if (TextUtils.isEmpty(id)) {
+                    throw new IllegalArgumentException("id must not be null or empty");
+                }
+                if (service == null) {
+                    throw new IllegalArgumentException("service must not be null");
+                }
+                if (TextUtils.isEmpty(name)) {
+                    throw new IllegalArgumentException("name must not be null or empty");
+                }
+                mId = id;
+                mService = service;
+                mName = name;
+            }
+
+            /**
+             * Sets the destination's description for display to the user, or null if none.
+             */
+            public @NonNull Builder setDescription(@Nullable CharSequence description) {
+                mDescription = description;
+                return this;
+            }
+
+            /**
+             * Sets an icon resource from this package used to identify the destination,
+             * or -1 if none.
+             */
+            public @NonNull Builder setIconResourceId(@DrawableRes int resid) {
+                mIconResourceId = resid;
+                return this;
+            }
+
+            /**
+             * Gets optional extra information about the destination, or null if none.
+             */
+            public @NonNull Builder setExtras(@Nullable Bundle extras) {
+                mExtras = extras;
+                return this;
+            }
+
+            /**
+             * Builds the {@link DestinationInfo} object.
+             */
+            public @NonNull DestinationInfo build() {
+                return new DestinationInfo(mId, mService, mName, mDescription,
+                        mIconResourceId, mExtras);
+            }
+        }
+    }
+
+    /**
+     * Describes metadata about a {@link MediaRouteService} which is providing
+     * access to certain kinds of destinations.
+     */
+    public static final class ServiceMetadata {
+        private final ServiceInfo mService;
+        private CharSequence mLabel;
+        private Drawable mIcon;
+
+        ServiceMetadata(Service service) throws NameNotFoundException {
+            mService = service.getPackageManager().getServiceInfo(
+                    new ComponentName(service, service.getClass()),
+                    PackageManager.GET_META_DATA);
+        }
+
+        ServiceMetadata(ServiceInfo service) {
+            mService = service;
+        }
+
+        /**
+         * Gets the service's component information including it name, label and icon.
+         */
+        public @NonNull ServiceInfo getService() {
+            return mService;
+        }
+
+        /**
+         * Gets the service's component name.
+         */
+        public @NonNull ComponentName getComponentName() {
+            return new ComponentName(mService.packageName, mService.name);
+        }
+
+        /**
+         * Gets the service's package name.
+         */
+        public @NonNull String getPackageName() {
+            return mService.packageName;
+        }
+
+        /**
+         * Gets the service's name for display to the user, or null if none.
+         */
+        public @NonNull CharSequence getLabel(@NonNull PackageManager pm) {
+            if (mLabel == null) {
+                mLabel = mService.loadLabel(pm);
+            }
+            return mLabel;
+        }
+
+        /**
+         * Gets the icon drawable, or null if none.
+         */
+        public @Nullable Drawable getIcon(@NonNull PackageManager pm) {
+            if (mIcon == null) {
+                mIcon = mService.loadIcon(pm);
+            }
+            return mIcon;
+        }
+
+        // TODO: add service metadata
+
+        Drawable getDrawable(PackageManager pm, int resid) {
+            return pm.getDrawable(getPackageName(), resid, mService.applicationInfo);
+        }
+
+        @Override
+        public @NonNull String toString() {
+            return "ServiceInfo{ service=" + getComponentName().toShortString() + " }";
+        }
+    }
+
+    /**
+     * Describes a request to discover routes on behalf of an application.
+     */
+    public static final class DiscoveryRequest {
+        private final ArrayList<MediaRouteSelector> mSelectors =
+                new ArrayList<MediaRouteSelector>();
+        private int mFlags;
+
+        DiscoveryRequest(@NonNull List<MediaRouteSelector> selectors) {
+            setSelectors(selectors);
+        }
+
+        /**
+         * Sets the list of media route selectors to consider during discovery.
+         */
+        public void setSelectors(@NonNull List<MediaRouteSelector> selectors) {
+            if (selectors == null) {
+                throw new IllegalArgumentException("selectors");
+            }
+            mSelectors.clear();
+            mSelectors.addAll(selectors);
+        }
+
+        /**
+         * Gets the list of media route selectors to consider during discovery.
+         */
+        public @NonNull List<MediaRouteSelector> getSelectors() {
+            return mSelectors;
+        }
+
+        /**
+         * Gets discovery flags, such as {@link MediaRouter#DISCOVERY_FLAG_BACKGROUND}.
+         */
+        public @DiscoveryFlags int getFlags() {
+            return mFlags;
+        }
+
+        /**
+         * Sets discovery flags, such as {@link MediaRouter#DISCOVERY_FLAG_BACKGROUND}.
+         */
+        public void setFlags(@DiscoveryFlags int flags) {
+            mFlags = flags;
+        }
+
+        @Override
+        public @NonNull String toString() {
+            return "DiscoveryRequest{ selectors=" + mSelectors
+                    + ", flags=0x" + Integer.toHexString(mFlags)
+                    + " }";
+        }
+    }
+
+    /**
+     * Describes a request to connect to a previously discovered route on
+     * behalf of an application.
+     */
+    public static final class ConnectionRequest {
+        private RouteInfo mRoute;
+        private int mFlags;
+        private Bundle mExtras;
+
+        ConnectionRequest(@NonNull RouteInfo route) {
+            setRoute(route);
+        }
+
+        /**
+         * Gets the route to which to connect.
+         */
+        public @NonNull RouteInfo getRoute() {
+            return mRoute;
+        }
+
+        /**
+         * Sets the route to which to connect.
+         */
+        public void setRoute(@NonNull RouteInfo route) {
+            if (route == null) {
+                throw new IllegalArgumentException("route must not be null");
+            }
+            mRoute = route;
+        }
+
+        /**
+         * Gets connection flags, such as {@link MediaRouter#CONNECTION_FLAG_BARGE}.
+         */
+        public @ConnectionFlags int getFlags() {
+            return mFlags;
+        }
+
+        /**
+         * Sets connection flags, such as {@link MediaRouter#CONNECTION_FLAG_BARGE}.
+         */
+        public void setFlags(@ConnectionFlags int flags) {
+            mFlags = flags;
+        }
+
+        /**
+         * Gets optional extras supplied by the application as part of the call to
+         * connect, or null if none.  The media route service may use this
+         * information to configure the route during connection.
+         */
+        public @Nullable Bundle getExtras() {
+            return mExtras;
+        }
+
+        /**
+         * Sets optional extras supplied by the application as part of the call to
+         * connect, or null if none.  The media route service may use this
+         * information to configure the route during connection.
+         */
+        public void setExtras(@Nullable Bundle extras) {
+            mExtras = extras;
+        }
+
+        @Override
+        public @NonNull String toString() {
+            return "ConnectionRequest{ route=" + mRoute
+                    + ", flags=0x" + Integer.toHexString(mFlags)
+                    + ", extras=" + mExtras + " }";
+        }
+    }
+
+    /**
+     * Callback interface to specify policy for route discovery, filtering,
+     * and connection establishment as well as observe media router state changes.
+     */
+    public static abstract class RoutingCallback extends StateCallback {
+        /**
+         * Called to prepare a discovery request object to specify the desired
+         * media route selectors when the media router has been asked to start discovery.
+         * <p>
+         * By default, the discovery request contains all of the selectors which
+         * have been added to the media router.  Subclasses may override the list of
+         * selectors by modifying the discovery request object before returning.
+         * </p>
+         *
+         * @param request The discovery request object which may be modified by
+         * this method to alter how discovery will be performed.
+         * @param selectors The immutable list of media route selectors which were
+         * added to the media router.
+         * @return True to allow discovery to proceed or false to abort it.
+         * By default, this methods returns true.
+         */
+        public boolean onPrepareDiscoveryRequest(@NonNull DiscoveryRequest request,
+                @NonNull List<MediaRouteSelector> selectors) {
+            return true;
+        }
+
+        /**
+         * Called to prepare a connection request object to specify the desired
+         * route and connection parameters when the media router has been asked to
+         * connect to a particular destination.
+         * <p>
+         * By default, the connection request specifies the first available route
+         * to the destination.  Subclasses may override the route and destination
+         * or set additional connection parameters by modifying the connection request
+         * object before returning.
+         * </p>
+         *
+         * @param request The connection request object which may be modified by
+         * this method to alter how the connection will be established.
+         * @param destination The destination to which the media router was asked
+         * to connect.
+         * @param routes The list of routes that belong to that destination sorted
+         * in the same order as their matching media route selectors which were
+         * used during discovery.
+         * @return True to allow the connection to proceed or false to abort it.
+         * By default, this methods returns true.
+         */
+        public boolean onPrepareConnectionRequest(
+                @NonNull ConnectionRequest request,
+                @NonNull DestinationInfo destination, @NonNull List<RouteInfo> routes) {
+            return true;
+        }
+    }
+
+    /**
+     * Callback class to receive events from a {@link MediaRouter.Delegate}.
+     */
+    public static abstract class StateCallback {
+        /**
+         * Called when the media router has been released.
+         */
+        public void onReleased() { }
+
+        /**
+         * Called when the discovery state has changed.
+         *
+         * @param state The new discovery state: one of
+         * {@link #DISCOVERY_STATE_STOPPED} or {@link #DISCOVERY_STATE_STARTED}.
+         */
+        public void onDiscoveryStateChanged(@DiscoveryState int state) { }
+
+        /**
+         * Called when the connection state has changed.
+         *
+         * @param state The new connection state: one of
+         * {@link #CONNECTION_STATE_DISCONNECTED}, {@link #CONNECTION_STATE_CONNECTING}
+         * or {@link #CONNECTION_STATE_CONNECTED}.
+         */
+        public void onConnectionStateChanged(@ConnectionState int state) { }
+
+        /**
+         * Called when the selected destination has changed.
+         *
+         * @param destination The new selected destination, or null if none.
+         */
+        public void onSelectedDestinationChanged(@Nullable DestinationInfo destination) { }
+
+        /**
+         * Called when route discovery has started.
+         */
+        public void onDiscoveryStarted() { }
+
+        /**
+         * Called when route discovery has stopped normally.
+         * <p>
+         * Abnormal termination is reported via {@link #onDiscoveryFailed}.
+         * </p>
+         */
+        public void onDiscoveryStopped() { }
+
+        /**
+         * Called when discovery has failed in a non-recoverable manner.
+         *
+         * @param error The error code: one of
+         * {@link MediaRouter#DISCOVERY_ERROR_UNKNOWN},
+         * {@link MediaRouter#DISCOVERY_ERROR_ABORTED},
+         * or {@link MediaRouter#DISCOVERY_ERROR_NO_CONNECTIVITY}.
+         * @param message The localized error message, or null if none.  This message
+         * may be shown to the user.
+         * @param extras Additional information about the error which a client
+         * may use, or null if none.
+         */
+        public void onDiscoveryFailed(@DiscoveryError int error, @Nullable CharSequence message,
+                @Nullable Bundle extras) { }
+
+        /**
+         * Called when a new destination is found or has changed during discovery.
+         * <p>
+         * Certain destinations may be omitted because they have been filtered
+         * out by the media router's routing callback.
+         * </p>
+         *
+         * @param destination The destination that was found.
+         */
+        public void onDestinationFound(@NonNull DestinationInfo destination) { }
+
+        /**
+         * Called when a destination is no longer reachable or is no longer
+         * offering any routes that satisfy the discovery request.
+         *
+         * @param destination The destination that went away.
+         */
+        public void onDestinationLost(@NonNull DestinationInfo destination) { }
+
+        /**
+         * Called when a connection attempt begins.
+         */
+        public void onConnecting() { }
+
+        /**
+         * Called when the connection succeeds.
+         */
+        public void onConnected() { }
+
+        /**
+         * Called when the connection is terminated normally.
+         * <p>
+         * Abnormal termination is reported via {@link #onConnectionFailed}.
+         * </p>
+         */
+        public void onDisconnected() { }
+
+        /**
+         * Called when a connection attempt or connection in
+         * progress has failed in a non-recoverable manner.
+         *
+         * @param error The error code: one of
+         * {@link MediaRouter#CONNECTION_ERROR_ABORTED},
+         * {@link MediaRouter#CONNECTION_ERROR_UNAUTHORIZED},
+         * {@link MediaRouter#CONNECTION_ERROR_UNREACHABLE},
+         * {@link MediaRouter#CONNECTION_ERROR_BUSY},
+         * {@link MediaRouter#CONNECTION_ERROR_TIMEOUT},
+         * {@link MediaRouter#CONNECTION_ERROR_BROKEN},
+         * or {@link MediaRouter#CONNECTION_ERROR_BARGED}.
+         * @param message The localized error message, or null if none.  This message
+         * may be shown to the user.
+         * @param extras Additional information about the error which a client
+         * may use, or null if none.
+         */
+        public void onConnectionFailed(@ConnectionError int error,
+                @Nullable CharSequence message, @Nullable Bundle extras) { }
+    }
+}
diff --git a/media/java/android/media/session/RouteCommand.aidl b/media/java/android/media/routing/ParcelableConnectionInfo.aidl
similarity index 89%
copy from media/java/android/media/session/RouteCommand.aidl
copy to media/java/android/media/routing/ParcelableConnectionInfo.aidl
index 725b308..4a9ec94 100644
--- a/media/java/android/media/session/RouteCommand.aidl
+++ b/media/java/android/media/routing/ParcelableConnectionInfo.aidl
@@ -13,6 +13,6 @@
 ** limitations under the License.
 */
 
-package android.media.session;
+package android.media.routing;
 
-parcelable RouteCommand;
+parcelable ParcelableConnectionInfo;
diff --git a/media/java/android/media/routing/ParcelableConnectionInfo.java b/media/java/android/media/routing/ParcelableConnectionInfo.java
new file mode 100644
index 0000000..45cfe9f
--- /dev/null
+++ b/media/java/android/media/routing/ParcelableConnectionInfo.java
@@ -0,0 +1,71 @@
+/*
+ * 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.routing;
+
+import android.media.AudioAttributes;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Internal parcelable representation of a media route connection.
+ */
+class ParcelableConnectionInfo implements Parcelable {
+    public AudioAttributes audioAttributes;
+    public int presentationDisplayId = -1;
+    // todo: volume
+    public IBinder[] protocolBinders;
+    public Bundle extras;
+
+    public static final Parcelable.Creator<ParcelableConnectionInfo> CREATOR =
+            new Parcelable.Creator<ParcelableConnectionInfo>() {
+        @Override
+        public ParcelableConnectionInfo createFromParcel(Parcel source) {
+            ParcelableConnectionInfo info = new ParcelableConnectionInfo();
+            if (source.readInt() != 0) {
+                info.audioAttributes = AudioAttributes.CREATOR.createFromParcel(source);
+            }
+            info.presentationDisplayId = source.readInt();
+            info.protocolBinders = source.createBinderArray();
+            info.extras = source.readBundle();
+            return info;
+        }
+
+        @Override
+        public ParcelableConnectionInfo[] newArray(int size) {
+            return new ParcelableConnectionInfo[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        if (audioAttributes != null) {
+            dest.writeInt(1);
+            audioAttributes.writeToParcel(dest, flags);
+        } else {
+            dest.writeInt(0);
+        }
+        dest.writeInt(presentationDisplayId);
+        dest.writeBinderArray(protocolBinders);
+        dest.writeBundle(extras);
+    }
+}
diff --git a/media/java/android/media/session/RouteCommand.aidl b/media/java/android/media/routing/ParcelableDestinationInfo.aidl
similarity index 89%
copy from media/java/android/media/session/RouteCommand.aidl
copy to media/java/android/media/routing/ParcelableDestinationInfo.aidl
index 725b308..bf1c198 100644
--- a/media/java/android/media/session/RouteCommand.aidl
+++ b/media/java/android/media/routing/ParcelableDestinationInfo.aidl
@@ -13,6 +13,6 @@
 ** limitations under the License.
 */
 
-package android.media.session;
+package android.media.routing;
 
-parcelable RouteCommand;
+parcelable ParcelableDestinationInfo;
diff --git a/media/java/android/media/routing/ParcelableDestinationInfo.java b/media/java/android/media/routing/ParcelableDestinationInfo.java
new file mode 100644
index 0000000..eca5eec
--- /dev/null
+++ b/media/java/android/media/routing/ParcelableDestinationInfo.java
@@ -0,0 +1,65 @@
+/*
+ * 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.routing;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Internal parcelable representation of a media destination.
+ */
+class ParcelableDestinationInfo implements Parcelable {
+    public String id;
+    public CharSequence name;
+    public CharSequence description;
+    public int iconResourceId;
+    public Bundle extras;
+
+    public static final Parcelable.Creator<ParcelableDestinationInfo> CREATOR =
+            new Parcelable.Creator<ParcelableDestinationInfo>() {
+        @Override
+        public ParcelableDestinationInfo createFromParcel(Parcel source) {
+            ParcelableDestinationInfo info = new ParcelableDestinationInfo();
+            info.id = source.readString();
+            info.name = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+            info.description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+            info.iconResourceId = source.readInt();
+            info.extras = source.readBundle();
+            return info;
+        }
+
+        @Override
+        public ParcelableDestinationInfo[] newArray(int size) {
+            return new ParcelableDestinationInfo[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(id);
+        TextUtils.writeToParcel(name, dest, flags);
+        TextUtils.writeToParcel(description, dest, flags);
+        dest.writeInt(iconResourceId);
+        dest.writeBundle(extras);
+    }
+}
diff --git a/media/java/android/media/session/RouteCommand.aidl b/media/java/android/media/routing/ParcelableRouteInfo.aidl
similarity index 90%
rename from media/java/android/media/session/RouteCommand.aidl
rename to media/java/android/media/routing/ParcelableRouteInfo.aidl
index 725b308..126afaa 100644
--- a/media/java/android/media/session/RouteCommand.aidl
+++ b/media/java/android/media/routing/ParcelableRouteInfo.aidl
@@ -13,6 +13,6 @@
 ** limitations under the License.
 */
 
-package android.media.session;
+package android.media.routing;
 
-parcelable RouteCommand;
+parcelable ParcelableRouteInfo;
diff --git a/media/java/android/media/routing/ParcelableRouteInfo.java b/media/java/android/media/routing/ParcelableRouteInfo.java
new file mode 100644
index 0000000..fb1a547
--- /dev/null
+++ b/media/java/android/media/routing/ParcelableRouteInfo.java
@@ -0,0 +1,64 @@
+/*
+ * 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.routing;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Internal parcelable representation of a media route.
+ */
+class ParcelableRouteInfo implements Parcelable {
+    public String id;
+    public int selectorIndex; // index of selector within list used for discovery
+    public int features;
+    public String[] protocols;
+    public Bundle extras;
+
+    public static final Parcelable.Creator<ParcelableRouteInfo> CREATOR =
+            new Parcelable.Creator<ParcelableRouteInfo>() {
+        @Override
+        public ParcelableRouteInfo createFromParcel(Parcel source) {
+            ParcelableRouteInfo info = new ParcelableRouteInfo();
+            info.id = source.readString();
+            info.selectorIndex = source.readInt();
+            info.features = source.readInt();
+            info.protocols = source.createStringArray();
+            info.extras = source.readBundle();
+            return info;
+        }
+
+        @Override
+        public ParcelableRouteInfo[] newArray(int size) {
+            return new ParcelableRouteInfo[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(id);
+        dest.writeInt(selectorIndex);
+        dest.writeInt(features);
+        dest.writeStringArray(protocols);
+        dest.writeBundle(extras);
+    }
+}
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index 5bc0de4..a92350b 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -17,10 +17,8 @@
 
 import android.content.ComponentName;
 import android.media.MediaMetadata;
+import android.media.routing.IMediaRouter;
 import android.media.session.ISessionController;
-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;
@@ -34,17 +32,10 @@
     ISessionController getController();
     void setFlags(int flags);
     void setActive(boolean active);
+    void setMediaRouter(in IMediaRouter router);
     void setMediaButtonReceiver(in ComponentName mbr);
     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 disconnectFromRoute(in RouteInfo route);
-    void sendRouteCommand(in RouteCommand event, in ResultReceiver cb);
-
     // These commands are for the TransportPerformer
     void setMetadata(in MediaMetadata metadata);
     void setPlaybackState(in PlaybackState state);
@@ -53,4 +44,4 @@
     // These commands relate to volume handling
     void configureVolumeHandling(int type, int arg1, int arg2);
     void setCurrentVolume(int currentVolume);
-}
\ No newline at end of file
+}
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
index 0316d1fa..e554e27 100644
--- a/media/java/android/media/session/ISessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -16,9 +16,6 @@
 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;
@@ -29,11 +26,6 @@
 oneway interface ISessionCallback {
     void onCommand(String command, in Bundle extras, in ResultReceiver cb);
     void onMediaButton(in Intent mediaButtonIntent, int sequenceNumber, in ResultReceiver cb);
-    void onRequestRouteChange(in RouteInfo route);
-    void onRouteConnected(in RouteInfo route, in RouteOptions options);
-    void onRouteDisconnected(in RouteInfo route, int reason);
-    void onRouteStateChange(int state);
-    void onRouteEvent(in RouteEvent event);
 
     // These callbacks are for the TransportPerformer
     void onPlay();
@@ -49,4 +41,4 @@
     // These callbacks are for volume handling
     void onAdjustVolumeBy(int delta);
     void onSetVolumeTo(int value);
-}
\ No newline at end of file
+}
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index b4c11f6..6cf5ef2 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -18,6 +18,8 @@
 import android.content.Intent;
 import android.media.MediaMetadata;
 import android.media.Rating;
+import android.media.routing.IMediaRouterDelegate;
+import android.media.routing.IMediaRouterStateCallback;
 import android.media.session.ISessionControllerCallback;
 import android.media.session.MediaSessionInfo;
 import android.media.session.ParcelableVolumeInfo;
@@ -36,14 +38,15 @@
     void registerCallbackListener(in ISessionControllerCallback cb);
     void unregisterCallbackListener(in ISessionControllerCallback cb);
     boolean isTransportControlEnabled();
-    void showRoutePicker();
     MediaSessionInfo getSessionInfo();
     long getFlags();
     ParcelableVolumeInfo getVolumeAttributes();
     void adjustVolumeBy(int delta, int flags);
     void setVolumeTo(int value, int flags);
 
-    // These commands are for the TransportController
+    IMediaRouterDelegate createMediaRouterDelegate(IMediaRouterStateCallback callback);
+
+    // These commands are for the TransportControls
     void play();
     void pause();
     void stop();
@@ -56,4 +59,4 @@
     MediaMetadata getMetadata();
     PlaybackState getPlaybackState();
     int getRatingType();
-}
\ No newline at end of file
+}
diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/java/android/media/session/ISessionControllerCallback.aidl
index baa1379..64d2bc7 100644
--- a/media/java/android/media/session/ISessionControllerCallback.aidl
+++ b/media/java/android/media/session/ISessionControllerCallback.aidl
@@ -16,7 +16,6 @@
 package android.media.session;
 
 import android.media.MediaMetadata;
-import android.media.session.RouteInfo;
 import android.media.session.ParcelableVolumeInfo;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
@@ -26,10 +25,9 @@
  */
 oneway interface ISessionControllerCallback {
     void onEvent(String event, in Bundle extras);
-    void onRouteChanged(in RouteInfo route);
 
     // These callbacks are for the TransportController
     void onPlaybackStateChanged(in PlaybackState state);
     void onMetadataChanged(in MediaMetadata metadata);
     void onVolumeInfoChanged(in ParcelableVolumeInfo info);
-}
\ No newline at end of file
+}
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index edb69bc..cc8b31a 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -22,6 +22,7 @@
 import android.media.MediaMetadata;
 import android.media.Rating;
 import android.media.VolumeProvider;
+import android.media.routing.MediaRouter;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -52,8 +53,7 @@
     private static final int MSG_EVENT = 1;
     private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
     private static final int MSG_UPDATE_METADATA = 3;
-    private static final int MSG_ROUTE = 4;
-    private static final int MSG_UPDATE_VOLUME = 5;
+    private static final int MSG_UPDATE_VOLUME = 4;
 
     private final ISessionController mSessionBinder;
 
@@ -64,11 +64,11 @@
     private boolean mCbRegistered = false;
     private MediaSessionInfo mInfo;
 
-    private TransportControls mTransportController;
+    private final TransportControls mTransportControls;
 
     private MediaController(ISessionController sessionBinder) {
         mSessionBinder = sessionBinder;
-        mTransportController = new TransportControls();
+        mTransportControls = new TransportControls();
     }
 
     /**
@@ -91,12 +91,24 @@
     }
 
     /**
-     * Get a {@link TransportControls} instance for this session.
+     * Get a {@link TransportControls} instance to send transport actions to
+     * the associated session.
      *
-     * @return A controls instance
+     * @return A transport controls instance.
      */
     public @NonNull TransportControls getTransportControls() {
-        return mTransportController;
+        return mTransportControls;
+    }
+
+    /**
+     * Creates a media router delegate through which the destination of the media
+     * router may be observed and controlled.
+     *
+     * @return The media router delegate, or null if the media session does
+     * not support media routing.
+     */
+    public @Nullable MediaRouter.Delegate createMediaRouterDelegate() {
+        return new MediaRouter.Delegate();
     }
 
     /**
@@ -308,20 +320,6 @@
     }
 
     /**
-     * Request that the route picker be shown for this session. This should
-     * generally be called in response to a user action.
-     *
-     * @hide
-     */
-    public void showRoutePicker() {
-        try {
-            mSessionBinder.showRoutePicker();
-        } catch (RemoteException e) {
-            Log.d(TAG, "Dead object in showRoutePicker", e);
-        }
-    }
-
-    /**
      * Get the info for the session this controller is connected to.
      *
      * @return The session info for the connected session.
@@ -421,15 +419,6 @@
         }
 
         /**
-         * Override to handle route changes for this session.
-         *
-         * @param route The new route
-         * @hide
-         */
-        public void onRouteChanged(RouteInfo route) {
-        }
-
-        /**
          * Override to handle changes in playback state.
          *
          * @param state The new playback state of the session
@@ -670,14 +659,6 @@
         }
 
         @Override
-        public void onRouteChanged(RouteInfo route) {
-            MediaController controller = mController.get();
-            if (controller != null) {
-                controller.postMessage(MSG_ROUTE, route, null);
-            }
-        }
-
-        @Override
         public void onPlaybackStateChanged(PlaybackState state) {
             MediaController controller = mController.get();
             if (controller != null) {
@@ -719,9 +700,6 @@
                 case MSG_EVENT:
                     mCallback.onSessionEvent((String) msg.obj, msg.getData());
                     break;
-                case MSG_ROUTE:
-                    mCallback.onRouteChanged((RouteInfo) msg.obj);
-                    break;
                 case MSG_UPDATE_PLAYBACK_STATE:
                     mCallback.onPlaybackStateChanged((PlaybackState) msg.obj);
                     break;
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 2cbdc96..34997bd 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -25,6 +25,7 @@
 import android.media.MediaMetadata;
 import android.media.Rating;
 import android.media.VolumeProvider;
+import android.media.routing.MediaRouter;
 import android.media.session.ISessionController;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
@@ -93,40 +94,6 @@
     public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 1 << 16;
 
     /**
-     * Indicates the session was disconnected because the user that the session
-     * belonged to is stopping.
-     *
-     * @hide
-     */
-    public static final int DISCONNECT_REASON_USER_STOPPING = 1;
-
-    /**
-     * Indicates the session was disconnected because the provider disconnected
-     * the route.
-     * @hide
-     */
-    public static final int DISCONNECT_REASON_PROVIDER_DISCONNECTED = 2;
-
-    /**
-     * Indicates the session was disconnected because the route has changed.
-     * @hide
-     */
-    public static final int DISCONNECT_REASON_ROUTE_CHANGED = 3;
-
-    /**
-     * Indicates the session was disconnected because the session owner
-     * requested it disconnect.
-     * @hide
-     */
-    public static final int DISCONNECT_REASON_SESSION_DISCONNECTED = 4;
-
-    /**
-     * Indicates the session was disconnected because it was destroyed.
-     * @hide
-     */
-    public static final int DISCONNECT_REASON_SESSION_DESTROYED = 5;
-
-    /**
      * The session uses local playback.
      */
     public static final int PLAYBACK_TYPE_LOCAL = 1;
@@ -146,11 +113,7 @@
             = new ArrayList<CallbackMessageHandler>();
     private final ArrayList<TransportMessageHandler> mTransportCallbacks
             = new ArrayList<TransportMessageHandler>();
-    // TODO route interfaces
-    private final ArrayMap<String, RouteInterface.EventListener> mInterfaceListeners
-            = new ArrayMap<String, RouteInterface.EventListener>();
 
-    private Route mRoute;
     private VolumeProvider mVolumeProvider;
 
     private boolean mActive = false;
@@ -228,6 +191,23 @@
     }
 
     /**
+     * Associates a {@link MediaRouter} with this session to control the destination
+     * of media content.
+     * <p>
+     * A media router may only be associated with at most one session at a time.
+     * </p>
+     *
+     * @param router The media router, or null to remove the current association.
+     */
+    public void setMediaRouter(@Nullable MediaRouter router) {
+        try {
+            mBinder.setMediaRouter(router != null ? router.getBinder() : null);
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
+        }
+    }
+
+    /**
      * Set a media button event receiver component to use to restart playback
      * after an app has been stopped.
      *
@@ -377,90 +357,6 @@
     }
 
     /**
-     * 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.
-     * @hide
-     */
-    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.
-     *
-     * @hide
-     */
-    public void disconnect() {
-        if (mRoute != null) {
-            try {
-                mBinder.disconnectFromRoute(mRoute.getRouteInfo());
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error disconnecting from route");
-            }
-        }
-    }
-
-    /**
-     * 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.
-     * @hide
-     */
-    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;
-    }
-
-    /**
      * Add a callback to receive transport controls on, such as play, rewind, or
      * fast forward.
      *
@@ -670,34 +566,6 @@
         }
     }
 
-    private void postRequestRouteChange(RouteInfo route) {
-        synchronized (mLock) {
-            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-                mCallbacks.get(i).post(CallbackMessageHandler.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(CallbackMessageHandler.MSG_ROUTE_CONNECTED, mRoute);
-            }
-        }
-    }
-
-    private void postRouteDisconnected(RouteInfo route, int reason) {
-        synchronized (mLock) {
-            if (mRoute != null && TextUtils.equals(mRoute.getRouteInfo().getId(), route.getId())) {
-                for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-                    mCallbacks.get(i).post(CallbackMessageHandler.MSG_ROUTE_DISCONNECTED, mRoute,
-                            reason);
-                }
-            }
-        }
-    }
-
     /**
      * Return true if this is considered an active playback state.
      *
@@ -796,47 +664,6 @@
         public void onControlCommand(@NonNull String command, @Nullable Bundle extras,
                 @Nullable ResultReceiver cb) {
         }
-
-        /**
-         * Called when the user has selected a different route to connect to.
-         * The app is responsible for connecting to the new route and migrating
-         * ongoing playback if necessary.
-         *
-         * @param route
-         * @hide
-         */
-        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
-         * @hide
-         */
-        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>
-         * <li>{@link #DISCONNECT_REASON_USER_STOPPING}</li>
-         * <li>{@link #DISCONNECT_REASON_PROVIDER_DISCONNECTED}</li>
-         * <li>{@link #DISCONNECT_REASON_ROUTE_CHANGED}</li>
-         * <li>{@link #DISCONNECT_REASON_SESSION_DISCONNECTED}</li>
-         * <li>{@link #DISCONNECT_REASON_SESSION_DESTROYED}</li>
-         * </ul>
-         *
-         * @param route The route that disconnected
-         * @param reason The reason for the disconnect
-         * @hide
-         */
-        public void onRouteDisconnected(Route route, int reason) {
-        }
     }
 
     /**
@@ -902,17 +729,6 @@
          */
         public void onSetRating(@NonNull Rating rating) {
         }
-
-        /**
-         * Report that audio focus has changed on the app. This only happens if
-         * you have indicated you have started playing with
-         * {@link #setPlaybackState}.
-         *
-         * @param focusChange The type of focus change, TBD.
-         * @hide
-         */
-        public void onRouteFocusChange(int focusChange) {
-        }
     }
 
     /**
@@ -926,8 +742,7 @@
         }
 
         @Override
-        public void onCommand(String command, Bundle extras, ResultReceiver cb)
-                throws RemoteException {
+        public void onCommand(String command, Bundle extras, ResultReceiver cb) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.postCommand(command, extras, cb);
@@ -935,8 +750,8 @@
         }
 
         @Override
-        public void onMediaButton(Intent mediaButtonIntent, int sequenceNumber, ResultReceiver cb)
-                throws RemoteException {
+        public void onMediaButton(Intent mediaButtonIntent, int sequenceNumber,
+                ResultReceiver cb) {
             MediaSession session = mMediaSession.get();
             try {
                 if (session != null) {
@@ -950,31 +765,7 @@
         }
 
         @Override
-        public void onRequestRouteChange(RouteInfo route) throws RemoteException {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.postRequestRouteChange(route);
-            }
-        }
-
-        @Override
-        public void onRouteConnected(RouteInfo route, RouteOptions options) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.postRouteConnected(route, options);
-            }
-        }
-
-        @Override
-        public void onRouteDisconnected(RouteInfo route, int reason) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.postRouteDisconnected(route, reason);
-            }
-        }
-
-        @Override
-        public void onPlay() throws RemoteException {
+        public void onPlay() {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchPlay();
@@ -982,7 +773,7 @@
         }
 
         @Override
-        public void onPause() throws RemoteException {
+        public void onPause() {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchPause();
@@ -990,7 +781,7 @@
         }
 
         @Override
-        public void onStop() throws RemoteException {
+        public void onStop() {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchStop();
@@ -998,7 +789,7 @@
         }
 
         @Override
-        public void onNext() throws RemoteException {
+        public void onNext() {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchNext();
@@ -1006,7 +797,7 @@
         }
 
         @Override
-        public void onPrevious() throws RemoteException {
+        public void onPrevious() {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchPrevious();
@@ -1014,7 +805,7 @@
         }
 
         @Override
-        public void onFastForward() throws RemoteException {
+        public void onFastForward() {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchFastForward();
@@ -1022,7 +813,7 @@
         }
 
         @Override
-        public void onRewind() throws RemoteException {
+        public void onRewind() {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchRewind();
@@ -1030,7 +821,7 @@
         }
 
         @Override
-        public void onSeekTo(long pos) throws RemoteException {
+        public void onSeekTo(long pos) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchSeekTo(pos);
@@ -1038,7 +829,7 @@
         }
 
         @Override
-        public void onRate(Rating rating) throws RemoteException {
+        public void onRate(Rating rating) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchRate(rating);
@@ -1046,26 +837,7 @@
         }
 
         @Override
-        public void onRouteEvent(RouteEvent event) throws RemoteException {
-            MediaSession 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
-        }
-
-        @Override
-        public void onAdjustVolumeBy(int delta) throws RemoteException {
+        public void onAdjustVolumeBy(int delta) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 if (session.mVolumeProvider != null) {
@@ -1075,7 +847,7 @@
         }
 
         @Override
-        public void onSetVolumeTo(int value) throws RemoteException {
+        public void onSetVolumeTo(int value) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 if (session.mVolumeProvider != null) {
@@ -1089,9 +861,6 @@
     private class CallbackMessageHandler extends Handler {
         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 int MSG_ROUTE_DISCONNECTED = 5;
 
         private MediaSession.Callback mCallback;
 
@@ -1114,15 +883,6 @@
                         Command cmd = (Command) msg.obj;
                         mCallback.onControlCommand(cmd.command, cmd.extras, cmd.stub);
                         break;
-                    case MSG_ROUTE_CHANGE:
-                        mCallback.onRequestRouteChange((RouteInfo) msg.obj);
-                        break;
-                    case MSG_ROUTE_CONNECTED:
-                        mCallback.onRouteConnected((Route) msg.obj);
-                        break;
-                    case MSG_ROUTE_DISCONNECTED:
-                        mCallback.onRouteDisconnected((Route) msg.obj, msg.arg1);
-                        break;
                 }
             }
         }
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index 5d1a7e02..11f7720 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -202,7 +202,7 @@
                 if (up) {
                     flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
                 } else {
-                    flags = AudioManager.FLAG_SHOW_UI;
+                    flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
                 }
             }
 
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index c73a8d3..c477406 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -35,9 +35,8 @@
 import java.util.List;
 
 /**
- * MediaSessionManager allows the creation and control of MediaSessions in the
- * system. A MediaSession enables publishing information about ongoing media and
- * interacting with MediaControllers and MediaRoutes.
+ * Provides support for interacting with {@link MediaSession media sessions}
+ * that applications have published to express their ongoing media playback state.
  * <p>
  * Use <code>Context.getSystemService(Context.MEDIA_SESSION_SERVICE)</code> to
  * get an instance of this class.
@@ -256,8 +255,8 @@
     }
 
     /**
-     * Dispatch an adjust volume request to the system. It will be routed to the
-     * most relevant stream/session.
+     * Dispatch an adjust volume request to the system. It will be sent to the
+     * most relevant audio stream or media session.
      *
      * @param suggestedStream The stream to fall back to if there isn't a
      *            relevant stream
@@ -292,8 +291,7 @@
 
         private final IActiveSessionsListener.Stub mStub = new IActiveSessionsListener.Stub() {
             @Override
-            public void onActiveSessionsChanged(List<MediaSession.Token> tokens)
-                    throws RemoteException {
+            public void onActiveSessionsChanged(List<MediaSession.Token> tokens) {
                 ArrayList<MediaController> controllers = new ArrayList<MediaController>();
                 int size = tokens.size();
                 for (int i = 0; i < size; i++) {
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 6125cb4..9ae2436 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -157,10 +157,11 @@
 
     /**
      * 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 #STATE_NONE}. If
-     * the connection failed {@link #STATE_ERROR} should be used.
-     * @hide
+     * new destination.  Depending on the implementation you may return to the previous
+     * state when the connection finishes or enter {@link #STATE_NONE}.
+     * If the connection failed {@link #STATE_ERROR} should be used.
+     *
+     * @see #setState
      */
     public final static int STATE_CONNECTING = 8;
 
diff --git a/media/java/android/media/session/Route.java b/media/java/android/media/session/Route.java
deleted file mode 100644
index 935eb5b..0000000
--- a/media/java/android/media/session/Route.java
+++ /dev/null
@@ -1,100 +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.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.
- * @hide
- */
-public final class Route {
-    private static final String TAG = "Route";
-    private final RouteInfo mInfo;
-    private final MediaSession mSession;
-    private final RouteOptions mOptions;
-
-    /**
-     * @hide
-     */
-    public Route(RouteInfo info, RouteOptions options, MediaSession 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
-     */
-    MediaSession getSession() {
-        return mSession;
-    }
-}
diff --git a/media/java/android/media/session/RouteCommand.java b/media/java/android/media/session/RouteCommand.java
deleted file mode 100644
index 358bc0a..0000000
--- a/media/java/android/media/session/RouteCommand.java
+++ /dev/null
@@ -1,117 +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.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/RouteEvent.aidl b/media/java/android/media/session/RouteEvent.aidl
deleted file mode 100644
index 6966207..0000000
--- a/media/java/android/media/session/RouteEvent.aidl
+++ /dev/null
@@ -1,18 +0,0 @@
-/* Copyright 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;
-
-parcelable RouteEvent;
diff --git a/media/java/android/media/session/RouteEvent.java b/media/java/android/media/session/RouteEvent.java
deleted file mode 100644
index 918e410..0000000
--- a/media/java/android/media/session/RouteEvent.java
+++ /dev/null
@@ -1,120 +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.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/RouteInfo.aidl b/media/java/android/media/session/RouteInfo.aidl
deleted file mode 100644
index c5f50c8..0000000
--- a/media/java/android/media/session/RouteInfo.aidl
+++ /dev/null
@@ -1,18 +0,0 @@
-/* Copyright 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;
-
-parcelable RouteInfo;
diff --git a/media/java/android/media/session/RouteInfo.java b/media/java/android/media/session/RouteInfo.java
deleted file mode 100644
index 02f78f9..0000000
--- a/media/java/android/media/session/RouteInfo.java
+++ /dev/null
@@ -1,234 +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.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.
- * @hide
- */
-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
deleted file mode 100644
index 8de4d89..0000000
--- a/media/java/android/media/session/RouteInterface.java
+++ /dev/null
@@ -1,213 +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.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.ResultReceiver;
-import android.util.Log;
-
-import java.util.ArrayList;
-
-/**
- * A route can support multiple interfaces for a {@link MediaSession} 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 RoutePlaybackControls for an example
- * @hide
- */
-public final class RouteInterface {
-    private static final String TAG = "RouteInterface";
-
-    /**
-     * 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 Route mRoute;
-    private final String mIface;
-    private final MediaSession mSession;
-
-    private final Object mLock = new Object();
-    private final ArrayList<EventHandler> mListeners = new ArrayList<EventHandler>();
-
-    /**
-     * @hide
-     */
-    RouteInterface(Route route, String iface, MediaSession session) {
-        mRoute = route;
-        mIface = iface;
-        mSession = session;
-        mSession.addInterfaceListener(iface, mEventListener);
-    }
-
-    /**
-     * 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) {
-        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) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener may not be null");
-        }
-        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.
-     */
-    public static abstract class EventListener {
-        /**
-         * This is called when an event is received from the interface. Events
-         * are sent by the session owner and will be delivered to all
-         * controllers that are listening to the interface.
-         *
-         * @param event The event that occurred.
-         * @param args Any extras that were included with the event. May be
-         *            null.
-         */
-        public abstract void onEvent(String event, Bundle args);
-    }
-
-    private static final class EventHandler extends Handler {
-
-        private final EventListener mListener;
-
-        public EventHandler(Looper looper, EventListener cb) {
-            super(looper, null, true);
-            mListener = cb;
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            mListener.onEvent((String) msg.obj, msg.getData());
-        }
-
-        public void postEvent(String event, Bundle args) {
-            Message msg = obtainMessage(0, event);
-            msg.setData(args);
-            msg.sendToTarget();
-        }
-    }
-}
diff --git a/media/java/android/media/session/RouteOptions.aidl b/media/java/android/media/session/RouteOptions.aidl
deleted file mode 100644
index feaf517..0000000
--- a/media/java/android/media/session/RouteOptions.aidl
+++ /dev/null
@@ -1,18 +0,0 @@
-/* Copyright 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;
-
-parcelable RouteOptions;
diff --git a/media/java/android/media/session/RouteOptions.java b/media/java/android/media/session/RouteOptions.java
deleted file mode 100644
index b4fb341..0000000
--- a/media/java/android/media/session/RouteOptions.java
+++ /dev/null
@@ -1,164 +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.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.
- * @hide
- */
-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
deleted file mode 100644
index 8211983..0000000
--- a/media/java/android/media/session/RoutePlaybackControls.java
+++ /dev/null
@@ -1,163 +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.MediaMetadata;
-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.
- * @hide
- */
-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/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index 223013f..260ee397 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -74,7 +74,7 @@
         </activity>
 
         <receiver
-            android:name=".NotificationController$NotificationBroadcastReceiver"
+            android:name=".model.NotificationController$NotificationBroadcastReceiver"
             android:exported="false" >
         </receiver>
 
diff --git a/packages/PrintSpooler/res/values/constants.xml b/packages/PrintSpooler/res/values/constants.xml
index c17c73b..faad527 100644
--- a/packages/PrintSpooler/res/values/constants.xml
+++ b/packages/PrintSpooler/res/values/constants.xml
@@ -42,4 +42,7 @@
 
     <integer name="print_option_column_count">2</integer>
 
+    <fraction name="page_selected_alpha">100%</fraction>
+    <fraction name="page_unselected_alpha">50%</fraction>
+
 </resources>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
index d37ccc0..3134e93 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
@@ -56,8 +56,6 @@
     private static final String INTENT_ACTION_RESTART_PRINTJOB = "INTENT_ACTION_RESTART_PRINTJOB";
 
     private static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID";
-    private static final String EXTRA_PRINTJOB_LABEL = "EXTRA_PRINTJOB_LABEL";
-    private static final String EXTRA_PRINTER_NAME = "EXTRA_PRINTER_NAME";
 
     private final Context mContext;
     private final NotificationManager mNotificationManager;
@@ -69,7 +67,7 @@
     }
 
     public void onUpdateNotifications(List<PrintJobInfo> printJobs) {
-        List<PrintJobInfo> notifyPrintJobs = new ArrayList<PrintJobInfo>();
+        List<PrintJobInfo> notifyPrintJobs = new ArrayList<>();
 
         final int printJobCount = printJobs.size();
         for (int i = 0; i < printJobCount; i++) {
@@ -252,8 +250,6 @@
         Intent intent = new Intent(mContext, NotificationBroadcastReceiver.class);
         intent.setAction(INTENT_ACTION_CANCEL_PRINTJOB + "_" + printJob.getId().flattenToString());
         intent.putExtra(EXTRA_PRINT_JOB_ID, printJob.getId());
-        intent.putExtra(EXTRA_PRINTJOB_LABEL, printJob.getLabel());
-        intent.putExtra(EXTRA_PRINTER_NAME, printJob.getPrinterName());
         return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
     }
 
@@ -302,17 +298,14 @@
             String action = intent.getAction();
             if (action != null && action.startsWith(INTENT_ACTION_CANCEL_PRINTJOB)) {
                 PrintJobId printJobId = intent.getExtras().getParcelable(EXTRA_PRINT_JOB_ID);
-                String printJobLabel = intent.getExtras().getString(EXTRA_PRINTJOB_LABEL);
-                String printerName = intent.getExtras().getString(EXTRA_PRINTER_NAME);
-                handleCancelPrintJob(context, printJobId, printJobLabel, printerName);
+                handleCancelPrintJob(context, printJobId);
             } else if (action != null && action.startsWith(INTENT_ACTION_RESTART_PRINTJOB)) {
                 PrintJobId printJobId = intent.getExtras().getParcelable(EXTRA_PRINT_JOB_ID);
                 handleRestartPrintJob(context, printJobId);
             }
         }
 
-        private void handleCancelPrintJob(final Context context, final PrintJobId printJobId,
-                final String printJobLabel, final String printerName) {
+        private void handleCancelPrintJob(final Context context, final PrintJobId printJobId) {
             if (DEBUG) {
                 Log.i(LOG_TAG, "handleCancelPrintJob() printJobId:" + printJobId);
             }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
index d802cd8..8a65a2e 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
@@ -27,6 +27,7 @@
 import android.print.PrinterId;
 import android.print.PrinterInfo;
 import android.printservice.PrintServiceInfo;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -68,10 +69,10 @@
     private static final int MAX_FAVORITE_PRINTER_COUNT = 4;
 
     private final List<PrinterInfo> mPrinters =
-            new ArrayList<PrinterInfo>();
+            new ArrayList<>();
 
     private final List<PrinterInfo> mFavoritePrinters =
-            new ArrayList<PrinterInfo>();
+            new ArrayList<>();
 
     private final PersistenceManager mPersistenceManager;
 
@@ -92,7 +93,7 @@
 
     private void computeAndDeliverResult(ArrayMap<PrinterId, PrinterInfo> discoveredPrinters,
             ArrayMap<PrinterId, PrinterInfo> favoritePrinters) {
-        List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
+        List<PrinterInfo> printers = new ArrayList<>();
 
         // Add the updated favorite printers.
         final int favoritePrinterCount = favoritePrinters.size();
@@ -142,7 +143,7 @@
         // The contract is that if we already have a valid,
         // result the we have to deliver it immediately.
         if (!mPrinters.isEmpty()) {
-            deliverResult(new ArrayList<PrinterInfo>(mPrinters));
+            deliverResult(new ArrayList<>(mPrinters));
         }
         // Always load the data to ensure discovery period is
         // started and to make sure obsolete printers are updated.
@@ -184,11 +185,12 @@
                                 + mDiscoverySession.getPrinters().size()
                                 + " " + FusedPrintersProvider.this.hashCode());
                     }
+
                     updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters);
                 }
             });
             final int favoriteCount = mFavoritePrinters.size();
-            List<PrinterId> printerIds = new ArrayList<PrinterId>(favoriteCount);
+            List<PrinterId> printerIds = new ArrayList<>(favoriteCount);
             for (int i = 0; i < favoriteCount; i++) {
                 printerIds.add(mFavoritePrinters.get(i).getId());
             }
@@ -208,16 +210,19 @@
 
         mPrintersUpdatedBefore = true;
 
-        ArrayMap<PrinterId, PrinterInfo> printersMap =
-                new ArrayMap<PrinterId, PrinterInfo>();
+        // Some of the found printers may have be a printer that is in the
+        // history but with its name changed. Hence, we try to update the
+        // printer to use its current name instead of the historical one.
+        mPersistenceManager.updatePrintersHistoricalNamesIfNeeded(printers);
+
+        ArrayMap<PrinterId, PrinterInfo> printersMap = new ArrayMap<>();
         final int printerCount = printers.size();
         for (int i = 0; i < printerCount; i++) {
             PrinterInfo printer = printers.get(i);
             printersMap.put(printer.getId(), printer);
         }
 
-        ArrayMap<PrinterId, PrinterInfo> favoritePrintersMap =
-                new ArrayMap<PrinterId, PrinterInfo>();
+        ArrayMap<PrinterId, PrinterInfo> favoritePrintersMap = new ArrayMap<>();
         final int favoritePrinterCount = favoritePrinters.size();
         for (int i = 0; i < favoritePrinterCount; i++) {
             PrinterInfo favoritePrinter = favoritePrinters.get(i);
@@ -271,6 +276,10 @@
         onStopLoading();
     }
 
+    public boolean areHistoricalPrintersLoaded() {
+        return mPersistenceManager.mReadHistoryCompleted;
+    }
+
     public void setTrackedPrinter(PrinterId printerId) {
         if (isStarted() && mDiscoverySession != null
                 && mDiscoverySession.isPrinterDiscoveryStarted()) {
@@ -306,7 +315,7 @@
         for (int i = 0; i < favoritePrinterCount; i++) {
             PrinterInfo favoritePrinter = mFavoritePrinters.get(i);
             if (favoritePrinter.getId().equals(printerId)) {
-                newFavoritePrinters = new ArrayList<PrinterInfo>();
+                newFavoritePrinters = new ArrayList<>();
                 newFavoritePrinters.addAll(mPrinters);
                 newFavoritePrinters.remove(i);
                 break;
@@ -340,7 +349,7 @@
 
         private final AtomicFile mStatePersistFile;
 
-        private List<PrinterInfo> mHistoricalPrinters = new ArrayList<PrinterInfo>();
+        private List<PrinterInfo> mHistoricalPrinters = new ArrayList<>();
 
         private boolean mReadHistoryCompleted;
         private boolean mReadHistoryInProgress;
@@ -378,17 +387,42 @@
             mReadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
         }
 
-        @SuppressWarnings("unchecked")
+        public void updatePrintersHistoricalNamesIfNeeded(List<PrinterInfo> printers) {
+            boolean writeHistory = false;
+
+            final int printerCount = printers.size();
+            for (int i = 0; i < printerCount; i++) {
+                PrinterInfo printer = printers.get(i);
+                writeHistory |= renamePrinterIfNeeded(printer);
+            }
+
+            if (writeHistory) {
+                writePrinterHistory();
+            }
+        }
+
+        public boolean renamePrinterIfNeeded(PrinterInfo printer) {
+            boolean renamed = false;
+            final int printerCount = mHistoricalPrinters.size();
+            for (int i = 0; i < printerCount; i++) {
+                PrinterInfo historicalPrinter = mHistoricalPrinters.get(i);
+                if (historicalPrinter.getId().equals(printer.getId())
+                        && !TextUtils.equals(historicalPrinter.getName(), printer.getName())) {
+                    mHistoricalPrinters.set(i, printer);
+                    renamed = true;
+                }
+            }
+            return renamed;
+        }
+
         public void addPrinterAndWritePrinterHistory(PrinterInfo printer) {
             if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) {
                 mHistoricalPrinters.remove(0);
             }
             mHistoricalPrinters.add(printer);
-            new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
-                    new ArrayList<PrinterInfo>(mHistoricalPrinters));
+            writePrinterHistory();
         }
 
-        @SuppressWarnings("unchecked")
         public void removeHistoricalPrinterAndWritePrinterHistory(PrinterId printerId) {
             boolean writeHistory = false;
             final int printerCount = mHistoricalPrinters.size();
@@ -400,18 +434,22 @@
                 }
             }
             if (writeHistory) {
-                new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
-                        new ArrayList<PrinterInfo>(mHistoricalPrinters));
+                writePrinterHistory();
             }
         }
 
+        @SuppressWarnings("unchecked")
+        private void writePrinterHistory() {
+            new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
+                    new ArrayList<>(mHistoricalPrinters));
+        }
+
         public boolean isHistoryChanged() {
             return mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified();
         }
 
         private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) {
-            Map<PrinterId, PrinterRecord> recordMap =
-                    new ArrayMap<PrinterId, PrinterRecord>();
+            Map<PrinterId, PrinterRecord> recordMap = new ArrayMap<>();
 
             // Recompute the weights.
             float currentWeight = 1.0f;
@@ -429,14 +467,14 @@
             }
 
             // Soft the favorite printers.
-            List<PrinterRecord> favoriteRecords = new ArrayList<PrinterRecord>(
+            List<PrinterRecord> favoriteRecords = new ArrayList<>(
                     recordMap.values());
             Collections.sort(favoriteRecords);
 
             // Write the favorites to the output.
             final int favoriteCount = Math.min(favoriteRecords.size(),
                     MAX_FAVORITE_PRINTER_COUNT);
-            List<PrinterInfo> favoritePrinters = new ArrayList<PrinterInfo>(favoriteCount);
+            List<PrinterInfo> favoritePrinters = new ArrayList<>(favoriteCount);
             for (int i = 0; i < favoriteCount; i++) {
                 PrinterInfo printer = favoriteRecords.get(i).printer;
                 favoritePrinters.add(printer);
@@ -478,7 +516,7 @@
                 List<PrintServiceInfo> services = printManager
                         .getEnabledPrintServices();
 
-                Set<ComponentName> enabledComponents = new ArraySet<ComponentName>();
+                Set<ComponentName> enabledComponents = new ArraySet<>();
                 final int installedServiceCount = services.size();
                 for (int i = 0; i < installedServiceCount; i++) {
                     ServiceInfo serviceInfo = services.get(i).getResolveInfo().serviceInfo;
@@ -524,28 +562,23 @@
                         Log.i(LOG_TAG, "No existing printer history "
                                 + FusedPrintersProvider.this.hashCode());
                     }
-                    return new ArrayList<PrinterInfo>();
+                    return new ArrayList<>();
                 }
                 try {
-                    List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
+                    List<PrinterInfo> printers = new ArrayList<>();
                     XmlPullParser parser = Xml.newPullParser();
                     parser.setInput(in, null);
                     parseState(parser, printers);
                     // Take a note which version of the history was read.
                     mLastReadHistoryTimestamp = mStatePersistFile.getBaseFile().lastModified();
                     return printers;
-                } catch (IllegalStateException ise) {
-                    Slog.w(LOG_TAG, "Failed parsing ", ise);
-                } catch (NullPointerException npe) {
-                    Slog.w(LOG_TAG, "Failed parsing ", npe);
-                } catch (NumberFormatException nfe) {
-                    Slog.w(LOG_TAG, "Failed parsing ", nfe);
-                } catch (XmlPullParserException xppe) {
-                    Slog.w(LOG_TAG, "Failed parsing ", xppe);
-                } catch (IOException ioe) {
-                    Slog.w(LOG_TAG, "Failed parsing ", ioe);
-                } catch (IndexOutOfBoundsException iobe) {
-                    Slog.w(LOG_TAG, "Failed parsing ", iobe);
+                } catch (IllegalStateException
+                        | NullPointerException
+                        | NumberFormatException
+                        | XmlPullParserException
+                        | IOException
+                        | IndexOutOfBoundsException e) {
+                    Slog.w(LOG_TAG, "Failed parsing ", e);
                 } finally {
                     IoUtils.closeQuietly(in);
                 }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
index 8885a7b..eaf268d 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
@@ -90,7 +90,10 @@
     private int mSelectedPageCount;
 
     private float mSelectedPageElevation;
+    private float mSelectedPageAlpha;
+
     private float mUnselectedPageElevation;
+    private float mUnselectedPageAlpha;
 
     private int mPreviewPageMargin;
     private int mPreviewListPadding;
@@ -127,8 +130,13 @@
 
         mSelectedPageElevation = mContext.getResources().getDimension(
                 R.dimen.selected_page_elevation);
+        mSelectedPageAlpha = mContext.getResources().getFraction(
+                R.fraction.page_selected_alpha, 1, 1);
+
         mUnselectedPageElevation = mContext.getResources().getDimension(
                 R.dimen.unselected_page_elevation);
+        mUnselectedPageAlpha = mContext.getResources().getFraction(
+                R.fraction.page_unselected_alpha, 1, 1);
 
         mPreviewPageMargin = mContext.getResources().getDimensionPixelSize(
                 R.dimen.preview_page_margin);
@@ -339,9 +347,11 @@
         if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) >= 0) {
             checkbox.setChecked(true);
             page.setTranslationZ(mSelectedPageElevation);
+            page.setAlpha(mSelectedPageAlpha);
         } else {
             checkbox.setChecked(false);
             page.setTranslationZ(mUnselectedPageElevation);
+            page.setAlpha(mUnselectedPageAlpha);
         }
 
         TextView pageNumberView = (TextView) page.findViewById(R.id.page_number);
@@ -760,11 +770,13 @@
             if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) < 0) {
                 mConfirmedPagesInDocument.put(pageInDocument, null);
                 pageSelector.setChecked(true);
-                page.animate().translationZ(mSelectedPageElevation);
+                page.animate().translationZ(mSelectedPageElevation)
+                        .alpha(mSelectedPageAlpha);
             } else {
                 mConfirmedPagesInDocument.remove(pageInDocument);
                 pageSelector.setChecked(false);
-                page.animate().translationZ(mUnselectedPageElevation);
+                page.animate().translationZ(mUnselectedPageElevation)
+                        .alpha(mUnselectedPageAlpha);
             }
         }
     }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 5ec2111..5e646ff 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -190,7 +190,7 @@
 
     private MediaSizeComparator mMediaSizeComparator;
 
-    private PrinterInfo mOldCurrentPrinter;
+    private PrinterInfo mCurrentPrinter;
 
     private PageRange[] mSelectedPages;
 
@@ -576,7 +576,7 @@
             }
         }
 
-        PrinterId printerId = mOldCurrentPrinter.getId();
+        PrinterId printerId = mCurrentPrinter.getId();
         final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
         mDestinationSpinner.setSelection(index);
     }
@@ -810,7 +810,7 @@
     }
 
     private void requestCreatePdfFileOrFinish() {
-        if (getCurrentPrinter() == mDestinationSpinnerAdapter.getPdfPrinter()) {
+        if (mCurrentPrinter == mDestinationSpinnerAdapter.getPdfPrinter()) {
             startCreateDocumentActivity();
         } else {
             finish();
@@ -898,19 +898,14 @@
     }
 
     private void addCurrentPrinterToHistory() {
-        PrinterInfo currentPrinter = getCurrentPrinter();
-        if (currentPrinter != null) {
+        if (mCurrentPrinter != null) {
             PrinterId fakePdfPrinterId = mDestinationSpinnerAdapter.getPdfPrinter().getId();
-            if (!currentPrinter.getId().equals(fakePdfPrinterId)) {
-                mPrinterRegistry.addHistoricalPrinter(currentPrinter);
+            if (!mCurrentPrinter.getId().equals(fakePdfPrinterId)) {
+                mPrinterRegistry.addHistoricalPrinter(mCurrentPrinter);
             }
         }
     }
 
-    private PrinterInfo getCurrentPrinter() {
-        return ((PrinterHolder) mDestinationSpinner.getSelectedItem()).printer;
-    }
-
     private void cancelPrint() {
         setState(STATE_PRINT_CANCELED);
         updateOptionsUi();
@@ -970,7 +965,6 @@
         mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
         mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
         mDestinationSpinner.setOnItemSelectedListener(itemSelectedListener);
-        mDestinationSpinner.setSelection(0);
 
         // Media size.
         mMediaSizeSpinnerAdapter = new ArrayAdapter<>(
@@ -1036,16 +1030,14 @@
         @Override
         public void onClick(View view) {
             if (view == mPrintButton) {
-                PrinterInfo currentPrinter = getCurrentPrinter();
-                if (currentPrinter != null) {
+                if (mCurrentPrinter != null) {
                     confirmPrint();
                 } else {
                     cancelPrint();
                 }
             } else if (view == mMoreOptionsButton) {
-                PrinterInfo currentPrinter = getCurrentPrinter();
-                if (currentPrinter != null) {
-                    startAdvancedPrintOptionsActivity(currentPrinter);
+                if (mCurrentPrinter != null) {
+                    startAdvancedPrintOptionsActivity(mCurrentPrinter);
                 }
             }
         }
@@ -1090,8 +1082,7 @@
 
         // If no current printer, or it has no capabilities, or it is not
         // available, we disable all print options except the destination.
-        PrinterInfo currentPrinter = getCurrentPrinter();
-        if (currentPrinter == null || !canPrint(currentPrinter)) {
+        if (mCurrentPrinter == null || !canPrint(mCurrentPrinter)) {
             mCopiesEditText.setEnabled(false);
             mMediaSizeSpinner.setEnabled(false);
             mColorModeSpinner.setEnabled(false);
@@ -1103,7 +1094,7 @@
             return;
         }
 
-        PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
+        PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
         PrintAttributes defaultAttributes = capabilities.getDefaults();
 
         // Destination.
@@ -1291,7 +1282,7 @@
         }
 
         // Advanced print options
-        ComponentName serviceName = currentPrinter.getId().getServiceName();
+        ComponentName serviceName = mCurrentPrinter.getId().getServiceName();
         if (!TextUtils.isEmpty(PrintOptionUtils.getAdvancedOptionsActivityName(
                 this, serviceName))) {
             mAdvancedPrintOptionsContainer.setVisibility(View.VISIBLE);
@@ -1302,7 +1293,7 @@
         }
 
         // Print
-        if (mDestinationSpinnerAdapter.getPdfPrinter() != currentPrinter) {
+        if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) {
             mPrintButton.setImageResource(com.android.internal.R.drawable.ic_print);
         } else {
             mPrintButton.setImageResource(com.android.internal.R.drawable.ic_menu_save);
@@ -1317,7 +1308,7 @@
         }
 
         // Copies
-        if (mDestinationSpinnerAdapter.getPdfPrinter() != currentPrinter) {
+        if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) {
             mCopiesEditText.setEnabled(true);
         } else {
             mCopiesEditText.setEnabled(false);
@@ -1395,8 +1386,7 @@
     }
 
     public void onPrinterAvailable(PrinterInfo printer) {
-        PrinterInfo currentPrinter = getCurrentPrinter();
-        if (currentPrinter.equals(printer)) {
+        if (mCurrentPrinter.equals(printer)) {
             setState(STATE_CONFIGURING);
             if (canUpdateDocument()) {
                 updateDocument(true, false);
@@ -1407,7 +1397,7 @@
     }
 
     public void onPrinterUnavailable(PrinterInfo printer) {
-        if (getCurrentPrinter().getId().equals(printer.getId())) {
+        if (mCurrentPrinter.getId().equals(printer.getId())) {
             setState(STATE_PRINTER_UNAVAILABLE);
             if (mPrintedDocument.isUpdating()) {
                 mPrintedDocument.cancel();
@@ -1444,15 +1434,14 @@
             return false;
         }
 
-        PrinterInfo currentPrinter = getCurrentPrinter();
-        if (currentPrinter == null) {
+        if (mCurrentPrinter == null) {
             return false;
         }
-        PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
+        PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
         if (capabilities == null) {
             return false;
         }
-        if (currentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
+        if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
             return false;
         }
 
@@ -1560,8 +1549,13 @@
 
         private final PrinterHolder mFakePdfPrinterHolder;
 
+        private boolean mHistoricalPrintersLoaded;
+
         public DestinationAdapter() {
-            addPrinters(mPrinterHolders, mPrinterRegistry.getPrinters());
+            mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded();
+            if (mHistoricalPrintersLoaded) {
+                addPrinters(mPrinterHolders, mPrinterRegistry.getPrinters());
+            }
             mPrinterRegistry.setOnPrintersChangeListener(this);
             mFakePdfPrinterHolder = new PrinterHolder(createFakePdfPrinter());
         }
@@ -1602,7 +1596,10 @@
 
         @Override
         public int getCount() {
-            return Math.min(mPrinterHolders.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT);
+            if (mHistoricalPrintersLoaded) {
+                return Math.min(mPrinterHolders.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT);
+            }
+            return 0;
         }
 
         @Override
@@ -1731,6 +1728,12 @@
             // not shown in the initial short list. Therefore, we have
             // to keep the printer order.
 
+            // Check if historical printers are loaded as this adapter is open
+            // for busyness only if they are. This member is updated here and
+            // when the adapter is created because the historical printers may
+            // be loaded before or after the adapter is created.
+            mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded();
+
             // No old printers - do not bother keeping their position.
             if (mPrinterHolders.isEmpty()) {
                 addPrinters(mPrinterHolders, printers);
@@ -1839,7 +1842,7 @@
     private final class PrintersObserver extends DataSetObserver {
         @Override
         public void onChanged() {
-            PrinterInfo oldPrinterState = mOldCurrentPrinter;
+            PrinterInfo oldPrinterState = mCurrentPrinter;
             if (oldPrinterState == null) {
                 return;
             }
@@ -1927,14 +1930,15 @@
                     return;
                 }
 
-                PrinterInfo currentPrinter = getCurrentPrinter();
+                PrinterHolder currentItem = (PrinterHolder) mDestinationSpinner.getSelectedItem();
+                PrinterInfo currentPrinter = (currentItem != null) ? currentItem.printer : null;
 
                 // Why on earth item selected is called if no selection changed.
-                if (mOldCurrentPrinter == currentPrinter) {
+                if (mCurrentPrinter == currentPrinter) {
                     return;
                 }
 
-                mOldCurrentPrinter = currentPrinter;
+                mCurrentPrinter = currentPrinter;
 
                 PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder(
                         currentPrinter.getId());
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java
index 7816d66..a3d7f01 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java
@@ -83,6 +83,10 @@
         getPrinterProvider().setTrackedPrinter(printerId);
     }
 
+    public boolean areHistoricalPrintersLoaded() {
+        return getPrinterProvider().areHistoricalPrintersLoaded();
+    }
+
     private FusedPrintersProvider getPrinterProvider() {
         Loader<?> loader = mActivity.getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER);
         return (FusedPrintersProvider) loader;
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
index afdbb2a..555aa97 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
@@ -147,6 +147,7 @@
         mExpandCollapseIcon = findViewById(R.id.expand_collapse_icon);
 
         mExpandCollapseHandle.setOnClickListener(this);
+        mSummaryContent.setOnClickListener(this);
 
         // Make sure we start in a closed options state.
         onDragProgress(1.0f);
@@ -154,7 +155,7 @@
 
     @Override
     public void onClick(View view) {
-        if (view == mExpandCollapseHandle) {
+        if (view == mExpandCollapseHandle || view == mSummaryContent) {
             if (isOptionsClosed() && mOptionsStateController.canOpenOptions()) {
                 openOptions();
             } else if (isOptionsOpened() && mOptionsStateController.canCloseOptions()) {
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_01.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_01.xml
deleted file mode 100644
index a6c2cf8..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_01.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M10.25,1.75c-0.6,-0.6 -1.5,-0.6 -2.1,0.0l-6.4,6.4c-0.6,0.6 -0.6,1.5 0.0,2.1l12.0,12.0c0.6,0.6 1.5,0.6 2.1,0.0l6.4,-6.4c0.6,-0.6 0.6,-1.5 0.0,-2.1L10.25,1.75zM14.85,21.25l-12.0,-12.0l6.4,-6.4l12.0,12.0L14.85,21.25z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M16.55,2.5c3.3,1.5 5.6,4.7 6.0,8.5l1.5,0.0c-0.6,-6.2 -5.7,-11.0 -12.0,-11.0c-0.2,0.0 -0.4,0.0 -0.7,0.0l3.8,3.8L16.55,2.5z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M7.55,21.5c-3.3,-1.5 -5.6,-4.7 -6.0,-8.5l-1.4,0.0c0.5,6.2 5.6,11.0 11.9,11.0c0.2,0.0 0.4,0.0 0.7,0.0l-3.8,-3.8L7.55,21.5z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_02.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_02.xml
deleted file mode 100644
index 4107c46..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_02.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M10.24,2.43C9.67,1.88 8.83,1.88 8.28,2.45L2.34,8.48c-0.56,0.57 -0.55,1.41 0.02,1.96l11.3,11.13c0.57,0.56 1.41,0.55 1.96,-0.02l5.93,-6.03c0.56,-0.57 0.55,-1.41 -0.02,-1.96L10.24,2.43zM14.68,20.62L3.38,9.5l5.93,-6.03l11.3,11.13L14.68,20.62z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M16.91,2.71c3.23,1.64 5.39,4.94 5.62,8.76l1.5,0.07c-0.33,-6.22 -5.21,-11.24 -11.5,-11.52c-0.2,-0.01 -0.4,-0.02 -0.7,-0.03l3.63,3.96L16.91,2.71z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M7.09,21.29c-3.23,-1.64 -5.39,-4.94 -5.62,-8.76l-1.4,-0.06c0.23,6.22 5.11,11.24 11.4,11.51c0.2,0.01 0.4,0.02 0.7,0.03l-3.63,-3.96L7.09,21.29z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_03.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_03.xml
deleted file mode 100644
index 127296c4..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_03.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M11.07,4.12c-0.43,-0.49 -1.11,-0.53 -1.6,-0.1L4.3,8.57C3.81,9.0 3.77,9.68 4.19,10.17l8.54,9.71c0.43,0.49 1.11,0.53 1.6,0.1l5.18,-4.55c0.49,-0.43 0.53,-1.11 0.1,-1.6L11.07,4.12zM13.61,19.17L5.08,9.46l5.18,-4.55l8.54,9.71L13.61,19.17z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M17.72,3.2c3.06,1.94 4.9,5.43 4.77,9.25l1.49,0.21C24.23,6.43 19.84,0.97 13.6,0.11c-0.2,-0.03 -0.4,-0.06 -0.69,-0.1l3.24,4.29L17.72,3.2z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M6.19,20.78c-3.06,-1.94 -4.9,-5.43 -4.77,-9.25l-1.39,-0.19c-0.36,6.21 4.03,11.67 10.27,12.53c0.2,0.03 0.4,0.06 0.69,0.1l-3.24,-4.29L6.19,20.78z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_04.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_04.xml
deleted file mode 100644
index d00262ab..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_04.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M11.88,5.72c-0.3,-0.42 -0.83,-0.51 -1.25,-0.21L6.19,8.69c-0.42,0.3 -0.51,0.83 -0.21,1.25l5.95,8.34c0.3,0.42 0.83,0.51 1.25,0.21l4.45,-3.17c0.42,-0.3 0.51,-0.83 0.21,-1.25L11.88,5.72zM12.68,17.79L6.73,9.45l4.45,-3.17l5.95,8.34L12.68,17.79z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M18.8,4.01c2.79,2.32 4.16,6.01 3.54,9.78l1.45,0.4c1.06,-6.14 -2.59,-12.11 -8.67,-13.78c-0.19,-0.05 -0.39,-0.11 -0.68,-0.18l2.66,4.67L18.8,4.01z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M5.11,19.96c-2.79,-2.32 -4.16,-6.01 -3.54,-9.78L0.21,9.81c-1.15,6.11 2.5,12.09 8.57,13.75c0.19,0.05 0.39,0.11 0.68,0.18L6.8,19.08L5.11,19.96z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_05.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_05.xml
deleted file mode 100644
index 570f51f..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_05.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M15.41,8.14c0.05,-0.42 -0.23,-0.78 -0.65,-0.83l-4.5,-0.54C9.84,6.72 9.48,7.0 9.43,7.42l-1.01,8.44c-0.05,0.42 0.23,0.78 0.65,0.83l4.5,0.54C14.0,17.28 14.35,17.0 14.4,16.58L15.41,8.14zM9.16,15.99l1.01,-8.44l4.5,0.54l-1.01,8.44L9.16,15.99z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M22.35,10.28c0.64,3.57 -0.69,7.28 -3.59,9.77l0.85,1.23c4.76,-4.01 5.82,-10.94 2.24,-16.12c-0.11,-0.16 -0.23,-0.33 -0.4,-0.58l-0.97,5.29L22.35,10.28z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M1.6,13.66c-0.64,-3.57 0.69,-7.28 3.59,-9.77L4.4,2.74c-4.82,3.93 -5.88,10.86 -2.3,16.04c0.11,0.16 0.23,0.33 0.4,0.58l0.97,-5.29L1.6,13.66z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_06.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_06.xml
deleted file mode 100644
index aaf9356..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_06.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M15.62,13.08c0.29,-0.1 0.44,-0.39 0.34,-0.69L14.9,9.27c-0.1,-0.29 -0.39,-0.44 -0.69,-0.34l-5.86,1.98c-0.29,0.1 -0.44,0.39 -0.34,0.69l1.06,3.12c0.1,0.29 0.39,0.44 0.69,0.34L15.62,13.08zM8.51,11.44l5.86,-1.98l1.06,3.12l-5.86,1.98L8.51,11.44z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M18.53,20.21c-2.8,2.3 -6.69,2.95 -10.27,1.64L7.6,23.2c5.83,2.2 12.39,-0.26 15.16,-5.92c0.09,-0.18 0.18,-0.36 0.31,-0.63l-5.09,1.73L18.53,20.21z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M5.45,3.76c2.8,-2.3 6.69,-2.95 10.27,-1.64l0.62,-1.26C10.56,-1.42 4.0,1.04 1.22,6.69C1.13,6.87 1.05,7.05 0.91,7.32L6.0,5.59L5.45,3.76z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_07.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_07.xml
deleted file mode 100644
index 330ce6a..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_07.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M12.23,14.48c0.13,0.16 0.35,0.17 0.5,0.04l1.66,-1.4c0.16,-0.13 0.17,-0.35 0.04,-0.5l-2.62,-3.11c-0.13,-0.16 -0.35,-0.17 -0.5,-0.04l-1.66,1.4c-0.16,0.13 -0.17,0.35 -0.04,0.5L12.23,14.48zM11.53,9.73l2.62,3.11l-1.66,1.4l-2.62,-3.11L11.53,9.73z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M6.04,20.63c-3.01,-2.02 -4.75,-5.56 -4.52,-9.37l-1.48,-0.25c-0.43,6.21 3.81,11.79 10.02,12.83c0.2,0.03 0.39,0.07 0.69,0.12l-3.12,-4.37L6.04,20.63z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M18.04,3.37c3.01,2.02 4.75,5.56 4.52,9.37l1.38,0.23c0.53,-6.2 -3.71,-11.77 -9.93,-12.81c-0.2,-0.03 -0.39,-0.07 -0.69,-0.12l3.12,4.37L18.04,3.37z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_08.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_08.xml
deleted file mode 100644
index 1c7f1a1..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_08.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M10.55,12.73c-0.06,0.12 -0.02,0.25 0.1,0.32l1.26,0.68c0.12,0.06 0.25,0.02 0.32,-0.1l1.27,-2.36c0.06,-0.12 0.02,-0.25 -0.1,-0.32l-1.26,-0.68c-0.12,-0.06 -0.25,-0.02 -0.32,0.1L10.55,12.73zM13.29,11.15l-1.27,2.36l-1.26,-0.68l1.27,-2.36L13.29,11.15z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M1.55,12.95C1.18,9.34 2.78,5.74 5.86,3.47L5.1,2.18C0.05,5.83 -1.51,12.66 1.67,18.09c0.1,0.17 0.2,0.35 0.35,0.6l1.36,-5.2L1.55,12.95z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M22.5,11.12c0.37,3.61 -1.23,7.21 -4.31,9.47l0.71,1.21c5.1,-3.56 6.67,-10.39 3.48,-15.83c-0.1,-0.17 -0.2,-0.35 -0.35,-0.6l-1.36,5.2L22.5,11.12z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_09.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_09.xml
deleted file mode 100644
index ebfbad6..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_09.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:pathData="M11.45,12c0,0.14,0.12,0.25,0.25,0.25l0.06,0l0,-0.5l-0.06,0C11.57,11.75,11.45,11.86,11.45,12z"
-        android:fill="#00000000"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M11.77,11.62l-0.06,0.0c-0.21,0.0 -0.37,0.17 -0.38,0.37c0.0,0.21 0.17,0.37 0.37,0.37l0.06,0.0c0.0,0.07 0.06,0.12 0.12,0.13l0.62,0.0c0.07,0.0 0.12,-0.06 0.13,-0.12l0.0,-0.75c0.0,-0.07 -0.06,-0.12 -0.12,-0.13l-0.62,0.0C11.83,11.5 11.77,11.56 11.77,11.62zM12.33,12.0c0.0,0.07 -0.06,0.12 -0.13,0.12c-0.07,0.0 -0.12,-0.06 -0.12,-0.13c0.0,-0.07 0.06,-0.12 0.13,-0.12C12.28,11.88 12.33,11.93 12.33,12.0zM11.77,11.75l0.0,0.5l-0.06,0.0c-0.14,0.0 -0.26,-0.11 -0.25,-0.25c0.0,-0.14 0.12,-0.25 0.26,-0.25L11.77,11.75z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M3.83,5.44c2.21,-2.87 5.85,-4.38 9.64,-3.9l0.34,-1.46C7.64,-0.75 1.81,3.12 0.37,9.25C0.32,9.45 0.28,9.64 0.21,9.93L4.78,7.1L3.83,5.44zM20.28,18.53c-2.21,2.87 -5.85,4.38 -9.64,3.9l-0.32,1.36c6.15,0.93 11.99,-2.95 13.42,-9.08c0.05,-0.19 0.09,-0.39 0.16,-0.68l-4.57,2.83L20.28,18.53z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_10.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_10.xml
deleted file mode 100644
index 21dda8c..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_10.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:pathData="M10.42,9.96c-0.42,0.42,-0.39,1.12,0.03,1.54l0.19,0.19l1.51,-1.53l-0.19,-0.19       C11.54,9.55,10.84,9.54,10.42,9.96z"
-        android:fill="#00000000"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M12.53,9.78l-0.19,-0.19c-0.63,-0.63 -1.66,-0.62 -2.28,0.02c-0.63,0.63 -0.62,1.66 0.02,2.28l0.19,0.19c-0.21,0.21 -0.21,0.55 0.01,0.76l1.92,1.89c0.21,0.21 0.55,0.21 0.76,-0.01l2.27,-2.3c0.21,-0.21 0.21,-0.55 -0.01,-0.76l-1.92,-1.89C13.08,9.56 12.74,9.56 12.53,9.78zM13.12,12.62c-0.21,0.21 -0.55,0.21 -0.76,0.01c-0.21,-0.21 -0.21,-0.55 -0.01,-0.76c0.21,-0.21 0.55,-0.21 0.76,-0.01C13.33,12.07 13.33,12.41 13.12,12.62zM12.15,10.16l-1.51,1.53l-0.19,-0.19c-0.42,-0.42 -0.45,-1.12 -0.03,-1.54c0.42,-0.42 1.12,-0.41 1.54,0.01L12.15,10.16z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M7.82,2.36c3.3,-1.49 7.23,-1.12 10.35,1.09l0.99,-1.13C14.09,-1.32 7.12,-0.64 2.97,4.1C2.84,4.25 2.71,4.4 2.51,4.62l5.36,-0.36L7.82,2.36zM16.18,21.64c-3.3,1.49 -7.23,1.12 -10.35,-1.09l-0.92,1.05c4.99,3.71 11.97,3.03 16.12,-1.71c0.13,-0.15 0.26,-0.3 0.46,-0.53l-5.36,0.36L16.18,21.64z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_11.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_11.xml
deleted file mode 100644
index f4186fe..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_11.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:pathData="M10.53,8.85c-0.7,0.37,-0.95,1.28,-0.58,1.98l0.17,0.32l2.55,-1.36L12.5,9.48       C12.12,8.78,11.23,8.48,10.53,8.85z"
-        android:fill="#00000000"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M13.3,9.46l-0.17,-0.32c-0.56,-1.05 -1.87,-1.45 -2.93,-0.89s-1.45,1.87 -0.89,2.93l0.17,0.32c-0.35,0.19 -0.48,0.62 -0.3,0.98l1.7,3.18c0.19,0.35 0.62,0.48 0.98,0.3l3.82,-2.04c0.35,-0.19 0.48,-0.62 0.3,-0.98l-1.7,-3.18C14.09,9.4 13.65,9.27 13.3,9.46zM12.92,13.34c-0.35,0.19 -0.79,0.06 -0.98,-0.3c-0.19,-0.35 -0.05,-0.79 0.3,-0.98c0.35,-0.19 0.79,-0.05 0.98,0.3C13.41,12.72 13.27,13.15 12.92,13.34zM12.67,9.8l-2.55,1.36l-0.17,-0.32c-0.37,-0.7 -0.13,-1.61 0.58,-1.98c0.7,-0.37 1.59,-0.08 1.97,0.63L12.67,9.8z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M11.48,1.5c3.62,-0.23 7.16,1.5 9.3,4.66l1.32,-0.71C18.65,0.27 11.89,-1.55 6.34,1.42C6.16,1.51 5.98,1.61 5.72,1.75l5.14,1.56L11.48,1.5zM12.52,22.5c-3.62,0.23 -7.16,-1.5 -9.3,-4.66L1.98,18.5c3.37,5.23 10.13,7.06 15.68,4.08c0.18,-0.09 0.35,-0.19 0.62,-0.33l-5.14,-1.56L12.52,22.5z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_12.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_12.xml
deleted file mode 100644
index d408e28..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_12.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:pathData="M10.83,8.49c-0.91,0.3,-1.39,1.31,-1.09,2.22l0.13,0.41l3.28,-1.08l-0.13,-0.41       C12.72,8.72,11.73,8.19,10.83,8.49z"
-        android:fill="#00000000"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M13.98,9.77l-0.13,-0.41C13.4,8.0 11.93,7.26 10.57,7.71c-1.36,0.45 -2.1,1.91 -1.65,3.27l0.13,0.41c-0.45,0.15 -0.7,0.64 -0.55,1.09l1.35,4.1c0.15,0.45 0.64,0.7 1.09,0.55l4.92,-1.62c0.45,-0.15 0.7,-0.64 0.55,-1.09l-1.35,-4.1C14.92,9.87 14.43,9.62 13.98,9.77zM12.73,14.27c-0.45,0.15 -0.94,-0.1 -1.09,-0.55c-0.15,-0.45 0.1,-0.94 0.55,-1.09c0.45,-0.15 0.94,0.1 1.09,0.55C13.43,13.64 13.18,14.12 12.73,14.27zM13.16,10.04l-3.28,1.08l-0.13,-0.41c-0.3,-0.91 0.18,-1.92 1.09,-2.22c0.91,-0.3 1.9,0.24 2.19,1.14L13.16,10.04z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M13.55,1.6c3.59,0.48 6.72,2.87 8.21,6.39l1.44,-0.44C20.82,1.8 14.54,-1.31 8.51,0.52c-0.19,0.06 -0.38,0.12 -0.67,0.2l4.74,2.53L13.55,1.6zM10.45,22.4c-3.59,-0.48 -6.72,-2.87 -8.21,-6.39L0.9,16.41c2.28,5.79 8.55,8.9 14.58,7.07c0.19,-0.06 0.38,-0.12 0.67,-0.2l-4.74,-2.53L10.45,22.4z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_13.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_13.xml
deleted file mode 100644
index 1ac6b39..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_13.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:pathData="M11.17,7.71c-1.09,0.19,-1.8,1.28,-1.61,2.37l0.09,0.49l3.94,-0.7l-0.09,-0.49C13.3,8.3,12.26,7.52,11.17,7.71       z"
-        android:fill="#00000000"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M14.57,9.7l-0.09,-0.49C14.19,7.58 12.63,6.49 11.0,6.78c-1.63,0.29 -2.71,1.85 -2.43,3.48l0.08,0.49c-0.54,0.1 -0.91,0.62 -0.81,1.16l0.87,4.92c0.1,0.54 0.62,0.91 1.16,0.81l5.91,-1.05c0.54,-0.1 0.91,-0.61 0.81,-1.16l-0.87,-4.92C15.63,9.97 15.11,9.61 14.57,9.7zM12.4,14.66c-0.54,0.1 -1.06,-0.27 -1.16,-0.81c-0.1,-0.54 0.27,-1.06 0.81,-1.16s1.06,0.27 1.16,0.81C13.3,14.04 12.94,14.56 12.4,14.66zM13.58,9.88l-3.94,0.7l-0.09,-0.49c-0.19,-1.09 0.52,-2.17 1.61,-2.37s2.13,0.58 2.33,1.67L13.58,9.88z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M14.99,1.92c3.49,0.97 6.26,3.78 7.24,7.48l1.48,-0.23c-1.55,-6.03 -7.32,-9.99 -13.55,-9.02c-0.2,0.03 -0.4,0.06 -0.69,0.11l4.34,3.17L14.99,1.92zM9.01,22.08C5.52,21.1 2.76,18.3 1.78,14.6L0.4,14.82c1.45,6.05 7.22,10.01 13.45,9.04c0.2,-0.03 0.4,-0.06 0.69,-0.11l-4.34,-3.17L9.01,22.08z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_14.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_14.xml
deleted file mode 100644
index c43e363c..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_14.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:pathData="M11.72,7.61c-1.1,0.08,-1.93,1.08,-1.86,2.18l0.03,0.5l3.99,-0.27l-0.03,-0.5       C13.78,8.42,12.82,7.54,11.72,7.61z"
-        android:fill="#00000000"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M14.88,9.95l-0.03,-0.5c-0.11,-1.65 -1.54,-2.9 -3.2,-2.79C10.0,6.78 8.75,8.21 8.87,9.86l0.03,0.5c-0.55,0.04 -0.97,0.52 -0.93,1.07l0.34,4.99c0.04,0.55 0.52,0.97 1.07,0.93l5.99,-0.41c0.55,-0.04 0.97,-0.51 0.93,-1.07l-0.34,-4.99C15.91,10.33 15.43,9.91 14.88,9.95zM12.2,14.64c-0.55,0.04 -1.03,-0.38 -1.07,-0.93c-0.04,-0.55 0.38,-1.03 0.93,-1.07c0.55,-0.04 1.03,0.38 1.07,0.93C13.16,14.13 12.75,14.61 12.2,14.64zM13.88,10.02l-3.99,0.27l-0.03,-0.5c-0.08,-1.1 0.75,-2.11 1.86,-2.18c1.1,-0.08 2.06,0.81 2.13,1.91L13.88,10.02z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M15.71,2.17c3.41,1.23 5.96,4.23 6.67,7.98l1.5,-0.12c-1.1,-6.13 -6.58,-10.5 -12.86,-9.99c-0.2,0.02 -0.4,0.03 -0.7,0.06l4.1,3.48L15.71,2.17zM8.29,21.83c-3.41,-1.23 -5.96,-4.23 -6.67,-7.98l-1.4,0.11c1.0,6.14 6.48,10.51 12.76,9.99c0.2,-0.02 0.4,-0.03 0.7,-0.06l-4.1,-3.48L8.29,21.83z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_15.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_15.xml
deleted file mode 100644
index 22fa428..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_15.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:pathData="M12.05,7.7c-1.1,0,-2,0.94,-2,2.05v0.5h4v-0.5C14.05,8.65,13.15,7.7,12.05,7.7z"
-        android:fill="#00000000"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M15.05,10.25l0.0,-0.5c0.0,-1.66 -1.34,-3.0 -3.0,-3.0s-2.99,1.34 -2.99,3.0l-0.01,0.5c-0.55,0.0 -1.0,0.45 -1.0,1.0l0.0,5.0c0.0,0.55 0.45,1.0 1.0,1.0l6.0,0.0c0.55,0.0 1.0,-0.45 1.0,-1.0l0.0,-5.0C16.05,10.7 15.6,10.25 15.05,10.25zM12.05,14.75c-0.55,0.0 -1.0,-0.45 -1.0,-1.0c0.0,-0.55 0.45,-1.0 1.0,-1.0s1.0,0.45 1.0,1.0C13.05,14.3 12.6,14.75 12.05,14.75zM14.05,10.25l-4.0,0.0l0.0,-0.5c0.0,-1.1 0.9,-2.05 2.0,-2.05s2.0,0.94 2.0,2.05L14.05,10.25z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M16.5,2.5c3.3,1.5 5.6,4.7 6.0,8.5L24.0,11.0C23.4,4.8 18.3,0.0 12.0,0.0c-0.2,0.0 -0.4,0.0 -0.7,0.0l3.8,3.8L16.5,2.5zM7.5,21.5c-3.3,-1.5 -5.6,-4.7 -6.0,-8.5L0.1,13.0C0.6,19.2 5.7,24.0 12.0,24.0c0.2,0.0 0.4,0.0 0.7,0.0l-3.8,-3.8L7.5,21.5z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_landscape.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_landscape.xml
new file mode 100644
index 0000000..e4c7cb5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_rotation_landscape.xml
@@ -0,0 +1,28 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <size
+        android:width="64dp"
+        android:height="64dp"/>
+
+    <viewport
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
+
+    <path
+        android:fill="#FFFFFFFF"
+        android:pathData="M2.0,14.0l0.0,20.0c0.0,2.2 1.8,4.0 4.0,4.0l36.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,14.0c0.0,-2.2 -1.8,-4.0 -4.0,-4.0L6.0,10.0C3.8,10.0 2.0,11.8 2.0,14.0zM38.0,14.0l0.0,20.0L10.0,34.0L10.0,14.0L38.0,14.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_locked.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_locked.xml
deleted file mode 100644
index 75e20f0..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_locked.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-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.
--->
-<animation-list
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:oneshot="true">
-    <item android:drawable="@drawable/ic_qs_rotation_01" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_02" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_03" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_04" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_05" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_06" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_07" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_08" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_09" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_10" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_11" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_12" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_13" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_14" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_15" android:duration="16" />
-</animation-list>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_portrait.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_portrait.xml
new file mode 100644
index 0000000..e4bf367
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_rotation_portrait.xml
@@ -0,0 +1,28 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <size
+        android:width="64dp"
+        android:height="64dp"/>
+
+    <viewport
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
+
+    <path
+        android:fill="#FFFFFFFF"
+        android:pathData="M34.0,2.0L14.0,2.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,36.0c0.0,2.2 1.8,4.0 4.0,4.0l20.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L38.0,6.0C38.0,3.8 36.2,2.0 34.0,2.0zM34.0,38.0L14.0,38.0L14.0,10.0l20.0,0.0L34.0,38.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_unlocked.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_unlocked.xml
index a1cedb9..a6c2cf8 100644
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_unlocked.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_rotation_unlocked.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
 Copyright (C) 2014 The Android Open Source Project
 
@@ -14,22 +13,22 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-<animation-list
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:oneshot="true">
-    <item android:drawable="@drawable/ic_qs_rotation_15" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_14" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_13" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_12" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_11" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_10" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_09" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_08" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_07" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_06" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_05" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_04" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_03" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_02" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_01" android:duration="16" />
-</animation-list>
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <size
+        android:width="64dp"
+        android:height="64dp"/>
+
+    <viewport
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"/>
+
+    <path
+        android:fill="#FFFFFFFF"
+        android:pathData="M10.25,1.75c-0.6,-0.6 -1.5,-0.6 -2.1,0.0l-6.4,6.4c-0.6,0.6 -0.6,1.5 0.0,2.1l12.0,12.0c0.6,0.6 1.5,0.6 2.1,0.0l6.4,-6.4c0.6,-0.6 0.6,-1.5 0.0,-2.1L10.25,1.75zM14.85,21.25l-12.0,-12.0l6.4,-6.4l12.0,12.0L14.85,21.25z"/>
+    <path
+        android:fill="#FFFFFFFF"
+        android:pathData="M16.55,2.5c3.3,1.5 5.6,4.7 6.0,8.5l1.5,0.0c-0.6,-6.2 -5.7,-11.0 -12.0,-11.0c-0.2,0.0 -0.4,0.0 -0.7,0.0l3.8,3.8L16.55,2.5z"/>
+    <path
+        android:fill="#FFFFFFFF"
+        android:pathData="M7.55,21.5c-3.3,-1.5 -5.6,-4.7 -6.0,-8.5l-1.4,0.0c0.5,6.2 5.6,11.0 11.9,11.0c0.2,0.0 0.4,0.0 0.7,0.0l-3.8,-3.8L7.55,21.5z"/>
+</vector>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index bae7ed7..3d53f9c 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -44,6 +44,7 @@
     <color name="qs_detail_empty">#24B0BEC5</color><!-- 14% blue grey 200-->
     <color name="data_usage_secondary">#99FFFFFF</color><!-- 60% white -->
     <color name="data_usage_graph_track">#33FFFFFF</color><!-- 20% white -->
+    <color name="data_usage_graph_warning">#FFFFFFFF</color>
     <color name="status_bar_clock_color">#33FFFFFF</color>
 
     <!-- Tint color for the content on the notification overflow card. -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6a7b450..b79dbbe 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -572,6 +572,8 @@
     <string name="quick_settings_cellular_detail_data_used"><xliff:g id="data_used" example="2.0 GB">%s</xliff:g> used</string>
     <!-- QuickSettings: Cellular detail panel, data limit format string [CHAR LIMIT=NONE] -->
     <string name="quick_settings_cellular_detail_data_limit"><xliff:g id="data_limit" example="2.0 GB">%s</xliff:g> limit</string>
+    <!-- QuickSettings: Cellular detail panel, data warning format string [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_cellular_detail_data_warning"><xliff:g id="data_limit" example="2.0 GB">%s</xliff:g> warning</string>
 
     <!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
     <string name="recents_empty_message">No recent apps</string>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java b/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java
index fa11af6..d55ceaa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java
@@ -28,34 +28,34 @@
 
 public class DataUsageGraph extends View {
 
-    private final int mBackgroundColor;
     private final int mTrackColor;
     private final int mUsageColor;
     private final int mOverlimitColor;
+    private final int mWarningColor;
     private final int mMarkerWidth;
     private final RectF mTmpRect = new RectF();
     private final Paint mTmpPaint = new Paint();
 
-    private long mMaxLevel = 1;
     private long mLimitLevel;
     private long mWarningLevel;
     private long mUsageLevel;
+    private long mMaxLevel;
 
     public DataUsageGraph(Context context, AttributeSet attrs) {
         super(context, attrs);
         final Resources res = context.getResources();
-        mBackgroundColor = res.getColor(R.color.system_primary_color);
         mTrackColor = res.getColor(R.color.data_usage_graph_track);
         mUsageColor = res.getColor(R.color.system_accent_color);
         mOverlimitColor = res.getColor(R.color.system_warning_color);
+        mWarningColor = res.getColor(R.color.data_usage_graph_warning);
         mMarkerWidth = res.getDimensionPixelSize(R.dimen.data_usage_graph_marker_width);
     }
 
-    public void setLevels(long maxLevel, long limitLevel, long warningLevel, long usageLevel) {
-        mMaxLevel = Math.max(maxLevel, 1);
-        mLimitLevel = limitLevel;
-        mWarningLevel = warningLevel;
-        mUsageLevel = usageLevel;
+    public void setLevels(long limitLevel, long warningLevel, long usageLevel) {
+        mLimitLevel = Math.max(0, limitLevel);
+        mWarningLevel = Math.max(0, warningLevel);
+        mUsageLevel = Math.max(0, usageLevel);
+        mMaxLevel = Math.max(Math.max(Math.max(mLimitLevel, mWarningLevel), mUsageLevel), 1);
         postInvalidate();
     }
 
@@ -68,21 +68,22 @@
         final int w = getWidth();
         final int h = getHeight();
 
-        // draw track
-        r.set(0, 0, w, h);
-        p.setColor(mTrackColor);
-        canvas.drawRect(r, p);
-
-        final boolean hasLimit = mLimitLevel > 0;
-        final boolean overLimit = hasLimit && mUsageLevel > mLimitLevel;
-
-        final long maxLevel = hasLimit ? Math.max(mUsageLevel, mLimitLevel) : mMaxLevel;
-        final long usageLevel = hasLimit ? Math.min(mUsageLevel, mLimitLevel) : mUsageLevel;
-        float usageRight = w * (usageLevel / (float) maxLevel);
+        final boolean overLimit = mLimitLevel > 0 && mUsageLevel > mLimitLevel;
+        float usageRight = w * (mUsageLevel / (float) mMaxLevel);
         if (overLimit) {
-            usageRight -= (mMarkerWidth / 2);
-            usageRight = Math.min(usageRight, w - mMarkerWidth * 2);
-            usageRight = Math.max(usageRight, mMarkerWidth);
+            // compute the gap
+            usageRight = w * (mLimitLevel / (float) mMaxLevel) - (mMarkerWidth / 2);
+            usageRight = Math.min(Math.max(usageRight, mMarkerWidth), w - mMarkerWidth * 2);
+
+            // draw overlimit
+            r.set(usageRight + mMarkerWidth, 0, w, h);
+            p.setColor(mOverlimitColor);
+            canvas.drawRect(r, p);
+        } else {
+            // draw track
+            r.set(0, 0, w, h);
+            p.setColor(mTrackColor);
+            canvas.drawRect(r, p);
         }
 
         // draw usage
@@ -90,16 +91,11 @@
         p.setColor(mUsageColor);
         canvas.drawRect(r, p);
 
-        if (overLimit) {
-            // draw gap
-            r.set(usageRight, 0, usageRight + mMarkerWidth, h);
-            p.setColor(mBackgroundColor);
-            canvas.drawRect(r, p);
-
-            // draw overlimit
-            r.set(usageRight + mMarkerWidth, 0, w, h);
-            p.setColor(mOverlimitColor);
-            canvas.drawRect(r, p);
-        }
+        // draw warning marker
+        float warningLeft = w * (mWarningLevel / (float) mMaxLevel) - mMarkerWidth / 2;
+        warningLeft = Math.min(Math.max(warningLeft, 0), w - mMarkerWidth);
+        r.set(warningLeft, 0, warningLeft + mMarkerWidth, h);
+        p.setColor(mWarningColor);
+        canvas.drawRect(r, p);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 449cc1d..72474b8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -108,6 +108,9 @@
             mColumns = columns;
             postInvalidate();
         }
+        if (mListening) {
+            refreshAllTiles();
+        }
     }
 
     public void setExpanded(boolean expanded) {
@@ -123,9 +126,9 @@
         mListening = listening;
         for (TileRecord r : mRecords) {
             r.tile.setListening(mListening);
-            if (mListening) {
-                r.tile.refreshState();
-            }
+        }
+        if (mListening) {
+            refreshAllTiles();
         }
         if (listening) {
             mBrightnessController.registerCallbacks();
@@ -134,6 +137,12 @@
         }
     }
 
+    private void refreshAllTiles() {
+        for (TileRecord r : mRecords) {
+            r.tile.refreshState();
+        }
+    }
+
     private void showDetail(boolean show, TileRecord r) {
         mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 1f12b2a..d76a2fe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -216,21 +216,27 @@
             final DataUsageInfo info = mController.getDataUsageInfo();
             if (info == null) return v;
             final Resources res = mContext.getResources();
-            int titleId;
-            long bytes;
+            final int titleId;
+            final long bytes;
             int usageColor = R.color.system_accent_color;
-            String top = null, bottom = null;
-            if (info.limitLevel <= 0) { // no limit
+            final String top;
+            String bottom = null;
+            if (info.usageLevel < info.warningLevel || info.limitLevel <= 0) {
+                // under warning, or no limit
                 titleId = R.string.quick_settings_cellular_detail_data_usage;
                 bytes = info.usageLevel;
-            } else if (info.usageLevel <= info.limitLevel) { // under limit
+                top = res.getString(R.string.quick_settings_cellular_detail_data_warning,
+                        formatBytes(info.warningLevel));
+            } else if (info.usageLevel <= info.limitLevel) {
+                // over warning, under limit
                 titleId = R.string.quick_settings_cellular_detail_remaining_data;
                 bytes = info.limitLevel - info.usageLevel;
                 top = res.getString(R.string.quick_settings_cellular_detail_data_used,
                         formatBytes(info.usageLevel));
                 bottom = res.getString(R.string.quick_settings_cellular_detail_data_limit,
                         formatBytes(info.limitLevel));
-            } else { // over limit
+            } else {
+                // over limit
                 titleId = R.string.quick_settings_cellular_detail_over_limit;
                 bytes = info.usageLevel - info.limitLevel;
                 top = res.getString(R.string.quick_settings_cellular_detail_data_used,
@@ -246,7 +252,7 @@
             usage.setText(formatBytes(bytes));
             usage.setTextColor(res.getColor(usageColor));
             final DataUsageGraph graph = (DataUsageGraph) v.findViewById(R.id.usage_graph);
-            graph.setLevels(info.maxLevel, info.limitLevel, info.warningLevel, info.usageLevel);
+            graph.setLevels(info.limitLevel, info.warningLevel, info.usageLevel);
             final TextView carrier = (TextView) v.findViewById(R.id.usage_carrier_text);
             carrier.setText(info.carrier);
             final TextView period = (TextView) v.findViewById(R.id.usage_period_text);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 3be97cc..21cf9ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -18,7 +18,6 @@
 
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.drawable.AnimationDrawable;
 
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile;
@@ -61,35 +60,19 @@
         final boolean rotationLocked = mController.isRotationLocked();
         state.visible = mController.isRotationLockAffordanceVisible();
         final Resources res = mContext.getResources();
-        if (state.value != rotationLocked) {
-            state.value = rotationLocked;
-            final AnimationDrawable d = (AnimationDrawable) res.getDrawable(rotationLocked
-                    ? R.drawable.ic_qs_rotation_locked
-                    : R.drawable.ic_qs_rotation_unlocked);
-            state.icon = d;
-            mUiHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    d.start();
-                }
-            });
-        }
+        state.value = rotationLocked;
         if (rotationLocked) {
-            final int lockOrientation = mController.getRotationLockOrientation();
-            final int label = lockOrientation == Configuration.ORIENTATION_PORTRAIT
-                    ? R.string.quick_settings_rotation_locked_portrait_label
-                    : lockOrientation == Configuration.ORIENTATION_LANDSCAPE
-                    ? R.string.quick_settings_rotation_locked_landscape_label
-                    : R.string.quick_settings_rotation_locked_label;
+            final boolean portrait = res.getConfiguration().orientation
+                    != Configuration.ORIENTATION_LANDSCAPE;
+            final int label = portrait ? R.string.quick_settings_rotation_locked_portrait_label
+                    : R.string.quick_settings_rotation_locked_landscape_label;
+            final int icon = portrait ? R.drawable.ic_qs_rotation_portrait
+                    : R.drawable.ic_qs_rotation_landscape;
             state.label = mContext.getString(label);
-            if (state.icon == null) {
-                state.icon = res.getDrawable(R.drawable.ic_qs_rotation_15);
-            }
+            state.icon = mContext.getDrawable(icon);
         } else {
             state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label);
-            if (state.icon == null) {
-                state.icon = res.getDrawable(R.drawable.ic_qs_rotation_01);
-            }
+            state.icon = res.getDrawable(R.drawable.ic_qs_rotation_unlocked);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index 8a80b76..e7ac2e1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -32,7 +32,7 @@
             // Enables the filtering of tasks according to their grouping
             public static final boolean EnableTaskFiltering = false;
             // Enables clipping of tasks against each other
-            public static final boolean EnableTaskStackClipping = true;
+            public static final boolean EnableTaskStackClipping = false;
             // Enables tapping on the TaskBar to launch the task
             public static final boolean EnableTaskBarTouchEvents = true;
             // Enables app-info pane on long-pressing the icon
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 56de0be..1e581c1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -458,8 +458,6 @@
         filter.addAction(ACTION_TOGGLE_RECENTS_ACTIVITY);
         filter.addAction(ACTION_START_ENTER_ANIMATION);
         registerReceiver(mServiceBroadcastReceiver, filter);
-
-        mVisible = true;
     }
 
     @Override
@@ -485,6 +483,8 @@
                 }
             }, 1);
         }
+
+        mVisible = true;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java
index 31825af..4c0ff48 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java
@@ -72,7 +72,12 @@
 
     /** Adds a runnable to the last-decrement runnables list. */
     public void addLastDecrementRunnable(Runnable r) {
+        // To ensure that the last decrement always calls, we increment and decrement after setting
+        // the last decrement runnable
+        boolean ensureLastDecrement = (mCount == 0);
+        if (ensureLastDecrement) increment();
         mLastDecRunnables.add(r);
+        if (ensureLastDecrement) decrement();
     }
 
     /** Decrements the ref count */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index bda195b..607e155 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -16,16 +16,10 @@
 
 package com.android.systemui.recents.misc;
 
-import android.app.ActivityManager;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.graphics.Color;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.ParcelFileDescriptor;
 import com.android.systemui.recents.RecentsConfiguration;
 
-import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 
@@ -73,22 +67,25 @@
         }
     }
 
-    /** Calculates the luminance-preserved greyscale of a given color. */
-    public static int colorToGreyscale(int color) {
-        return Math.round(0.2126f * Color.red(color) + 0.7152f * Color.green(color) +
-                0.0722f * Color.blue(color));
-    }
+    /** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */
+    public static float computeContrastBetweenColors(int bg, int fg) {
+        float bgR = Color.red(bg) / 255f;
+        float bgG = Color.green(bg) / 255f;
+        float bgB = Color.blue(bg) / 255f;
+        bgR = (bgR < 0.03928f) ? bgR / 12.92f : (float) Math.pow((bgR + 0.055f) / 1.055f, 2.4f);
+        bgG = (bgG < 0.03928f) ? bgG / 12.92f : (float) Math.pow((bgG + 0.055f) / 1.055f, 2.4f);
+        bgB = (bgB < 0.03928f) ? bgB / 12.92f : (float) Math.pow((bgB + 0.055f) / 1.055f, 2.4f);
+        float bgL = 0.2126f * bgR + 0.7152f * bgG + 0.0722f * bgB;
+        
+        float fgR = Color.red(fg) / 255f;
+        float fgG = Color.green(fg) / 255f;
+        float fgB = Color.blue(fg) / 255f;
+        fgR = (fgR < 0.03928f) ? fgR / 12.92f : (float) Math.pow((fgR + 0.055f) / 1.055f, 2.4f);
+        fgG = (fgG < 0.03928f) ? fgG / 12.92f : (float) Math.pow((fgG + 0.055f) / 1.055f, 2.4f);
+        fgB = (fgB < 0.03928f) ? fgB / 12.92f : (float) Math.pow((fgB + 0.055f) / 1.055f, 2.4f);
+        float fgL = 0.2126f * fgR + 0.7152f * fgG + 0.0722f * fgB;
 
-    /** Returns the ideal color to draw on top of a specified background color. */
-    public static int getIdealColorForBackgroundColorGreyscale(int greyscale, int lightRes,
-                                                               int darkRes) {
-        return (greyscale < 128) ? lightRes : darkRes;
-    }
-    /** Returns the ideal drawable to draw on top of a specified background color. */
-    public static Drawable getIdealResourceForBackgroundColorGreyscale(int greyscale,
-                                                                       Drawable lightRes,
-                                                                       Drawable darkRes) {
-        return (greyscale < 128) ? lightRes : darkRes;
+        return Math.abs((fgL + 0.05f) / (bgL + 0.05f));
     }
 
     /** Sets some private shadow properties. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java
index 1344729..757c07f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java
@@ -29,6 +29,6 @@
     @Override
     protected int computeSize(Bitmap b) {
         // The cache size will be measured in kilobytes rather than number of items
-        return b.getAllocationByteCount() / 1024;
+        return b.getAllocationByteCount();
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java
index 61d19da..5b50358 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java
@@ -31,6 +31,6 @@
         // The cache size will be measured in kilobytes rather than number of items
         // NOTE: this isn't actually correct, as the icon may be smaller
         int maxBytes = (d.getIntrinsicWidth() * d.getIntrinsicHeight() * 4);
-        return maxBytes / 1024;
+        return maxBytes;
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
index 3ccca9a2..5f4fabe 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
@@ -73,11 +73,6 @@
         return mCache.get(key);
     }
 
-    /** Gets the previous task key that matches the specified key. */
-    final Task.TaskKey getKey(Task.TaskKey key) {
-        return mKeys.get(key);
-    }
-
     /** Puts an entry in the cache for a specific key. */
     final void put(Task.TaskKey key, V value) {
         mCache.put(key, value);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
index 2d50659..2f1c1c4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
@@ -45,7 +45,7 @@
         mSystemServicesProxy = new SystemServicesProxy(context);
         mCb = cb;
         try {
-            register(context, Looper.getMainLooper(), false);
+            register(context, Looper.getMainLooper(), true);
         } catch (IllegalStateException e) {
             e.printStackTrace();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 854ea1c..cbb8892 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -27,38 +27,29 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.UserHandle;
-import android.util.Pair;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.misc.Console;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 
-import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 
 /** A bitmap load queue */
 class TaskResourceLoadQueue {
     ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
-    ConcurrentHashMap<Task.TaskKey, Boolean> mForceLoadSet =
-            new ConcurrentHashMap<Task.TaskKey, Boolean>();
-
-    static final Boolean sFalse = new Boolean(false);
 
     /** Adds a new task to the load queue */
-    void addTask(Task t, boolean forceLoad) {
+    void addTask(Task t) {
         if (Console.Enabled) {
             Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|addTask]");
         }
         if (!mQueue.contains(t)) {
             mQueue.add(t);
         }
-        if (forceLoad) {
-            mForceLoadSet.put(t.key, new Boolean(true));
-        }
         synchronized(this) {
             notifyAll();
         }
@@ -68,19 +59,11 @@
      * Retrieves the next task from the load queue, as well as whether we want that task to be
      * force reloaded.
      */
-    Pair<Task, Boolean> nextTask() {
+    Task nextTask() {
         if (Console.Enabled) {
             Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|nextTask]");
         }
-        Task task = mQueue.poll();
-        Boolean forceLoadTask = null;
-        if (task != null) {
-            forceLoadTask = mForceLoadSet.remove(task.key);
-        }
-        if (forceLoadTask == null) {
-            forceLoadTask = sFalse;
-        }
-        return new Pair<Task, Boolean>(task, forceLoadTask);
+        return mQueue.poll();
     }
 
     /** Removes a task from the load queue */
@@ -89,7 +72,6 @@
             Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|removeTask]");
         }
         mQueue.remove(t);
-        mForceLoadSet.remove(t.key);
     }
 
     /** Clears all the tasks from the load queue */
@@ -98,7 +80,6 @@
             Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|clearTasks]");
         }
         mQueue.clear();
-        mForceLoadSet.clear();
     }
 
     /** Returns whether the load queue is empty */
@@ -119,19 +100,20 @@
     DrawableLruCache mApplicationIconCache;
     BitmapLruCache mThumbnailCache;
     Bitmap mDefaultThumbnail;
+    BitmapDrawable mDefaultApplicationIcon;
 
     boolean mCancelled;
     boolean mWaitingOnLoadQueue;
 
     /** Constructor, creates a new loading thread that loads task resources in the background */
-    public TaskResourceLoader(TaskResourceLoadQueue loadQueue,
-                              DrawableLruCache applicationIconCache,
-                              BitmapLruCache thumbnailCache,
-                              Bitmap defaultThumbnail) {
+    public TaskResourceLoader(TaskResourceLoadQueue loadQueue, DrawableLruCache applicationIconCache,
+                              BitmapLruCache thumbnailCache, Bitmap defaultThumbnail,
+                              BitmapDrawable defaultApplicationIcon) {
         mLoadQueue = loadQueue;
         mApplicationIconCache = applicationIconCache;
         mThumbnailCache = thumbnailCache;
         mDefaultThumbnail = defaultThumbnail;
+        mDefaultApplicationIcon = defaultApplicationIcon;
         mMainThreadHandler = new Handler();
         mLoadThread = new HandlerThread("Recents-TaskResourceLoader");
         mLoadThread.setPriority(Thread.NORM_PRIORITY - 1);
@@ -200,59 +182,51 @@
                 SystemServicesProxy ssp = mSystemServicesProxy;
 
                 // Load the next item from the queue
-                Pair<Task, Boolean> nextTaskData = mLoadQueue.nextTask();
-                final Task t = nextTaskData.first;
-                final boolean forceLoadTask = nextTaskData.second;
+                final Task t = mLoadQueue.nextTask();
                 if (t != null) {
-                    Drawable loadIcon = mApplicationIconCache.getCheckLastActiveTime(t.key);
-                    Bitmap loadThumbnail = mThumbnailCache.getCheckLastActiveTime(t.key);
+                    Drawable cachedIcon = mApplicationIconCache.getCheckLastActiveTime(t.key);
+                    Bitmap cachedThumbnail = mThumbnailCache.getCheckLastActiveTime(t.key);
                     if (Console.Enabled) {
                         Console.log(Constants.Log.App.TaskDataLoader,
                                 "  [TaskResourceLoader|load]",
-                                t + " icon: " + loadIcon + " thumbnail: " + loadThumbnail +
-                                        " forceLoad: " + forceLoadTask);
+                                t + " icon: " + cachedIcon + " thumbnail: " + cachedThumbnail);
                     }
-                    // Load the application icon
-                    if (loadIcon == null || forceLoadTask) {
+                    // Load the application icon if it is stale or we haven't cached one yet
+                    if (cachedIcon == null) {
+                        Drawable icon = null;
                         ActivityInfo info = ssp.getActivityInfo(t.key.baseIntent.getComponent(),
                                 t.userId);
-                        Drawable icon = ssp.getActivityIcon(info, t.userId);
-                        if (!mCancelled) {
-                            if (icon != null) {
-                                if (Console.Enabled) {
-                                    Console.log(Constants.Log.App.TaskDataLoader,
-                                            "    [TaskResourceLoader|loadIcon]", icon);
-                                }
-                                loadIcon = icon;
-                                mApplicationIconCache.put(t.key, icon);
+                        if (info != null) {
+                            icon = ssp.getActivityIcon(info, t.userId);
+                            if (Console.Enabled) {
+                                Console.log(Constants.Log.App.TaskDataLoader,
+                                        "    [TaskResourceLoader|loadedIcon]", icon);
                             }
                         }
+                        // If we can't load the icon, then set the default application icon into the
+                        // cache.  This will remain until the task's last active time is updated.
+                        cachedIcon = icon != null ? icon : mDefaultApplicationIcon;
+                        mApplicationIconCache.put(t.key, cachedIcon);
                     }
-                    // Load the thumbnail
-                    if (loadThumbnail == null || forceLoadTask) {
+                    // Load the thumbnail if it is stale or we haven't cached one yet
+                    if (cachedThumbnail == null) {
                         Bitmap thumbnail = ssp.getTaskThumbnail(t.key.id);
-                        if (!mCancelled) {
-                            if (thumbnail != null) {
-                                if (Console.Enabled) {
-                                    Console.log(Constants.Log.App.TaskDataLoader,
-                                            "    [TaskResourceLoader|loadThumbnail]", thumbnail);
-                                }
-                                thumbnail.setHasAlpha(false);
-                                loadThumbnail = thumbnail;
-                            } else {
-                                loadThumbnail = mDefaultThumbnail;
-                                Console.logError(mContext,
-                                        "Failed to load task top thumbnail for: " +
-                                                t.key.baseIntent.getComponent().getPackageName());
+                        if (thumbnail != null) {
+                            thumbnail.setHasAlpha(false);
+                            if (Console.Enabled) {
+                                Console.log(Constants.Log.App.TaskDataLoader,
+                                        "    [TaskResourceLoader|loadedThumbnail]", thumbnail);
                             }
-                            // We put the default thumbnail in the cache anyways
-                            mThumbnailCache.put(t.key, loadThumbnail);
                         }
+                        // Even if we can't load the icon, we set the default thumbnail into the
+                        // cache.  This will remain until the task's last active time is updated.
+                        cachedThumbnail = thumbnail != null ? thumbnail : mDefaultThumbnail;
+                        mThumbnailCache.put(t.key, cachedThumbnail);
                     }
                     if (!mCancelled) {
                         // Notify that the task data has changed
-                        final Drawable newIcon = loadIcon;
-                        final Bitmap newThumbnail = loadThumbnail;
+                        final Drawable newIcon = cachedIcon;
+                        final Bitmap newThumbnail = cachedThumbnail;
                         mMainThreadHandler.post(new Runnable() {
                             @Override
                             public void run() {
@@ -306,11 +280,11 @@
     /** Private Constructor */
     private RecentsTaskLoader(Context context) {
         // Calculate the cache sizes, we just use a reasonable number here similar to those
-        // suggested in the Android docs, 1/8th for the thumbnail cache and 1/32 of the max memory
+        // suggested in the Android docs, 1/6th for the thumbnail cache and 1/30 of the max memory
         // for icons.
-        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
-        mMaxThumbnailCacheSize = maxMemory / 8;
-        mMaxIconCacheSize = mMaxThumbnailCacheSize / 4;
+        int maxMemory = (int) Runtime.getRuntime().maxMemory();
+        mMaxThumbnailCacheSize = maxMemory / 6;
+        mMaxIconCacheSize = mMaxThumbnailCacheSize / 5;
         int iconCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
                 mMaxIconCacheSize;
         int thumbnailCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
@@ -340,7 +314,7 @@
         mApplicationIconCache = new DrawableLruCache(iconCacheSize);
         mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
         mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache,
-                mDefaultThumbnail);
+                mDefaultThumbnail, mDefaultApplicationIcon);
 
         if (Console.Enabled) {
             Console.log(Constants.Log.App.TaskDataLoader,
@@ -394,7 +368,7 @@
         }
         RecentsConfiguration config = RecentsConfiguration.getInstance();
         Resources res = context.getResources();
-        ArrayList<Task> tasksToForceLoad = new ArrayList<Task>();
+        LinkedHashSet<Task> tasksToLoad = new LinkedHashSet<Task>();
         TaskStack stack = new TaskStack();
         SpaceNode root = new SpaceNode();
         root.setStack(stack);
@@ -446,15 +420,16 @@
                     if (isForemostTask) {
                         // We force loading the application icon for the foremost task
                         task.applicationIcon = ssp.getActivityIcon(info, task.userId);
-                        if (task.applicationIcon != null) {
-                            mApplicationIconCache.put(task.key, task.applicationIcon);
-                        } else {
+                        if (task.applicationIcon == null) {
                             task.applicationIcon = mDefaultApplicationIcon;
                         }
+                        // Even if we can't load the icon we set the default application icon into
+                        // the cache.  This will remain until the task's last active time is updated.
+                        mApplicationIconCache.put(task.key, task.applicationIcon);
                     } else {
-                        // Either the task has updated, or we haven't cached any information for the
-                        // task, so reload it
-                        tasksToForceLoad.add(task);
+                        // Either the task has changed since the last active time, or it was not
+                        // previously cached, so try and load the task anew.
+                        tasksToLoad.add(task);
                     }
                 }
 
@@ -473,11 +448,13 @@
                         } else {
                             task.thumbnail = mDefaultThumbnail;
                         }
+                        // Even if we can't load the thumbnail we set the default thumbnail into
+                        // the cache.  This will remain until the task's last active time is updated.
                         mThumbnailCache.put(task.key, task.thumbnail);
                     } else {
-                        // Either the task has updated, or we haven't cached any information for the
-                        // task, so reload it
-                        tasksToForceLoad.add(task);
+                        // Either the task has changed since the last active time, or it was not
+                        // previously cached, so try and load the task anew.
+                        tasksToLoad.add(task);
                     }
                 }
             }
@@ -496,14 +473,14 @@
         }
 
         // Simulate the groupings that we describe
-        stack.createSimulatedAffiliatedGroupings();
+        stack.createAffiliatedGroupings();
 
         // Start the task loader
         mLoader.start(context);
 
-        // Add all the tasks that we are force/re-loading
-        for (Task t : tasksToForceLoad) {
-            mLoadQueue.addTask(t, true);
+        // Add all the tasks that we are reloading
+        for (Task t : tasksToLoad) {
+            mLoadQueue.addTask(t);
         }
 
         // Update the package monitor with the list of packages to listen for
@@ -526,7 +503,7 @@
             stack.addTask(new Task(t.persistentId, true, t.baseIntent, t.affiliatedTaskId, null,
                     null, 0, 0, t.firstActiveTime, t.lastActiveTime, (i == (taskCount - 1))));
         }
-        stack.createSimulatedAffiliatedGroupings();
+        stack.createAffiliatedGroupings();
         return stack;
     }
 
@@ -551,7 +528,7 @@
             requiresLoad = true;
         }
         if (requiresLoad) {
-            mLoadQueue.addTask(t, false);
+            mLoadQueue.addTask(t);
         }
         t.notifyTaskDataLoaded(thumbnail, applicationIcon);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 88e9f40..1670735 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -18,6 +18,7 @@
 
 import android.content.Intent;
 import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.graphics.drawable.Drawable;
 import com.android.systemui.recents.misc.Utilities;
 
@@ -84,7 +85,7 @@
     public Drawable activityIcon;
     public String activityLabel;
     public int colorPrimary;
-    public int colorPrimaryGreyscale;
+    public boolean useLightOnPrimaryColor;
     public Bitmap thumbnail;
     public boolean isActive;
     public boolean canLockToTask;
@@ -104,7 +105,8 @@
         this.activityLabel = activityTitle;
         this.activityIcon = activityIcon;
         this.colorPrimary = colorPrimary;
-        this.colorPrimaryGreyscale = Utilities.colorToGreyscale(colorPrimary);
+        this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(colorPrimary,
+                Color.WHITE) > 3f;
         this.isActive = isActive;
         this.canLockToTask = canLockToTask;
         this.userId = userId;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 7dd15a6..e3bcff0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -320,7 +320,7 @@
     /**
      * Temporary: This method will simulate affiliation groups by
      */
-    public void createSimulatedAffiliatedGroupings() {
+    public void createAffiliatedGroupings() {
         if (Constants.DebugFlags.App.EnableSimulatedTaskGroups) {
             HashMap<Task.TaskKey, Task> taskMap = new HashMap<Task.TaskKey, Task>();
             // Sort all tasks by increasing firstActiveTime of the task
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 99b012e..73bbf86 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -191,10 +191,6 @@
 
     /** Requests all task stacks to start their enter-recents animation */
     public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) {
-        // Handle the case when there are no views by incrementing and decrementing after all
-        // animations are started.
-        ctx.postAnimationTrigger.increment();
-
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
@@ -203,18 +199,10 @@
                 stackView.startEnterRecentsAnimation(ctx);
             }
         }
-
-        // Handle the case when there are no views by incrementing and decrementing after all
-        // animations are started.
-        ctx.postAnimationTrigger.decrement();
     }
 
     /** Requests all task stacks to start their exit-recents animation */
     public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
-        // Handle the case when there are no views by incrementing and decrementing after all
-        // animations are started.
-        ctx.postAnimationTrigger.increment();
-
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
@@ -224,10 +212,6 @@
             }
         }
 
-        // Handle the case when there are no views by incrementing and decrementing after all
-        // animations are started.
-        ctx.postAnimationTrigger.decrement();
-
         // Notify of the exit animation
         mCb.onExitToHomeAnimationTriggered();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
index db84962..deb9df3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
@@ -162,11 +162,10 @@
         }
         // Try and apply the system ui tint
         setBackgroundColor(t.colorPrimary);
-        mActivityDescription.setTextColor(Utilities.getIdealColorForBackgroundColorGreyscale(
-                t.colorPrimaryGreyscale, mConfig.taskBarViewLightTextColor,
-                mConfig.taskBarViewDarkTextColor));
-        mDismissButton.setImageDrawable(Utilities.getIdealResourceForBackgroundColorGreyscale(
-                t.colorPrimaryGreyscale, mLightDismissDrawable, mDarkDismissDrawable));
+        mActivityDescription.setTextColor(t.useLightOnPrimaryColor ?
+                mConfig.taskBarViewLightTextColor : mConfig.taskBarViewDarkTextColor);
+        mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
+                mLightDismissDrawable : mDarkDismissDrawable);
     }
 
     /** Unbinds the bar view from the task */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 599c590..adc808a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -302,7 +302,6 @@
             int[] visibleRange = mTmpVisibleRange;
             updateStackTransforms(mCurrentTaskTransforms, tasks, stackScroll, visibleRange, false);
             TaskViewTransform tmpTransform = new TaskViewTransform();
-            TaskStack.GroupTaskIndex gti = new TaskStack.GroupTaskIndex();
 
             // Return all the invisible children to the pool
             HashMap<Task, TaskView> taskChildViewMap = getTaskChildViewMap();
@@ -355,6 +354,47 @@
         }
     }
 
+    /** Updates the clip for each of the task views. */
+    void clipTaskViews() {
+        // Update the clip on each task child
+        if (Constants.DebugFlags.App.EnableTaskStackClipping) {
+            int childCount = getChildCount();
+            for (int i = 0; i < childCount - 1; i++) {
+                TaskView tv = (TaskView) getChildAt(i);
+                TaskView nextTv = null;
+                TaskView tmpTv = null;
+                int clipBottom = 0;
+                if (tv.shouldClipViewInStack()) {
+                    // Find the next view to clip against
+                    int nextIndex = i;
+                    while (nextIndex < getChildCount()) {
+                        tmpTv = (TaskView) getChildAt(++nextIndex);
+                        if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
+                            nextTv = tmpTv;
+                            break;
+                        }
+                    }
+
+                    // Clip against the next view, this is just an approximation since we are
+                    // stacked and we can make assumptions about the visibility of the this
+                    // task relative to the ones in front of it.
+                    if (nextTv != null) {
+                        // XXX: Can hash the visible rects for this run
+                        tv.getHitRect(mTmpRect);
+                        nextTv.getHitRect(mTmpRect2);
+                        clipBottom = (mTmpRect.bottom - mTmpRect2.top);
+                    }
+                }
+                tv.setClipFromBottom(clipBottom);
+            }
+        }
+        if (getChildCount() > 0) {
+            // The front most task should never be clipped
+            TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
+            tv.setClipFromBottom(0);
+        }
+    }
+
     /** Sets the current stack scroll */
     public void setStackScroll(int value) {
         mStackScroll = value;
@@ -641,50 +681,10 @@
                     Console.AnsiPurple);
         }
         synchronizeStackViewsWithModel();
+        clipTaskViews();
         super.dispatchDraw(canvas);
     }
 
-    @Override
-    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
-        if (Constants.DebugFlags.App.EnableTaskStackClipping) {
-            TaskView tv = (TaskView) child;
-            TaskView nextTv = null;
-            TaskView tmpTv = null;
-            if (tv.shouldClipViewInStack()) {
-                int curIndex = indexOfChild(tv);
-
-                // Find the next view to clip against
-                while (nextTv == null && curIndex < getChildCount()) {
-                    tmpTv = (TaskView) getChildAt(++curIndex);
-                    if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
-                        nextTv = tmpTv;
-                    }
-                }
-
-                // Clip against the next view (if we aren't animating its alpha)
-                if (nextTv != null) {
-                    Rect curRect = tv.getClippingRect(mTmpRect);
-                    Rect nextRect = nextTv.getClippingRect(mTmpRect2);
-                    // The hit rects are relative to the task view, which needs to be offset by
-                    // the system bar height
-                    curRect.offset(0, mConfig.systemInsets.top);
-                    nextRect.offset(0, mConfig.systemInsets.top);
-                    // Compute the clip region
-                    Region clipRegion = new Region();
-                    clipRegion.op(curRect, Region.Op.UNION);
-                    clipRegion.op(nextRect, Region.Op.DIFFERENCE);
-                    // Clip the canvas
-                    int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
-                    canvas.clipRegion(clipRegion);
-                    boolean invalidate = super.drawChild(canvas, child, drawingTime);
-                    canvas.restoreToCount(saveCount);
-                    return invalidate;
-                }
-            }
-        }
-        return super.drawChild(canvas, child, drawingTime);
-    }
-
     /** Computes the stack and task rects */
     public void computeRects(int width, int height, int insetLeft, int insetBottom) {
         // Compute the rects in the stack algorithm
@@ -1155,6 +1155,11 @@
         mStack.removeTask(task);
     }
 
+    @Override
+    public void onTaskViewClipStateChanged(TaskView tv) {
+        invalidate(mStackAlgorithm.mStackRect);
+    }
+
     /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
index 908e063..9c48896 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -135,9 +135,9 @@
         // Set the y translation
         if (boundedT < 0f) {
             transformOut.translationY = (int) ((Math.max(-numPeekCards, boundedT) /
-                    numPeekCards) * peekHeight - scaleYOffset - scaleBarYOffset);
+                    numPeekCards) * peekHeight - scaleYOffset);
         } else {
-            transformOut.translationY = (int) (boundedT * overlapHeight - scaleYOffset - scaleBarYOffset);
+            transformOut.translationY = (int) (boundedT * overlapHeight - scaleYOffset);
         }
 
         // Set the z translation
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 5524e15..ab14863 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -46,8 +46,9 @@
     interface TaskViewCallbacks {
         public void onTaskViewAppIconClicked(TaskView tv);
         public void onTaskViewAppInfoClicked(TaskView tv);
-        public void onTaskViewClicked(TaskView tv, Task t, boolean lockToTask);
+        public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask);
         public void onTaskViewDismissed(TaskView tv);
+        public void onTaskViewClipStateChanged(TaskView tv);
     }
 
     RecentsConfiguration mConfig;
@@ -65,7 +66,7 @@
     boolean mIsFocused;
     boolean mIsStub;
     boolean mClipViewInStack;
-    Rect mTmpRect = new Rect();
+    int mClipFromBottom;
     Paint mLayerPaint = new Paint();
 
     TaskThumbnailView mThumbnailView;
@@ -118,7 +119,9 @@
         setOutlineProvider(new ViewOutlineProvider() {
             @Override
             public boolean getOutline(View view, Outline outline) {
-                int height = getHeight() - mMaxFooterHeight + mFooterHeight;
+                // The current height is measured with the footer, so account for the footer height
+                // and the current clip (in the stack)
+                int height = getMeasuredHeight() - mClipFromBottom - mMaxFooterHeight + mFooterHeight;
                 outline.setRoundRect(0, 0, getWidth(), height,
                         mConfig.taskViewRoundedCornerRadiusPx);
                 return true;
@@ -483,15 +486,6 @@
         mBarView.setNoUserInteractionState();
     }
 
-    /** Returns the rect we want to clip (it may not be the full rect) */
-    Rect getClippingRect(Rect outRect) {
-        getHitRect(outRect);
-        // XXX: We should get the hit rect of the thumbnail view and intersect, but this is faster
-        outRect.right = outRect.left + mThumbnailView.getRight();
-        outRect.bottom = outRect.top + mThumbnailView.getBottom();
-        return outRect;
-    }
-
     /** Enable the hw layers on this task view */
     void enableHwLayers() {
         mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
@@ -506,7 +500,7 @@
         mLockToAppButtonView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
     }
 
-    /** Sets the stubbed state of this task view. */
+    /** Sets the stubbed state of this task view.
     void setStubState(boolean isStub) {
         if (!mIsStub && isStub) {
             // This is now a stub task view, so clip to the bar height, hide the thumbnail
@@ -519,7 +513,7 @@
             mThumbnailView.setVisibility(View.VISIBLE);
         }
         mIsStub = isStub;
-    }
+    } */
 
     /**
      * Returns whether this view should be clipped, or any views below should clip against this
@@ -533,19 +527,26 @@
     void setClipViewInStack(boolean clip) {
         if (clip != mClipViewInStack) {
             mClipViewInStack = clip;
-            if (getParent() instanceof View) {
-                getHitRect(mTmpRect);
-                ((View) getParent()).invalidate(mTmpRect);
-            }
+            mCb.onTaskViewClipStateChanged(this);
+        }
+    }
+
+    void setClipFromBottom(int clipFromBottom) {
+        clipFromBottom = Math.max(0, Math.min(getMeasuredHeight(), clipFromBottom));
+        if (mClipFromBottom != clipFromBottom) {
+            mClipFromBottom = clipFromBottom;
+            invalidateOutline();
         }
     }
 
     /** Sets the footer height. */
-    public void setFooterHeight(int height) {
-        mFooterHeight = height;
-        invalidateOutline();
-        invalidate(0, getMeasuredHeight() - mMaxFooterHeight, getMeasuredWidth(),
-                getMeasuredHeight());
+    public void setFooterHeight(int footerHeight) {
+        if (footerHeight != mFooterHeight) {
+            mFooterHeight = footerHeight;
+            invalidateOutline();
+            invalidate(0, getMeasuredHeight() - mMaxFooterHeight, getMeasuredWidth(),
+                    getMeasuredHeight());
+        }
     }
 
     /** Gets the footer height. */
@@ -677,6 +678,7 @@
         mTask = t;
         mTask.setCallbacks(this);
         if (getMeasuredWidth() == 0) {
+            // If we haven't yet measured, we should just set the footer height with any animation
             animateFooterVisibility(t.canLockToTask, 0, 0);
         } else {
             animateFooterVisibility(t.canLockToTask, mConfig.taskViewLockToAppLongAnimDuration, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataController.java
index f2dfe05..33d68bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataController.java
@@ -20,6 +20,8 @@
 import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
 import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
 import static android.telephony.TelephonyManager.SIM_STATE_READY;
+import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
+import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
 
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -33,19 +35,23 @@
 import android.os.ServiceManager;
 import android.telephony.TelephonyManager;
 import android.text.format.DateUtils;
+import android.text.format.Time;
 import android.util.Log;
 
 import com.android.systemui.statusbar.policy.NetworkController.DataUsageInfo;
 
-import java.text.SimpleDateFormat;
 import java.util.Date;
+import java.util.Locale;
 
 public class MobileDataController {
     private static final String TAG = "MobileDataController";
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final SimpleDateFormat MMM_D = new SimpleDateFormat("MMM d");
+    private static final long DEFAULT_WARNING_LEVEL = 2L * 1024 * 1024 * 1024;
     private static final int FIELDS = FIELD_RX_BYTES | FIELD_TX_BYTES;
+    private static final StringBuilder PERIOD_BUILDER = new StringBuilder(50);
+    private static final java.util.Formatter PERIOD_FORMATTER = new java.util.Formatter(
+            PERIOD_BUILDER, Locale.getDefault());
 
     private final Context mContext;
     private final TelephonyManager mTelephonyManager;
@@ -87,6 +93,13 @@
         return null;
     }
 
+    private static Time addMonth(Time t, int months) {
+        final Time rt = new Time(t);
+        rt.set(t.monthDay, t.month + months, t.year);
+        rt.normalize(false);
+        return rt;
+    }
+
     public DataUsageInfo getDataUsageInfo() {
         final String subscriberId = getActiveSubscriberId(mContext);
         if (subscriberId == null) {
@@ -101,9 +114,28 @@
         try {
             final NetworkStatsHistory history = mSession.getHistoryForNetwork(template, FIELDS);
             final long now = System.currentTimeMillis();
-            // period = last 4 wks for now
-            final long start = now - DateUtils.WEEK_IN_MILLIS * 4;
-            final long end = now;
+            final long start, end;
+            if (policy != null && policy.cycleDay > 0) {
+                // period = determined from cycleDay
+                if (DEBUG) Log.d(TAG, "Cycle day=" + policy.cycleDay + " tz="
+                        + policy.cycleTimezone);
+                final Time nowTime = new Time(policy.cycleTimezone);
+                nowTime.setToNow();
+                final Time policyTime = new Time(nowTime);
+                policyTime.set(policy.cycleDay, policyTime.month, policyTime.year);
+                policyTime.normalize(false);
+                if (nowTime.after(policyTime)) {
+                    start = policyTime.toMillis(false);
+                    end = addMonth(policyTime, 1).toMillis(false);
+                } else {
+                    start = addMonth(policyTime, -1).toMillis(false);
+                    end = policyTime.toMillis(false);
+                }
+            } else {
+                // period = last 4 wks
+                end = now;
+                start = now - DateUtils.WEEK_IN_MILLIS * 4;
+            }
             final long callStart = System.currentTimeMillis();
             final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null);
             final long callEnd = System.currentTimeMillis();
@@ -115,12 +147,13 @@
             }
             final long totalBytes = entry.rxBytes + entry.txBytes;
             final DataUsageInfo usage = new DataUsageInfo();
-            usage.maxLevel = (long) (totalBytes / .4);
             usage.usageLevel = totalBytes;
-            usage.period = MMM_D.format(new Date(start)) + " - " + MMM_D.format(new Date(end));
+            usage.period = formatDateRange(start, end);
             if (policy != null) {
                 usage.limitLevel = policy.limitBytes > 0 ? policy.limitBytes : 0;
                 usage.warningLevel = policy.warningBytes > 0 ? policy.warningBytes : 0;
+            } else {
+                usage.warningLevel = DEFAULT_WARNING_LEVEL;
             }
             return usage;
         } catch (RemoteException e) {
@@ -178,6 +211,15 @@
         return actualSubscriberId;
     }
 
+    private String formatDateRange(long start, long end) {
+        final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
+        synchronized (PERIOD_BUILDER) {
+            PERIOD_BUILDER.setLength(0);
+            return DateUtils.formatDateRange(mContext, PERIOD_FORMATTER, start, end, flags, null)
+                    .toString();
+        }
+    }
+
     public interface Callback {
         void onMobileDataEnabled(boolean enabled);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index d058bd0..6d8b400 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -61,7 +61,6 @@
     public static class DataUsageInfo {
         public String carrier;
         public String period;
-        public long maxLevel;
         public long limitLevel;
         public long warningLevel;
         public long usageLevel;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index ad2cf75..0c5d2a5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -908,6 +908,9 @@
 
             if (mDialog != null) {
                 mDialog.show();
+                if (mCallback != null) {
+                    mCallback.onVisible(true);
+                }
             }
         }
 
@@ -1160,6 +1163,9 @@
                         mDialog.dismiss();
                         clearRemoteStreamController();
                         mActiveStreamType = -1;
+                        if (mCallback != null) {
+                            mCallback.onVisible(false);
+                        }
                     }
                 }
                 synchronized (sConfirmSafeVolumeLock) {
@@ -1262,5 +1268,6 @@
     public interface Callback {
         void onZenSettings();
         void onInteraction();
+        void onVisible(boolean visible);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index e4f5870..375f94c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -99,6 +99,13 @@
                     kvm.userActivity();
                 }
             }
+
+            @Override
+            public void onVisible(boolean visible) {
+                if (mAudioManager != null && mVolumeController != null) {
+                    mAudioManager.notifyVolumeControllerVisible(mVolumeController, visible);
+                }
+            }
         });
         mDialogPanel = mPanel;
     }
diff --git a/services/core/java/com/android/server/media/MediaRouteProviderProxy.java b/services/core/java/com/android/server/media/MediaRouteProviderProxy.java
deleted file mode 100644
index b31153b..0000000
--- a/services/core/java/com/android/server/media/MediaRouteProviderProxy.java
+++ /dev/null
@@ -1,419 +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 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.MediaSession;
-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.io.PrintWriter;
-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;
-    // Interfaces declared in the manifest
-    private final ArrayList<String> mInterfaces = new ArrayList<String>();
-    private final ArrayList<RouteConnectionRecord> mConnections
-            = new ArrayList<RouteConnectionRecord>();
-    // The sessions that have a route from this provider selected
-    private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
-    private final Handler mHandler = new Handler();
-
-    private Intent mBindIntent;
-    private IRouteProvider mBinder;
-    private boolean mRunning;
-    private boolean mPaused;
-    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;
-        if (interfaces != null) {
-            mInterfaces.addAll(interfaces);
-        }
-        mBindIntent = new Intent(RouteProviderService.SERVICE_INTERFACE);
-        mBindIntent.setComponent(mComponentName);
-    }
-
-    public void destroy() {
-        stop();
-        mSessions.clear();
-        updateBinding();
-    }
-
-    /**
-     * 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, mComponentName.getPackageName(), mUserId);
-                        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;
-    }
-
-    public void addSession(MediaSessionRecord session) {
-        mSessions.add(session);
-    }
-
-    public void removeSession(MediaSessionRecord session) {
-        mSessions.remove(session);
-        updateBinding();
-    }
-
-    public int getSessionCount() {
-        return mSessions.size();
-    }
-
-    public void dump(PrintWriter pw, String prefix) {
-        pw.println(prefix + mId + " " + this);
-        String indent = prefix + "  ";
-
-        pw.println(indent + "component=" + mComponentName.toString());
-        pw.println(indent + "user id=" + mUserId);
-        pw.println(indent + "interfaces=" + mInterfaces.toString());
-        pw.println(indent + "connections=" + mConnections.toString());
-        pw.println(indent + "running=" + mRunning);
-        pw.println(indent + "interested=" + mInterested);
-        pw.println(indent + "bound=" + mBound);
-    }
-
-    private void updateBinding() {
-        if (shouldBind()) {
-            bind();
-        } else {
-            unbind();
-        }
-    }
-
-    // We want to bind as long as we're interested in this provider or there are
-    // sessions connected to it.
-    private boolean shouldBind() {
-        return (mRunning && mInterested) || (!mSessions.isEmpty());
-    }
-
-    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
deleted file mode 100644
index 734eab9..0000000
--- a/services/core/java/com/android/server/media/MediaRouteProviderWatcher.java
+++ /dev/null
@@ -1,239 +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 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);
-        }
-    }
-
-    // Stop discovering providers and routes. Providers that still have an
-    // active session connected to them will not unbind.
-    public void stop() {
-        if (mRunning) {
-            mRunning = false;
-
-            mContext.unregisterReceiver(mScanPackagesReceiver);
-            mHandler.removeCallbacks(mScanPackagesRunnable);
-
-            // Stop all inactive providers.
-            for (int i = mProviders.size() - 1; i >= 0; i--) {
-                mProviders.get(i).stop();
-            }
-        }
-    }
-
-    // Clean up the providers forcibly unbinding if necessary
-    public void destroy() {
-        for (int i = mProviders.size() - 1; i >= 0; i--) {
-            mProviders.get(i).destroy();
-            mProviders.remove(i);
-        }
-    }
-
-    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 0fbcd7e..341c7a9 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -19,19 +19,16 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.media.routeprovider.RouteRequest;
+import android.media.routing.IMediaRouter;
+import android.media.routing.IMediaRouterDelegate;
+import android.media.routing.IMediaRouterStateCallback;
 import android.media.session.ISessionController;
 import android.media.session.ISessionControllerCallback;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
 import android.media.session.MediaController;
-import android.media.session.RouteCommand;
-import android.media.session.RouteInfo;
-import android.media.session.RouteOptions;
-import android.media.session.RouteEvent;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionInfo;
-import android.media.session.RouteInterface;
 import android.media.session.PlaybackState;
 import android.media.session.ParcelableVolumeInfo;
 import android.media.AudioManager;
@@ -94,14 +91,9 @@
     private final Object mLock = new Object();
     private final ArrayList<ISessionControllerCallback> mControllerCallbacks =
             new ArrayList<ISessionControllerCallback>();
-    private final ArrayList<RouteRequest> mRequests = new ArrayList<RouteRequest>();
 
-    private RouteInfo mRoute;
-    private RouteOptions mRequest;
-    private RouteConnectionRecord mConnection;
-    // TODO define a RouteState class with relevant info
-    private int mRouteState;
     private long mFlags;
+    private IMediaRouter mMediaRouter;
     private ComponentName mMediaButtonReceiver;
 
     // TransportPerformer fields
@@ -160,24 +152,6 @@
     }
 
     /**
-     * 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.
@@ -229,41 +203,6 @@
     }
 
     /**
-     * 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) {
-                disconnect(MediaSession.DISCONNECT_REASON_ROUTE_CHANGED);
-            }
-            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);
-    }
-
-    /**
      * Send a volume adjustment to the session owner.
      *
      * @param delta The amount to adjust the volume by.
@@ -338,40 +277,6 @@
     }
 
     /**
-     * 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 (mDestroyed) {
-                Log.i(TAG, "setRouteConnected: session has been destroyed");
-                connection.disconnect();
-                return false;
-            }
-            if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) {
-                Log.w(TAG, "setRouteConnected: connected route is stale");
-                connection.disconnect();
-                return false;
-            }
-            if (request != mRequest) {
-                Log.w(TAG, "setRouteConnected: connection request is stale");
-                connection.disconnect();
-                return false;
-            }
-            mConnection = connection;
-            mConnection.setListener(mConnectionListener);
-            mSessionCb.sendRouteConnected();
-        }
-        return true;
-    }
-
-    /**
      * Check if this session has been set to active by the app.
      *
      * @return True if the session is active, false otherwise.
@@ -460,30 +365,6 @@
         return mOptimisticVolume;
     }
 
-    /**
-     * @return True if this session is currently connected to a route.
-     */
-    public boolean isConnected() {
-        return mConnection != null;
-    }
-
-    public void disconnect(int reason) {
-        synchronized (mLock) {
-            if (!mDestroyed) {
-                disconnectLocked(reason);
-            }
-        }
-    }
-
-    private void disconnectLocked(int reason) {
-        if (mConnection != null) {
-            mConnection.setListener(null);
-            mConnection.disconnect();
-            mConnection = null;
-            pushDisconnected(reason);
-        }
-    }
-
     public boolean isTransportControlEnabled() {
         return hasFlag(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
     }
@@ -502,11 +383,6 @@
             if (mDestroyed) {
                 return;
             }
-            if (isConnected()) {
-                disconnectLocked(MediaSession.DISCONNECT_REASON_SESSION_DESTROYED);
-            }
-            mRoute = null;
-            mRequest = null;
             mDestroyed = true;
         }
     }
@@ -532,15 +408,6 @@
         pw.println(indent + "controllers: " + mControllerCallbacks.size());
         pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString()));
         pw.println(indent + "metadata:" + getShortMetadataString());
-        pw.println(indent + "route requests {");
-        int size = mRequests.size();
-        for (int i = 0; i < size; i++) {
-            pw.println(indent + "  " + mRequests.get(i).toString());
-        }
-        pw.println(indent + "}");
-        pw.println(indent + "route=" + (mRoute == null ? null : mRoute.toString()));
-        pw.println(indent + "connection=" + (mConnection == null ? null : mConnection.toString()));
-        pw.println(indent + "params=" + (mRequest == null ? null : mRequest.toString()));
     }
 
     private String getShortMetadataString() {
@@ -550,12 +417,6 @@
         return "size=" + fields + ", title=" + title;
     }
 
-    private void pushDisconnected(int reason) {
-        synchronized (mLock) {
-            mSessionCb.sendRouteDisconnected(reason);
-        }
-    }
-
     private void pushPlaybackStateUpdate() {
         synchronized (mLock) {
             if (mDestroyed) {
@@ -613,25 +474,6 @@
         }
     }
 
-    private void pushRouteUpdate() {
-        synchronized (mLock) {
-            if (mDestroyed) {
-                return;
-            }
-            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
-                ISessionControllerCallback cb = mControllerCallbacks.get(i);
-                try {
-                    cb.onRouteChanged(mRoute);
-                } catch (DeadObjectException e) {
-                    Log.w(TAG, "Removing dead callback in pushRouteUpdate.", e);
-                    mControllerCallbacks.remove(i);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "unexpected exception in pushRouteUpdate.", e);
-                }
-            }
-        }
-    }
-
     private void pushEvent(String event, Bundle data) {
         synchronized (mLock) {
             if (mDestroyed) {
@@ -651,25 +493,6 @@
         }
     }
 
-    private void pushRouteCommand(RouteCommand command, ResultReceiver cb) {
-        synchronized (mLock) {
-            if (mDestroyed) {
-                return;
-            }
-            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 PlaybackState getStateWithUpdatedPosition() {
         PlaybackState state = mPlaybackState;
         long duration = -1;
@@ -708,21 +531,6 @@
         return -1;
     }
 
-    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() {
-            MediaSessionRecord.this.disconnect(MediaSession.DISCONNECT_REASON_PROVIDER_DISCONNECTED);
-        }
-    };
-
     private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
         @Override
         public void run() {
@@ -769,6 +577,12 @@
         }
 
         @Override
+        public void setMediaRouter(IMediaRouter router) {
+            mMediaRouter = router;
+            mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
+        }
+
+        @Override
         public void setMediaButtonReceiver(ComponentName mbr) {
             mMediaButtonReceiver = mbr;
         }
@@ -797,46 +611,6 @@
         }
 
         @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 disconnectFromRoute(RouteInfo route) {
-            if (route != null && mRoute != null
-                    && TextUtils.equals(route.getId(), mRoute.getId())) {
-                disconnect(MediaSession.DISCONNECT_REASON_SESSION_DISCONNECTED);
-            }
-        }
-
-        @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);
-            }
-        }
-
-        @Override
         public void setCurrentVolume(int volume) {
             mCurrentVolume = volume;
             mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
@@ -903,46 +677,6 @@
             }
         }
 
-        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 sendRouteDisconnected(int reason) {
-            try {
-                mCb.onRouteDisconnected(mRoute, reason);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote failure in sendRouteDisconnected");
-            }
-        }
-
         public void play() {
             try {
                 mCb.onPlay();
@@ -1187,20 +921,19 @@
         }
 
         @Override
-        public void showRoutePicker() {
-            mService.showRoutePickerForSession(MediaSessionRecord.this);
+        public IMediaRouterDelegate createMediaRouterDelegate(
+                IMediaRouterStateCallback callback) {
+            // todo
+            return null;
         }
     }
 
     private class MessageHandler extends Handler {
         private static final int MSG_UPDATE_METADATA = 1;
         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;
-        private static final int MSG_UPDATE_SESSION_STATE = 7;
-        private static final int MSG_UPDATE_VOLUME = 8;
+        private static final int MSG_SEND_EVENT = 3;
+        private static final int MSG_UPDATE_SESSION_STATE = 4;
+        private static final int MSG_UPDATE_VOLUME = 5;
 
         public MessageHandler(Looper looper) {
             super(looper);
@@ -1214,17 +947,9 @@
                 case MSG_UPDATE_PLAYBACK_STATE:
                     pushPlaybackStateUpdate();
                     break;
-                case MSG_UPDATE_ROUTE:
-                    pushRouteUpdate();
-                    break;
                 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;
                 case MSG_UPDATE_SESSION_STATE:
                     // TODO add session state
                     break;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 310f3e9..4c475d9 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -30,14 +30,11 @@
 import android.media.AudioManager;
 import android.media.IAudioService;
 import android.media.IRemoteVolumeController;
-import android.media.routeprovider.RouteRequest;
 import android.media.session.IActiveSessionsListener;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
 import android.media.session.ISessionManager;
 import android.media.session.MediaSession;
-import android.media.session.RouteInfo;
-import android.media.session.RouteOptions;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -74,15 +71,12 @@
     private static final int WAKELOCK_TIMEOUT = 5000;
 
     private final SessionManagerImpl mSessionManagerImpl;
-    // private final MediaRouteProviderWatcher mRouteProviderWatcher;
     private final MediaSessionStack mPriorityStack;
 
     private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
     private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
     private final ArrayList<SessionsListenerRecord> mSessionsListeners
             = new ArrayList<SessionsListenerRecord>();
-    // private final ArrayList<MediaRouteProviderProxy> mProviders
-    // = new ArrayList<MediaRouteProviderProxy>();
     private final Object mLock = new Object();
     private final MessageHandler mHandler = new MessageHandler();
     private final PowerManager.WakeLock mMediaEventWakeLock;
@@ -94,10 +88,6 @@
     private MediaSessionRecord mPrioritySession;
     private int mCurrentUserId = -1;
 
-    // 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;
-
     // Used to notify system UI when remote volume was changed. TODO find a
     // better way to handle this.
     private IRemoteVolumeController mRvc;
@@ -126,69 +116,6 @@
         return IAudioService.Stub.asInterface(b);
     }
 
-    /**
-     * 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)
-        synchronized (mLock) {
-            if (!mAllSessions.contains(record)) {
-                Log.d(TAG, "Unknown session tried to show route picker. Ignoring.");
-                return;
-            }
-            RouteInfo current = record.getRoute();
-            UserRecord user = mUserRecords.get(record.getUserId());
-            if (current != null) {
-                // For now send null to mean the local route
-                MediaRouteProviderProxy proxy = user.getProviderLocked(current.getProvider());
-                if (proxy != null) {
-                    proxy.removeSession(record);
-                }
-                record.selectRoute(null);
-                return;
-            }
-            ArrayList<MediaRouteProviderProxy> providers = user.getProvidersLocked();
-            mShowRoutesRequestId++;
-            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) {
-            if (!mAllSessions.contains(session)) {
-                Log.d(TAG, "Unknown session attempting to connect to route. Ignoring");
-                return;
-            }
-            UserRecord user = mUserRecords.get(session.getUserId());
-            if (user == null) {
-                Log.wtf(TAG, "connectToRoute: User " + session.getUserId() + " does not exist.");
-                return;
-            }
-            MediaRouteProviderProxy proxy = user.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);
-            proxy.connectToRoute(session, route, request);
-        }
-    }
-
     public void updateSession(MediaSessionRecord record) {
         synchronized (mLock) {
             if (!mAllSessions.contains(record)) {
@@ -552,110 +479,49 @@
         }
     }
 
-    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 = mAllSessions.get(index);
-                    RouteInfo route = routes.get(0);
-                    record.selectRoute(route);
-                    UserRecord user = mUserRecords.get(record.getUserId());
-                    MediaRouteProviderProxy provider = user.getProviderLocked(route.getProvider());
-                    provider.addSession(record);
-                }
-            }
-        }
-
-        @Override
-        public void onRouteConnected(String sessionId, RouteInfo route,
-                RouteRequest options, RouteConnectionRecord connection) {
-            synchronized (mLock) {
-                int index = findIndexOfSessionForIdLocked(sessionId);
-                if (index != -1) {
-                    MediaSessionRecord session = mAllSessions.get(index);
-                    session.setRouteConnected(route, options.getConnectionOptions(), connection);
-                }
-            }
-        }
-    };
-
     /**
      * Information about a particular user. The contents of this object is
      * guarded by mLock.
      */
     final class UserRecord {
         private final int mUserId;
-        private final MediaRouteProviderWatcher mRouteProviderWatcher;
-        private final ArrayList<MediaRouteProviderProxy> mProviders
-                = new ArrayList<MediaRouteProviderProxy>();
         private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
 
         public UserRecord(Context context, int userId) {
             mUserId = userId;
-            mRouteProviderWatcher = new MediaRouteProviderWatcher(context,
-                    mProviderWatcherCallback, mHandler, userId);
         }
 
         public void startLocked() {
-            mRouteProviderWatcher.start();
+            // nothing for now
         }
 
         public void stopLocked() {
-            mRouteProviderWatcher.stop();
-            updateInterestLocked();
+            // nothing for now
         }
 
         public void destroyLocked() {
             for (int i = mSessions.size() - 1; i >= 0; i--) {
                 MediaSessionRecord session = mSessions.get(i);
                 MediaSessionService.this.destroySessionLocked(session);
-                if (session.isConnected()) {
-                    session.disconnect(MediaSession.DISCONNECT_REASON_USER_STOPPING);
-                }
             }
         }
 
-        public ArrayList<MediaRouteProviderProxy> getProvidersLocked() {
-            return mProviders;
-        }
-
         public ArrayList<MediaSessionRecord> getSessionsLocked() {
             return mSessions;
         }
 
         public void addSessionLocked(MediaSessionRecord session) {
             mSessions.add(session);
-            updateInterestLocked();
         }
 
         public void removeSessionLocked(MediaSessionRecord session) {
             mSessions.remove(session);
-            RouteInfo route = session.getRoute();
-            if (route != null) {
-                MediaRouteProviderProxy provider = getProviderLocked(route.getProvider());
-                if (provider != null) {
-                    provider.removeSession(session);
-                }
-            }
-            updateInterestLocked();
         }
 
         public void dumpLocked(PrintWriter pw, String prefix) {
             pw.println(prefix + "Record for user " + mUserId);
             String indent = prefix + "  ";
-            int size = mProviders.size();
-            pw.println(indent + size + " Providers:");
-            for (int i = 0; i < size; i++) {
-                mProviders.get(i).dump(pw, indent);
-            }
-            pw.println();
-            size = mSessions.size();
+            int size = mSessions.size();
             pw.println(indent + size + " Sessions:");
             for (int i = 0; i < size; i++) {
                 // Just print the session info, the full session dump will
@@ -663,48 +529,6 @@
                 pw.println(indent + mSessions.get(i).getSessionInfo());
             }
         }
-
-        public void updateInterestLocked() {
-            // TODO go through the sessions and build up the set of interfaces
-            // we're interested in. Update the provider watcher.
-            // For now, just express interest in all providers for the current
-            // user
-            boolean interested = mUserId == mCurrentUserId;
-            for (int i = mProviders.size() - 1; i >= 0; i--) {
-                mProviders.get(i).setInterested(interested);
-            }
-        }
-
-        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 MediaRouteProviderWatcher.Callback mProviderWatcherCallback
-                = new MediaRouteProviderWatcher.Callback() {
-            @Override
-            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);
-                }
-            }
-        };
     }
 
     final class SessionsListenerRecord implements IBinder.DeathRecipient {
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index e26a2eb..3d1ecb3 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -25,7 +25,7 @@
 
 /**
  * Keeps track of media sessions and their priority for notifications, media
- * button routing, etc.
+ * button dispatch, etc.
  */
 public class MediaSessionStack {
     /**
@@ -277,8 +277,8 @@
                 lastActiveIndex++;
                 lastPublishedIndex++;
             } else if (session.isPlaybackActive(true)) {
-                // TODO replace getRoute() == null with real local route check
-                if(session.getRoute() == null) {
+                // TODO this with real local route check
+                if (true) {
                     // Active local sessions get top priority
                     result.add(lastLocalIndex, session);
                     lastLocalIndex++;
diff --git a/services/core/java/com/android/server/media/RouteConnectionRecord.java b/services/core/java/com/android/server/media/RouteConnectionRecord.java
deleted file mode 100644
index 90ddf29..0000000
--- a/services/core/java/com/android/server/media/RouteConnectionRecord.java
+++ /dev/null
@@ -1,118 +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 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 final String mPackageName;
-    private final int mUid;
-    private Listener mListener;
-
-    public RouteConnectionRecord(IRouteConnection binder, String packageName, int uid) {
-        mBinder = binder;
-        mPackageName = packageName;
-        mUid = uid;
-    }
-
-    /**
-     * 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();
-        }
-    }
-
-    @Override
-    public String toString() {
-        return "RouteConnection { binder=" + mBinder.toString() + ", package=" + mPackageName
-                + ", uid=" + mUid + "}";
-    }
-
-    /**
-     * 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/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 5233297..c0923ca 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -656,14 +656,13 @@
                     }
                     break;
                 case MSG_USER_SWITCHED: {
-                    mCurrentUser = msg.arg1;
-
                     UserManager userManager =
                             (UserManager) mContext.getSystemService(Context.USER_SERVICE);
                     if (userManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) {
                         Slog.v(TAG, "Switched to user with DISALLOW_USB_FILE_TRANSFER restriction;"
                                 + " disabling USB.");
                         setUsbConfig("none");
+                        mCurrentUser = msg.arg1;
                         break;
                     }
 
@@ -675,6 +674,7 @@
                         setUsbConfig("none");
                         setUsbConfig(mCurrentFunctions);
                     }
+                    mCurrentUser = msg.arg1;
                     break;
                 }
             }
diff --git a/tests/OneMedia/Android.mk b/tests/OneMedia/Android.mk
index 93b9c9a..4feac68 100644
--- a/tests/OneMedia/Android.mk
+++ b/tests/OneMedia/Android.mk
@@ -10,8 +10,7 @@
 LOCAL_CERTIFICATE := platform
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-        android-support-v7-appcompat \
-        android-support-v7-mediarouter
+    android-support-media-protocols
 
 LOCAL_PROGUARD_ENABLED := disabled
 
diff --git a/tests/OneMedia/AndroidManifest.xml b/tests/OneMedia/AndroidManifest.xml
index 504d471..9d78ca5 100644
--- a/tests/OneMedia/AndroidManifest.xml
+++ b/tests/OneMedia/AndroidManifest.xml
@@ -27,11 +27,11 @@
             android:process="com.android.onemedia.service" />
         <service
             android:name=".provider.OneMediaRouteProvider"
-            android:permission="android.permission.BIND_ROUTE_PROVIDER"
+            android:permission="android.permission.BIND_MEDIA_ROUTE_SERVICE"
             android:exported="true"
             android:process="com.android.onemedia.provider">
             <intent-filter>
-                <action android:name="com.android.media.session.MediaRouteProvider" />
+                <action android:name="android.media.routing.MediaRouteService" />
             </intent-filter>
           </service>
     </application>
diff --git a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
index ee407ad..894377b 100644
--- a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
+++ b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
@@ -28,8 +28,6 @@
 import android.widget.EditText;
 import android.widget.TextView;
 
-import com.android.onemedia.playback.Renderer;
-
 public class OnePlayerActivity extends Activity {
     private static final String TAG = "OnePlayerActivity";
 
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerController.java b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
index 145b389..802f473 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerController.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
@@ -18,7 +18,6 @@
 
 import android.media.MediaMetadata;
 import android.media.session.MediaController;
-import android.media.session.RouteInfo;
 import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
@@ -121,7 +120,7 @@
     }
 
     public void showRoutePicker() {
-        mController.showRoutePicker();
+        // TODO
     }
 
     private void unbindFromService() {
@@ -173,11 +172,6 @@
 
     private class SessionCallback extends MediaController.Callback {
         @Override
-        public void onRouteChanged(RouteInfo route) {
-            // TODO
-        }
-
-        @Override
         public void onPlaybackStateChanged(PlaybackState state) {
             if (state == null) {
                 return;
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
index a220107..7c0eabe 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
@@ -17,14 +17,17 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.media.session.Route;
-import android.media.session.RouteInfo;
-import android.media.session.RouteOptions;
-import android.media.session.RoutePlaybackControls;
+import android.media.routing.MediaRouteSelector;
+import android.media.routing.MediaRouter;
+import android.media.routing.MediaRouter.ConnectionRequest;
+import android.media.routing.MediaRouter.DestinationInfo;
+import android.media.routing.MediaRouter.RouteInfo;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
+import android.support.media.protocols.MediaPlayerProtocol;
+import android.support.media.protocols.MediaPlayerProtocol.MediaStatus;
 import android.util.Log;
 import android.view.KeyEvent;
 
@@ -34,11 +37,13 @@
 import com.android.onemedia.playback.RequestUtils;
 
 import java.util.ArrayList;
+import java.util.List;
 
 public class PlayerSession {
     private static final String TAG = "PlayerSession";
 
     protected MediaSession mSession;
+    protected MediaRouter mRouter;
     protected Context mContext;
     protected Renderer mRenderer;
     protected MediaSession.Callback mCallback;
@@ -46,10 +51,6 @@
 
     protected PlaybackState mPlaybackState;
     protected Listener mListener;
-    protected ArrayList<RouteOptions> mRouteOptions;
-    protected Route mRoute;
-    protected RoutePlaybackControls mRouteControls;
-    protected RouteListener mRouteListener;
 
     private String mContent;
 
@@ -63,41 +64,53 @@
                 | 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();
-        }
+        releaseSession();
+
         MediaSessionManager man = (MediaSessionManager) mContext
                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
         Log.d(TAG, "Creating session for package " + mContext.getBasePackageName());
+
+        mRouter = new MediaRouter(mContext);
+        mRouter.addSelector(new MediaRouteSelector.Builder()
+                .addRequiredProtocol(MediaPlayerProtocol.class)
+                .build());
+        mRouter.addSelector(new MediaRouteSelector.Builder()
+                .setRequiredFeatures(MediaRouter.ROUTE_FEATURE_LIVE_AUDIO)
+                .setOptionalFeatures(MediaRouter.ROUTE_FEATURE_LIVE_VIDEO)
+                .build());
+        mRouter.setRoutingCallback(new RoutingCallback(), null);
+
         mSession = man.createSession("OneMedia");
         mSession.addCallback(mCallback);
         mSession.addTransportControlsCallback(new TransportCallback());
         mSession.setPlaybackState(mPlaybackState);
         mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
-        mSession.setRouteOptions(mRouteOptions);
+        mSession.setMediaRouter(mRouter);
         mSession.setActive(true);
     }
 
     public void onDestroy() {
-        if (mSession != null) {
-            mSession.release();
-        }
+        releaseSession();
         if (mRenderer != null) {
             mRenderer.unregisterListener(mRenderListener);
             mRenderer.onDestroy();
         }
     }
 
+    private void releaseSession() {
+        if (mSession != null) {
+            mSession.release();
+            mSession = null;
+        }
+        if (mRouter != null) {
+            mRouter.release();
+            mRouter = null;
+        }
+    }
+
     public void setListener(Listener listener) {
         mListener = listener;
     }
@@ -218,40 +231,6 @@
                 }
             }
         }
-
-        @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.STATE_NONE);
-            } else {
-                // Use remote route
-                mSession.connect(route, mRouteOptions.get(0));
-                mRenderer = null;
-                updateState(PlaybackState.STATE_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.STATE_NONE);
-        }
-
-        @Override
-        public void onRouteDisconnected(Route route, int reason) {
-
-        }
     }
 
     private class TransportCallback extends MediaSession.TransportControlsCallback {
@@ -266,12 +245,62 @@
         }
     }
 
-    private class RouteListener extends RoutePlaybackControls.Listener {
+    private class RoutingCallback extends MediaRouter.RoutingCallback {
         @Override
-        public void onPlaybackStateChange(int state) {
-            Log.d(TAG, "Updating state to " + state);
-            updateState(state);
+        public void onConnectionStateChanged(int state) {
+            if (state == MediaRouter.CONNECTION_STATE_CONNECTING) {
+                if (mRenderer != null) {
+                    mRenderer.onStop();
+                }
+                mRenderer = null;
+                updateState(PlaybackState.STATE_CONNECTING);
+                return;
+            }
+
+            MediaRouter.ConnectionInfo connection = mRouter.getConnection();
+            if (connection != null) {
+                MediaPlayerProtocol protocol =
+                        connection.getProtocolObject(MediaPlayerProtocol.class);
+                if (protocol != null) {
+                    Log.d(TAG, "Connected to route using media player protocol");
+
+                    protocol.setCallback(new PlayerCallback(), null);
+                    mRenderer = new OneMRPRenderer(protocol);
+                    updateState(PlaybackState.STATE_NONE);
+                    return;
+                }
+            }
+
+            // Use local route
+            mRenderer = new LocalRenderer(mContext, null);
+            mRenderer.registerListener(mRenderListener);
+            updateState(PlaybackState.STATE_NONE);
         }
     }
 
+    private class PlayerCallback extends MediaPlayerProtocol.Callback {
+        @Override
+        public void onStatusUpdated(MediaStatus status, Bundle extras) {
+            if (status != null) {
+                Log.d(TAG, "Received status update: " + status.toBundle());
+                switch (status.getPlayerState()) {
+                    case MediaStatus.PLAYER_STATE_BUFFERING:
+                        updateState(PlaybackState.STATE_BUFFERING);
+                        break;
+                    case MediaStatus.PLAYER_STATE_IDLE:
+                        updateState(PlaybackState.STATE_STOPPED);
+                        break;
+                    case MediaStatus.PLAYER_STATE_PAUSED:
+                        updateState(PlaybackState.STATE_PAUSED);
+                        break;
+                    case MediaStatus.PLAYER_STATE_PLAYING:
+                        updateState(PlaybackState.STATE_PLAYING);
+                        break;
+                    case MediaStatus.PLAYER_STATE_UNKNOWN:
+                        updateState(PlaybackState.STATE_NONE);
+                        break;
+                }
+            } 
+        }
+    }
 }
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java b/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java
index 05516d2..c133325 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java
@@ -15,10 +15,10 @@
  */
 package com.android.onemedia.playback;
 
+import android.media.MediaMetadata;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.support.v7.media.MediaItemMetadata;
 
 /**
  * TODO: Insert description here. (generated by epastern)
@@ -35,11 +35,11 @@
     }
 
     public String getTitle() {
-        return mBundle.getString(MediaItemMetadata.KEY_TITLE);
+        return mBundle.getString(MediaMetadata.METADATA_KEY_TITLE);
     }
 
     public String getArtist() {
-        return mBundle.getString(MediaItemMetadata.KEY_ALBUM_ARTIST);
+        return mBundle.getString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST);
     }
 
     /* (non-Javadoc)
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java b/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java
index 9b0a2b2..55eb92c 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java
@@ -1,39 +1,42 @@
 package com.android.onemedia.playback;
 
-import android.media.session.RoutePlaybackControls;
 import android.os.Bundle;
+import android.support.media.protocols.MediaPlayerProtocol;
+import android.support.media.protocols.MediaPlayerProtocol.MediaInfo;
 
 /**
  * Renderer for communicating with the OneMRP route
  */
 public class OneMRPRenderer extends Renderer {
-    private final RoutePlaybackControls mControls;
+    private final MediaPlayerProtocol mProtocol;
 
-    public OneMRPRenderer(RoutePlaybackControls controls) {
+    public OneMRPRenderer(MediaPlayerProtocol protocol) {
         super(null, null);
-        mControls = controls;
+        mProtocol = protocol;
     }
 
     @Override
     public void setContent(Bundle request) {
-        mControls.playNow(request.getString(RequestUtils.EXTRA_KEY_SOURCE));
+        MediaInfo mediaInfo = new MediaInfo(request.getString(RequestUtils.EXTRA_KEY_SOURCE),
+                MediaInfo.STREAM_TYPE_BUFFERED, "audio/mp3");
+        mProtocol.load(mediaInfo, true, 0, null);
     }
 
     @Override
     public boolean onStop() {
-        mControls.pause();
+        mProtocol.stop(null);
         return true;
     }
 
     @Override
     public boolean onPlay() {
-        mControls.resume();
+        mProtocol.play(null);
         return true;
     }
 
     @Override
     public boolean onPause() {
-        mControls.pause();
+        mProtocol.pause(null);
         return true;
     }
 
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java b/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
index dd0d982..3778c5f 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
@@ -16,7 +16,6 @@
 package com.android.onemedia.playback;
 
 import android.os.Bundle;
-import android.support.v7.media.MediaItemMetadata;
 
 import java.util.HashMap;
 import java.util.Map;
diff --git a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
index f2d691c..2e1478b 100644
--- a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
+++ b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
@@ -15,19 +15,20 @@
  */
 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.routing.MediaRouteSelector;
+import android.media.routing.MediaRouteService;
+import android.media.routing.MediaRouter.ConnectionInfo;
+import android.media.routing.MediaRouter.ConnectionRequest;
+import android.media.routing.MediaRouter.DestinationInfo;
+import android.media.routing.MediaRouter.DiscoveryRequest;
+import android.media.routing.MediaRouter.RouteInfo;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.Looper;
-import android.os.ResultReceiver;
+import android.os.Process;
+import android.support.media.protocols.MediaPlayerProtocol;
+import android.support.media.protocols.MediaPlayerProtocol.MediaInfo;
+import android.support.media.protocols.MediaPlayerProtocol.MediaStatus;
 import android.util.Log;
 
 import com.android.onemedia.playback.LocalRenderer;
@@ -35,29 +36,28 @@
 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 {
+public class OneMediaRouteProvider extends MediaRouteService {
     private static final String TAG = "OneMRP";
     private static final boolean DEBUG = true;
 
+    private static final String TEST_DESTINATION_ID = "testDestination";
+    private static final String TEST_ROUTE_ID = "testRoute";
+
     private Renderer mRenderer;
     private RenderListener mRenderListener;
     private PlaybackState mPlaybackState;
-    private RouteConnection mConnection;
-    private RoutePlaybackControlsHandler mControls;
-    private String mRouteId;
     private Handler mHandler;
 
+    private OneStub mStub;
+
     @Override
     public void onCreate() {
         mHandler = new Handler();
-        mRouteId = UUID.randomUUID().toString();
         mRenderer = new LocalRenderer(this, null);
         mRenderListener = new RenderListener();
         mPlaybackState = new PlaybackState();
@@ -65,81 +65,106 @@
                 | 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());
-            }
+    public ClientSession onCreateClientSession(ClientInfo client) {
+        if (client.getUid() != Process.myUid()) {
+            // for testing purposes, only allow connections from this application
+            // since this provider is not fully featured
+            return null;
         }
-        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;
+        return new OneSession(client);
     }
 
-    @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 final class OneSession extends ClientSession {
+        private final ClientInfo mClient;
 
-    private class PlayHandler extends RoutePlaybackControlsHandler.Listener {
-        private final String mRouteId;
-
-        public PlayHandler(String routeId) {
-            mRouteId = routeId;
+        public OneSession(ClientInfo client) {
+            mClient = client;
         }
 
         @Override
-        public void playNow(String content, ResultReceiver cb) {
+        public boolean onStartDiscovery(DiscoveryRequest req, DiscoveryCallback callback) {
+            for (MediaRouteSelector selector : req.getSelectors()) {
+                if (isMatch(selector)) {
+                    DestinationInfo destination = new DestinationInfo.Builder(
+                            TEST_DESTINATION_ID, getServiceMetadata(), "OneMedia")
+                            .setDescription("Test route from OneMedia app.")
+                            .build();
+                    ArrayList<RouteInfo> routes = new ArrayList<RouteInfo>();
+                    routes.add(new RouteInfo.Builder(
+                            TEST_ROUTE_ID, destination, selector).build());
+                    callback.onDestinationFound(destination, routes);
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public void onStopDiscovery() {
+        }
+
+        @Override
+        public boolean onConnect(ConnectionRequest req, ConnectionCallback callback) {
+            if (req.getRoute().getId().equals(TEST_ROUTE_ID)) {
+                mStub = new OneStub();
+                ConnectionInfo connection = new ConnectionInfo.Builder(req.getRoute())
+                        .setProtocolStub(MediaPlayerProtocol.class, mStub)
+                        .build();
+                callback.onConnected(connection);
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void onDisconnect() {
+            mStub = null;
+        }
+
+        private boolean isMatch(MediaRouteSelector selector) {
+            if (!selector.containsProtocol(MediaPlayerProtocol.class)) {
+                return false;
+            }
+            for (String protocol : selector.getRequiredProtocols()) {
+                if (!protocol.equals(MediaPlayerProtocol.class.getName())) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    private final class OneStub extends MediaPlayerProtocol.Stub {
+        MediaInfo mMediaInfo;
+
+        public OneStub() {
+            super(mHandler);
+        }
+
+        @Override
+        public void onLoad(MediaInfo mediaInfo, boolean autoplay, long playPosition,
+                Bundle extras) {
             if (DEBUG) {
-                Log.d(TAG, "Attempting to play " + content);
+                Log.d(TAG, "Attempting to play " + mediaInfo.getContentId());
             }
             // look up the route and send a play command to it
+            mMediaInfo = mediaInfo;
             Bundle bundle = new Bundle();
-            bundle.putString(RequestUtils.EXTRA_KEY_SOURCE, content);
+            bundle.putString(RequestUtils.EXTRA_KEY_SOURCE, mediaInfo.getContentId());
             mRenderer.setContent(bundle);
-            RouteInterfaceHandler.sendResult(cb, RouteInterface.RESULT_SUCCESS, null);
         }
 
         @Override
-        public boolean resume() {
+        public void onPlay(Bundle extras) {
             mRenderer.onPlay();
-            return true;
         }
 
         @Override
-        public boolean pause() {
+        public void onPause(Bundle extras) {
             mRenderer.onPause();
-            return true;
         }
     }
 
@@ -148,9 +173,7 @@
         @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.STATE_ERROR);
-            }
+            sendStatusUpdate(PlaybackState.STATE_ERROR);
         }
 
         @Override
@@ -186,7 +209,7 @@
                     break;
             }
 
-            mControls.sendPlaybackChangeEvent(mPlaybackState.getState());
+            sendStatusUpdate(mPlaybackState.getState());
         }
 
         @Override
@@ -203,5 +226,36 @@
         @Override
         public void onNextStarted() {
         }
+
+        private void sendStatusUpdate(int state) {
+            if (mStub != null) {
+                MediaStatus status = new MediaStatus(1, mStub.mMediaInfo);
+                switch (state) {
+                    case PlaybackState.STATE_BUFFERING:
+                    case PlaybackState.STATE_FAST_FORWARDING:
+                    case PlaybackState.STATE_REWINDING:
+                    case PlaybackState.STATE_SKIPPING_TO_NEXT:
+                    case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+                        status.setPlayerState(MediaStatus.PLAYER_STATE_BUFFERING);
+                        break;
+                    case PlaybackState.STATE_CONNECTING:
+                    case PlaybackState.STATE_STOPPED:
+                        status.setPlayerState(MediaStatus.PLAYER_STATE_IDLE);
+                        break;
+                    case PlaybackState.STATE_PAUSED:
+                        status.setPlayerState(MediaStatus.PLAYER_STATE_PAUSED);
+                        break;
+                    case PlaybackState.STATE_PLAYING:
+                        status.setPlayerState(MediaStatus.PLAYER_STATE_PLAYING);
+                        break;
+                    case PlaybackState.STATE_NONE:
+                    case PlaybackState.STATE_ERROR:
+                    default:
+                        status.setPlayerState(MediaStatus.PLAYER_STATE_UNKNOWN);
+                        break;
+                }
+                mStub.sendStatusUpdatedEvent(status, null);
+            }
+        }
     }
 }