From dc01faf80abb2a6960eed1fb90bcf6e5201fa508 Mon Sep 17 00:00:00 2001 From: shaoweishen Date: Mon, 14 Nov 2022 06:15:27 +0000 Subject: [Output Switcher] Deal with deplicated id and route preference 1. When contruct devices list, filtered duplicated devices by going through all devices and remove extra devices shared with same duplicated ids. 2. Get preference route list and order routes. 3. move suggested devices to the top in preference list before using it. reference : go/output-switcher-device-organization Bug: 257851968 Test: make RunSettingsLibRoboTests -j40 Change-Id: I38035240793ea67fc6b4f1ad886e8beee1e7464a --- .../settingslib/media/InfoMediaManager.java | 86 +++++++++++- .../settingslib/media/InfoMediaManagerTest.java | 154 +++++++++++++++++++++ 2 files changed, 237 insertions(+), 3 deletions(-) diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index 7353cc0393c5..e112915cecd7 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 33fb91d2252c..2820a132162b 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); -- cgit v1.2.3-59-g8ed1b