diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java index 961313de0c978..182aa6fcef028 100644 --- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java +++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java @@ -62,7 +62,11 @@ import java.util.Objects; private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver(); private int mDeviceVolume; + + @NonNull private MediaRoute2Info mDeviceRoute; + @Nullable + private MediaRoute2Info mSelectedRoute; @VisibleForTesting /* package */ AudioPoliciesDeviceRouteController(@NonNull Context context, @@ -91,14 +95,26 @@ import java.util.Objects; } @Override - public boolean selectRoute(@Nullable Integer type) { - // No-op as the controller does not support selection from the outside of the class. - return false; + public synchronized boolean selectRoute(@Nullable Integer type) { + if (type == null) { + mSelectedRoute = null; + return true; + } + + if (!isDeviceRouteType(type)) { + return false; + } + + mSelectedRoute = createRouteFromAudioInfo(type); + return true; } @Override @NonNull public synchronized MediaRoute2Info getDeviceRoute() { + if (mSelectedRoute != null) { + return mSelectedRoute; + } return mDeviceRoute; } @@ -109,6 +125,13 @@ import java.util.Objects; } mDeviceVolume = volume; + + if (mSelectedRoute != null) { + mSelectedRoute = new MediaRoute2Info.Builder(mSelectedRoute) + .setVolume(volume) + .build(); + } + mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute) .setVolume(volume) .build(); @@ -116,29 +139,47 @@ import java.util.Objects; return true; } + @NonNull 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 = R.string.default_audio_route_name_headphones; } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) { type = TYPE_WIRED_HEADSET; - name = R.string.default_audio_route_name_headphones; } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) { type = TYPE_DOCK; - name = R.string.default_audio_route_name_dock_speakers; } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) { type = TYPE_HDMI; - name = R.string.default_audio_route_name_external_device; } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) { type = TYPE_USB_DEVICE; - name = R.string.default_audio_route_name_usb; } } + return createRouteFromAudioInfo(type); + } + + @NonNull + private MediaRoute2Info createRouteFromAudioInfo(@MediaRoute2Info.Type int type) { + int name = R.string.default_audio_route_name; + + switch (type) { + case TYPE_WIRED_HEADPHONES: + case TYPE_WIRED_HEADSET: + name = R.string.default_audio_route_name_headphones; + break; + case TYPE_DOCK: + name = R.string.default_audio_route_name_dock_speakers; + break; + case TYPE_HDMI: + name = R.string.default_audio_route_name_external_device; + break; + case TYPE_USB_DEVICE: + name = R.string.default_audio_route_name_usb; + break; + } + synchronized (this) { return new MediaRoute2Info.Builder( DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString()) @@ -156,19 +197,43 @@ import java.util.Objects; } } - private void notifyDeviceRouteUpdate(@NonNull MediaRoute2Info deviceRoute) { - mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute); + /** + * Checks if the given type is a device route. + * + *
Device route means a route which is either built-in or wired to the current device.
+ *
+ * @param type specifies the type of the device.
+ * @return {@code true} if the device is wired or built-in and {@code false} otherwise.
+ */
+ private boolean isDeviceRouteType(@MediaRoute2Info.Type int type) {
+ switch (type) {
+ case TYPE_BUILTIN_SPEAKER:
+ case TYPE_WIRED_HEADPHONES:
+ case TYPE_WIRED_HEADSET:
+ case TYPE_DOCK:
+ case TYPE_HDMI:
+ case TYPE_USB_DEVICE:
+ return true;
+ default:
+ return false;
+ }
}
private class AudioRoutesObserver extends IAudioRoutesObserver.Stub {
@Override
public void dispatchAudioRoutesChanged(AudioRoutesInfo newAudioRoutes) {
+ boolean isDeviceRouteChanged;
MediaRoute2Info deviceRoute = createRouteFromAudioInfo(newAudioRoutes);
+
synchronized (AudioPoliciesDeviceRouteController.this) {
mDeviceRoute = deviceRoute;
+ isDeviceRouteChanged = mSelectedRoute == null;
+ }
+
+ if (isDeviceRouteChanged) {
+ mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute);
}
- notifyDeviceRouteUpdate(deviceRoute);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
new file mode 100644
index 0000000000000..1e73a45a0c229
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.Mockito.anyInt;
+import static org.mockito.Mockito.when;
+
+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 com.android.internal.R;
+import com.android.server.audio.AudioService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class AudioPoliciesDeviceRouteControllerTest {
+
+ private static final String ROUTE_NAME_DEFAULT = "default";
+ private static final String ROUTE_NAME_DOCK = "dock";
+ private static final String ROUTE_NAME_HEADPHONES = "headphones";
+
+ private static final int VOLUME_SAMPLE_1 = 25;
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private AudioManager mAudioManager;
+ @Mock
+ private AudioService mAudioService;
+ @Mock
+ private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
+
+ @Captor
+ private ArgumentCaptor