From 14ca04c3611b4ae123992aee61b59e77f25222f8 Mon Sep 17 00:00:00 2001 From: Jan Sebechlebsky Date: Wed, 14 Dec 2022 14:28:09 +0100 Subject: [PATCH] Configure session ids of AudioRecord for device-specific context. Bug: 261698699 Bug: 249777386 Test: atest AudioRecordUnitTest AudioRecordTest Change-Id: I1361d83e902e74a9472a8ff5fe75e01b8430f00c --- media/java/android/media/AudioRecord.java | 58 ++++++- .../unit/AudioRecordUnitTest.java | 149 ++++++++++++++++++ 2 files changed, 202 insertions(+), 5 deletions(-) create mode 100644 media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 8be1bcdc94795..6e3829a040a57 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -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 diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java new file mode 100644 index 0000000000000..9c813c28d7d9b --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java @@ -0,0 +1,149 @@ +/* + * 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; + } + +}