diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index 7353cc0393c55..e112915cecd77 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -41,11 +41,13 @@ import android.bluetooth.BluetoothDevice; import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; import android.os.Build; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.DoNotInline; import androidx.annotation.RequiresApi; import com.android.internal.annotations.VisibleForTesting; @@ -53,9 +55,13 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import java.util.stream.Collectors; /** * InfoMediaManager provide interface to get InfoMediaDevice list. @@ -448,10 +454,12 @@ public class InfoMediaManager extends MediaManager { } private synchronized List getAvailableRoutes(String packageName) { - final List infos = new ArrayList<>(); + List infos = new ArrayList<>(); RoutingSessionInfo routingSessionInfo = getRoutingSessionInfo(packageName); + List selectedRouteInfos = new ArrayList<>(); if (routingSessionInfo != null) { - infos.addAll(mRouterManager.getSelectedRoutes(routingSessionInfo)); + selectedRouteInfos = mRouterManager.getSelectedRoutes(routingSessionInfo); + infos.addAll(selectedRouteInfos); infos.addAll(mRouterManager.getSelectableRoutes(routingSessionInfo)); } final List transferableRoutes = @@ -468,11 +476,26 @@ public class InfoMediaManager extends MediaManager { infos.add(transferableRoute); } } - return infos; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + RouteListingPreference routeListingPreference = + mRouterManager.getRouteListingPreference(mPackageName); + if (routeListingPreference != null) { + final List preferenceRouteListing = + Api34Impl.composePreferenceRouteListing( + routeListingPreference); + infos = Api34Impl.arrangeRouteListByPreference(selectedRouteInfos, + infos, + preferenceRouteListing); + } + return Api34Impl.filterDuplicatedIds(infos); + } else { + return infos; + } } @VisibleForTesting void addMediaDevice(MediaRoute2Info route) { + //TODO(b/258141461): Attach flag and disable reason in MediaDevice final int deviceType = route.getType(); MediaDevice mediaDevice = null; switch (deviceType) { @@ -574,4 +597,61 @@ public class InfoMediaManager extends MediaManager { refreshDevices(); } } + + @RequiresApi(34) + private static class Api34Impl { + @DoNotInline + static List composePreferenceRouteListing( + RouteListingPreference routeListingPreference) { + List finalizedItemList = new ArrayList<>(); + List itemList = routeListingPreference.getItems(); + for (RouteListingPreference.Item item : itemList) { + //Put suggested devices on the top first before further organization + if (item.getFlags() == RouteListingPreference.Item.FLAG_SUGGESTED_ROUTE) { + finalizedItemList.add(0, item); + } else { + finalizedItemList.add(item); + } + } + return finalizedItemList; + } + + + @DoNotInline + static synchronized List filterDuplicatedIds(List infos) { + List filteredInfos = new ArrayList<>(); + Set foundDeduplicationIds = new HashSet<>(); + for (MediaRoute2Info mediaRoute2Info : infos) { + if (!Collections.disjoint(mediaRoute2Info.getDeduplicationIds(), + foundDeduplicationIds)) { + continue; + } + filteredInfos.add(mediaRoute2Info); + foundDeduplicationIds.addAll(mediaRoute2Info.getDeduplicationIds()); + } + return filteredInfos; + } + + @DoNotInline + static List arrangeRouteListByPreference( + List selectedRouteInfos, List infolist, + List preferenceRouteListing) { + final List sortedInfoList = new ArrayList<>(selectedRouteInfos); + for (RouteListingPreference.Item item : preferenceRouteListing) { + for (MediaRoute2Info info : infolist) { + if (item.getRouteId().equals(info.getId()) + && !selectedRouteInfos.contains(info)) { + sortedInfoList.add(info); + break; + } + } + } + if (sortedInfoList.size() != infolist.size()) { + infolist.removeAll(sortedInfoList); + sortedInfoList.addAll(infolist.stream().filter( + MediaRoute2Info::isSystemRoute).collect(Collectors.toList())); + } + return sortedInfoList; + } + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index 33fb91d2252c3..2820a132162be 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -19,6 +19,7 @@ package com.android.settingslib.media; import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP; import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER; import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER; +import static android.media.MediaRoute2Info.TYPE_REMOTE_TV; import static android.media.MediaRoute2Info.TYPE_USB_DEVICE; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; import static android.media.MediaRoute2ProviderService.REASON_NETWORK_ERROR; @@ -37,14 +38,18 @@ import android.bluetooth.BluetoothDevice; import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; import android.media.session.MediaSessionManager; +import android.os.Build; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.testutils.shadow.ShadowRouter2Manager; +import com.google.common.collect.ImmutableList; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -53,9 +58,11 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; import java.util.List; +import java.util.Set; @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowRouter2Manager.class}) @@ -63,7 +70,15 @@ public class InfoMediaManagerTest { private static final String TEST_PACKAGE_NAME = "com.test.packagename"; private static final String TEST_ID = "test_id"; + private static final String TEST_ID_1 = "test_id_1"; + private static final String TEST_ID_2 = "test_id_2"; + private static final String TEST_ID_3 = "test_id_3"; + private static final String TEST_ID_4 = "test_id_4"; + private static final String TEST_NAME = "test_name"; + private static final String TEST_DUPLICATED_ID_1 = "test_duplicated_id_1"; + private static final String TEST_DUPLICATED_ID_2 = "test_duplicated_id_2"; + private static final String TEST_DUPLICATED_ID_3 = "test_duplicated_id_3"; @Mock private MediaRouter2Manager mRouterManager; @@ -104,6 +119,7 @@ public class InfoMediaManagerTest { final MediaRoute2Info info = mock(MediaRoute2Info.class); when(info.getId()).thenReturn(TEST_ID); when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + when(info.getDeduplicationIds()).thenReturn(Set.of()); final List routes = new ArrayList<>(); routes.add(info); @@ -155,6 +171,7 @@ public class InfoMediaManagerTest { final MediaRoute2Info info = mock(MediaRoute2Info.class); when(info.getId()).thenReturn(TEST_ID); when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + when(info.getDeduplicationIds()).thenReturn(Set.of()); final List routes = new ArrayList<>(); routes.add(info); @@ -191,6 +208,7 @@ public class InfoMediaManagerTest { final MediaRoute2Info info = mock(MediaRoute2Info.class); when(info.getId()).thenReturn(TEST_ID); when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + when(info.getDeduplicationIds()).thenReturn(Set.of()); final List routes = new ArrayList<>(); routes.add(info); @@ -207,12 +225,105 @@ public class InfoMediaManagerTest { assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size()); } + @Test + public void onRoutesChanged_getAvailableRoutes_shouldFilterDevice() { + ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", + Build.VERSION_CODES.UPSIDE_DOWN_CAKE); + final List routingSessionInfos = new ArrayList<>(); + final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class); + routingSessionInfos.add(sessionInfo); + + final List selectedRoutes = new ArrayList<>(); + selectedRoutes.add(TEST_ID); + when(sessionInfo.getSelectedRoutes()).thenReturn(selectedRoutes); + mShadowRouter2Manager.setRoutingSessions(routingSessionInfos); + + mShadowRouter2Manager.setTransferableRoutes(getRoutesListWithDuplicatedIds()); + + final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID); + assertThat(mediaDevice).isNull(); + + mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated(); + + final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0); + assertThat(infoDevice.getId()).isEqualTo(TEST_ID); + assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice); + assertThat(mInfoMediaManager.mMediaDevices).hasSize(2); + } + + @Test + public void onRouteChanged_getAvailableRoutesWithPrefernceListExit_ordersRoutes() { + ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", + Build.VERSION_CODES.UPSIDE_DOWN_CAKE); + final List preferenceItemList = new ArrayList<>(); + RouteListingPreference.Item item1 = new RouteListingPreference.Item.Builder( + TEST_ID_4).build(); + RouteListingPreference.Item item2 = new RouteListingPreference.Item.Builder( + TEST_ID_3).build(); + preferenceItemList.add(item1); + preferenceItemList.add(item2); + when(mRouterManager.getRouteListingPreference(TEST_PACKAGE_NAME)) + .thenReturn(new RouteListingPreference.Builder().setItems( + preferenceItemList).setUseSystemOrdering(false).build()); + + final List selectedRoutes = new ArrayList<>(); + final MediaRoute2Info info = mock(MediaRoute2Info.class); + when(info.getId()).thenReturn(TEST_ID); + when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + when(info.isSystemRoute()).thenReturn(true); + selectedRoutes.add(info); + when(mRouterManager.getSelectedRoutes(any())).thenReturn(selectedRoutes); + + final List routingSessionInfos = new ArrayList<>(); + final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class); + routingSessionInfos.add(sessionInfo); + + when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)).thenReturn(routingSessionInfos); + when(sessionInfo.getSelectedRoutes()).thenReturn(ImmutableList.of(TEST_ID)); + + setTransferableRoutesList(); + + mInfoMediaManager.mRouterManager = mRouterManager; + + mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated(); + + assertThat(mInfoMediaManager.mMediaDevices.get(0).getId()).isEqualTo(TEST_ID); + assertThat(mInfoMediaManager.mMediaDevices.get(1).getId()).isEqualTo(TEST_ID_4); + assertThat(mInfoMediaManager.mMediaDevices.get(2).getId()).isEqualTo(TEST_ID_3); + assertThat(mInfoMediaManager.mMediaDevices).hasSize(3); + } + + private List setTransferableRoutesList() { + final List transferableRoutes = new ArrayList<>(); + final MediaRoute2Info transferableInfo1 = mock(MediaRoute2Info.class); + when(transferableInfo1.getId()).thenReturn(TEST_ID_2); + when(transferableInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + when(transferableInfo1.getType()).thenReturn(TYPE_REMOTE_TV); + transferableRoutes.add(transferableInfo1); + + final MediaRoute2Info transferableInfo2 = mock(MediaRoute2Info.class); + when(transferableInfo2.getId()).thenReturn(TEST_ID_3); + when(transferableInfo2.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + transferableRoutes.add(transferableInfo2); + + final MediaRoute2Info transferableInfo3 = mock(MediaRoute2Info.class); + when(transferableInfo3.getId()).thenReturn(TEST_ID_4); + when(transferableInfo3.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + transferableRoutes.add(transferableInfo3); + + when(mRouterManager.getTransferableRoutes(TEST_PACKAGE_NAME)).thenReturn( + transferableRoutes); + + return transferableRoutes; + } + @Test public void onRoutesChanged_buildAllRoutes_shouldAddMediaDevice() { final MediaRoute2Info info = mock(MediaRoute2Info.class); when(info.getId()).thenReturn(TEST_ID); when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); when(info.isSystemRoute()).thenReturn(true); + when(info.getDeduplicationIds()).thenReturn(Set.of()); final List routes = new ArrayList<>(); routes.add(info); @@ -229,6 +340,47 @@ public class InfoMediaManagerTest { assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size()); } + private List getRoutesListWithDuplicatedIds() { + final List routes = new ArrayList<>(); + final MediaRoute2Info info = mock(MediaRoute2Info.class); + when(info.getId()).thenReturn(TEST_ID); + when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + when(info.isSystemRoute()).thenReturn(true); + when(info.getDeduplicationIds()).thenReturn( + Set.of(TEST_DUPLICATED_ID_1, TEST_DUPLICATED_ID_2)); + routes.add(info); + + final MediaRoute2Info info1 = mock(MediaRoute2Info.class); + when(info1.getId()).thenReturn(TEST_ID_1); + when(info1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + when(info1.isSystemRoute()).thenReturn(true); + when(info1.getDeduplicationIds()).thenReturn(Set.of(TEST_DUPLICATED_ID_3)); + routes.add(info1); + + final MediaRoute2Info info2 = mock(MediaRoute2Info.class); + when(info2.getId()).thenReturn(TEST_ID_2); + when(info2.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + when(info2.isSystemRoute()).thenReturn(true); + when(info2.getDeduplicationIds()).thenReturn(Set.of(TEST_DUPLICATED_ID_3)); + routes.add(info2); + + final MediaRoute2Info info3 = mock(MediaRoute2Info.class); + when(info3.getId()).thenReturn(TEST_ID_3); + when(info3.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + when(info3.isSystemRoute()).thenReturn(true); + when(info3.getDeduplicationIds()).thenReturn(Set.of(TEST_DUPLICATED_ID_1)); + routes.add(info3); + + final MediaRoute2Info info4 = mock(MediaRoute2Info.class); + when(info4.getId()).thenReturn(TEST_ID_4); + when(info4.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + when(info4.isSystemRoute()).thenReturn(true); + when(info4.getDeduplicationIds()).thenReturn(Set.of(TEST_DUPLICATED_ID_2)); + routes.add(info4); + + return routes; + } + @Test public void connectDeviceWithoutPackageName_noSession_returnFalse() { final MediaRoute2Info info = mock(MediaRoute2Info.class); @@ -255,6 +407,7 @@ public class InfoMediaManagerTest { final MediaRoute2Info info = mock(MediaRoute2Info.class); when(info.getId()).thenReturn(TEST_ID); when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + when(info.getDeduplicationIds()).thenReturn(Set.of()); final List routes = new ArrayList<>(); routes.add(info); @@ -620,6 +773,7 @@ public class InfoMediaManagerTest { final MediaRoute2Info info = mock(MediaRoute2Info.class); mInfoMediaManager.registerCallback(mCallback); + when(info.getDeduplicationIds()).thenReturn(Set.of()); when(info.getId()).thenReturn(TEST_ID); when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);