Merge "Create DeviceRouteController interface" into udc-dev

This commit is contained in:
Alex Dadukin
2023-03-06 11:39:56 +00:00
committed by Android (Google) Code Review
3 changed files with 211 additions and 146 deletions

View File

@@ -16,32 +16,12 @@
package com.android.server.media; 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.NonNull;
import android.annotation.Nullable;
import android.content.Context; import android.content.Context;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.AudioRoutesInfo;
import android.media.IAudioRoutesObserver;
import android.media.IAudioService; import android.media.IAudioService;
import android.media.MediaRoute2Info; import android.media.MediaRoute2Info;
import android.os.RemoteException;
import android.os.ServiceManager; 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. * Controls device routes.
@@ -49,145 +29,52 @@ import java.util.Objects;
* <p>A device route is a system wired route, for example, built-in speaker, wired * <p>A device route is a system wired route, for example, built-in speaker, wired
* headsets and headphones, dock, hdmi, or usb devices. * headsets and headphones, dock, hdmi, or usb devices.
* *
* <p>Thread safe.
*
* @see SystemMediaRoute2Provider * @see SystemMediaRoute2Provider
*/ */
/* package */ final class DeviceRouteController { /* package */ interface 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;
/**
* Returns a new instance of {@link DeviceRouteController}.
*/
/* package */ static DeviceRouteController createInstance(@NonNull Context context, /* package */ static DeviceRouteController createInstance(@NonNull Context context,
@NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) { @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
AudioManager audioManager = context.getSystemService(AudioManager.class); AudioManager audioManager = context.getSystemService(AudioManager.class);
IAudioService audioService = IAudioService.Stub.asInterface( IAudioService audioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE)); ServiceManager.getService(Context.AUDIO_SERVICE));
return new DeviceRouteController(context, return new LegacyDeviceRouteController(context,
audioManager, audioManager,
audioService, audioService,
onDeviceRouteChangedListener); onDeviceRouteChangedListener);
} }
@VisibleForTesting /**
/* package */ DeviceRouteController(@NonNull Context context, * Returns currently selected device (built-in or wired) route.
@NonNull AudioManager audioManager, *
@NonNull IAudioService audioService, * @return non-null device route.
@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 @NonNull
/* package */ synchronized MediaRoute2Info getDeviceRoute() { MediaRoute2Info getDeviceRoute();
return mDeviceRoute;
}
/* package */ synchronized boolean updateVolume(int volume) { /**
if (mDeviceVolume == volume) { * Updates device route volume.
return false; *
} * @param volume specifies a volume for the device route or 0 for unknown.
* @return {@code true} if updated successfully and {@code false} otherwise.
*/
boolean updateVolume(int volume);
mDeviceVolume = volume; /**
mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute) * Interface for receiving events when device route has changed.
.setVolume(volume) */
.build(); interface OnDeviceRouteChangedListener {
return true; /**
} * Called when device route has changed.
*
private MediaRoute2Info createRouteFromAudioInfo(@Nullable AudioRoutesInfo newRoutes) { * @param deviceRoute non-null device route.
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); 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);
}
}
} }

View File

@@ -0,0 +1,178 @@
/*
* 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.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 LegacyDeviceRouteController implements 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;
@VisibleForTesting
/* package */ LegacyDeviceRouteController(@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);
}
@Override
@NonNull
public synchronized MediaRoute2Info getDeviceRoute() {
return mDeviceRoute;
}
@Override
public 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);
}
private class AudioRoutesObserver extends IAudioRoutesObserver.Stub {
@Override
public void dispatchAudioRoutesChanged(AudioRoutesInfo newAudioRoutes) {
MediaRoute2Info deviceRoute = createRouteFromAudioInfo(newAudioRoutes);
synchronized (LegacyDeviceRouteController.this) {
mDeviceRoute = deviceRoute;
}
notifyDeviceRouteUpdate(deviceRoute);
}
}
}

View File

@@ -50,7 +50,7 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@RunWith(Enclosed.class) @RunWith(Enclosed.class)
public class DeviceRouteControllerTest { public class LegacyDeviceRouteControllerTest {
private static final String DEFAULT_ROUTE_NAME = "default_route"; private static final String DEFAULT_ROUTE_NAME = "default_route";
private static final String DEFAULT_HEADPHONES_NAME = "headphone"; private static final String DEFAULT_HEADPHONES_NAME = "headphone";
@@ -97,7 +97,7 @@ public class DeviceRouteControllerTest {
// Default route should be initialized even when AudioService returns null. // Default route should be initialized even when AudioService returns null.
when(mAudioService.startWatchingRoutes(any())).thenReturn(null); when(mAudioService.startWatchingRoutes(any())).thenReturn(null);
DeviceRouteController deviceRouteController = new DeviceRouteController( LegacyDeviceRouteController deviceRouteController = new LegacyDeviceRouteController(
mContext, mContext,
mAudioManager, mAudioManager,
mAudioService, mAudioService,
@@ -122,7 +122,7 @@ public class DeviceRouteControllerTest {
AudioRoutesInfo fakeBluetoothAudioRoute = createFakeBluetoothAudioRoute(); AudioRoutesInfo fakeBluetoothAudioRoute = createFakeBluetoothAudioRoute();
when(mAudioService.startWatchingRoutes(any())).thenReturn(fakeBluetoothAudioRoute); when(mAudioService.startWatchingRoutes(any())).thenReturn(fakeBluetoothAudioRoute);
DeviceRouteController deviceRouteController = new DeviceRouteController( LegacyDeviceRouteController deviceRouteController = new LegacyDeviceRouteController(
mContext, mContext,
mAudioManager, mAudioManager,
mAudioService, mAudioService,
@@ -236,7 +236,7 @@ public class DeviceRouteControllerTest {
when(mResources.getText(mExpectedRouteNameResource)) when(mResources.getText(mExpectedRouteNameResource))
.thenReturn(mExpectedRouteNameValue); .thenReturn(mExpectedRouteNameValue);
DeviceRouteController deviceRouteController = new DeviceRouteController( LegacyDeviceRouteController deviceRouteController = new LegacyDeviceRouteController(
mContext, mContext,
mAudioManager, mAudioManager,
mAudioService, mAudioService,
@@ -269,7 +269,7 @@ public class DeviceRouteControllerTest {
@Captor @Captor
private ArgumentCaptor<IAudioRoutesObserver.Stub> mAudioRoutesObserverCaptor; private ArgumentCaptor<IAudioRoutesObserver.Stub> mAudioRoutesObserverCaptor;
private DeviceRouteController mDeviceRouteController; private LegacyDeviceRouteController mDeviceRouteController;
private IAudioRoutesObserver.Stub mAudioRoutesObserver; private IAudioRoutesObserver.Stub mAudioRoutesObserver;
@Before @Before
@@ -287,7 +287,7 @@ public class DeviceRouteControllerTest {
when(mAudioService.startWatchingRoutes(mAudioRoutesObserverCaptor.capture())) when(mAudioService.startWatchingRoutes(mAudioRoutesObserverCaptor.capture()))
.thenReturn(audioRoutesInfo); .thenReturn(audioRoutesInfo);
mDeviceRouteController = new DeviceRouteController( mDeviceRouteController = new LegacyDeviceRouteController(
mContext, mContext,
mAudioManager, mAudioManager,
mAudioService, mAudioService,