Merge "Use session hints when create a session from MR2Manager" into rvc-dev am: e66ac51ea3

Change-Id: I6b5b7387030050ed3eeb3ec1a039e19ceccf3d44
diff --git a/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl
index dc06153..a8b82ba 100644
--- a/media/java/android/media/IMediaRouter2.aidl
+++ b/media/java/android/media/IMediaRouter2.aidl
@@ -24,11 +24,15 @@
  * @hide
  */
 oneway interface IMediaRouter2 {
-    void notifyRestoreRoute();
     void notifyRoutesAdded(in List<MediaRoute2Info> routes);
     void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
     void notifyRoutesChanged(in List<MediaRoute2Info> routes);
     void notifySessionCreated(int requestId, in @nullable RoutingSessionInfo sessionInfo);
     void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo);
     void notifySessionReleased(in RoutingSessionInfo sessionInfo);
+    /**
+     * Gets hints of the new session for the given route.
+     * Call MediaRouterService#notifySessionHintsForCreatingSession to pass the result.
+     */
+    void getSessionHintsForCreatingSession(long uniqueRequestId, in MediaRoute2Info route);
 }
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 0d87736..52bac67 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -59,6 +59,8 @@
 
     void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId,
             in MediaRoute2Info route, in @nullable Bundle sessionHints);
+    void notifySessionHintsForCreatingSession(IMediaRouter2 router, long uniqueRequestId,
+                in MediaRoute2Info route, in @nullable Bundle sessionHints);
     void selectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route);
     void deselectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route);
     void transferToRouteWithRouter2(IMediaRouter2 router, String sessionId,
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index cfe6db9..0ea9624 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -690,6 +690,31 @@
         matchingController.releaseInternal(/* shouldReleaseSession= */ false);
     }
 
+    void onGetControllerHintsForCreatingSessionOnHandler(long uniqueRequestId,
+            MediaRoute2Info route) {
+        OnGetControllerHintsListener listener = mOnGetControllerHintsListener;
+        Bundle controllerHints = null;
+        if (listener != null) {
+            controllerHints = listener.onGetControllerHints(route);
+            if (controllerHints != null) {
+                controllerHints = new Bundle(controllerHints);
+            }
+        }
+
+        MediaRouter2Stub stub;
+        synchronized (sRouterLock) {
+            stub = mStub;
+        }
+        if (stub != null) {
+            try {
+                mMediaRouterService.notifySessionHintsForCreatingSession(
+                        stub, uniqueRequestId, route, controllerHints);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "getSessionHintsOnHandler: Unable to request.", ex);
+            }
+        }
+    }
+
     private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
             RouteDiscoveryPreference discoveryRequest) {
         return routes.stream()
@@ -820,13 +845,14 @@
      */
     public interface OnGetControllerHintsListener {
         /**
-         * Called when the {@link MediaRouter2} is about to request
-         * the media route provider service to create a controller with the given route.
+         * Called when the {@link MediaRouter2} or the system is about to request
+         * a media route provider service to create a controller with the given route.
          * The {@link Bundle} returned here will be sent to media route provider service as a hint.
          * <p>
-         * To send hints when creating the controller, set the listener before calling
-         * {@link #transferTo(MediaRoute2Info)}. The method will be called
-         * on the same thread which calls {@link #transferTo(MediaRoute2Info)}.
+         * Since controller creation can be requested by the {@link MediaRouter2} and the system,
+         * set the listener as soon as possible after acquiring {@link MediaRouter2} instance.
+         * The method will be called on the same thread that calls
+         * {@link #transferTo(MediaRoute2Info)} or the main thread if it is requested by the system.
          *
          * @param route The route to create controller with
          * @return An optional bundle of app-specific arguments to send to the provider,
@@ -1378,9 +1404,6 @@
 
     class MediaRouter2Stub extends IMediaRouter2.Stub {
         @Override
-        public void notifyRestoreRoute() throws RemoteException {}
-
-        @Override
         public void notifyRoutesAdded(List<MediaRoute2Info> routes) {
             mHandler.sendMessage(obtainMessage(MediaRouter2::addRoutesOnHandler,
                     MediaRouter2.this, routes));
@@ -1415,5 +1438,13 @@
             mHandler.sendMessage(obtainMessage(MediaRouter2::releaseControllerOnHandler,
                     MediaRouter2.this, sessionInfo));
         }
+
+        @Override
+        public void getSessionHintsForCreatingSession(long uniqueRequestId,
+                @NonNull MediaRoute2Info route) {
+            mHandler.sendMessage(obtainMessage(
+                    MediaRouter2::onGetControllerHintsForCreatingSessionOnHandler,
+                    MediaRouter2.this, uniqueRequestId, route));
+        }
     }
 }
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 207534b..6a1e965 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -48,6 +48,7 @@
 import android.media.MediaRouter2Utils;
 import android.media.RouteDiscoveryPreference;
 import android.media.RoutingSessionInfo;
+import android.os.Bundle;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -74,6 +75,8 @@
     private static final String TAG = "MediaRouter2ManagerTest";
     private static final int WAIT_TIME_MS = 2000;
     private static final int TIMEOUT_MS = 5000;
+    private static final String TEST_KEY = "test_key";
+    private static final String TEST_VALUE = "test_value";
 
     private Context mContext;
     private MediaRouter2Manager mManager;
@@ -513,6 +516,56 @@
         assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax());
     }
 
+    @Test
+    public void testRouter2SetOnGetControllerHintsListener() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
+        addRouterCallback(new RouteCallback() {});
+
+        MediaRoute2Info route = routes.get(ROUTE_ID1);
+        assertNotNull(route);
+
+        final Bundle controllerHints = new Bundle();
+        controllerHints.putString(TEST_KEY, TEST_VALUE);
+        final CountDownLatch hintLatch = new CountDownLatch(1);
+        final MediaRouter2.OnGetControllerHintsListener listener =
+                route1 -> {
+                    hintLatch.countDown();
+                    return controllerHints;
+                };
+
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+
+        addManagerCallback(new MediaRouter2Manager.Callback() {
+            @Override
+            public void onTransferred(RoutingSessionInfo oldSession,
+                    RoutingSessionInfo newSession) {
+                assertTrue(newSession.getSelectedRoutes().contains(route.getId()));
+                // The StubMediaRoute2ProviderService is supposed to set control hints
+                // with the given controllerHints.
+                Bundle controlHints = newSession.getControlHints();
+                assertNotNull(controlHints);
+                assertTrue(controlHints.containsKey(TEST_KEY));
+                assertEquals(TEST_VALUE, controlHints.getString(TEST_KEY));
+
+                successLatch.countDown();
+            }
+
+            @Override
+            public void onTransferFailed(RoutingSessionInfo session,
+                    MediaRoute2Info requestedRoute) {
+                failureLatch.countDown();
+            }
+        });
+
+        mRouter2.setOnGetControllerHintsListener(listener);
+        mManager.selectRoute(mPackageName, route);
+        assertTrue(hintLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        assertFalse(failureLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
     Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeFeatures)
             throws Exception {
         CountDownLatch addedLatch = new CountDownLatch(1);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 0d89997..d8bf9ed 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.media;
 
+import static android.media.MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE;
+import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
 import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE;
 import static android.media.MediaRouter2Utils.getOriginalId;
 import static android.media.MediaRouter2Utils.getProviderId;
@@ -247,6 +249,22 @@
         }
     }
 
+    public void notifySessionHintsForCreatingSession(IMediaRouter2 router,
+            long uniqueRequestId, MediaRoute2Info route, Bundle sessionHints) {
+        Objects.requireNonNull(router, "router must not be null");
+        Objects.requireNonNull(route, "route must not be null");
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                notifySessionHintsForCreatingSessionLocked(uniqueRequestId,
+                        router, route, sessionHints);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     public void selectRouteWithRouter2(IMediaRouter2 router, String uniqueSessionId,
             MediaRoute2Info route) {
         Objects.requireNonNull(router, "router must not be null");
@@ -265,7 +283,6 @@
         }
     }
 
-
     public void deselectRouteWithRouter2(IMediaRouter2 router, String uniqueSessionId,
             MediaRoute2Info route) {
         Objects.requireNonNull(router, "router must not be null");
@@ -634,12 +651,30 @@
 
         long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId);
         routerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::requestCreateSessionOnHandler,
+                obtainMessage(UserHandler::requestCreateSessionWithRouter2OnHandler,
                         routerRecord.mUserRecord.mHandler,
-                        uniqueRequestId, routerRecord, /* managerRecord= */ null, route,
+                        uniqueRequestId, routerRecord, route,
                         sessionHints));
     }
 
+    private void notifySessionHintsForCreatingSessionLocked(long uniqueRequestId,
+            @NonNull IMediaRouter2 router,
+            @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
+        final IBinder binder = router.asBinder();
+        final RouterRecord routerRecord = mAllRouterRecords.get(binder);
+
+        if (routerRecord == null) {
+            Slog.w(TAG, "requestCreateSessionWithRouter2ByManagerRequestLocked: "
+                    + "Ignoring unknown router.");
+            return;
+        }
+
+        routerRecord.mUserRecord.mHandler.sendMessage(
+                obtainMessage(UserHandler::requestCreateSessionWithManagerOnHandler,
+                        routerRecord.mUserRecord.mHandler,
+                        uniqueRequestId, routerRecord, route, sessionHints));
+    }
+
     private void selectRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
         final IBinder binder = router.asBinder();
@@ -826,12 +861,13 @@
         }
 
         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
-        //TODO(b/152851868): Use MediaRouter2's OnCreateSessionListener to send session hints.
+
+        // Before requesting to the provider, get session hints from the media router.
+        // As a return, media router will request to create a session.
         routerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::requestCreateSessionOnHandler,
+                obtainMessage(UserHandler::getSessionHintsForCreatingSessionOnHandler,
                         routerRecord.mUserRecord.mHandler,
-                        uniqueRequestId, routerRecord, managerRecord, route,
-                        /* sessionHints= */ null));
+                        uniqueRequestId, routerRecord, managerRecord, route));
     }
 
     private void selectRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager,
@@ -1149,7 +1185,6 @@
                     this, provider, uniqueRequestId, sessionInfo));
         }
 
-
         @Override
         public void onSessionUpdated(@NonNull MediaRoute2Provider provider,
                 @NonNull RoutingSessionInfo sessionInfo) {
@@ -1267,8 +1302,26 @@
             return -1;
         }
 
-        private void requestCreateSessionOnHandler(long uniqueRequestId,
-                @NonNull RouterRecord routerRecord, @Nullable ManagerRecord managerRecord,
+        private void getSessionHintsForCreatingSessionOnHandler(long uniqueRequestId,
+                @NonNull RouterRecord routerRecord, @NonNull ManagerRecord managerRecord,
+                @NonNull MediaRoute2Info route) {
+            SessionCreationRequest request =
+                    new SessionCreationRequest(routerRecord, uniqueRequestId, route, managerRecord);
+            mSessionCreationRequests.add(request);
+
+            try {
+                routerRecord.mRouter.getSessionHintsForCreatingSession(uniqueRequestId, route);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "requestGetSessionHintsOnHandler: "
+                        + "Failed to request. Router probably died.");
+                mSessionCreationRequests.remove(request);
+                notifyRequestFailedToManager(managerRecord.mManager,
+                        toOriginalRequestId(uniqueRequestId), REASON_UNKNOWN_ERROR);
+            }
+        }
+
+        private void requestCreateSessionWithRouter2OnHandler(long uniqueRequestId,
+                @NonNull RouterRecord routerRecord,
                 @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
 
             final MediaRoute2Provider provider = findProvider(route.getProviderId());
@@ -1281,13 +1334,50 @@
             }
 
             SessionCreationRequest request =
-                    new SessionCreationRequest(routerRecord, uniqueRequestId, route, managerRecord);
+                    new SessionCreationRequest(routerRecord, uniqueRequestId, route, null);
             mSessionCreationRequests.add(request);
 
             provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName,
                     route.getOriginalId(), sessionHints);
         }
 
+        private void requestCreateSessionWithManagerOnHandler(long uniqueRequestId,
+                @NonNull RouterRecord routerRecord,
+                @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
+            SessionCreationRequest matchingRequest = null;
+            for (SessionCreationRequest request : mSessionCreationRequests) {
+                if (request.mUniqueRequestId == uniqueRequestId) {
+                    matchingRequest = request;
+                    break;
+                }
+            }
+            if (matchingRequest == null) {
+                Slog.w(TAG, "requestCreateSessionWithKnownRequestOnHandler: "
+                        + "Ignoring an unknown request.");
+                return;
+            }
+
+            if (!TextUtils.equals(matchingRequest.mRoute.getId(), route.getId())) {
+                Slog.w(TAG, "requestCreateSessionWithKnownRequestOnHandler: "
+                        + "The given route is different from the requested route.");
+                return;
+            }
+
+            final MediaRoute2Provider provider = findProvider(route.getProviderId());
+            if (provider == null) {
+                Slog.w(TAG, "Ignoring session creation request since no provider found for"
+                        + " given route=" + route);
+
+                mSessionCreationRequests.remove(matchingRequest);
+                notifyRequestFailedToManager(matchingRequest.mRequestedManagerRecord.mManager,
+                        toOriginalRequestId(uniqueRequestId), REASON_ROUTE_NOT_AVAILABLE);
+                return;
+            }
+
+            provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName,
+                    route.getOriginalId(), sessionHints);
+        }
+
         // routerRecord can be null if the session is system's or RCN.
         private void selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord,
                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index d6bf9fb..bf2cc5e 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -487,6 +487,14 @@
 
     // Binder call
     @Override
+    public void notifySessionHintsForCreatingSession(IMediaRouter2 router,
+            long uniqueRequestId, MediaRoute2Info route, Bundle sessionHints) {
+        mService2.notifySessionHintsForCreatingSession(router,
+                uniqueRequestId, route, sessionHints);
+    }
+
+    // Binder call
+    @Override
     public void selectRouteWithRouter2(IMediaRouter2 router, String sessionId,
             MediaRoute2Info route) {
         mService2.selectRouteWithRouter2(router, sessionId, route);