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

Commit 73ce019c authored by Shaowei Shen's avatar Shaowei Shen Committed by Android (Google) Code Review
Browse files

Merge "[Output Switcher] Deal with deplicated id and route preference"

parents f89fbbe5 dc01faf8
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);