Add flag for MediaRouter2-based InfoMediaManager

InfoMediaManager.createInstance() will gate MediaRouter2-based and
MediaRouter2Manager-based instances based on the new flag.

Also, return a NoOpInfoMediaManager when the target package name does
not exist.

Bug: 192657812
Test: Testmapping + Manual Flag Testing
Change-Id: I8dcdd1c1366ee4246b8a4805f2347a810f51afec
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index c244ca0..3d35bad 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -58,6 +58,7 @@
         "setupdesign",
         "zxing-core-1.7",
         "androidx.room_room-runtime",
+        "settingslib_flags_lib",
 
     ],
 
@@ -88,3 +89,16 @@
         "SettingsLib",
     ],
 }
+
+aconfig_declarations {
+    name: "settingslib_media_flags",
+    package: "com.android.settingslib.media.flags",
+    srcs: [
+        "aconfig/settingslib_media_flag_declarations.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "settingslib_flags_lib",
+    aconfig_declarations: "settingslib_media_flags",
+}
diff --git a/packages/SettingsLib/AndroidManifest.xml b/packages/SettingsLib/AndroidManifest.xml
index 13f8a37..322d6cf 100644
--- a/packages/SettingsLib/AndroidManifest.xml
+++ b/packages/SettingsLib/AndroidManifest.xml
@@ -18,6 +18,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.settingslib">
 
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+
     <application>
         <activity
             android:name="com.android.settingslib.users.AvatarPickerActivity"
diff --git a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
new file mode 100644
index 0000000..0b74fa8
--- /dev/null
+++ b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.settingslib.media.flags"
+
+flag {
+  name: "use_media_router2_for_info_media_manager"
+  namespace: "placeholder_namespace"
+  description: "Gates whether to use a MediaRouter2-based implementation of InfoMediaManager, instead of the legacy MediaRouter2Manager-based implementation."
+  bug: "192657812"
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 1ea155a6..bf63f5d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -62,6 +62,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.media.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -105,6 +106,28 @@
         }
     }
 
+    /** Creates an instance of InfoMediaManager. */
+    public static InfoMediaManager createInstance(
+            Context context,
+            String packageName,
+            Notification notification,
+            LocalBluetoothManager localBluetoothManager) {
+        if (Flags.useMediaRouter2ForInfoMediaManager()) {
+            try {
+                return new RouterInfoMediaManager(
+                        context, packageName, notification, localBluetoothManager);
+            } catch (PackageNotAvailableException ex) {
+                // TODO: b/293578081 - Propagate this exception to callers for proper handling.
+                Log.w(TAG, "Returning a no-op InfoMediaManager for package " + packageName);
+                return new NoOpInfoMediaManager(
+                        context, packageName, notification, localBluetoothManager);
+            }
+        } else {
+            return new ManagerInfoMediaManager(
+                    context, packageName, notification, localBluetoothManager);
+        }
+    }
+
     @Override
     public void startScan() {
         mMediaDevices.clear();
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 4fb0487..fe3ef5d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -137,7 +137,7 @@
         }
 
         mInfoMediaManager =
-                new ManagerInfoMediaManager(
+                InfoMediaManager.createInstance(
                         context, packageName, notification, mLocalBluetoothManager);
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
new file mode 100644
index 0000000..9d578bc
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 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 com.android.settingslib.media;
+
+import android.app.Notification;
+import android.content.Context;
+import android.media.MediaRoute2Info;
+import android.media.RouteListingPreference;
+import android.media.RoutingSessionInfo;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * No-op implementation of {@link InfoMediaManager}.
+ *
+ * <p>This implementation is used when {@link RouterInfoMediaManager} throws a {@link
+ * InfoMediaManager.PackageNotAvailableException}.
+ */
+// TODO - b/293578081: Remove once PackageNotAvailableException is propagated to library clients.
+/* package */ final class NoOpInfoMediaManager extends InfoMediaManager {
+
+    NoOpInfoMediaManager(
+            Context context,
+            String packageName,
+            Notification notification,
+            LocalBluetoothManager localBluetoothManager) {
+        super(context, packageName, notification, localBluetoothManager);
+    }
+
+    @Override
+    public void stopScan() {
+        // Do nothing.
+    }
+
+    @Override
+    protected void startScanOnRouter() {
+        // Do nothing.
+    }
+
+    @Override
+    protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
+        return false;
+    }
+
+    @Override
+    protected void transferToRoute(@NonNull MediaRoute2Info route) {
+        // Do nothing.
+    }
+
+    @Override
+    protected void selectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
+        // Do nothing.
+    }
+
+    @Override
+    protected void deselectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
+        // Do nothing.
+    }
+
+    @Override
+    protected void releaseSession(@NonNull RoutingSessionInfo sessionInfo) {
+        // Do nothing.
+    }
+
+    @NonNull
+    @Override
+    protected List<MediaRoute2Info> getSelectableRoutes(@NonNull RoutingSessionInfo info) {
+        return Collections.emptyList();
+    }
+
+    @NonNull
+    @Override
+    protected List<MediaRoute2Info> getDeselectableRoutes(@NonNull RoutingSessionInfo info) {
+        return Collections.emptyList();
+    }
+
+    @NonNull
+    @Override
+    protected List<MediaRoute2Info> getSelectedRoutes(@NonNull RoutingSessionInfo info) {
+        return Collections.emptyList();
+    }
+
+    @Override
+    protected void setSessionVolume(@NonNull RoutingSessionInfo info, int volume) {
+        // Do nothing.
+    }
+
+    @Override
+    protected void setRouteVolume(@NonNull MediaRoute2Info route, int volume) {
+        // Do nothing.
+    }
+
+    @Nullable
+    @Override
+    protected RouteListingPreference getRouteListingPreference() {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    protected List<RoutingSessionInfo> getRemoteSessions() {
+        return Collections.emptyList();
+    }
+
+    @NonNull
+    @Override
+    protected List<RoutingSessionInfo> getRoutingSessionsForPackage() {
+        return Collections.emptyList();
+    }
+
+    @Nullable
+    @Override
+    protected RoutingSessionInfo getRoutingSessionById(@NonNull String sessionId) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    protected List<MediaRoute2Info> getAllRoutes() {
+        return Collections.emptyList();
+    }
+
+    @NonNull
+    @Override
+    protected List<MediaRoute2Info> getAvailableRoutesFromRouter() {
+        return Collections.emptyList();
+    }
+
+    @NonNull
+    @Override
+    protected List<MediaRoute2Info> getTransferableRoutes(@NonNull String packageName) {
+        return Collections.emptyList();
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index ff3eeec..8970deb 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -48,11 +48,14 @@
         "androidx.test.core",
         "androidx.test.rules",
         "androidx.test.espresso.core",
+        "flag-junit",
         "mockito-target-minus-junit4",
+        "platform-test-annotations",
         "truth-prebuilt",
         "SettingsLibDeviceStateRotationLock",
         "SettingsLibSettingsSpinner",
         "SettingsLibUsageProgressBarPreference",
+        "settingslib_flags_lib",
     ],
 
     dxflags: ["--multi-dex"],
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
new file mode 100644
index 0000000..c647cbb
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 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 com.android.settingslib.media;
+
+import static com.android.settingslib.media.flags.Flags.FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class InfoMediaManagerIntegTest {
+
+    private static final String FAKE_PACKAGE = "FAKE_PACKAGE";
+
+    private Context mContext;
+    private UiAutomation mUiAutomation;
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.MEDIA_CONTENT_CONTROL);
+    }
+
+    @After
+    public void tearDown() {
+        mUiAutomation.dropShellPermissionIdentity();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
+    public void createInstance_withMR2FlagOn_returnsRouterInfoMediaManager() {
+        InfoMediaManager manager =
+                InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null, null);
+        assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
+    public void createInstance_withMR2FlagOn_withFakePackage_returnsNoOpInfoMediaManager() {
+        InfoMediaManager manager =
+                InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null, null);
+        assertThat(manager).isInstanceOf(NoOpInfoMediaManager.class);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
+    public void createInstance_withMR2FlagOn_withNullPackage_returnsRouterInfoMediaManager() {
+        InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null, null);
+        assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
+    }
+
+    @Test
+    @RequiresFlagsDisabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
+    public void createInstance_withMR2FlagOff_returnsManagerInfoMediaManager() {
+        InfoMediaManager manager =
+                InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null, null);
+        assertThat(manager).isInstanceOf(ManagerInfoMediaManager.class);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
index f908481..785a1e8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
@@ -18,8 +18,8 @@
 
 import android.content.Context
 import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.media.InfoMediaManager
 import com.android.settingslib.media.LocalMediaManager
-import com.android.settingslib.media.ManagerInfoMediaManager
 import javax.inject.Inject
 
 /** Factory to create [LocalMediaManager] objects. */
@@ -31,8 +31,7 @@
 ) {
     /** Creates a [LocalMediaManager] for the given package. */
     fun create(packageName: String): LocalMediaManager {
-        return ManagerInfoMediaManager(context, packageName, null, localBluetoothManager).run {
-            LocalMediaManager(context, localBluetoothManager, this, packageName)
-        }
+        return InfoMediaManager.createInstance(context, packageName, null, localBluetoothManager)
+            .run { LocalMediaManager(context, localBluetoothManager, this, packageName) }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 83631b0..be42569 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -76,7 +76,6 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.media.InfoMediaManager;
 import com.android.settingslib.media.LocalMediaManager;
-import com.android.settingslib.media.ManagerInfoMediaManager;
 import com.android.settingslib.media.MediaDevice;
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.R;
@@ -194,7 +193,7 @@
         mKeyGuardManager = keyGuardManager;
         mFeatureFlags = featureFlags;
         mUserTracker = userTracker;
-        InfoMediaManager imm = new ManagerInfoMediaManager(mContext, packageName, null, lbm);
+        InfoMediaManager imm = InfoMediaManager.createInstance(mContext, packageName, null, lbm);
         mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
         mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
         mDialogLaunchAnimator = dialogLaunchAnimator;