Minor code cleanup to enable reuse of the protocol.

Extract the protocol constants into another class.
Add a callback interface for the watcher.
Minor doc tweaks.

Bug: 11257292
Change-Id: I74b5ecee0d287d6b892ccb56e7777762bbce30de
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java b/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java
index d4d96f3..638701f 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java
@@ -105,7 +105,8 @@
  *
  * <h3>Remote playback routes</h3>
  * <p>
- * Remote playback routes present media remotely by playing content from a Uri.
+ * {@link #CATEGORY_REMOTE_PLAYBACK Remote playback} routes present media remotely
+ * by playing content from a Uri.
  * These routes destinations take responsibility for fetching and rendering content
  * on their own.  Applications do not render the content themselves; instead, applications
  * send control requests to initiate play, pause, resume, or stop media items and receive
@@ -267,6 +268,8 @@
  * </p>
  */
 public final class MediaControlIntent {
+    /* Route categories. */
+
     /**
      * Media control category: Live audio.
      * <p>
@@ -276,6 +279,8 @@
      * </p><p>
      * When a live audio route is selected, audio routing is transparent to the application.
      * All audio played on the media stream will be routed to the selected destination.
+     * </p><p>
+     * Refer to the class documentation for details about live audio routes.
      * </p>
      */
     public static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
@@ -293,6 +298,8 @@
      * destination.  For certain live video routes, the application may also use a
      * {@link android.app.Presentation Presentation} to replace the mirrored view
      * on the external display with different content.
+     * </p><p>
+     * Refer to the class documentation for details about live video routes.
      * </p>
      *
      * @see MediaRouter.RouteInfo#getPresentationDisplay()
@@ -311,6 +318,8 @@
      * playing on the destination by sending media control actions to the route.
      * The application may also receive status updates from the route regarding
      * remote playback.
+     * </p><p>
+     * Refer to the class documentation for details about remote playback routes.
      * </p>
      *
      * @see MediaRouter.RouteInfo#sendControlRequest
@@ -871,6 +880,8 @@
      */
     public static final String ACTION_END_SESSION = "android.media.intent.action.END_SESSION";
 
+    /* Extras and related constants. */
+
     /**
      * Bundle extra: Media session id.
      * <p>
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderProtocol.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderProtocol.java
new file mode 100644
index 0000000..f44dcac
--- /dev/null
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderProtocol.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2013 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.support.v7.media;
+
+import android.content.Intent;
+import android.os.Messenger;
+
+/**
+ * Defines the communication protocol for media route provider services.
+ * @hide
+ */
+abstract class MediaRouteProviderProtocol {
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     * Put this in your manifest.
+     */
+    public static final String SERVICE_INTERFACE =
+            "android.media.MediaRouteProviderService";
+
+    /*
+     * Messages sent from the client to the service.
+     * DO NOT RENUMBER THESE!
+     */
+
+    /** (client v1)
+     * Register client.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     * - arg2    : client version
+     */
+    public static final int CLIENT_MSG_REGISTER = 1;
+
+    /** (client v1)
+     * Unregister client.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     */
+    public static final int CLIENT_MSG_UNREGISTER = 2;
+
+    /** (client v1)
+     * Create route controller.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     * - arg2    : route controller id
+     * - CLIENT_DATA_ROUTE_ID : route id string
+     */
+    public static final int CLIENT_MSG_CREATE_ROUTE_CONTROLLER = 3;
+
+    /** (client v1)
+     * Release route controller.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     * - arg2    : route controller id
+     */
+    public static final int CLIENT_MSG_RELEASE_ROUTE_CONTROLLER = 4;
+
+    /** (client v1)
+     * Select route.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     * - arg2    : route controller id
+     */
+    public static final int CLIENT_MSG_SELECT_ROUTE = 5;
+
+    /** (client v1)
+     * Unselect route.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     * - arg2    : route controller id
+     */
+    public static final int CLIENT_MSG_UNSELECT_ROUTE = 6;
+
+    /** (client v1)
+     * Set route volume.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     * - arg2    : route controller id
+     * - CLIENT_DATA_VOLUME : volume integer
+     */
+    public static final int CLIENT_MSG_SET_ROUTE_VOLUME = 7;
+
+    /** (client v1)
+     * Update route volume.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     * - arg2    : route controller id
+     * - CLIENT_DATA_VOLUME : volume delta integer
+     */
+    public static final int CLIENT_MSG_UPDATE_ROUTE_VOLUME = 8;
+
+    /** (client v1)
+     * Route control request.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     * - arg2    : route controller id
+     * - obj     : media control intent
+     */
+    public static final int CLIENT_MSG_ROUTE_CONTROL_REQUEST = 9;
+
+    /** (client v1)
+     * Sets the discovery request.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     * - obj     : discovery request bundle, or null if none
+     */
+    public static final int CLIENT_MSG_SET_DISCOVERY_REQUEST = 10;
+
+    public static final String CLIENT_DATA_ROUTE_ID = "routeId";
+    public static final String CLIENT_DATA_VOLUME = "volume";
+
+    /*
+     * Messages sent from the service to the client.
+     * DO NOT RENUMBER THESE!
+     */
+
+    /** (service v1)
+     * Generic failure sent in response to any unrecognized or malformed request.
+     * - arg1    : request id
+     */
+    public static final int SERVICE_MSG_GENERIC_FAILURE = 0;
+
+    /** (service v1)
+     * Generic failure sent in response to a successful message.
+     * - arg1    : request id
+     */
+    public static final int SERVICE_MSG_GENERIC_SUCCESS = 1;
+
+    /** (service v1)
+     * Registration succeeded.
+     * - arg1    : request id
+     * - arg2    : server version
+     * - obj     : route provider descriptor bundle, or null
+     */
+    public static final int SERVICE_MSG_REGISTERED = 2;
+
+    /** (service v1)
+     * Route control request success result.
+     * - arg1    : request id
+     * - obj     : result data bundle, or null
+     */
+    public static final int SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED = 3;
+
+    /** (service v1)
+     * Route control request failure result.
+     * - arg1    : request id
+     * - obj     : result data bundle, or null
+     * - SERVICE_DATA_ERROR: error message
+     */
+    public static final int SERVICE_MSG_CONTROL_REQUEST_FAILED = 4;
+
+    /** (service v1)
+     * Route provider descriptor changed.  (unsolicited event)
+     * - arg1    : reserved (0)
+     * - obj     : route provider descriptor bundle, or null
+     */
+    public static final int SERVICE_MSG_DESCRIPTOR_CHANGED = 5;
+
+    public static final String SERVICE_DATA_ERROR = "error";
+
+    /*
+     * Recognized client version numbers.  (Reserved for future use.)
+     * DO NOT RENUMBER THESE!
+     */
+
+    public static final int CLIENT_VERSION_1 = 1;
+    public static final int CLIENT_VERSION_CURRENT = CLIENT_VERSION_1;
+
+    /*
+     * Recognized server version numbers.  (Reserved for future use.)
+     * DO NOT RENUMBER THESE!
+     */
+
+    public static final int SERVICE_VERSION_1 = 1;
+    public static final int SERVICE_VERSION_CURRENT = SERVICE_VERSION_1;
+
+    /**
+     * Returns true if the messenger object is valid.
+     * <p>
+     * The messenger constructor and unparceling code does not check whether the
+     * provided IBinder is a valid IMessenger object.  As a result, it's possible
+     * for a peer to send an invalid IBinder that will result in crashes downstream.
+     * This method checks that the messenger is in a valid state.
+     * </p>
+     */
+    public static boolean isValidRemoteMessenger(Messenger messenger) {
+        try {
+            return messenger != null && messenger.getBinder() != null;
+        } catch (NullPointerException ex) {
+            // If the messenger was constructed with a binder interface other than
+            // IMessenger then the call to getBinder() will crash with an NPE.
+            return false;
+        }
+    }
+}
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
index de34c8e..d3f548d 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
@@ -32,6 +32,8 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 
+import static android.support.v7.media.MediaRouteProviderProtocol.*;
+
 /**
  * Base class for media route provider services.
  * <p>
@@ -68,164 +70,7 @@
      * The {@link Intent} that must be declared as handled by the service.
      * Put this in your manifest.
      */
-    public static final String SERVICE_INTERFACE =
-            "android.media.MediaRouteProviderService";
-
-    /*
-     * Messages sent from the client to the service.
-     * DO NOT RENUMBER THESE!
-     */
-
-    /** (client v1)
-     * Register client.
-     * - replyTo : client messenger
-     * - arg1    : request id
-     * - arg2    : client version
-     */
-    static final int CLIENT_MSG_REGISTER = 1;
-
-    /** (client v1)
-     * Unregister client.
-     * - replyTo : client messenger
-     * - arg1    : request id
-     */
-    static final int CLIENT_MSG_UNREGISTER = 2;
-
-    /** (client v1)
-     * Create route controller.
-     * - replyTo : client messenger
-     * - arg1    : request id
-     * - arg2    : route controller id
-     * - CLIENT_DATA_ROUTE_ID : route id string
-     */
-    static final int CLIENT_MSG_CREATE_ROUTE_CONTROLLER = 3;
-
-    /** (client v1)
-     * Release route controller.
-     * - replyTo : client messenger
-     * - arg1    : request id
-     * - arg2    : route controller id
-     */
-    static final int CLIENT_MSG_RELEASE_ROUTE_CONTROLLER = 4;
-
-    /** (client v1)
-     * Select route.
-     * - replyTo : client messenger
-     * - arg1    : request id
-     * - arg2    : route controller id
-     */
-    static final int CLIENT_MSG_SELECT_ROUTE = 5;
-
-    /** (client v1)
-     * Unselect route.
-     * - replyTo : client messenger
-     * - arg1    : request id
-     * - arg2    : route controller id
-     */
-    static final int CLIENT_MSG_UNSELECT_ROUTE = 6;
-
-    /** (client v1)
-     * Set route volume.
-     * - replyTo : client messenger
-     * - arg1    : request id
-     * - arg2    : route controller id
-     * - CLIENT_DATA_VOLUME : volume integer
-     */
-    static final int CLIENT_MSG_SET_ROUTE_VOLUME = 7;
-
-    /** (client v1)
-     * Update route volume.
-     * - replyTo : client messenger
-     * - arg1    : request id
-     * - arg2    : route controller id
-     * - CLIENT_DATA_VOLUME : volume delta integer
-     */
-    static final int CLIENT_MSG_UPDATE_ROUTE_VOLUME = 8;
-
-    /** (client v1)
-     * Route control request.
-     * - replyTo : client messenger
-     * - arg1    : request id
-     * - arg2    : route controller id
-     * - obj     : media control intent
-     */
-    static final int CLIENT_MSG_ROUTE_CONTROL_REQUEST = 9;
-
-    /** (client v1)
-     * Sets the discovery request.
-     * - replyTo : client messenger
-     * - arg1    : request id
-     * - obj     : discovery request bundle, or null if none
-     */
-    static final int CLIENT_MSG_SET_DISCOVERY_REQUEST = 10;
-
-    static final String CLIENT_DATA_ROUTE_ID = "routeId";
-    static final String CLIENT_DATA_VOLUME = "volume";
-
-    /*
-     * Messages sent from the service to the client.
-     * DO NOT RENUMBER THESE!
-     */
-
-    /** (service v1)
-     * Generic failure sent in response to any unrecognized or malformed request.
-     * - arg1    : request id
-     */
-    static final int SERVICE_MSG_GENERIC_FAILURE = 0;
-
-    /** (service v1)
-     * Generic failure sent in response to a successful message.
-     * - arg1    : request id
-     */
-    static final int SERVICE_MSG_GENERIC_SUCCESS = 1;
-
-    /** (service v1)
-     * Registration succeeded.
-     * - arg1    : request id
-     * - arg2    : server version
-     * - obj     : route provider descriptor bundle, or null
-     */
-    static final int SERVICE_MSG_REGISTERED = 2;
-
-    /** (service v1)
-     * Route control request success result.
-     * - arg1    : request id
-     * - obj     : result data bundle, or null
-     */
-    static final int SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED = 3;
-
-    /** (service v1)
-     * Route control request failure result.
-     * - arg1    : request id
-     * - obj     : result data bundle, or null
-     * - SERVICE_DATA_ERROR: error message
-     */
-    static final int SERVICE_MSG_CONTROL_REQUEST_FAILED = 4;
-
-    /** (service v1)
-     * Route provider descriptor changed.  (unsolicited event)
-     * - arg1    : reserved (0)
-     * - obj     : route provider descriptor bundle, or null
-     */
-    static final int SERVICE_MSG_DESCRIPTOR_CHANGED = 5;
-
-    static final String SERVICE_DATA_ERROR = "error";
-
-    /*
-     * Recognized client version numbers.  (Reserved for future use.)
-     * DO NOT RENUMBER THESE!
-     */
-
-    static final int CLIENT_VERSION_1 = 1;
-    static final int CLIENT_VERSION_CURRENT = CLIENT_VERSION_1;
-
-    /*
-     * Recognized server version numbers.  (Reserved for future use.)
-     * DO NOT RENUMBER THESE!
-     */
-
-    static final int SERVICE_VERSION_1 = 1;
-    static final int SERVICE_VERSION_CURRENT = SERVICE_VERSION_1;
+    public static final String SERVICE_INTERFACE = MediaRouteProviderProtocol.SERVICE_INTERFACE;
 
     /*
      * Private messages used internally.  (Yes, you can renumber these.)
@@ -614,25 +459,6 @@
         return "Client connection " + messenger.getBinder().toString();
     }
 
-    /**
-     * Returns true if the messenger object is valid.
-     * <p>
-     * The messenger constructor and unparceling code does not check whether the
-     * provided IBinder is a valid IMessenger object.  As a result, it's possible
-     * for a peer to send an invalid IBinder that will result in crashes downstream.
-     * This method checks that the messenger is in a valid state.
-     * </p>
-     */
-    static boolean isValidRemoteMessenger(Messenger messenger) {
-        try {
-            return messenger != null && messenger.getBinder() != null;
-        } catch (NullPointerException ex) {
-            // If the messenger was constructed with a binder interface other than
-            // IMessenger then the call to getBinder() will crash with an NPE.
-            return false;
-        }
-    }
-
     private final class PrivateHandler extends Handler {
         @Override
         public void handleMessage(Message msg) {
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
index 247267d..fbddd9d 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
@@ -96,7 +96,7 @@
      * Flag for {@link #addCallback}: Do not filter route events.
      * <p>
      * When this flag is specified, the callback will be invoked for events that affect any
-     * route event if they do not match the callback's associated media route selector.
+     * route even if they do not match the callback's filter.
      * </p>
      */
     public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 1 << 1;
@@ -1368,9 +1368,10 @@
      * state and the bulk of the media router implementation lives here.
      * </p>
      */
-    private static final class GlobalMediaRouter implements SystemMediaRouteProvider.SyncCallback {
+    private static final class GlobalMediaRouter
+            implements SystemMediaRouteProvider.SyncCallback,
+            RegisteredMediaRouteProviderWatcher.Callback {
         private final Context mApplicationContext;
-        private final MediaRouter mApplicationRouter;
         private final ArrayList<WeakReference<MediaRouter>> mRouters =
                 new ArrayList<WeakReference<MediaRouter>>();
         private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
@@ -1394,7 +1395,6 @@
         GlobalMediaRouter(Context applicationContext) {
             mApplicationContext = applicationContext;
             mDisplayManager = DisplayManagerCompat.getInstance(applicationContext);
-            mApplicationRouter = getRouter(applicationContext);
 
             // Add the system media route provider for interoperating with
             // the framework media router.  This one is special and receives
@@ -1407,7 +1407,7 @@
             // Start watching for routes published by registered media route
             // provider services.
             mRegisteredProviderWatcher = new RegisteredMediaRouteProviderWatcher(
-                    mApplicationContext, mApplicationRouter);
+                    mApplicationContext, this);
             mRegisteredProviderWatcher.start();
         }
 
@@ -1584,6 +1584,7 @@
             }
         }
 
+        @Override
         public void addProvider(MediaRouteProvider providerInstance) {
             int index = findProviderInfo(providerInstance);
             if (index < 0) {
@@ -1603,6 +1604,7 @@
             }
         }
 
+        @Override
         public void removeProvider(MediaRouteProvider providerInstance) {
             int index = findProviderInfo(providerInstance);
             if (index >= 0) {
diff --git a/v7/mediarouter/src/android/support/v7/media/RegisteredMediaRouteProvider.java b/v7/mediarouter/src/android/support/v7/media/RegisteredMediaRouteProvider.java
index b179b85..b9e21b1 100644
--- a/v7/mediarouter/src/android/support/v7/media/RegisteredMediaRouteProvider.java
+++ b/v7/mediarouter/src/android/support/v7/media/RegisteredMediaRouteProvider.java
@@ -36,6 +36,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import static android.support.v7.media.MediaRouteProviderProtocol.*;
+
 /**
  * Maintains a connection to a particular media route provider service.
  */
@@ -154,7 +156,7 @@
                 Log.d(TAG, this + ": Binding");
             }
 
-            Intent service = new Intent(MediaRouteProviderService.SERVICE_INTERFACE);
+            Intent service = new Intent(MediaRouteProviderProtocol.SERVICE_INTERFACE);
             service.setComponent(mComponentName);
             try {
                 mBound = getContext().bindService(service, this, Context.BIND_AUTO_CREATE);
@@ -191,7 +193,7 @@
             disconnect();
 
             Messenger messenger = (service != null ? new Messenger(service) : null);
-            if (MediaRouteProviderService.isValidRemoteMessenger(messenger)) {
+            if (isValidRemoteMessenger(messenger)) {
                 Connection connection = new Connection(messenger);
                 if (connection.register()) {
                     mActiveConnection = connection;
@@ -397,9 +399,9 @@
 
         public boolean register() {
             mPendingRegisterRequestId = mNextRequestId++;
-            if (!sendRequest(MediaRouteProviderService.CLIENT_MSG_REGISTER,
+            if (!sendRequest(CLIENT_MSG_REGISTER,
                     mPendingRegisterRequestId,
-                    MediaRouteProviderService.CLIENT_VERSION_CURRENT, null, null)) {
+                    CLIENT_VERSION_CURRENT, null, null)) {
                 return false;
             }
 
@@ -413,7 +415,7 @@
         }
 
         public void dispose() {
-            sendRequest(MediaRouteProviderService.CLIENT_MSG_UNREGISTER, 0, 0, null, null);
+            sendRequest(CLIENT_MSG_UNREGISTER, 0, 0, null, null);
             mReceiveHandler.dispose();
             mServiceMessenger.getBinder().unlinkToDeath(this, 0);
 
@@ -454,7 +456,7 @@
                 Bundle descriptorBundle) {
             if (mServiceVersion == 0
                     && requestId == mPendingRegisterRequestId
-                    && serviceVersion >= MediaRouteProviderService.SERVICE_VERSION_1) {
+                    && serviceVersion >= SERVICE_VERSION_1) {
                 mPendingRegisterRequestId = 0;
                 mServiceVersion = serviceVersion;
                 onConnectionDescriptorChanged(this,
@@ -507,45 +509,45 @@
         public int createRouteController(String routeId) {
             int controllerId = mNextControllerId++;
             Bundle data = new Bundle();
-            data.putString(MediaRouteProviderService.CLIENT_DATA_ROUTE_ID, routeId);
-            sendRequest(MediaRouteProviderService.CLIENT_MSG_CREATE_ROUTE_CONTROLLER,
+            data.putString(CLIENT_DATA_ROUTE_ID, routeId);
+            sendRequest(CLIENT_MSG_CREATE_ROUTE_CONTROLLER,
                     mNextRequestId++, controllerId, null, data);
             return controllerId;
         }
 
         public void releaseRouteController(int controllerId) {
-            sendRequest(MediaRouteProviderService.CLIENT_MSG_RELEASE_ROUTE_CONTROLLER,
+            sendRequest(CLIENT_MSG_RELEASE_ROUTE_CONTROLLER,
                     mNextRequestId++, controllerId, null, null);
         }
 
         public void selectRoute(int controllerId) {
-            sendRequest(MediaRouteProviderService.CLIENT_MSG_SELECT_ROUTE,
+            sendRequest(CLIENT_MSG_SELECT_ROUTE,
                     mNextRequestId++, controllerId, null, null);
         }
 
         public void unselectRoute(int controllerId) {
-            sendRequest(MediaRouteProviderService.CLIENT_MSG_UNSELECT_ROUTE,
+            sendRequest(CLIENT_MSG_UNSELECT_ROUTE,
                     mNextRequestId++, controllerId, null, null);
         }
 
         public void setVolume(int controllerId, int volume) {
             Bundle data = new Bundle();
-            data.putInt(MediaRouteProviderService.CLIENT_DATA_VOLUME, volume);
-            sendRequest(MediaRouteProviderService.CLIENT_MSG_SET_ROUTE_VOLUME,
+            data.putInt(CLIENT_DATA_VOLUME, volume);
+            sendRequest(CLIENT_MSG_SET_ROUTE_VOLUME,
                     mNextRequestId++, controllerId, null, data);
         }
 
         public void updateVolume(int controllerId, int delta) {
             Bundle data = new Bundle();
-            data.putInt(MediaRouteProviderService.CLIENT_DATA_VOLUME, delta);
-            sendRequest(MediaRouteProviderService.CLIENT_MSG_UPDATE_ROUTE_VOLUME,
+            data.putInt(CLIENT_DATA_VOLUME, delta);
+            sendRequest(CLIENT_MSG_UPDATE_ROUTE_VOLUME,
                     mNextRequestId++, controllerId, null, data);
         }
 
         public boolean sendControlRequest(int controllerId, Intent intent,
                 ControlRequestCallback callback) {
             int requestId = mNextRequestId++;
-            if (sendRequest(MediaRouteProviderService.CLIENT_MSG_ROUTE_CONTROL_REQUEST,
+            if (sendRequest(CLIENT_MSG_ROUTE_CONTROL_REQUEST,
                     requestId, controllerId, intent, null)) {
                 if (callback != null) {
                     mPendingCallbacks.put(requestId, callback);
@@ -556,7 +558,7 @@
         }
 
         public void setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
-            sendRequest(MediaRouteProviderService.CLIENT_MSG_SET_DISCOVERY_REQUEST,
+            sendRequest(CLIENT_MSG_SET_DISCOVERY_REQUEST,
                     mNextRequestId++, 0, request != null ? request.asBundle() : null, null);
         }
 
@@ -574,7 +576,7 @@
             } catch (DeadObjectException ex) {
                 // The service died.
             } catch (RemoteException ex) {
-                if (what != MediaRouteProviderService.CLIENT_MSG_UNREGISTER) {
+                if (what != CLIENT_MSG_UNREGISTER) {
                     Log.e(TAG, "Could not send message to service.", ex);
                 }
             }
@@ -627,37 +629,37 @@
         private boolean processMessage(Connection connection,
                 int what, int requestId, int arg, Object obj, Bundle data) {
             switch (what) {
-                case MediaRouteProviderService.SERVICE_MSG_GENERIC_FAILURE:
+                case SERVICE_MSG_GENERIC_FAILURE:
                     connection.onGenericFailure(requestId);
                     return true;
 
-                case MediaRouteProviderService.SERVICE_MSG_GENERIC_SUCCESS:
+                case SERVICE_MSG_GENERIC_SUCCESS:
                     connection.onGenericSuccess(requestId);
                     return true;
 
-                case MediaRouteProviderService.SERVICE_MSG_REGISTERED:
+                case SERVICE_MSG_REGISTERED:
                     if (obj == null || obj instanceof Bundle) {
                         return connection.onRegistered(requestId, arg, (Bundle)obj);
                     }
                     break;
 
-                case MediaRouteProviderService.SERVICE_MSG_DESCRIPTOR_CHANGED:
+                case SERVICE_MSG_DESCRIPTOR_CHANGED:
                     if (obj == null || obj instanceof Bundle) {
                         return connection.onDescriptorChanged((Bundle)obj);
                     }
                     break;
 
-                case MediaRouteProviderService.SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED:
+                case SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED:
                     if (obj == null || obj instanceof Bundle) {
                         return connection.onControlRequestSucceeded(
                                 requestId, (Bundle)obj);
                     }
                     break;
 
-                case MediaRouteProviderService.SERVICE_MSG_CONTROL_REQUEST_FAILED:
+                case SERVICE_MSG_CONTROL_REQUEST_FAILED:
                     if (obj == null || obj instanceof Bundle) {
                         String error = (data == null ? null :
-                                data.getString(MediaRouteProviderService.SERVICE_DATA_ERROR));
+                                data.getString(SERVICE_DATA_ERROR));
                         return connection.onControlRequestFailed(
                                 requestId, error, (Bundle)obj);
                     }
diff --git a/v7/mediarouter/src/android/support/v7/media/RegisteredMediaRouteProviderWatcher.java b/v7/mediarouter/src/android/support/v7/media/RegisteredMediaRouteProviderWatcher.java
index 6476459..018fb22 100644
--- a/v7/mediarouter/src/android/support/v7/media/RegisteredMediaRouteProviderWatcher.java
+++ b/v7/mediarouter/src/android/support/v7/media/RegisteredMediaRouteProviderWatcher.java
@@ -24,6 +24,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.os.Handler;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -36,33 +37,57 @@
  */
 final class RegisteredMediaRouteProviderWatcher {
     private final Context mContext;
-    private final MediaRouter mRouter;
-    private final ArrayList<RegisteredMediaRouteProvider> mProviders =
-            new ArrayList<RegisteredMediaRouteProvider>();
+    private final Callback mCallback;
+    private final Handler mHandler;
     private final PackageManager mPackageManager;
 
-    public RegisteredMediaRouteProviderWatcher(Context context, MediaRouter router) {
+    private final ArrayList<RegisteredMediaRouteProvider> mProviders =
+            new ArrayList<RegisteredMediaRouteProvider>();
+    private boolean mRunning;
+
+    public RegisteredMediaRouteProviderWatcher(Context context, Callback callback) {
         mContext = context;
-        mRouter = router;
+        mCallback = callback;
+        mHandler = new Handler();
         mPackageManager = context.getPackageManager();
     }
 
     public void start() {
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
-        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        mContext.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                scanPackages();
-            }
-        }, filter);
+        if (!mRunning) {
+            mRunning = true;
 
-        scanPackages();
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+            mContext.registerReceiver(mScanPackagesReceiver,
+                    filter, null, mHandler);
+
+            // Scan packages.
+            // Also has the side-effect of restarting providers if needed.
+            mHandler.post(mScanPackagesRunnable);
+        }
+    }
+
+    public void stop() {
+        if (mRunning) {
+            mRunning = false;
+
+            mContext.unregisterReceiver(mScanPackagesReceiver);
+            mHandler.removeCallbacks(mScanPackagesRunnable);
+
+            // Stop all providers.
+            for (int i = mProviders.size() - 1; i >= 0; i--) {
+                mProviders.get(i).stop();
+            }
+        }
     }
 
     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;
@@ -77,9 +102,10 @@
                             new ComponentName(serviceInfo.packageName, serviceInfo.name));
                     provider.start();
                     mProviders.add(targetIndex++, provider);
-                    mRouter.addProvider(provider);
+                    mCallback.addProvider(provider);
                 } else if (sourceIndex >= targetIndex) {
                     RegisteredMediaRouteProvider provider = mProviders.get(sourceIndex);
+                    provider.start(); // restart the provider if needed
                     provider.rebindIfDisconnected();
                     Collections.swap(mProviders, sourceIndex, targetIndex++);
                 }
@@ -90,7 +116,7 @@
         if (targetIndex < mProviders.size()) {
             for (int i = mProviders.size() - 1; i >= targetIndex; i--) {
                 RegisteredMediaRouteProvider provider = mProviders.get(i);
-                mRouter.removeProvider(provider);
+                mCallback.removeProvider(provider);
                 mProviders.remove(provider);
                 provider.stop();
             }
@@ -107,4 +133,23 @@
         }
         return -1;
     }
+
+    private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            scanPackages();
+        }
+    };
+
+    private final Runnable mScanPackagesRunnable = new Runnable() {
+        @Override
+        public void run() {
+            scanPackages();
+        }
+    };
+
+    public interface Callback {
+        void addProvider(MediaRouteProvider provider);
+        void removeProvider(MediaRouteProvider provider);
+    }
 }