Merge "MediaRouter: Address API review issues"
diff --git a/api/current.txt b/api/current.txt
index 06271c8..12d8b70 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -26704,7 +26704,6 @@
     method public int getVolume();
     method public int getVolumeHandling();
     method public int getVolumeMax();
-    method public boolean hasAnyFeatures(@NonNull java.util.Collection<java.lang.String>);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
     field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index eae13d0..dd01243 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -79,6 +79,11 @@
      */
     public static final int CONNECTION_STATE_CONNECTED = 2;
 
+    /** @hide */
+    @IntDef({PLAYBACK_VOLUME_FIXED, PLAYBACK_VOLUME_VARIABLE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PlaybackVolume {}
+
     /**
      * Playback information indicating the playback volume is fixed, i&#46;e&#46; it cannot be
      * controlled from this object. An example of fixed playback volume is a remote player,
@@ -326,6 +331,7 @@
      *
      * @return {@link #PLAYBACK_VOLUME_FIXED} or {@link #PLAYBACK_VOLUME_VARIABLE}
      */
+    @PlaybackVolume
     public int getVolumeHandling() {
         return mVolumeHandling;
     }
@@ -375,6 +381,7 @@
      *
      * @param features the list of route features to consider
      * @return true if the route has at least one feature in the list
+     * @hide
      */
     public boolean hasAnyFeatures(@NonNull Collection<String> features) {
         Objects.requireNonNull(features, "features must not be null");
@@ -548,6 +555,12 @@
 
         /**
          * Adds a feature for the route.
+         * @param feature a feature that the route has. May be one of predefined features
+         *                such as {@link #FEATURE_LIVE_AUDIO}, {@link #FEATURE_LIVE_VIDEO} or
+         *                {@link #FEATURE_REMOTE_PLAYBACK} or a custom feature defined by
+         *                a provider.
+         *
+         * @see #addFeatures(Collection)
          */
         @NonNull
         public Builder addFeature(@NonNull String feature) {
@@ -560,6 +573,12 @@
 
         /**
          * Adds features for the route. A route must support at least one route type.
+         * @param features features that the route has. May include predefined features
+         *                such as {@link #FEATURE_LIVE_AUDIO}, {@link #FEATURE_LIVE_VIDEO} or
+         *                {@link #FEATURE_REMOTE_PLAYBACK} or custom features defined by
+         *                a provider.
+         *
+         * @see #addFeature(String)
          */
         @NonNull
         public Builder addFeatures(@NonNull Collection<String> features) {
@@ -643,7 +662,7 @@
          * Sets the route's volume handling.
          */
         @NonNull
-        public Builder setVolumeHandling(int volumeHandling) {
+        public Builder setVolumeHandling(@PlaybackVolume int volumeHandling) {
             mVolumeHandling = volumeHandling;
             return this;
         }
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 1e8b188..e39b7bc 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -21,6 +21,7 @@
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SdkConstant;
 import android.app.Service;
 import android.content.Intent;
 import android.os.Binder;
@@ -57,6 +58,11 @@
 public abstract class MediaRoute2ProviderService extends Service {
     private static final String TAG = "MR2ProviderService";
 
+    /**
+     * The {@link Intent} action that must be declared as handled by the service.
+     * Put this in your manifest to provide media routes.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
     public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
 
     /**
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 18670e9..7b9a44f 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -49,10 +49,10 @@
 /**
  * Media Router 2 allows applications to control the routing of media channels
  * and streams from the current device to remote speakers and devices.
- *
  */
 // TODO: Add method names at the beginning of log messages. (e.g. updateControllerOnHandler)
 //       Not only MediaRouter2, but also to service / manager / provider.
+// TODO: ensure thread-safe and document it
 public class MediaRouter2 {
     private static final String TAG = "MR2";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -159,7 +159,8 @@
     /**
      * Registers a callback to discover routes and to receive events when they change.
      * <p>
-     * If you register the same callback twice or more, it will be ignored.
+     * If the specified callback is already registered, its registration will be updated for the
+     * given {@link Executor executor} and {@link RouteDiscoveryPreference discovery preference}.
      * </p>
      */
     public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor,
@@ -170,10 +171,11 @@
         Objects.requireNonNull(preference, "preference must not be null");
 
         RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, preference);
-        if (!mRouteCallbackRecords.addIfAbsent(record)) {
-            Log.w(TAG, "Ignoring the same callback");
-            return;
-        }
+
+        mRouteCallbackRecords.remove(record);
+        // It can fail to add the callback record if another registration with the same callback
+        // is happening but it's okay because either this or the other registration should be done.
+        mRouteCallbackRecords.addIfAbsent(record);
 
         synchronized (sRouterLock) {
             if (mClient == null) {