Merge "Add new api for output switcher"

This commit is contained in:
TreeHugger Robot
2020-02-17 03:38:58 +00:00
committed by Android (Google) Code Review
4 changed files with 522 additions and 43 deletions

View File

@@ -33,6 +33,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -83,10 +84,179 @@ public class InfoMediaManager extends MediaManager {
* Get current device that played media.
* @return MediaDevice
*/
public MediaDevice getCurrentConnectedDevice() {
MediaDevice getCurrentConnectedDevice() {
return mCurrentConnectedDevice;
}
/**
* Transfer MediaDevice for media without package name.
*/
boolean connectDeviceWithoutPackageName(MediaDevice device) {
boolean isConnected = false;
final List<RoutingSessionInfo> infos = mRouterManager.getActiveSessions();
if (infos.size() > 0) {
final RoutingSessionInfo info = infos.get(0);
final MediaRouter2Manager.RoutingController controller =
mRouterManager.getControllerForSession(info);
controller.transferToRoute(device.mRouteInfo);
isConnected = true;
}
return isConnected;
}
/**
* Add a MediaDevice to let it play current media.
*
* @param device MediaDevice
* @return If add device successful return {@code true}, otherwise return {@code false}
*/
boolean addDeviceToPlayMedia(MediaDevice device) {
if (TextUtils.isEmpty(mPackageName)) {
Log.w(TAG, "addDeviceToPlayMedia() package name is null or empty!");
return false;
}
final RoutingSessionInfo info = getRoutingSessionInfo();
if (info != null && info.getSelectableRoutes().contains(device.mRouteInfo.getId())) {
mRouterManager.getControllerForSession(info).selectRoute(device.mRouteInfo);
return true;
}
Log.w(TAG, "addDeviceToPlayMedia() Ignoring selecting a non-selectable device : "
+ device.getName());
return false;
}
private RoutingSessionInfo getRoutingSessionInfo() {
for (RoutingSessionInfo info : mRouterManager.getRoutingSessions(mPackageName)) {
if (TextUtils.equals(info.getClientPackageName(), mPackageName)) {
return info;
}
}
Log.w(TAG, "RoutingSessionInfo() cannot found match packagename : " + mPackageName);
return null;
}
/**
* Remove a {@code device} from current media.
*
* @param device MediaDevice
* @return If device stop successful return {@code true}, otherwise return {@code false}
*/
boolean removeDeviceFromPlayMedia(MediaDevice device) {
if (TextUtils.isEmpty(mPackageName)) {
Log.w(TAG, "removeDeviceFromMedia() package name is null or empty!");
return false;
}
final RoutingSessionInfo info = getRoutingSessionInfo();
if (info != null && info.getSelectedRoutes().contains(device.mRouteInfo.getId())) {
mRouterManager.getControllerForSession(info).deselectRoute(device.mRouteInfo);
return true;
}
Log.w(TAG, "removeDeviceFromMedia() Ignoring deselecting a non-deselectable device : "
+ device.getName());
return false;
}
/**
* Get the MediaDevice list that can be added to current media.
*
* @return list of MediaDevice
*/
List<MediaDevice> getSelectableMediaDevice() {
final List<MediaDevice> deviceList = new ArrayList<>();
if (TextUtils.isEmpty(mPackageName)) {
Log.w(TAG, "getSelectableMediaDevice() package name is null or empty!");
return deviceList;
}
final RoutingSessionInfo info = getRoutingSessionInfo();
if (info != null) {
for (MediaRoute2Info route : mRouterManager.getControllerForSession(info)
.getSelectableRoutes()) {
deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
route, mPackageName));
}
return deviceList;
}
Log.w(TAG, "getSelectableMediaDevice() cannot found selectable MediaDevice from : "
+ mPackageName);
return deviceList;
}
/**
* Adjust the volume of {@link android.media.RoutingSessionInfo}.
*
* @param volume the value of volume
*/
void adjustSessionVolume(int volume) {
if (TextUtils.isEmpty(mPackageName)) {
Log.w(TAG, "adjustSessionVolume() package name is null or empty!");
return;
}
final RoutingSessionInfo info = getRoutingSessionInfo();
if (info != null) {
Log.d(TAG, "adjustSessionVolume() adjust volume : " + volume + ", with : "
+ mPackageName);
mRouterManager.setSessionVolume(info, volume);
return;
}
Log.w(TAG, "adjustSessionVolume() can't found corresponding RoutingSession with : "
+ mPackageName);
}
/**
* Gets the maximum volume of the {@link android.media.RoutingSessionInfo}.
*
* @return maximum volume of the session, and return -1 if not found.
*/
public int getSessionVolumeMax() {
if (TextUtils.isEmpty(mPackageName)) {
Log.w(TAG, "getSessionVolumeMax() package name is null or empty!");
return -1;
}
final RoutingSessionInfo info = getRoutingSessionInfo();
if (info != null) {
return info.getVolumeMax();
}
Log.w(TAG, "getSessionVolumeMax() can't found corresponding RoutingSession with : "
+ mPackageName);
return -1;
}
/**
* Gets the current volume of the {@link android.media.RoutingSessionInfo}.
*
* @return current volume of the session, and return -1 if not found.
*/
public int getSessionVolume() {
if (TextUtils.isEmpty(mPackageName)) {
Log.w(TAG, "getSessionVolume() package name is null or empty!");
return -1;
}
final RoutingSessionInfo info = getRoutingSessionInfo();
if (info != null) {
return info.getVolume();
}
Log.w(TAG, "getSessionVolume() can't found corresponding RoutingSession with : "
+ mPackageName);
return -1;
}
private void refreshDevices() {
mMediaDevices.clear();
mCurrentConnectedDevice = null;
@@ -150,23 +320,6 @@ public class InfoMediaManager extends MediaManager {
}
}
/**
* Transfer MediaDevice for media without package name.
*/
public boolean connectDeviceWithoutPackageName(MediaDevice device) {
boolean isConnected = false;
final List<RoutingSessionInfo> infos = mRouterManager.getActiveSessions();
if (infos.size() > 0) {
final RoutingSessionInfo info = infos.get(0);
final MediaRouter2Manager.RoutingController controller =
mRouterManager.getControllerForSession(info);
controller.transferToRoute(device.mRouteInfo);
isConnected = true;
}
return isConnected;
}
class RouterManagerCallback extends MediaRouter2Manager.Callback {
@Override

View File

@@ -252,6 +252,62 @@ public class LocalMediaManager implements BluetoothCallback {
return devices;
}
/**
* Add a MediaDevice to let it play current media.
*
* @param device MediaDevice
* @return If add device successful return {@code true}, otherwise return {@code false}
*/
public boolean addDeviceToPlayMedia(MediaDevice device) {
return mInfoMediaManager.addDeviceToPlayMedia(device);
}
/**
* Remove a {@code device} from current media.
*
* @param device MediaDevice
* @return If device stop successful return {@code true}, otherwise return {@code false}
*/
public boolean removeDeviceFromPlayMedia(MediaDevice device) {
return mInfoMediaManager.removeDeviceFromPlayMedia(device);
}
/**
* Get the MediaDevice list that can be added to current media.
*
* @return list of MediaDevice
*/
public List<MediaDevice> getSelectableMediaDevice() {
return mInfoMediaManager.getSelectableMediaDevice();
}
/**
* Adjust the volume of session.
*
* @param volume the value of volume
*/
public void adjustSessionVolume(int volume) {
mInfoMediaManager.adjustSessionVolume(volume);
}
/**
* Gets the maximum volume of the {@link android.media.RoutingSessionInfo}.
*
* @return maximum volume of the session, and return -1 if not found.
*/
public int getSessionVolumeMax() {
return mInfoMediaManager.getSessionVolumeMax();
}
/**
* Gets the current volume of the {@link android.media.RoutingSessionInfo}.
*
* @return current volume of the session, and return -1 if not found.
*/
public int getSessionVolume() {
return mInfoMediaManager.getSessionVolume();
}
private MediaDevice updateCurrentConnectedDevice() {
MediaDevice phoneMediaDevice = null;
for (MediaDevice device : mMediaDevices) {

View File

@@ -28,6 +28,7 @@ import android.media.MediaRouter2Manager;
import android.media.RoutingSessionInfo;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.testutils.shadow.ShadowRouter2Manager;
import org.junit.Before;
import org.junit.Test;
@@ -36,15 +37,18 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowRouter2Manager.class})
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_NAME = "test_name";
@Mock
private MediaRouter2Manager mRouterManager;
@@ -53,6 +57,7 @@ public class InfoMediaManagerTest {
private InfoMediaManager mInfoMediaManager;
private Context mContext;
private ShadowRouter2Manager mShadowRouter2Manager;
@Before
public void setUp() {
@@ -61,22 +66,8 @@ public class InfoMediaManagerTest {
mInfoMediaManager =
new InfoMediaManager(mContext, TEST_PACKAGE_NAME, null, mLocalBluetoothManager);
mInfoMediaManager.mRouterManager = mRouterManager;
}
@Test
public void stopScan_shouldRemoveCallback() {
mInfoMediaManager.stopScan();
verify(mRouterManager).unregisterCallback(mInfoMediaManager.mMediaRouterCallback);
}
@Test
public void startScan_shouldAddCallback() {
mInfoMediaManager.startScan();
verify(mRouterManager).registerCallback(mInfoMediaManager.mExecutor,
mInfoMediaManager.mMediaRouterCallback);
mShadowRouter2Manager = ShadowRouter2Manager.getShadow();
mInfoMediaManager.mRouterManager = MediaRouter2Manager.getInstance(mContext);
}
@Test
@@ -87,7 +78,7 @@ public class InfoMediaManagerTest {
final List<MediaRoute2Info> routes = new ArrayList<>();
routes.add(info);
when(mRouterManager.getAvailableRoutes(TEST_PACKAGE_NAME)).thenReturn(routes);
mShadowRouter2Manager.setAvailableRoutes(routes);
final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
assertThat(mediaDevice).isNull();
@@ -108,7 +99,7 @@ public class InfoMediaManagerTest {
final List<MediaRoute2Info> routes = new ArrayList<>();
routes.add(info);
when(mRouterManager.getAllRoutes()).thenReturn(routes);
mShadowRouter2Manager.setAllRoutes(routes);
final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
assertThat(mediaDevice).isNull();
@@ -129,7 +120,7 @@ public class InfoMediaManagerTest {
final List<MediaRoute2Info> routes = new ArrayList<>();
routes.add(info);
when(mRouterManager.getAvailableRoutes(TEST_PACKAGE_NAME)).thenReturn(routes);
mShadowRouter2Manager.setAvailableRoutes(routes);
final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
assertThat(mediaDevice).isNull();
@@ -157,7 +148,7 @@ public class InfoMediaManagerTest {
final List<MediaRoute2Info> routes = new ArrayList<>();
routes.add(info);
when(mRouterManager.getAvailableRoutes(TEST_PACKAGE_NAME)).thenReturn(routes);
mShadowRouter2Manager.setAvailableRoutes(routes);
final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
assertThat(mediaDevice).isNull();
@@ -178,7 +169,7 @@ public class InfoMediaManagerTest {
final List<MediaRoute2Info> routes = new ArrayList<>();
routes.add(info);
when(mRouterManager.getAllRoutes()).thenReturn(routes);
mShadowRouter2Manager.setAllRoutes(routes);
final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
assertThat(mediaDevice).isNull();
@@ -194,12 +185,12 @@ public class InfoMediaManagerTest {
@Test
public void connectDeviceWithoutPackageName_noSession_returnFalse() {
final MediaRoute2Info info = mock(MediaRoute2Info.class);
final MediaDevice device = new InfoMediaDevice(mContext, mRouterManager, info,
TEST_PACKAGE_NAME);
final MediaDevice device = new InfoMediaDevice(mContext, mInfoMediaManager.mRouterManager,
info, TEST_PACKAGE_NAME);
final List<RoutingSessionInfo> infos = new ArrayList<>();
when(mRouterManager.getActiveSessions()).thenReturn(infos);
mShadowRouter2Manager.setActiveSessions(infos);
assertThat(mInfoMediaManager.connectDeviceWithoutPackageName(device)).isFalse();
}
@@ -239,10 +230,212 @@ public class InfoMediaManagerTest {
assertThat(mediaDevice).isNull();
mInfoMediaManager.mPackageName = "";
mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes);
mInfoMediaManager.mMediaRouterCallback.onRoutesRemoved(routes);
final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
}
@Test
public void addDeviceToPlayMedia_packageNameIsNull_returnFalse() {
mInfoMediaManager.mPackageName = null;
final MediaDevice device = mock(MediaDevice.class);
assertThat(mInfoMediaManager.addDeviceToPlayMedia(device)).isFalse();
}
@Test
public void addDeviceToPlayMedia_containSelectableRoutes_returnTrue() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
routingSessionInfos.add(info);
final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
final MediaDevice device =
new InfoMediaDevice(mContext, mInfoMediaManager.mRouterManager, route2Info,
TEST_PACKAGE_NAME);
final List<String> list = new ArrayList<>();
list.add(TEST_ID);
mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
when(info.getSelectableRoutes()).thenReturn(list);
when(route2Info.getId()).thenReturn(TEST_ID);
when(route2Info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
assertThat(mInfoMediaManager.addDeviceToPlayMedia(device)).isTrue();
}
@Test
public void addDeviceToPlayMedia_notContainSelectableRoutes_returnFalse() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
routingSessionInfos.add(info);
final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
final MediaDevice device =
new InfoMediaDevice(mContext, mInfoMediaManager.mRouterManager, route2Info,
TEST_PACKAGE_NAME);
final List<String> list = new ArrayList<>();
list.add("fake_id");
mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
when(info.getSelectableRoutes()).thenReturn(list);
when(route2Info.getId()).thenReturn(TEST_ID);
when(route2Info.getName()).thenReturn(TEST_NAME);
when(route2Info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
assertThat(mInfoMediaManager.addDeviceToPlayMedia(device)).isFalse();
}
@Test
public void removeDeviceFromMedia_packageNameIsNull_returnFalse() {
mInfoMediaManager.mPackageName = null;
final MediaDevice device = mock(MediaDevice.class);
assertThat(mInfoMediaManager.removeDeviceFromPlayMedia(device)).isFalse();
}
@Test
public void removeDeviceFromMedia_containSelectedRoutes_returnTrue() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
routingSessionInfos.add(info);
final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
final MediaDevice device =
new InfoMediaDevice(mContext, mInfoMediaManager.mRouterManager, route2Info,
TEST_PACKAGE_NAME);
final List<String> list = new ArrayList<>();
list.add(TEST_ID);
mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
when(info.getSelectedRoutes()).thenReturn(list);
when(route2Info.getId()).thenReturn(TEST_ID);
when(route2Info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
assertThat(mInfoMediaManager.removeDeviceFromPlayMedia(device)).isTrue();
}
@Test
public void removeDeviceFromMedia_notContainSelectedRoutes_returnFalse() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
routingSessionInfos.add(info);
final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
final MediaDevice device =
new InfoMediaDevice(mContext, mInfoMediaManager.mRouterManager, route2Info,
TEST_PACKAGE_NAME);
final List<String> list = new ArrayList<>();
list.add("fake_id");
mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
when(info.getSelectedRoutes()).thenReturn(list);
when(route2Info.getId()).thenReturn(TEST_ID);
when(route2Info.getName()).thenReturn(TEST_NAME);
when(route2Info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
assertThat(mInfoMediaManager.removeDeviceFromPlayMedia(device)).isFalse();
}
@Test
public void getSelectableMediaDevice_packageNameIsNull_returnFalse() {
mInfoMediaManager.mPackageName = null;
assertThat(mInfoMediaManager.getSelectableMediaDevice()).isEmpty();
}
@Test
public void getSelectableMediaDevice_notContainPackageName_returnEmpty() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
routingSessionInfos.add(info);
mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
when(info.getClientPackageName()).thenReturn("com.fake.packagename");
assertThat(mInfoMediaManager.getSelectableMediaDevice()).isEmpty();
}
@Test
public void adjustSessionVolume_packageNameIsNull_noCrash() {
mInfoMediaManager.mPackageName = null;
mInfoMediaManager.adjustSessionVolume(10);
}
@Test
public void getSessionVolumeMax_packageNameIsNull_returnNotFound() {
mInfoMediaManager.mPackageName = null;
assertThat(mInfoMediaManager.getSessionVolumeMax()).isEqualTo(-1);
}
@Test
public void getSessionVolumeMax_containPackageName_returnMaxVolume() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
routingSessionInfos.add(info);
mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
mInfoMediaManager.getSessionVolumeMax();
verify(info).getVolumeMax();
}
@Test
public void getSessionVolumeMax_notContainPackageName_returnNotFound() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
routingSessionInfos.add(info);
mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
when(info.getClientPackageName()).thenReturn("com.fake.packagename");
assertThat(mInfoMediaManager.getSessionVolumeMax()).isEqualTo(-1);
}
@Test
public void getSessionVolume_packageNameIsNull_returnNotFound() {
mInfoMediaManager.mPackageName = null;
assertThat(mInfoMediaManager.getSessionVolume()).isEqualTo(-1);
}
@Test
public void getSessionVolume_containPackageName_returnMaxVolume() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
routingSessionInfos.add(info);
mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
mInfoMediaManager.getSessionVolume();
verify(info).getVolume();
}
@Test
public void getSessionVolume_notContainPackageName_returnNotFound() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
routingSessionInfos.add(info);
mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
when(info.getClientPackageName()).thenReturn("com.fake.packagename");
assertThat(mInfoMediaManager.getSessionVolume()).isEqualTo(-1);
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2019 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.testutils.shadow;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
import android.media.RoutingSessionInfo;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;
import java.util.List;
@Implements(MediaRouter2Manager.class)
public class ShadowRouter2Manager {
private List<MediaRoute2Info> mAvailableRoutes;
private List<MediaRoute2Info> mAllRoutes;
private List<RoutingSessionInfo> mActiveSessions;
private List<RoutingSessionInfo> mRoutingSessions;
@Implementation
protected List<MediaRoute2Info> getAvailableRoutes(String packageName) {
return mAvailableRoutes;
}
public void setAvailableRoutes(List<MediaRoute2Info> infos) {
mAvailableRoutes = infos;
}
@Implementation
protected List<MediaRoute2Info> getAllRoutes() {
return mAllRoutes;
}
public void setAllRoutes(List<MediaRoute2Info> infos) {
mAllRoutes = infos;
}
@Implementation
protected List<RoutingSessionInfo> getActiveSessions() {
return mActiveSessions;
}
public void setActiveSessions(List<RoutingSessionInfo> infos) {
mActiveSessions = infos;
}
@Implementation
protected List<RoutingSessionInfo> getRoutingSessions(String packageName) {
return mRoutingSessions;
}
public void setRoutingSessions(List<RoutingSessionInfo> infos) {
mRoutingSessions = infos;
}
public static ShadowRouter2Manager getShadow() {
return (ShadowRouter2Manager) Shadow.extract(
MediaRouter2Manager.getInstance(RuntimeEnvironment.application));
}
}