Merge "Extract wired routes logic into a separate class." into udc-dev
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.server.media;
|
||||
|
||||
import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
|
||||
import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
|
||||
import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK;
|
||||
import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
|
||||
import static android.media.MediaRoute2Info.TYPE_DOCK;
|
||||
import static android.media.MediaRoute2Info.TYPE_HDMI;
|
||||
import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
|
||||
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
|
||||
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioRoutesInfo;
|
||||
import android.media.IAudioRoutesObserver;
|
||||
import android.media.IAudioService;
|
||||
import android.media.MediaRoute2Info;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Controls device routes.
|
||||
*
|
||||
* <p>A device route is a system wired route, for example, built-in speaker, wired
|
||||
* headsets and headphones, dock, hdmi, or usb devices.
|
||||
*
|
||||
* <p>Thread safe.
|
||||
*
|
||||
* @see SystemMediaRoute2Provider
|
||||
*/
|
||||
/* package */ final class DeviceRouteController {
|
||||
|
||||
private static final String TAG = "WiredRoutesController";
|
||||
|
||||
private static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE";
|
||||
|
||||
@NonNull
|
||||
private final Context mContext;
|
||||
@NonNull
|
||||
private final AudioManager mAudioManager;
|
||||
@NonNull
|
||||
private final IAudioService mAudioService;
|
||||
|
||||
@NonNull
|
||||
private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
|
||||
@NonNull
|
||||
private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver();
|
||||
|
||||
private int mDeviceVolume;
|
||||
private MediaRoute2Info mDeviceRoute;
|
||||
|
||||
/* package */ static DeviceRouteController createInstance(@NonNull Context context,
|
||||
@NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
|
||||
AudioManager audioManager = context.getSystemService(AudioManager.class);
|
||||
IAudioService audioService = IAudioService.Stub.asInterface(
|
||||
ServiceManager.getService(Context.AUDIO_SERVICE));
|
||||
|
||||
return new DeviceRouteController(context,
|
||||
audioManager,
|
||||
audioService,
|
||||
onDeviceRouteChangedListener);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
/* package */ DeviceRouteController(@NonNull Context context,
|
||||
@NonNull AudioManager audioManager,
|
||||
@NonNull IAudioService audioService,
|
||||
@NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
|
||||
Objects.requireNonNull(context);
|
||||
Objects.requireNonNull(audioManager);
|
||||
Objects.requireNonNull(audioService);
|
||||
Objects.requireNonNull(onDeviceRouteChangedListener);
|
||||
|
||||
mContext = context;
|
||||
mOnDeviceRouteChangedListener = onDeviceRouteChangedListener;
|
||||
|
||||
mAudioManager = audioManager;
|
||||
mAudioService = audioService;
|
||||
|
||||
AudioRoutesInfo newAudioRoutes = null;
|
||||
try {
|
||||
newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Cannot connect to audio service to start listen to routes", e);
|
||||
}
|
||||
|
||||
mDeviceRoute = createRouteFromAudioInfo(newAudioRoutes);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
/* package */ synchronized MediaRoute2Info getDeviceRoute() {
|
||||
return mDeviceRoute;
|
||||
}
|
||||
|
||||
/* package */ synchronized boolean updateVolume(int volume) {
|
||||
if (mDeviceVolume == volume) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mDeviceVolume = volume;
|
||||
mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute)
|
||||
.setVolume(volume)
|
||||
.build();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private MediaRoute2Info createRouteFromAudioInfo(@Nullable AudioRoutesInfo newRoutes) {
|
||||
int name = R.string.default_audio_route_name;
|
||||
int type = TYPE_BUILTIN_SPEAKER;
|
||||
|
||||
if (newRoutes != null) {
|
||||
if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) {
|
||||
type = TYPE_WIRED_HEADPHONES;
|
||||
name = com.android.internal.R.string.default_audio_route_name_headphones;
|
||||
} else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
|
||||
type = TYPE_WIRED_HEADSET;
|
||||
name = com.android.internal.R.string.default_audio_route_name_headphones;
|
||||
} else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
|
||||
type = TYPE_DOCK;
|
||||
name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
|
||||
} else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) {
|
||||
type = TYPE_HDMI;
|
||||
name = com.android.internal.R.string.default_audio_route_name_external_device;
|
||||
} else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) {
|
||||
type = TYPE_USB_DEVICE;
|
||||
name = com.android.internal.R.string.default_audio_route_name_usb;
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
return new MediaRoute2Info.Builder(
|
||||
DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
|
||||
.setVolumeHandling(mAudioManager.isVolumeFixed()
|
||||
? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
|
||||
: MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
|
||||
.setVolume(mDeviceVolume)
|
||||
.setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
|
||||
.setType(type)
|
||||
.addFeature(FEATURE_LIVE_AUDIO)
|
||||
.addFeature(FEATURE_LIVE_VIDEO)
|
||||
.addFeature(FEATURE_LOCAL_PLAYBACK)
|
||||
.setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyDeviceRouteUpdate(@NonNull MediaRoute2Info deviceRoute) {
|
||||
mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute);
|
||||
}
|
||||
|
||||
/* package */ interface OnDeviceRouteChangedListener {
|
||||
void onDeviceRouteChanged(@NonNull MediaRoute2Info deviceRoute);
|
||||
}
|
||||
|
||||
private class AudioRoutesObserver extends IAudioRoutesObserver.Stub {
|
||||
|
||||
@Override
|
||||
public void dispatchAudioRoutesChanged(AudioRoutesInfo newAudioRoutes) {
|
||||
MediaRoute2Info deviceRoute = createRouteFromAudioInfo(newAudioRoutes);
|
||||
synchronized (DeviceRouteController.this) {
|
||||
mDeviceRoute = deviceRoute;
|
||||
}
|
||||
notifyDeviceRouteUpdate(deviceRoute);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,25 +16,12 @@
|
||||
|
||||
package com.android.server.media;
|
||||
|
||||
import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
|
||||
import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
|
||||
import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK;
|
||||
import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
|
||||
import static android.media.MediaRoute2Info.TYPE_DOCK;
|
||||
import static android.media.MediaRoute2Info.TYPE_HDMI;
|
||||
import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
|
||||
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
|
||||
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioRoutesInfo;
|
||||
import android.media.IAudioRoutesObserver;
|
||||
import android.media.IAudioService;
|
||||
import android.media.MediaRoute2Info;
|
||||
import android.media.MediaRoute2ProviderInfo;
|
||||
import android.media.MediaRoute2ProviderService;
|
||||
@@ -43,14 +30,11 @@ import android.media.RoutingSessionInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
|
||||
import java.util.Objects;
|
||||
@@ -68,23 +52,21 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
|
||||
SystemMediaRoute2Provider.class.getName());
|
||||
|
||||
static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
|
||||
static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE";
|
||||
static final String SYSTEM_SESSION_ID = "SYSTEM_SESSION";
|
||||
|
||||
private final AudioManager mAudioManager;
|
||||
private final IAudioService mAudioService;
|
||||
private final Handler mHandler;
|
||||
private final Context mContext;
|
||||
private final UserHandle mUser;
|
||||
|
||||
private final DeviceRouteController mDeviceRouteController;
|
||||
private final BluetoothRouteController mBtRouteProvider;
|
||||
|
||||
private String mSelectedRouteId;
|
||||
// For apps without MODIFYING_AUDIO_ROUTING permission.
|
||||
// This should be the currently selected route.
|
||||
MediaRoute2Info mDefaultRoute;
|
||||
MediaRoute2Info mDeviceRoute;
|
||||
RoutingSessionInfo mDefaultSessionInfo;
|
||||
int mDeviceVolume;
|
||||
|
||||
private final AudioManagerBroadcastReceiver mAudioReceiver =
|
||||
new AudioManagerBroadcastReceiver();
|
||||
@@ -93,19 +75,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
|
||||
@GuardedBy("mRequestLock")
|
||||
private volatile SessionCreationRequest mPendingSessionCreationRequest;
|
||||
|
||||
final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
|
||||
@Override
|
||||
public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
|
||||
mHandler.post(() -> {
|
||||
updateDeviceRoute(newRoutes);
|
||||
notifyProviderState();
|
||||
if (updateSessionInfosIfNeeded()) {
|
||||
notifySessionInfoUpdated();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
SystemMediaRoute2Provider(Context context, UserHandle user) {
|
||||
super(COMPONENT_NAME);
|
||||
mIsSystemRouteProvider = true;
|
||||
@@ -114,8 +83,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
mAudioService = IAudioService.Stub.asInterface(
|
||||
ServiceManager.getService(Context.AUDIO_SERVICE));
|
||||
|
||||
mBtRouteProvider = BluetoothRouteController.createInstance(context, (routes) -> {
|
||||
publishProviderState();
|
||||
@@ -124,15 +91,18 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
|
||||
}
|
||||
});
|
||||
|
||||
AudioRoutesInfo newAudioRoutes = null;
|
||||
try {
|
||||
newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
mDeviceRouteController = DeviceRouteController.createInstance(context, (deviceRoute) -> {
|
||||
mHandler.post(() -> {
|
||||
publishProviderState();
|
||||
if (updateSessionInfosIfNeeded()) {
|
||||
notifySessionInfoUpdated();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// The methods below should be called after all fields are initialized, as they
|
||||
// These methods below should be called after all fields are initialized, as they
|
||||
// access the fields inside.
|
||||
updateDeviceRoute(newAudioRoutes);
|
||||
updateProviderState();
|
||||
updateSessionInfosIfNeeded();
|
||||
}
|
||||
|
||||
@@ -216,7 +186,9 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
|
||||
// The currently selected route is the default route.
|
||||
return;
|
||||
}
|
||||
if (TextUtils.equals(routeId, mDeviceRoute.getId())) {
|
||||
|
||||
MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute();
|
||||
if (TextUtils.equals(routeId, deviceRoute.getId())) {
|
||||
mBtRouteProvider.transferTo(null);
|
||||
} else {
|
||||
mBtRouteProvider.transferTo(routeId);
|
||||
@@ -254,9 +226,12 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
|
||||
if (mSessionInfos.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute();
|
||||
|
||||
RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
|
||||
SYSTEM_SESSION_ID, packageName).setSystemSession(true);
|
||||
builder.addSelectedRoute(mDeviceRoute.getId());
|
||||
builder.addSelectedRoute(deviceRoute.getId());
|
||||
for (MediaRoute2Info route : mBtRouteProvider.getAllBluetoothRoutes()) {
|
||||
builder.addTransferableRoute(route.getId());
|
||||
}
|
||||
@@ -264,47 +239,12 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDeviceRoute(AudioRoutesInfo newRoutes) {
|
||||
int name = R.string.default_audio_route_name;
|
||||
int type = TYPE_BUILTIN_SPEAKER;
|
||||
if (newRoutes != null) {
|
||||
if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) {
|
||||
type = TYPE_WIRED_HEADPHONES;
|
||||
name = com.android.internal.R.string.default_audio_route_name_headphones;
|
||||
} else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
|
||||
type = TYPE_WIRED_HEADSET;
|
||||
name = com.android.internal.R.string.default_audio_route_name_headphones;
|
||||
} else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
|
||||
type = TYPE_DOCK;
|
||||
name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
|
||||
} else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) {
|
||||
type = TYPE_HDMI;
|
||||
name = com.android.internal.R.string.default_audio_route_name_external_device;
|
||||
} else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) {
|
||||
type = TYPE_USB_DEVICE;
|
||||
name = com.android.internal.R.string.default_audio_route_name_usb;
|
||||
}
|
||||
}
|
||||
|
||||
mDeviceRoute = new MediaRoute2Info.Builder(
|
||||
DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
|
||||
.setVolumeHandling(mAudioManager.isVolumeFixed()
|
||||
? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
|
||||
: MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
|
||||
.setVolume(mDeviceVolume)
|
||||
.setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
|
||||
.setType(type)
|
||||
.addFeature(FEATURE_LIVE_AUDIO)
|
||||
.addFeature(FEATURE_LIVE_VIDEO)
|
||||
.addFeature(FEATURE_LOCAL_PLAYBACK)
|
||||
.setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
|
||||
.build();
|
||||
updateProviderState();
|
||||
}
|
||||
|
||||
private void updateProviderState() {
|
||||
MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
|
||||
builder.addRoute(mDeviceRoute);
|
||||
|
||||
// We must have a device route in the provider info.
|
||||
builder.addRoute(mDeviceRouteController.getDeviceRoute());
|
||||
|
||||
for (MediaRoute2Info route : mBtRouteProvider.getAllBluetoothRoutes()) {
|
||||
builder.addRoute(route);
|
||||
}
|
||||
@@ -327,11 +267,12 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
|
||||
SYSTEM_SESSION_ID, "" /* clientPackageName */)
|
||||
.setSystemSession(true);
|
||||
|
||||
MediaRoute2Info selectedRoute = mDeviceRoute;
|
||||
MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute();
|
||||
MediaRoute2Info selectedRoute = deviceRoute;
|
||||
MediaRoute2Info selectedBtRoute = mBtRouteProvider.getSelectedRoute();
|
||||
if (selectedBtRoute != null) {
|
||||
selectedRoute = selectedBtRoute;
|
||||
builder.addTransferableRoute(mDeviceRoute.getId());
|
||||
builder.addTransferableRoute(deviceRoute.getId());
|
||||
}
|
||||
mSelectedRouteId = selectedRoute.getId();
|
||||
mDefaultRoute = new MediaRoute2Info.Builder(DEFAULT_ROUTE_ID, selectedRoute)
|
||||
@@ -423,12 +364,9 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
|
||||
if (mBtRouteProvider.updateVolumeForDevices(devices, volume)) {
|
||||
return;
|
||||
}
|
||||
if (mDeviceVolume != volume) {
|
||||
mDeviceVolume = volume;
|
||||
mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute)
|
||||
.setVolume(volume)
|
||||
.build();
|
||||
}
|
||||
|
||||
mDeviceRouteController.updateVolume(volume);
|
||||
|
||||
publishProviderState();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,371 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.server.media;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.annotation.IdRes;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioRoutesInfo;
|
||||
import android.media.IAudioRoutesObserver;
|
||||
import android.media.MediaRoute2Info;
|
||||
import android.os.RemoteException;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.server.audio.AudioService;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.runners.Enclosed;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
@RunWith(Enclosed.class)
|
||||
public class DeviceRouteControllerTest {
|
||||
|
||||
private static final String DEFAULT_ROUTE_NAME = "default_route";
|
||||
private static final String DEFAULT_HEADPHONES_NAME = "headphone";
|
||||
private static final String DEFAULT_HEADSET_NAME = "headset";
|
||||
private static final String DEFAULT_DOCK_NAME = "dock";
|
||||
private static final String DEFAULT_HDMI_NAME = "hdmi";
|
||||
private static final String DEFAULT_USB_NAME = "usb";
|
||||
private static final int VOLUME_DEFAULT_VALUE = 0;
|
||||
private static final int VOLUME_VALUE_SAMPLE_1 = 10;
|
||||
|
||||
private static AudioRoutesInfo createFakeBluetoothAudioRoute() {
|
||||
AudioRoutesInfo btRouteInfo = new AudioRoutesInfo();
|
||||
btRouteInfo.mainType = AudioRoutesInfo.MAIN_SPEAKER;
|
||||
btRouteInfo.bluetoothName = "bt_device";
|
||||
return btRouteInfo;
|
||||
}
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public static class DefaultDeviceRouteValueTest {
|
||||
@Mock
|
||||
private Context mContext;
|
||||
@Mock
|
||||
private Resources mResources;
|
||||
@Mock
|
||||
private AudioManager mAudioManager;
|
||||
@Mock
|
||||
private AudioService mAudioService;
|
||||
@Mock
|
||||
private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
when(mContext.getResources()).thenReturn(mResources);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialize_noRoutesInfo_defaultRouteIsNotNull() {
|
||||
// Mocking default_audio_route_name.
|
||||
when(mResources.getText(R.string.default_audio_route_name))
|
||||
.thenReturn(DEFAULT_ROUTE_NAME);
|
||||
|
||||
// Default route should be initialized even when AudioService returns null.
|
||||
when(mAudioService.startWatchingRoutes(any())).thenReturn(null);
|
||||
|
||||
DeviceRouteController deviceRouteController = new DeviceRouteController(
|
||||
mContext,
|
||||
mAudioManager,
|
||||
mAudioService,
|
||||
mOnDeviceRouteChangedListener
|
||||
);
|
||||
|
||||
MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute();
|
||||
|
||||
assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
|
||||
assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME))
|
||||
.isTrue();
|
||||
assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialize_bluetoothRouteAvailable_deviceRouteReturnsDefaultRoute() {
|
||||
// Mocking default_audio_route_name.
|
||||
when(mResources.getText(R.string.default_audio_route_name))
|
||||
.thenReturn(DEFAULT_ROUTE_NAME);
|
||||
|
||||
// This route should be ignored.
|
||||
AudioRoutesInfo fakeBluetoothAudioRoute = createFakeBluetoothAudioRoute();
|
||||
when(mAudioService.startWatchingRoutes(any())).thenReturn(fakeBluetoothAudioRoute);
|
||||
|
||||
DeviceRouteController deviceRouteController = new DeviceRouteController(
|
||||
mContext,
|
||||
mAudioManager,
|
||||
mAudioService,
|
||||
mOnDeviceRouteChangedListener
|
||||
);
|
||||
|
||||
MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute();
|
||||
|
||||
assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
|
||||
assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME))
|
||||
.isTrue();
|
||||
assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class DeviceRouteInitializationTest {
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection<Object[]> data() {
|
||||
return Arrays.asList(new Object[][] {
|
||||
{ /* expected res */
|
||||
com.android.internal.R.string.default_audio_route_name_headphones,
|
||||
/* expected name */
|
||||
DEFAULT_HEADPHONES_NAME,
|
||||
/* expected type */
|
||||
MediaRoute2Info.TYPE_WIRED_HEADPHONES,
|
||||
/* actual audio route type */
|
||||
AudioRoutesInfo.MAIN_HEADPHONES },
|
||||
{ /* expected res */
|
||||
com.android.internal.R.string.default_audio_route_name_headphones,
|
||||
/* expected name */
|
||||
DEFAULT_HEADSET_NAME,
|
||||
/* expected type */
|
||||
MediaRoute2Info.TYPE_WIRED_HEADSET,
|
||||
/* actual audio route type */
|
||||
AudioRoutesInfo.MAIN_HEADSET },
|
||||
{ /* expected res */
|
||||
R.string.default_audio_route_name_dock_speakers,
|
||||
/* expected name */
|
||||
DEFAULT_DOCK_NAME,
|
||||
/* expected type */
|
||||
MediaRoute2Info.TYPE_DOCK,
|
||||
/* actual audio route type */
|
||||
AudioRoutesInfo.MAIN_DOCK_SPEAKERS },
|
||||
{ /* expected res */
|
||||
R.string.default_audio_route_name_external_device,
|
||||
/* expected name */
|
||||
DEFAULT_HDMI_NAME,
|
||||
/* expected type */
|
||||
MediaRoute2Info.TYPE_HDMI,
|
||||
/* actual audio route type */
|
||||
AudioRoutesInfo.MAIN_HDMI },
|
||||
{ /* expected res */
|
||||
R.string.default_audio_route_name_usb,
|
||||
/* expected name */
|
||||
DEFAULT_USB_NAME,
|
||||
/* expected type */
|
||||
MediaRoute2Info.TYPE_USB_DEVICE,
|
||||
/* actual audio route type */
|
||||
AudioRoutesInfo.MAIN_USB }
|
||||
});
|
||||
}
|
||||
|
||||
@Mock
|
||||
private Context mContext;
|
||||
@Mock
|
||||
private Resources mResources;
|
||||
@Mock
|
||||
private AudioManager mAudioManager;
|
||||
@Mock
|
||||
private AudioService mAudioService;
|
||||
@Mock
|
||||
private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
|
||||
|
||||
@IdRes
|
||||
private final int mExpectedRouteNameResource;
|
||||
private final String mExpectedRouteNameValue;
|
||||
private final int mExpectedRouteType;
|
||||
private final int mActualAudioRouteType;
|
||||
|
||||
public DeviceRouteInitializationTest(int expectedRouteNameResource,
|
||||
String expectedRouteNameValue,
|
||||
int expectedMediaRouteType,
|
||||
int actualAudioRouteType) {
|
||||
this.mExpectedRouteNameResource = expectedRouteNameResource;
|
||||
this.mExpectedRouteNameValue = expectedRouteNameValue;
|
||||
this.mExpectedRouteType = expectedMediaRouteType;
|
||||
this.mActualAudioRouteType = actualAudioRouteType;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
when(mContext.getResources()).thenReturn(mResources);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialize_wiredRouteAvailable_deviceRouteReturnsWiredRoute() {
|
||||
// Mocking default_audio_route_name.
|
||||
when(mResources.getText(R.string.default_audio_route_name))
|
||||
.thenReturn(DEFAULT_ROUTE_NAME);
|
||||
|
||||
// At first, WiredRouteController should initialize device
|
||||
// route based on AudioService response.
|
||||
AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
|
||||
audioRoutesInfo.mainType = mActualAudioRouteType;
|
||||
when(mAudioService.startWatchingRoutes(any())).thenReturn(audioRoutesInfo);
|
||||
|
||||
when(mResources.getText(mExpectedRouteNameResource))
|
||||
.thenReturn(mExpectedRouteNameValue);
|
||||
|
||||
DeviceRouteController deviceRouteController = new DeviceRouteController(
|
||||
mContext,
|
||||
mAudioManager,
|
||||
mAudioService,
|
||||
mOnDeviceRouteChangedListener
|
||||
);
|
||||
|
||||
MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute();
|
||||
|
||||
assertThat(actualMediaRoute.getType()).isEqualTo(mExpectedRouteType);
|
||||
assertThat(TextUtils.equals(actualMediaRoute.getName(), mExpectedRouteNameValue))
|
||||
.isTrue();
|
||||
// Volume did not change, so it should be set to default value (0).
|
||||
assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public static class VolumeAndDeviceRoutesChangesTest {
|
||||
@Mock
|
||||
private Context mContext;
|
||||
@Mock
|
||||
private Resources mResources;
|
||||
@Mock
|
||||
private AudioManager mAudioManager;
|
||||
@Mock
|
||||
private AudioService mAudioService;
|
||||
@Mock
|
||||
private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<IAudioRoutesObserver.Stub> mAudioRoutesObserverCaptor;
|
||||
|
||||
private DeviceRouteController mDeviceRouteController;
|
||||
private IAudioRoutesObserver.Stub mAudioRoutesObserver;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
when(mContext.getResources()).thenReturn(mResources);
|
||||
|
||||
when(mResources.getText(R.string.default_audio_route_name))
|
||||
.thenReturn(DEFAULT_ROUTE_NAME);
|
||||
|
||||
// Setting built-in speaker as default speaker.
|
||||
AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
|
||||
audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_SPEAKER;
|
||||
when(mAudioService.startWatchingRoutes(mAudioRoutesObserverCaptor.capture()))
|
||||
.thenReturn(audioRoutesInfo);
|
||||
|
||||
mDeviceRouteController = new DeviceRouteController(
|
||||
mContext,
|
||||
mAudioManager,
|
||||
mAudioService,
|
||||
mOnDeviceRouteChangedListener
|
||||
);
|
||||
|
||||
mAudioRoutesObserver = mAudioRoutesObserverCaptor.getValue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void newDeviceConnects_wiredDevice_deviceRouteReturnsWiredDevice() {
|
||||
// Connecting wired headset
|
||||
AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
|
||||
audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
|
||||
|
||||
when(mResources.getText(
|
||||
com.android.internal.R.string.default_audio_route_name_headphones))
|
||||
.thenReturn(DEFAULT_HEADPHONES_NAME);
|
||||
|
||||
// Simulating wired device being connected.
|
||||
callAudioRoutesObserver(audioRoutesInfo);
|
||||
|
||||
MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute();
|
||||
|
||||
assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
|
||||
assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_HEADPHONES_NAME))
|
||||
.isTrue();
|
||||
assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void newDeviceConnects_bluetoothDevice_deviceRouteReturnsBluetoothDevice() {
|
||||
// Simulating bluetooth speaker being connected.
|
||||
AudioRoutesInfo fakeBluetoothAudioRoute = createFakeBluetoothAudioRoute();
|
||||
callAudioRoutesObserver(fakeBluetoothAudioRoute);
|
||||
|
||||
MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute();
|
||||
|
||||
assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
|
||||
assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME))
|
||||
.isTrue();
|
||||
assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateVolume_differentValue_updatesDeviceRouteVolume() {
|
||||
MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute();
|
||||
assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
|
||||
|
||||
assertThat(mDeviceRouteController.updateVolume(VOLUME_VALUE_SAMPLE_1)).isTrue();
|
||||
|
||||
actualMediaRoute = mDeviceRouteController.getDeviceRoute();
|
||||
assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_VALUE_SAMPLE_1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateVolume_sameValue_returnsFalse() {
|
||||
assertThat(mDeviceRouteController.updateVolume(VOLUME_VALUE_SAMPLE_1)).isTrue();
|
||||
assertThat(mDeviceRouteController.updateVolume(VOLUME_VALUE_SAMPLE_1)).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates {@link IAudioRoutesObserver.Stub#dispatchAudioRoutesChanged(AudioRoutesInfo)}
|
||||
* from {@link AudioService}. This happens when there is a wired route change,
|
||||
* like a wired headset being connected.
|
||||
*
|
||||
* @param audioRoutesInfo updated state of connected wired device
|
||||
*/
|
||||
private void callAudioRoutesObserver(AudioRoutesInfo audioRoutesInfo) {
|
||||
try {
|
||||
// this is a captured observer implementation
|
||||
// from WiredRoutesController's AudioService#startWatchingRoutes call
|
||||
mAudioRoutesObserver.dispatchAudioRoutesChanged(audioRoutesInfo);
|
||||
} catch (RemoteException exception) {
|
||||
// Should not happen since the object is mocked.
|
||||
assertWithMessage("An unexpected RemoteException happened.").fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user