Merge "Delegate playSoundEffect calls to VDM for virtual context"
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 088ac06..8561018 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -57,6 +57,7 @@
import android.hardware.input.VirtualNavigationTouchpadConfig;
import android.hardware.input.VirtualTouchscreen;
import android.hardware.input.VirtualTouchscreenConfig;
+import android.media.AudioManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -303,6 +304,22 @@
}
/**
+ * Requests sound effect to be played on virtual device.
+ *
+ * @see android.media.AudioManager#playSoundEffect(int)
+ *
+ * @param deviceId - id of the virtual audio device
+ * @param effectType the type of sound effect
+ * @hide
+ */
+ public void playSoundEffect(int deviceId, @AudioManager.SystemSoundEffect int effectType) {
+ //TODO - handle requests to play sound effects by custom callbacks or SoundPool asociated
+ // with device session id.
+ // For now, this is intentionally left empty and effectively disables sound effects for
+ // virtual devices with custom device audio policy.
+ }
+
+ /**
* A virtual device has its own virtual display, audio output, microphone, and camera etc. The
* creator of a virtual device can take the output from the virtual display and stream it over
* to another device, and inject input events that are received from the remote device.
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 3a55993..fdd6233 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -16,6 +16,10 @@
package android.media;
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -34,6 +38,7 @@
import android.bluetooth.BluetoothCodecConfig;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeAudioCodecConfig;
+import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
@@ -100,6 +105,7 @@
private Context mOriginalContext;
private Context mApplicationContext;
+ private @Nullable VirtualDeviceManager mVirtualDeviceManager; // Lazy initialized.
private long mVolumeKeyUpTime;
private static final String TAG = "AudioManager";
private static final boolean DEBUG = false;
@@ -858,6 +864,14 @@
return sService;
}
+ private VirtualDeviceManager getVirtualDeviceManager() {
+ if (mVirtualDeviceManager != null) {
+ return mVirtualDeviceManager;
+ }
+ mVirtualDeviceManager = getContext().getSystemService(VirtualDeviceManager.class);
+ return mVirtualDeviceManager;
+ }
+
/**
* Sends a simulated key event for a media button.
* To simulate a key press, you must first send a KeyEvent built with a
@@ -3635,11 +3649,15 @@
* whether sounds are heard or not.
* @hide
*/
- public void playSoundEffect(@SystemSoundEffect int effectType, int userId) {
+ public void playSoundEffect(@SystemSoundEffect int effectType, int userId) {
if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
return;
}
+ if (delegateSoundEffectToVdm(effectType)) {
+ return;
+ }
+
final IAudioService service = getService();
try {
service.playSoundEffect(effectType, userId);
@@ -3657,11 +3675,15 @@
* NOTE: This version is for applications that have their own
* settings panel for enabling and controlling volume.
*/
- public void playSoundEffect(@SystemSoundEffect int effectType, float volume) {
+ public void playSoundEffect(@SystemSoundEffect int effectType, float volume) {
if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
return;
}
+ if (delegateSoundEffectToVdm(effectType)) {
+ return;
+ }
+
final IAudioService service = getService();
try {
service.playSoundEffectVolume(effectType, volume);
@@ -3671,6 +3693,28 @@
}
/**
+ * Checks whether this {@link AudioManager} instance is asociated with {@link VirtualDevice}
+ * configured with custom device policy for audio. If there is such device, request to play
+ * sound effect is forwarded to {@link VirtualDeviceManager}.
+ *
+ * @param effectType - The type of sound effect.
+ * @return true if the request was forwarded to {@link VirtualDeviceManager} instance,
+ * false otherwise.
+ */
+ private boolean delegateSoundEffectToVdm(@SystemSoundEffect int effectType) {
+ int deviceId = getContext().getDeviceId();
+ if (deviceId != DEVICE_ID_DEFAULT) {
+ VirtualDeviceManager vdm = getVirtualDeviceManager();
+ if (vdm != null && vdm.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)
+ != DEVICE_POLICY_DEFAULT) {
+ vdm.playSoundEffect(deviceId, effectType);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Load Sound effects.
* This method must be called when sound effects are enabled.
*/
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java
new file mode 100644
index 0000000..76543f4
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 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.mediaframeworktest.unit;
+
+
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.media.AudioManager.FX_KEY_CLICK;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.companion.virtual.VirtualDeviceManager;
+import android.content.Context;
+import android.media.AudioManager;
+import android.test.mock.MockContext;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AudioManagerUnitTest {
+ private static final int TEST_VIRTUAL_DEVICE_ID = 42;
+
+ @Test
+ public void testAudioManager_playSoundWithDefaultDeviceContext() {
+ VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+ DEVICE_POLICY_CUSTOM);
+ Context defaultDeviceContext = getVirtualDeviceMockContext(DEVICE_ID_DEFAULT, /*vdm=*/
+ mockVdm);
+ AudioManager audioManager = new AudioManager(defaultDeviceContext);
+
+ audioManager.playSoundEffect(FX_KEY_CLICK);
+
+ // We expect no interactions with VDM when running on default device.
+ verifyZeroInteractions(mockVdm);
+ }
+
+ @Test
+ public void testAudioManager_playSoundWithVirtualDeviceContextDefaultPolicy() {
+ VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+ DEVICE_POLICY_DEFAULT);
+ Context defaultDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, /*vdm=*/
+ mockVdm);
+ AudioManager audioManager = new AudioManager(defaultDeviceContext);
+
+ audioManager.playSoundEffect(FX_KEY_CLICK);
+
+ // We expect playback not to be delegated to VDM because of default device policy for audio.
+ verify(mockVdm, never()).playSoundEffect(anyInt(), anyInt());
+ }
+
+ @Test
+ public void testAudioManager_playSoundWithVirtualDeviceContextCustomPolicy() {
+ VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+ DEVICE_POLICY_CUSTOM);
+ Context defaultDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, /*vdm=*/
+ mockVdm);
+ AudioManager audioManager = new AudioManager(defaultDeviceContext);
+
+ audioManager.playSoundEffect(FX_KEY_CLICK);
+
+ // We expect playback to be delegated to VDM because of custom device policy for audio.
+ verify(mockVdm, times(1)).playSoundEffect(TEST_VIRTUAL_DEVICE_ID, FX_KEY_CLICK);
+ }
+
+ private static Context getVirtualDeviceMockContext(int deviceId, VirtualDeviceManager vdm) {
+ MockContext mockContext = mock(MockContext.class);
+ when(mockContext.getDeviceId()).thenReturn(deviceId);
+ when(mockContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
+ return mockContext;
+ }
+
+ private static VirtualDeviceManager getMockVirtualDeviceManager(
+ int deviceId, int audioDevicePolicy) {
+ VirtualDeviceManager vdmMock = mock(VirtualDeviceManager.class);
+ when(vdmMock.getDevicePolicy(anyInt(), anyInt())).thenReturn(DEVICE_POLICY_DEFAULT);
+ when(vdmMock.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)).thenReturn(audioDevicePolicy);
+ return vdmMock;
+ }
+}