Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dc01faf8 authored by shaoweishen's avatar shaoweishen Committed by Shaowei Shen
Browse files

[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
parent 287a6b7e
Loading
Loading
Loading
Loading
+83 −3
Original line number Diff line number Diff line
@@ -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<MediaRoute2Info> getAvailableRoutes(String packageName) {
        final List<MediaRoute2Info> infos = new ArrayList<>();
        List<MediaRoute2Info> infos = new ArrayList<>();
        RoutingSessionInfo routingSessionInfo = getRoutingSessionInfo(packageName);
        List<MediaRoute2Info> 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<MediaRoute2Info> transferableRoutes =
@@ -468,11 +476,26 @@ public class InfoMediaManager extends MediaManager {
                infos.add(transferableRoute);
            }
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            RouteListingPreference routeListingPreference =
                    mRouterManager.getRouteListingPreference(mPackageName);
            if (routeListingPreference != null) {
                final List<RouteListingPreference.Item> 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<RouteListingPreference.Item> composePreferenceRouteListing(
                RouteListingPreference routeListingPreference) {
            List<RouteListingPreference.Item> finalizedItemList = new ArrayList<>();
            List<RouteListingPreference.Item> 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<MediaRoute2Info> filterDuplicatedIds(List<MediaRoute2Info> infos) {
            List<MediaRoute2Info> filteredInfos = new ArrayList<>();
            Set<String> 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<MediaRoute2Info> arrangeRouteListByPreference(
                List<MediaRoute2Info> selectedRouteInfos, List<MediaRoute2Info> infolist,
                List<RouteListingPreference.Item> preferenceRouteListing) {
            final List<MediaRoute2Info> 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;
        }
    }
}
+154 −0
Original line number Diff line number Diff line
@@ -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<MediaRoute2Info> 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<MediaRoute2Info> 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<MediaRoute2Info> 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<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
        final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
        routingSessionInfos.add(sessionInfo);

        final List<String> 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<RouteListingPreference.Item> 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<MediaRoute2Info> 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<RoutingSessionInfo> 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<MediaRoute2Info> setTransferableRoutesList() {
        final List<MediaRoute2Info> 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<MediaRoute2Info> routes = new ArrayList<>();
        routes.add(info);
@@ -229,6 +340,47 @@ public class InfoMediaManagerTest {
        assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
    }

    private List<MediaRoute2Info> getRoutesListWithDuplicatedIds() {
        final List<MediaRoute2Info> 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<MediaRoute2Info> 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);