Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fb48c02d authored by Jan Sebechlebsky's avatar Jan Sebechlebsky
Browse files

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
parent fdc16a54
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -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
+46 −2
Original line number Diff line number Diff line
@@ -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
@@ -3640,6 +3654,10 @@ public class AudioManager {
            return;
        }

        if (delegateSoundEffectToVdm(effectType)) {
            return;
        }

        final IAudioService service = getService();
        try {
            service.playSoundEffect(effectType, userId);
@@ -3662,6 +3680,10 @@ public class AudioManager {
            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.
+104 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}