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

Commit 14ca04c3 authored by Jan Sebechlebsky's avatar Jan Sebechlebsky Committed by Ján Sebechlebský
Browse files

Configure session ids of AudioRecord for device-specific context.

Bug: 261698699
Bug: 249777386
Test: atest AudioRecordUnitTest AudioRecordTest
Change-Id: I1361d83e902e74a9472a8ff5fe75e01b8430f00c
parent 2d20d1c9
Loading
Loading
Loading
Loading
+53 −5
Original line number Diff line number Diff line
@@ -16,6 +16,11 @@

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 static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;

import android.annotation.CallbackExecutor;
import android.annotation.FloatRange;
import android.annotation.IntDef;
@@ -26,6 +31,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ActivityThread;
import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.AttributionSource;
import android.content.AttributionSource.ScopedParcelState;
@@ -455,7 +461,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,

        int[] sampleRate = new int[] {mSampleRate};
        int[] session = new int[1];
        session[0] = sessionId;
        session[0] = resolveSessionId(context, sessionId);

        //TODO: update native initialization when information about hardware init failure
        //      due to capture device already open is available.
@@ -624,15 +630,15 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,

        /**
         * Sets the context the record belongs to. This context will be used to pull information,
         * such as {@link android.content.AttributionSource}, which will be associated with
         * the AudioRecord. However, the context itself will not be retained by the AudioRecord.
         * such as {@link android.content.AttributionSource} and device specific session ids,
         * which will be associated with the {@link AudioRecord} the AudioRecord.
         * However, the context itself will not be retained by the AudioRecord.
         * @param context a non-null {@link Context} instance
         * @return the same Builder instance.
         */
        public @NonNull Builder setContext(@NonNull Context context) {
            Objects.requireNonNull(context);
            // keep reference, we only copy the data when building
            mContext = context;
            mContext = Objects.requireNonNull(context);
            return this;
        }

@@ -746,6 +752,9 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
        /**
         * @hide
         * To be only used by system components.
         *
         * Note, that if there's a device specific session id asociated with the context, explicitly
         * setting a session id using this method will override it.
         * @param sessionId ID of audio session the AudioRecord must be attached to, or
         *     {@link AudioManager#AUDIO_SESSION_ID_GENERATE} if the session isn't known at
         *     construction time.
@@ -983,6 +992,45 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
        }
    }

    /**
     * Helper method to resolve session id to be used for AudioRecord initialization.
     *
     * This method will assign session id in following way:
     * 1. Explicitly requested session id has the highest priority, if there is one,
     *    it will be used.
     * 2. If there's device-specific session id asociated with the provided context,
     *    it will be used.
     * 3. Otherwise {@link AUDIO_SESSION_ID_GENERATE} is returned.
     *
     * @param context {@link Context} to use for extraction of device specific session id.
     * @param requestedSessionId explicitly requested session id or AUDIO_SESSION_ID_GENERATE.
     * @return session id to be passed to AudioService for the {@link AudioRecord} instance given
     *   provided {@link Context} instance and explicitly requested session id.
     */
    private static int resolveSessionId(@Nullable Context context, int requestedSessionId) {
        if (requestedSessionId != AUDIO_SESSION_ID_GENERATE) {
            // Use explicitly requested session id.
            return requestedSessionId;
        }

        if (context == null) {
            return AUDIO_SESSION_ID_GENERATE;
        }

        int deviceId = context.getDeviceId();
        if (deviceId == DEVICE_ID_DEFAULT) {
            return AUDIO_SESSION_ID_GENERATE;
        }

        VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
        if (vdm == null || vdm.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)
                == DEVICE_POLICY_DEFAULT) {
            return AUDIO_SESSION_ID_GENERATE;
        }

        return vdm.getAudioRecordingSessionId(deviceId);
    }

    // Convenience method for the constructor's parameter checks.
    // This, getChannelMaskFromLegacyConfig and audioBuffSizeCheck are where constructor
    // IllegalArgumentException-s are thrown
+149 −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.AUDIO_SESSION_ID_GENERATE;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import android.companion.virtual.VirtualDeviceManager;
import android.content.Context;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.test.mock.MockContext;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;

import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
public class AudioRecordUnitTest {
    private static final int TEST_SAMPLE_RATE = 44000;
    private static final int TEST_VIRTUAL_DEVICE_ID = 42;
    private static final AudioFormat TEST_AUDIO_FORMAT = new AudioFormat.Builder().setSampleRate(
            TEST_SAMPLE_RATE).setEncoding(AudioFormat.ENCODING_PCM_16BIT).setChannelMask(
            AudioFormat.CHANNEL_IN_MONO).build();
    private static final int TEST_BUFFER_SIZE = AudioRecord.getMinBufferSize(
            TEST_AUDIO_FORMAT.getSampleRate(),
            TEST_AUDIO_FORMAT.getChannelMask(), TEST_AUDIO_FORMAT.getEncoding());

    @Test
    public void testBuilderConstructionWithContext_defaultDeviceExplicitSessionId() {
        Context mockDefaultDeviceContext = getVirtualDeviceMockContext(
                DEVICE_ID_DEFAULT, /*vdm=*/null);
        int sessionId = getContext().getSystemService(AudioManager.class).generateAudioSessionId();

        AudioRecord audioRecord = new AudioRecord.Builder().setContext(
                mockDefaultDeviceContext).setAudioSource(
                MediaRecorder.AudioSource.DEFAULT).setAudioFormat(
                TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).setSessionId(
                sessionId).build();

        assertEquals(AudioRecord.STATE_INITIALIZED, audioRecord.getState());
        assertEquals(sessionId, audioRecord.getAudioSessionId());
    }

    @Test
    public void testBuilderConstructionWithContext_virtualDeviceDefaultAudioPolicy() {
        int vdmPlaybackSessionId = getContext().getSystemService(
                AudioManager.class).generateAudioSessionId();
        VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
                vdmPlaybackSessionId, DEVICE_POLICY_DEFAULT);
        Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);

        AudioRecord audioRecord = new AudioRecord.Builder().setContext(
                virtualDeviceContext).setAudioSource(
                MediaRecorder.AudioSource.DEFAULT).setAudioFormat(
                TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).build();

        assertEquals(AudioRecord.STATE_INITIALIZED, audioRecord.getState());
        assertNotEquals(vdmPlaybackSessionId, audioRecord.getAudioSessionId());
    }

    @Test
    public void testBuilderConstructionWithContext_virtualDeviceCustomAudioPolicy() {
        int vdmRecordingSessionId = getContext().getSystemService(
                AudioManager.class).generateAudioSessionId();
        VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
                vdmRecordingSessionId, DEVICE_POLICY_CUSTOM);
        Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);

        AudioRecord audioRecord = new AudioRecord.Builder().setContext(
                virtualDeviceContext).setAudioSource(
                MediaRecorder.AudioSource.DEFAULT).setAudioFormat(
                TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).build();

        assertEquals(AudioRecord.STATE_INITIALIZED, audioRecord.getState());
        assertEquals(vdmRecordingSessionId, audioRecord.getAudioSessionId());
    }

    @Test
    public void testBuilderConstructionWithContext_virtualDeviceSetSessionIdOverridesContext() {
        int vdmRecordingSessionId = getContext().getSystemService(
                AudioManager.class).generateAudioSessionId();
        int anotherSessionId = getContext().getSystemService(
                AudioManager.class).generateAudioSessionId();
        VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
                vdmRecordingSessionId, DEVICE_POLICY_CUSTOM);
        Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);

        AudioRecord audioRecord =  new AudioRecord.Builder().setContext(
                        virtualDeviceContext).setAudioSource(
                        MediaRecorder.AudioSource.DEFAULT).setAudioSource(
                        MediaRecorder.AudioSource.DEFAULT).setBufferSizeInBytes(
                        TEST_BUFFER_SIZE).setSessionId(
                        anotherSessionId).build();

        assertEquals(AudioRecord.STATE_INITIALIZED, audioRecord.getState());
        assertEquals(anotherSessionId, audioRecord.getAudioSessionId());
    }

    private Context getContext() {
        return InstrumentationRegistry.getInstrumentation().getContext();
    }

    private Context getVirtualDeviceMockContext(int deviceId, VirtualDeviceManager vdm) {
        MockContext mockContext = mock(MockContext.class);
        when(mockContext.getDeviceId()).thenReturn(deviceId);
        when(mockContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
        when(mockContext.getAttributionSource()).thenReturn(getContext().getAttributionSource());
        return mockContext;
    }

    private static VirtualDeviceManager getMockVirtualDeviceManager(int deviceId,
            int recordingSessionId, int audioDevicePolicy) {
        VirtualDeviceManager vdmMock = mock(VirtualDeviceManager.class);
        when(vdmMock.getAudioRecordingSessionId(anyInt())).thenReturn(AUDIO_SESSION_ID_GENERATE);
        when(vdmMock.getAudioRecordingSessionId(deviceId)).thenReturn(recordingSessionId);
        when(vdmMock.getDevicePolicy(anyInt(), anyInt())).thenReturn(DEVICE_POLICY_DEFAULT);
        when(vdmMock.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)).thenReturn(audioDevicePolicy);
        return vdmMock;
    }

}