System MR2: Add tests for transferTo

Bug: 181122201
Test: Test passed
Change-Id: I337f0c53a50883a926dd50284b7883c03c2889ed
diff --git a/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java b/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java
index 5161cfb..62d4112 100644
--- a/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java
+++ b/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java
@@ -19,14 +19,18 @@
 import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
 import static android.media.cts.StubMediaRoute2ProviderService.FEATURE_SAMPLE;
 import static android.media.cts.StubMediaRoute2ProviderService.FEATURE_SPECIAL;
+import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID1;
 import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID2;
+import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID3_SESSION_CREATION_FAILED;
 import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID_VARIABLE_VOLUME;
 import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_NAME2;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
@@ -34,6 +38,7 @@
 import android.media.MediaRouter2;
 import android.media.MediaRouter2.RouteCallback;
 import android.media.MediaRouter2.RoutingController;
+import android.media.MediaRouter2.TransferCallback;
 import android.media.MediaRouter2Manager;
 import android.media.RouteDiscoveryPreference;
 import android.media.RoutingSessionInfo;
@@ -52,7 +57,9 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
@@ -77,7 +84,7 @@
     private RouteCallback mAppRouterPlaceHolderCallback = new RouteCallback() {};
 
     private final List<RouteCallback> mRouteCallbacks = new ArrayList<>();
-    private final List<MediaRouter2.TransferCallback> mTransferCallbacks = new ArrayList<>();
+    private final List<TransferCallback> mTransferCallbacks = new ArrayList<>();
 
     public static final List<String> FEATURES_ALL = new ArrayList();
     public static final List<String> FEATURES_SPECIAL = new ArrayList();
@@ -172,7 +179,7 @@
 
     @Test
     public void testGetAllRoutes() throws Exception {
-        waitForRoutesAdded(FEATURE_SPECIAL);
+        waitAndGetRoutes(FEATURE_SPECIAL);
 
         // Regardless of whether the app router registered its preference,
         // getAllRoutes() will return all the routes.
@@ -192,23 +199,16 @@
         // only the system routes will come out after creation.
         assertTrue(mSystemRouter2ForCts.getRoutes().isEmpty());
 
-        waitForRoutesAdded(FEATURE_SPECIAL);
+        waitAndGetRoutes(FEATURE_SPECIAL);
 
-        mRouteCallbacks.add(mAppRouterPlaceHolderCallback);
-        mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback,
-                new RouteDiscoveryPreference.Builder(FEATURES_SPECIAL, true).build());
-
-        new PollingCheck(TIMEOUT_MS) {
-            @Override
-            protected boolean check() {
-                for (MediaRoute2Info route : mSystemRouter2ForCts.getRoutes()) {
-                    if (route.getFeatures().contains(FEATURE_SPECIAL)) {
-                        return true;
-                    }
-                }
-                return false;
+        boolean routeFound = false;
+        for (MediaRoute2Info route : mSystemRouter2ForCts.getRoutes()) {
+            if (route.getFeatures().contains(FEATURE_SPECIAL)) {
+                routeFound = true;
+                break;
             }
-        }.run();
+        }
+        assertTrue(routeFound);
     }
 
     @Test
@@ -245,7 +245,7 @@
         mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback,
                 new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
 
-        waitForRoutesAdded(FEATURE_SAMPLE);
+        waitAndGetRoutes(FEATURE_SAMPLE);
 
         CountDownLatch removedLatch = new CountDownLatch(1);
         RouteCallback routeCallback = new RouteCallback() {
@@ -273,7 +273,7 @@
         mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback,
                 new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
 
-        waitForRoutesAdded(FEATURE_SAMPLE);
+        waitAndGetRoutes(FEATURE_SAMPLE);
 
         MediaRoute2Info routeToChangeVolume = null;
         for (MediaRoute2Info route : mSystemRouter2ForCts.getAllRoutes()) {
@@ -330,7 +330,175 @@
         assertTrue(featuresChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
-    private void waitForRoutesAdded(String feature) throws Exception {
+    @Test
+    public void testTransferToSuccess() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+        MediaRoute2Info route = routes.get(ROUTE_ID1);
+        assertNotNull(route);
+
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with this route
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
+                assertTrue(createRouteMap(newController.getSelectedRoutes()).containsKey(
+                        ROUTE_ID1));
+                controllers.add(newController);
+                successLatch.countDown();
+            }
+
+            @Override
+            public void onTransferFailure(MediaRoute2Info requestedRoute) {
+                failureLatch.countDown();
+            }
+        };
+
+        try {
+            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+            mSystemRouter2ForCts.transferTo(route);
+            assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            // onSessionCreationFailed should not be called.
+            assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    @Test
+    public void testTransferToFailure() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+        MediaRoute2Info route = routes.get(ROUTE_ID3_SESSION_CREATION_FAILED);
+        assertNotNull(route);
+
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with this route
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                controllers.add(newController);
+                successLatch.countDown();
+            }
+
+            @Override
+            public void onTransferFailure(MediaRoute2Info requestedRoute) {
+                assertEquals(route, requestedRoute);
+                failureLatch.countDown();
+            }
+        };
+
+        try {
+            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+            mSystemRouter2ForCts.transferTo(route);
+            assertTrue(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            // onTransfer should not be called.
+            assertFalse(successLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+
+    @Test
+    public void testTransferToTwice() throws Exception {
+        final CountDownLatch successLatch1 = new CountDownLatch(1);
+        final CountDownLatch successLatch2 = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+        final CountDownLatch stopLatch = new CountDownLatch(1);
+        final CountDownLatch onReleaseSessionLatch = new CountDownLatch(1);
+
+        final List<RoutingController> createdControllers = new ArrayList<>();
+
+        // Create session with this route
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                createdControllers.add(newController);
+                if (successLatch1.getCount() > 0) {
+                    successLatch1.countDown();
+                } else {
+                    successLatch2.countDown();
+                }
+            }
+
+            @Override
+            public void onTransferFailure(MediaRoute2Info requestedRoute) {
+                failureLatch.countDown();
+            }
+
+            @Override
+            public void onStop(RoutingController controller) {
+                stopLatch.countDown();
+            }
+        };
+
+        StubMediaRoute2ProviderService service = mService;
+        if (service != null) {
+            service.setProxy(new StubMediaRoute2ProviderService.Proxy() {
+                @Override
+                public void onReleaseSession(long requestId, String sessionId) {
+                    onReleaseSessionLatch.countDown();
+                }
+            });
+        }
+
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+        MediaRoute2Info route1 = routes.get(ROUTE_ID1);
+        MediaRoute2Info route2 = routes.get(ROUTE_ID2);
+        assertNotNull(route1);
+        assertNotNull(route2);
+
+        try {
+            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+            mSystemRouter2ForCts.transferTo(route1);
+            assertTrue(successLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            mSystemRouter2ForCts.transferTo(route2);
+            assertTrue(successLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            // onTransferFailure/onStop should not be called.
+            assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+            assertFalse(stopLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+
+            // Created controllers should have proper info
+            assertEquals(2, createdControllers.size());
+            RoutingController controller1 = createdControllers.get(0);
+            RoutingController controller2 = createdControllers.get(1);
+
+            assertNotEquals(controller1.getId(), controller2.getId());
+            assertTrue(createRouteMap(controller1.getSelectedRoutes()).containsKey(
+                    ROUTE_ID1));
+            assertTrue(createRouteMap(controller2.getSelectedRoutes()).containsKey(
+                    ROUTE_ID2));
+
+            // Should be able to release transferred controllers.
+            controller1.release();
+            assertTrue(onReleaseSessionLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(createdControllers);
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    private Map<String, MediaRoute2Info> waitAndGetRoutes(String feature) throws Exception {
+        List<String> features = new ArrayList<>();
+        features.add(feature);
+
+        mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback,
+                new RouteDiscoveryPreference.Builder(features, true).build());
+
         CountDownLatch latch = new CountDownLatch(1);
         RouteCallback routeCallback = new RouteCallback() {
             @Override
@@ -344,12 +512,26 @@
             }
         };
 
-        mRouteCallbacks.add(routeCallback);
         mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
                 RouteDiscoveryPreference.EMPTY);
-        // Note: The routes can be added before registering the callback,
-        // therefore no assertTrue() here.
-        latch.await(WAIT_MS, TimeUnit.MILLISECONDS);
+
+        try {
+            // Note: The routes can be added before registering the callback,
+            // therefore no assertTrue() here.
+            latch.await(WAIT_MS, TimeUnit.MILLISECONDS);
+            return createRouteMap(mSystemRouter2ForCts.getRoutes());
+        } finally {
+            mSystemRouter2ForCts.unregisterRouteCallback(routeCallback);
+        }
+    }
+
+    // Helper for getting routes easily. Uses original ID as a key
+    private static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
+        Map<String, MediaRoute2Info> routeMap = new HashMap<>();
+        for (MediaRoute2Info route : routes) {
+            routeMap.put(route.getOriginalId(), route);
+        }
+        return routeMap;
     }
 
     private void releaseAllSessions() {
@@ -366,10 +548,16 @@
         }
         mRouteCallbacks.clear();
 
-        for (MediaRouter2.TransferCallback transferCallback : mTransferCallbacks) {
+        for (TransferCallback transferCallback : mTransferCallbacks) {
             mAppRouter2.unregisterTransferCallback(transferCallback);
             mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
         }
         mTransferCallbacks.clear();
     }
+
+    static void releaseControllers(List<RoutingController> controllers) {
+        for (RoutingController controller : controllers) {
+            controller.release();
+        }
+    }
 }