From fb48c02d3fb53945c136799fdb8e613db1b21beb Mon Sep 17 00:00:00 2001 From: Jan Sebechlebsky Date: Fri, 16 Dec 2022 16:26:47 +0100 Subject: [PATCH] Delegate playSoundEffect calls to VDM for virtual context When playSound is invoked on AudioManager instance asociated with context belonging to VirtualDevice with custom device policy for audio, this change delegates the request to play sound to VirtualDeviceManager. With just this change, this effectively disables sound effects for virtual devices with custom audio policy, in follow-up changes more options to handle sound effects will be added to VirtualDeviceManager (custom callback vs. device specific SoundPool). Bug: 261698699 Bug: 249777386 Test: atest AudioManagerUnitTest Change-Id: Iba2df745951ed087b8f15a4f210b5b1c7fd936cd --- .../virtual/VirtualDeviceManager.java | 17 +++ media/java/android/media/AudioManager.java | 48 +++++++- .../unit/AudioManagerUnitTest.java | 104 ++++++++++++++++++ 3 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 088ac069dd7a6..856101875b041 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.VirtualNavigationTouchpad; 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; @@ -302,6 +303,22 @@ public final class VirtualDeviceManager { } } + /** + * 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 diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 3ed2c4b6ebc7b..44ecee79f90d4 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.app.compat.CompatChanges; 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 @@ public class AudioManager { 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 @@ public class AudioManager { 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 @@ public class AudioManager { * 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 @@ public class AudioManager { * 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); @@ -3670,6 +3692,28 @@ public class AudioManager { } } + /** + * 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 0000000000000..76543f4ce9890 --- /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; + } +}