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
This commit is contained in:
Jan Sebechlebsky
2022-12-16 16:26:47 +01:00
parent fdc16a549a
commit fb48c02d3f
3 changed files with 167 additions and 2 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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;
}
}