Fix volume adjustment for standalone static routes

MR2ProviderServiceAdapter does not currently map the
route id to the created route controller. This only
works for static groups and for dynamic route controllers.

This commit is a reworked version of the reverted
aosp/2525341.

Bug: 274793752
Test: ./gradlew :mediarouter:mediarouter:connectedDebugAndroidTest
Change-Id: I730ec99c4e862bc2e73387f9c7507269914b54a2
diff --git a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java
index eb44a5e..6601791 100644
--- a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java
+++ b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
@@ -63,7 +64,7 @@
 
     private Context mContext;
     private MediaRouter mRouter;
-    private MediaRouter.Callback mPlaceholderCallback = new MediaRouter.Callback() { };
+    private MediaRouter.Callback mPlaceholderCallback = new MediaRouter.Callback() {};
     StubMediaRouteProviderService mService;
     StubMediaRouteProviderService.StubMediaRouteProvider mProvider;
     MediaRouteProviderService.MediaRouteProviderServiceImplApi30 mServiceImpl;
@@ -117,14 +118,22 @@
 
     @After
     public void tearDown() {
-        getInstrumentation().runOnMainSync(() -> {
-            mRouter.removeCallback(mPlaceholderCallback);
-            for (MediaRouter.Callback callback : mCallbacks) {
-                mRouter.removeCallback(callback);
-            }
-            mCallbacks.clear();
-            MediaRouterTestHelper.resetMediaRouter();
-        });
+        getInstrumentation()
+                .runOnMainSync(
+                        () -> {
+                            for (RoutingSessionInfo sessionInfo :
+                                    mMr2ProviderServiceAdapter.getAllSessionInfo()) {
+                                mMr2ProviderServiceAdapter.onReleaseSession(
+                                        MediaRoute2ProviderService.REQUEST_ID_NONE,
+                                        sessionInfo.getId());
+                            }
+                            mRouter.removeCallback(mPlaceholderCallback);
+                            for (MediaRouter.Callback callback : mCallbacks) {
+                                mRouter.removeCallback(callback);
+                            }
+                            mCallbacks.clear();
+                            MediaRouterTestHelper.resetMediaRouter();
+                        });
         MediaRouter2TestActivity.finishActivity();
     }
 
@@ -191,6 +200,28 @@
 
     @SmallTest
     @Test
+    public void setRouteVolume_onStaticNonGroupRoute() {
+        // We run session creation on the main thread to ensure the route creation from the setup
+        // method happens before the session creation. Otherwise, this call may call into an
+        // inconsistent adapter state.
+        getInstrumentation()
+                .runOnMainSync(
+                        () ->
+                                mMr2ProviderServiceAdapter.onCreateSession(
+                                        MediaRoute2ProviderService.REQUEST_ID_NONE,
+                                        mContext.getPackageName(),
+                                        StubMediaRouteProviderService.ROUTE_ID1,
+                                        /* sessionHints= */ null));
+        StubMediaRouteProviderService.StubMediaRouteProvider.StubRouteController createdController =
+                mProvider.mControllers.get(StubMediaRouteProviderService.ROUTE_ID1);
+        assertNotNull(createdController); // Avoids nullability warning.
+        assertNull(createdController.mLastSetVolume);
+        mMr2ProviderServiceAdapter.setRouteVolume(StubMediaRouteProviderService.ROUTE_ID1, 100);
+        assertEquals(100, (int) createdController.mLastSetVolume);
+    }
+
+    @SmallTest
+    @Test
     public void onBinderDied_releaseRoutingSessions() throws Exception {
         String descriptorId = StubMediaRouteProviderService.ROUTE_ID1;
 
@@ -208,8 +239,9 @@
 
         try {
             List<Messenger> messengers =
-                    mServiceImpl.mClients.stream().map(client -> client.mMessenger)
-                    .collect(Collectors.toList());
+                    mServiceImpl.mClients.stream()
+                            .map(client -> client.mMessenger)
+                            .collect(Collectors.toList());
             getInstrumentation().runOnMainSync(() ->
                     messengers.forEach(mServiceImpl::onBinderDied));
             // It should have no session info.
diff --git a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/StubMediaRouteProviderService.java b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/StubMediaRouteProviderService.java
index a0f60b4..97a2e83 100644
--- a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/StubMediaRouteProviderService.java
+++ b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/StubMediaRouteProviderService.java
@@ -21,6 +21,7 @@
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.collection.ArrayMap;
 
 import java.util.ArrayList;
@@ -82,6 +83,7 @@
 
     class StubMediaRouteProvider extends MediaRouteProvider {
         Map<String, MediaRouteDescriptor> mRoutes = new ArrayMap<>();
+        Map<String, StubRouteController> mControllers = new ArrayMap<>();
         boolean mSupportsDynamicGroup = false;
 
         StubMediaRouteProvider(@NonNull Context context) {
@@ -90,7 +92,9 @@
 
         @Override
         public RouteController onCreateRouteController(@NonNull String routeId) {
-            return new StubRouteController(routeId);
+            StubRouteController newController = new StubRouteController(routeId);
+            mControllers.put(routeId, newController);
+            return newController;
         }
 
         public void initializeRoutes() {
@@ -114,6 +118,8 @@
         //TODO: Implement DynamicGroupRouteController
         class StubRouteController extends RouteController {
             final String mRouteId;
+            @Nullable Integer mLastSetVolume;
+
             StubRouteController(String routeId) {
                 mRouteId = routeId;
             }
@@ -133,6 +139,11 @@
                         .build());
                 publishRoutes();
             }
+
+            @Override
+            public void onSetVolume(int volume) {
+                mLastSetVolume = volume;
+            }
         }
     }
 }
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2ProviderServiceAdapter.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2ProviderServiceAdapter.java
index d5381f5..53c71e5 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2ProviderServiceAdapter.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2ProviderServiceAdapter.java
@@ -177,13 +177,20 @@
         RoutingSessionInfo sessionInfo = builder.build();
         sessionRecord.setSessionInfo(sessionInfo);
 
-        // Create member route controllers if it's a static group. Member route controllers
-        // for a dynamic group will be created after the group route is created.
-        // (DynamicGroupRouteController#notifyDynamicRoutesChanged is called)
-        if ((sessionFlags & (SessionRecord.SESSION_FLAG_GROUP | SessionRecord.SESSION_FLAG_DYNAMIC))
-                == SessionRecord.SESSION_FLAG_GROUP) {
-            sessionRecord.updateMemberRouteControllers(routeId, /*oldSession=*/null,
-                    sessionInfo);
+        if ((sessionFlags & SessionRecord.SESSION_FLAG_DYNAMIC) == 0) {
+            if ((sessionFlags & SessionRecord.SESSION_FLAG_GROUP) != 0) {
+                // Create member route controllers if it's a static group. Member route controllers
+                // for a dynamic group will be created after the group route is created.
+                // (DynamicGroupRouteController#notifyDynamicRoutesChanged is called).
+                sessionRecord.updateMemberRouteControllers(
+                        routeId, /* oldSession= */ null, sessionInfo);
+            } else {
+                // The session has a non-group static route controller, whose proxy route
+                // controller has already been created. We just need to map the route id to said
+                // controller, for the controller to be found by its corresponding route id via
+                // findControllerByRouteId (needed, for example, for route volume adjustment).
+                sessionRecord.setStaticMemberRouteId(routeId);
+            }
         }
 
         mServiceImpl.setDynamicRoutesChangedListener(controller);
@@ -665,6 +672,19 @@
             mClientRecord = new WeakReference<>(clientRecord);
         }
 
+        /**
+         * Maps the provided {@code routeId} to the top level route controller of this session.
+         *
+         * <p>This method can be used for mapping a route id to a non-group static route controller.
+         * The session record takes care of the creation of the member route controllers, but not of
+         * the top level route controller, which is provided via the constructor. In the case of
+         * non-group static routes, the top level route controller is the single route controller,
+         * and has already been created, so we only need to map the corresponding route id to it.
+         */
+        public void setStaticMemberRouteId(String routeId) {
+            mRouteIdToControllerMap.put(routeId, mController);
+        }
+
         public int getFlags() {
             return mFlags;
         }