diff options
| author | 2024-01-31 18:35:50 -0800 | |
|---|---|---|
| committer | 2024-02-12 11:22:17 -0800 | |
| commit | 2b259700c0bc69681ef56e90a436cda921047a1c (patch) | |
| tree | 6b7a29294f3cbcff87f6bc8426149bb46e0d5372 | |
| parent | cd452abe41ba3f29c24834ed1498fbc50a64ee6e (diff) | |
VolRef: add initial UT to cover volume APIs
Trying to improve the code coverage by covering all the test related
APIs in AudioService. Currently only trying to obtain a full method
coverage. More tests to come that will cover more branches and more
volume requirements.
Copied test methods from AudioDeviceManagerTest and
DeviceVolumeBehaviorTest into VolumeHelperTest in order to have a
central class that manages all the volume related API tests.
Test: build with EMMA_INSTRUMENT=true EMMA_INSTRUMENT_FRAMEWORK=true SKIP_BOOT_JARS_CHECK=true WITH_DEXPREOPT=false, atest VolumeHelperTest --experimental-coverage
Bug: 323273677
Change-Id: I54b243979b71c03f145808688bb36c702da621e4
9 files changed, 692 insertions, 29 deletions
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 04deb0271c06..329e388db3d4 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -963,6 +963,8 @@ public class AudioService extends IAudioService.Stub private final HardeningEnforcer mHardeningEnforcer; + private final AudioVolumeGroupHelperBase mAudioVolumeGroupHelper; + private final Object mSupportedSystemUsagesLock = new Object(); @GuardedBy("mSupportedSystemUsagesLock") private @AttributeSystemUsage int[] mSupportedSystemUsages = @@ -973,6 +975,13 @@ public class AudioService extends IAudioService.Stub return "card=" + card + ";device=" + device; } + private static class AudioVolumeGroupHelper extends AudioVolumeGroupHelperBase { + @Override + public List<AudioVolumeGroup> getAudioVolumeGroups() { + return AudioVolumeGroup.getAudioVolumeGroups(); + } + } + public static final class Lifecycle extends SystemService { private AudioService mService; @@ -982,6 +991,7 @@ public class AudioService extends IAudioService.Stub AudioSystemAdapter.getDefaultAdapter(), SystemServerAdapter.getDefaultAdapter(context), SettingsAdapter.getDefaultAdapter(), + new AudioVolumeGroupHelper(), new DefaultAudioPolicyFacade(), null); @@ -1061,16 +1071,19 @@ public class AudioService extends IAudioService.Stub /** * @param context * @param audioSystem Adapter for {@link AudioSystem} - * @param systemServer Adapter for privilieged functionality for system server components + * @param systemServer Adapter for privileged functionality for system server components * @param settings Adapter for {@link Settings} + * @param audioVolumeGroupHelper Adapter for {@link AudioVolumeGroup} + * @param audioPolicy Interface of a facade to IAudioPolicyManager * @param looper Looper to use for the service's message handler. If this is null, an * {@link AudioSystemThread} is created as the messaging thread instead. */ public AudioService(Context context, AudioSystemAdapter audioSystem, SystemServerAdapter systemServer, SettingsAdapter settings, - AudioPolicyFacade audioPolicy, @Nullable Looper looper) { - this (context, audioSystem, systemServer, settings, audioPolicy, looper, - context.getSystemService(AppOpsManager.class), + AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy, + @Nullable Looper looper) { + this (context, audioSystem, systemServer, settings, audioVolumeGroupHelper, + audioPolicy, looper, context.getSystemService(AppOpsManager.class), PermissionEnforcer.fromContext(context)); } @@ -1079,14 +1092,18 @@ public class AudioService extends IAudioService.Stub * @param audioSystem Adapter for {@link AudioSystem} * @param systemServer Adapter for privilieged functionality for system server components * @param settings Adapter for {@link Settings} + * @param audioVolumeGroupHelper Adapter for {@link AudioVolumeGroup} + * @param audioPolicy Interface of a facade to IAudioPolicyManager * @param looper Looper to use for the service's message handler. If this is null, an * {@link AudioSystemThread} is created as the messaging thread instead. + * @param appOps {@link AppOpsManager} system service + * @param enforcer Used for permission enforcing */ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) public AudioService(Context context, AudioSystemAdapter audioSystem, SystemServerAdapter systemServer, SettingsAdapter settings, - AudioPolicyFacade audioPolicy, @Nullable Looper looper, AppOpsManager appOps, - @NonNull PermissionEnforcer enforcer) { + AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy, + @Nullable Looper looper, AppOpsManager appOps, @NonNull PermissionEnforcer enforcer) { super(enforcer); sLifecycleLogger.enqueue(new EventLogger.StringEvent("AudioService()")); mContext = context; @@ -1095,6 +1112,7 @@ public class AudioService extends IAudioService.Stub mAudioSystem = audioSystem; mSystemServer = systemServer; + mAudioVolumeGroupHelper = audioVolumeGroupHelper; mSettings = settings; mAudioPolicy = audioPolicy; mPlatformType = AudioSystem.getPlatformType(context); @@ -2102,7 +2120,7 @@ public class AudioService extends IAudioService.Stub // verify permissions super.getAudioVolumeGroups_enforcePermission(); - return AudioVolumeGroup.getAudioVolumeGroups(); + return mAudioVolumeGroupHelper.getAudioVolumeGroups(); } private void checkAllAliasStreamVolumes() { @@ -3801,7 +3819,7 @@ public class AudioService extends IAudioService.Stub } /** - * Loops on aliasted stream, update the mute cache attribute of each + * Loops on aliased stream, update the mute cache attribute of each * {@see AudioService#VolumeStreamState}, and then apply the change. * It prevents to unnecessary {@see AudioSystem#setStreamVolume} done for each stream * and aliases before mute change changed and after. @@ -4038,18 +4056,6 @@ public class AudioService extends IAudioService.Stub } } - @Nullable - private AudioVolumeGroup getAudioVolumeGroupById(int volumeGroupId) { - for (AudioVolumeGroup avg : AudioVolumeGroup.getAudioVolumeGroups()) { - if (avg.getId() == volumeGroupId) { - return avg; - } - } - - Log.e(TAG, ": invalid volume group id: " + volumeGroupId + " requested"); - return null; - } - @Override @android.annotation.EnforcePermission(anyOf = { MODIFY_AUDIO_SETTINGS_PRIVILEGED, @@ -8240,7 +8246,7 @@ public class AudioService extends IAudioService.Stub index = 1; } // Set the volume index - AudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device); + mAudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device); } @GuardedBy("AudioService.VolumeStreamState.class") diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index 49ab19a816dc..7202fa286453 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -551,6 +551,11 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, return AudioSystem.setStreamVolumeIndexAS(stream, index, device); } + /** Same as {@link AudioSystem#setVolumeIndexForAttributes(AudioAttributes, int, int)} */ + public int setVolumeIndexForAttributes(AudioAttributes attributes, int index, int device) { + return AudioSystem.setVolumeIndexForAttributes(attributes, index, device); + } + /** * Same as {@link AudioSystem#setPhoneState(int, int)} * @param state diff --git a/services/core/java/com/android/server/audio/AudioVolumeGroupHelperBase.java b/services/core/java/com/android/server/audio/AudioVolumeGroupHelperBase.java new file mode 100644 index 000000000000..6f4de5bc5f81 --- /dev/null +++ b/services/core/java/com/android/server/audio/AudioVolumeGroupHelperBase.java @@ -0,0 +1,34 @@ +/* + * Copyright 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.server.audio; + +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; + +import android.media.audiopolicy.AudioVolumeGroup; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.List; + +/** Abstract class for {@link AudioVolumeGroup} related helper methods. */ +@VisibleForTesting(visibility = PACKAGE) +public class AudioVolumeGroupHelperBase { + public List<AudioVolumeGroup> getAudioVolumeGroups() { + return new ArrayList<>(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java index fc5819de861f..e756082bc912 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java @@ -69,6 +69,7 @@ public class AbsoluteVolumeBehaviorTest { private AudioSystemAdapter mSpyAudioSystem; private SystemServerAdapter mSystemServer; private SettingsAdapter mSettingsAdapter; + private AudioVolumeGroupHelperBase mAudioVolumeGroupHelper; private TestLooper mTestLooper; private AudioService mAudioService; @@ -93,9 +94,11 @@ public class AbsoluteVolumeBehaviorTest { mSpyAudioSystem = spy(new NoOpAudioSystemAdapter()); mSystemServer = new NoOpSystemServerAdapter(); mSettingsAdapter = new NoOpSettingsAdapter(); + mAudioVolumeGroupHelper = new AudioVolumeGroupHelperBase(); mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer, - mSettingsAdapter, mMockAudioPolicy, mTestLooper.getLooper()) { + mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, + mTestLooper.getLooper()) { @Override public int getDeviceForStream(int stream) { return AudioSystem.DEVICE_OUT_SPEAKER; diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java index d4d312894053..3623012b348f 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java @@ -56,6 +56,7 @@ public class AudioDeviceVolumeManagerTest { private AudioSystemAdapter mSpyAudioSystem; private SystemServerAdapter mSystemServer; private SettingsAdapter mSettingsAdapter; + private AudioVolumeGroupHelperBase mAudioVolumeGroupHelper; private TestLooper mTestLooper; private AudioPolicyFacade mAudioPolicyMock = mock(AudioPolicyFacade.class); @@ -71,8 +72,10 @@ public class AudioDeviceVolumeManagerTest { mSystemServer = new NoOpSystemServerAdapter(); mSettingsAdapter = new NoOpSettingsAdapter(); + mAudioVolumeGroupHelper = new AudioVolumeGroupHelperBase(); mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer, - mSettingsAdapter, mAudioPolicyMock, mTestLooper.getLooper()) { + mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock, + mTestLooper.getLooper()) { @Override public int getDeviceForStream(int stream) { return AudioSystem.DEVICE_OUT_SPEAKER; @@ -82,8 +85,9 @@ public class AudioDeviceVolumeManagerTest { mTestLooper.dispatchAll(); } + // ------------ AudioDeviceVolumeManager related tests ------------ @Test - public void testSetDeviceVolume() { + public void setDeviceVolume_checkIndex() { AudioManager am = mContext.getSystemService(AudioManager.class); final int minIndex = am.getStreamMinVolume(AudioManager.STREAM_MUSIC); final int maxIndex = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); @@ -110,7 +114,7 @@ public class AudioDeviceVolumeManagerTest { @Test @RequiresFlagsDisabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME) - public void testConfigurablePreScaleAbsoluteVolume() throws Exception { + public void configurablePreScaleAbsoluteVolume_checkIndex() throws Exception { AudioManager am = mContext.getSystemService(AudioManager.class); final int minIndex = am.getStreamMinVolume(AudioManager.STREAM_MUSIC); final int maxIndex = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); @@ -159,7 +163,7 @@ public class AudioDeviceVolumeManagerTest { @Test @RequiresFlagsEnabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME) - public void testDisablePreScaleAbsoluteVolume() throws Exception { + public void disablePreScaleAbsoluteVolume_checkIndex() throws Exception { AudioManager am = mContext.getSystemService(AudioManager.class); final int minIndex = am.getStreamMinVolume(AudioManager.STREAM_MUSIC); final int maxIndex = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java index e565faa1c00b..634877eb2539 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java @@ -60,6 +60,7 @@ public class AudioServiceTest { private Context mContext; private AudioSystemAdapter mSpyAudioSystem; private SettingsAdapter mSettingsAdapter; + private AudioVolumeGroupHelperBase mAudioVolumeGroupHelper; @Spy private NoOpSystemServerAdapter mSpySystemServer; @Mock private AppOpsManager mMockAppOpsManager; @@ -80,11 +81,12 @@ public class AudioServiceTest { mContext = InstrumentationRegistry.getTargetContext(); mSpyAudioSystem = spy(new NoOpAudioSystemAdapter()); mSettingsAdapter = new NoOpSettingsAdapter(); + mAudioVolumeGroupHelper = new AudioVolumeGroupHelperBase(); when(mMockAppOpsManager.noteOp(anyInt(), anyInt(), anyString(), anyString(), anyString())) .thenReturn(AppOpsManager.MODE_ALLOWED); mAudioService = new AudioService(mContext, mSpyAudioSystem, mSpySystemServer, - mSettingsAdapter, mMockAudioPolicy, null, mMockAppOpsManager, - mMockPermissionEnforcer); + mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, null, + mMockAppOpsManager, mMockPermissionEnforcer); } /** diff --git a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java index f5862acb2811..8dfcc1843fed 100644 --- a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java @@ -50,6 +50,7 @@ public class DeviceVolumeBehaviorTest { private AudioSystemAdapter mAudioSystem; private SystemServerAdapter mSystemServer; private SettingsAdapter mSettingsAdapter; + private AudioVolumeGroupHelperBase mAudioVolumeGroupHelper; private TestLooper mTestLooper; private AudioPolicyFacade mAudioPolicyMock = mock(AudioPolicyFacade.class); @@ -71,8 +72,10 @@ public class DeviceVolumeBehaviorTest { mAudioSystem = new NoOpAudioSystemAdapter(); mSystemServer = new NoOpSystemServerAdapter(); mSettingsAdapter = new NoOpSettingsAdapter(); + mAudioVolumeGroupHelper = new AudioVolumeGroupHelperBase(); mAudioService = new AudioService(mContext, mAudioSystem, mSystemServer, - mSettingsAdapter, mAudioPolicyMock, mTestLooper.getLooper()); + mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock, + mTestLooper.getLooper()); mTestLooper.dispatchAll(); } diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java index 0eac718c2f14..96ac5d251ffd 100644 --- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java +++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java @@ -137,6 +137,11 @@ public class NoOpAudioSystemAdapter extends AudioSystemAdapter { } @Override + public int setVolumeIndexForAttributes(AudioAttributes attributes, int index, int device) { + return AudioSystem.AUDIO_STATUS_OK; + } + + @Override @NonNull public ArrayList<AudioDeviceAttributes> getDevicesForAttributes( @NonNull AudioAttributes attributes, boolean forVolume) { diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java new file mode 100644 index 000000000000..4b9f8b73df86 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java @@ -0,0 +1,601 @@ +/* + * Copyright 2024 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.server.audio; + +import static android.media.AudioManager.ADJUST_LOWER; +import static android.media.AudioManager.DEVICE_OUT_DEFAULT; +import static android.media.AudioManager.DEVICE_OUT_SPEAKER; +import static android.media.AudioManager.DEVICE_OUT_USB_DEVICE; +import static android.media.AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET; +import static android.media.AudioManager.STREAM_MUSIC; +import static android.media.AudioManager.STREAM_NOTIFICATION; +import static android.media.AudioManager.STREAM_VOICE_CALL; +import static android.view.KeyEvent.ACTION_DOWN; +import static android.view.KeyEvent.KEYCODE_VOLUME_UP; + +import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.content.Context; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; +import android.media.AudioManager; +import android.media.AudioSystem; +import android.media.IDeviceVolumeBehaviorDispatcher; +import android.media.VolumeInfo; +import android.media.audiopolicy.AudioVolumeGroup; +import android.os.Looper; +import android.os.PermissionEnforcer; +import android.os.test.TestLooper; +import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.util.SparseIntArray; +import android.view.KeyEvent; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@Presubmit +public class VolumeHelperTest { + private static final Integer VOLUME_CHANGE_TIMEOUT_MS = 500; + + private static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, ""); + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + + private MyAudioService mAudioService; + + private Context mContext; + + private AudioSystemAdapter mSpyAudioSystem; + private SettingsAdapter mSettingsAdapter; + @Spy + private NoOpSystemServerAdapter mSpySystemServer; + @Mock + private AppOpsManager mMockAppOpsManager; + @Mock + private PermissionEnforcer mMockPermissionEnforcer; + @Mock + private AudioVolumeGroupHelperBase mAudioVolumeGroupHelper; + + private final AudioPolicyFacade mFakeAudioPolicy = lookbackAudio -> false; + + private AudioVolumeGroup mAudioMusicVolumeGroup; + + private TestLooper mTestLooper; + + public static final int[] BASIC_VOLUME_BEHAVIORS = { + AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED + }; + + private static class MyAudioService extends AudioService { + private SparseIntArray mStreamDevice = new SparseIntArray(); + + MyAudioService(Context context, AudioSystemAdapter audioSystem, + SystemServerAdapter systemServer, SettingsAdapter settings, + AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy, + @Nullable Looper looper, AppOpsManager appOps, + @NonNull PermissionEnforcer enforcer) { + super(context, audioSystem, systemServer, settings, audioVolumeGroupHelper, + audioPolicy, looper, appOps, enforcer); + } + + public void setDeviceForStream(int stream, int device) { + mStreamDevice.put(stream, device); + } + + @Override + public int getDeviceForStream(int stream) { + if (mStreamDevice.indexOfKey(stream) < 0) { + return DEVICE_OUT_SPEAKER; + } + return mStreamDevice.get(stream); + } + } + + private static class TestDeviceVolumeBehaviorDispatcherStub + extends IDeviceVolumeBehaviorDispatcher.Stub { + + private AudioDeviceAttributes mDevice; + private int mVolumeBehavior; + private int mTimesCalled; + + @Override + public void dispatchDeviceVolumeBehaviorChanged(@NonNull AudioDeviceAttributes device, + @AudioManager.DeviceVolumeBehavior int volumeBehavior) { + mDevice = device; + mVolumeBehavior = volumeBehavior; + mTimesCalled++; + } + + public void reset() { + mTimesCalled = 0; + mVolumeBehavior = DEVICE_VOLUME_BEHAVIOR_UNSET; + } + } + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mTestLooper = new TestLooper(); + + mSpyAudioSystem = spy(new NoOpAudioSystemAdapter()); + mSettingsAdapter = new NoOpSettingsAdapter(); + when(mMockAppOpsManager.noteOp(anyInt(), anyInt(), anyString(), anyString(), anyString())) + .thenReturn(AppOpsManager.MODE_ALLOWED); + + mAudioMusicVolumeGroup = getStreamTypeVolumeGroup(STREAM_MUSIC); + if (mAudioMusicVolumeGroup != null) { + when(mAudioVolumeGroupHelper.getAudioVolumeGroups()).thenReturn( + List.of(mAudioMusicVolumeGroup)); + } + + mAudioService = new MyAudioService(mContext, mSpyAudioSystem, mSpySystemServer, + mSettingsAdapter, mAudioVolumeGroupHelper, mFakeAudioPolicy, + mTestLooper.getLooper(), mMockAppOpsManager, mMockPermissionEnforcer); + + mTestLooper.dispatchAll(); + + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .adoptShellPermissionIdentity(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED, + Manifest.permission.MODIFY_AUDIO_ROUTING); + } + + private AudioVolumeGroup getStreamTypeVolumeGroup(int streamType) { + // get the volume group from the AudioManager to pass permission checks + // when requesting from real service + final List<AudioVolumeGroup> audioVolumeGroups = AudioManager.getAudioVolumeGroups(); + for (AudioVolumeGroup vg : audioVolumeGroups) { + for (int stream : vg.getLegacyStreamTypes()) { + if (stream == streamType) { + return vg; + } + } + } + + return null; + } + + @After + public void tearDown() { + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .dropShellPermissionIdentity(); + } + + @Test + public void check_setMasterMute() { + mAudioService.setMasterMute(true, /*flags=*/0, mContext.getOpPackageName(), + mContext.getUserId(), /*attributionTag*/""); + + assertTrue(mAudioService.isMasterMute()); + } + + @Test + public void check_isStreamAffectedByMute() { + assertFalse(mAudioService.isStreamAffectedByMute(STREAM_VOICE_CALL)); + } + + /** --------------- Volume Stream APIs --------------- */ + @Test + public void setStreamVolume_callsASSetStreamVolumeIndex() throws Exception { + int newIndex = circularIncrementAndGetVolume(STREAM_MUSIC); + + mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE); + mAudioService.setStreamVolume(STREAM_MUSIC, newIndex, /*flags=*/0, + mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + verify(mSpyAudioSystem, timeout(VOLUME_CHANGE_TIMEOUT_MS)).setStreamVolumeIndexAS( + eq(STREAM_MUSIC), eq(newIndex), eq(DEVICE_OUT_USB_DEVICE)); + } + + @Test + public void adjustStreamVolume_callsASSetStreamVolumeIndex() throws Exception { + mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE); + mAudioService.adjustStreamVolume(STREAM_MUSIC, ADJUST_LOWER, /*flags=*/0, + mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + verify(mSpyAudioSystem, timeout(VOLUME_CHANGE_TIMEOUT_MS)).setStreamVolumeIndexAS( + eq(STREAM_MUSIC), anyInt(), eq(DEVICE_OUT_USB_DEVICE)); + } + + @Test + public void adjustVolume_callsASSetStreamVolumeIndex() throws Exception { + mAudioService.adjustVolume(ADJUST_LOWER, /*flags=*/0); + mTestLooper.dispatchAll(); + + // for checking device we need to mock AudioSystem#generateAudioDeviceTypesSet and + // AudioProductStrategy#getAudioAttributesForStrategyWithLegacyStreamType + verify(mSpyAudioSystem, + timeout(VOLUME_CHANGE_TIMEOUT_MS).atLeast(1)).setStreamVolumeIndexAS( + eq(STREAM_MUSIC), anyInt(), eq(DEVICE_OUT_DEFAULT)); + } + + @Test + public void adjustSuggestedStreamVolume_callsASSetStreamVolumeIndex() throws Exception { + mAudioService.adjustSuggestedStreamVolume(ADJUST_LOWER, STREAM_NOTIFICATION, /*flags=*/0); + mTestLooper.dispatchAll(); + + // for checking device we need to mock AudioSystem#generateAudioDeviceTypesSet and + // AudioProductStrategy#getAudioAttributesForStrategyWithLegacyStreamType + verify(mSpyAudioSystem, timeout(VOLUME_CHANGE_TIMEOUT_MS)).setStreamVolumeIndexAS( + eq(STREAM_NOTIFICATION), anyInt(), eq(DEVICE_OUT_DEFAULT)); + } + + @Test + public void handleVolumeKey_callsASSetStreamVolumeIndex() throws Exception { + final KeyEvent keyEvent = new KeyEvent(ACTION_DOWN, KEYCODE_VOLUME_UP); + + mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE); + mAudioService.handleVolumeKey(keyEvent, /*isOnTv=*/false, mContext.getOpPackageName(), + "adjustSuggestedStreamVolume_callsAudioSystemSetStreamVolumeIndex"); + mTestLooper.dispatchAll(); + + verify(mSpyAudioSystem, timeout(VOLUME_CHANGE_TIMEOUT_MS)).setStreamVolumeIndexAS( + eq(STREAM_MUSIC), anyInt(), eq(DEVICE_OUT_USB_DEVICE)); + } + + /** --------------- Volume Group APIs --------------- */ + + @Test + public void setVolumeGroupVolumeIndex_callsASSetVolumeIndexForAttributes() throws Exception { + assumeNotNull(mAudioMusicVolumeGroup); + + mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE); + mAudioService.setVolumeGroupVolumeIndex(mAudioMusicVolumeGroup.getId(), + circularIncrementAndGetVolume(STREAM_MUSIC), /*flags=*/0, + mContext.getOpPackageName(), /*attributionTag*/null); + mTestLooper.dispatchAll(); + + verify(mSpyAudioSystem, timeout(VOLUME_CHANGE_TIMEOUT_MS)).setVolumeIndexForAttributes( + any(), anyInt(), eq(DEVICE_OUT_USB_DEVICE)); + } + + @Test + public void adjustVolumeGroupVolume_callsASSetVolumeIndexForAttributes() throws Exception { + assumeNotNull(mAudioMusicVolumeGroup); + + mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE); + mAudioService.adjustVolumeGroupVolume(mAudioMusicVolumeGroup.getId(), + ADJUST_LOWER, /*flags=*/0, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + verify(mSpyAudioSystem, timeout(VOLUME_CHANGE_TIMEOUT_MS)).setVolumeIndexForAttributes( + any(), anyInt(), eq(DEVICE_OUT_USB_DEVICE)); + } + + @Test + public void check_getVolumeGroupVolumeIndex() throws Exception { + assumeNotNull(mAudioMusicVolumeGroup); + + int newIndex = circularIncrementAndGetVolume(STREAM_MUSIC); + + mAudioService.setVolumeGroupVolumeIndex(mAudioMusicVolumeGroup.getId(), + newIndex, /*flags=*/0, mContext.getOpPackageName(), /*attributionTag*/null); + mTestLooper.dispatchAll(); + + assertEquals(mAudioService.getVolumeGroupVolumeIndex(mAudioMusicVolumeGroup.getId()), + newIndex); + assertEquals(mAudioService.getStreamVolume(STREAM_MUSIC), + newIndex); + } + + @Test + public void check_getVolumeGroupMaxVolumeIndex() throws Exception { + assumeNotNull(mAudioMusicVolumeGroup); + + assertEquals(mAudioService.getVolumeGroupMaxVolumeIndex(mAudioMusicVolumeGroup.getId()), + mAudioService.getStreamMaxVolume(STREAM_MUSIC)); + } + + @Test + public void check_getVolumeGroupMinVolumeIndex() throws Exception { + assumeNotNull(mAudioMusicVolumeGroup); + + assertEquals(mAudioService.getVolumeGroupMinVolumeIndex(mAudioMusicVolumeGroup.getId()), + mAudioService.getStreamMinVolume(STREAM_MUSIC)); + } + + @Test + public void check_getLastAudibleVolumeForVolumeGroup() throws Exception { + assumeNotNull(mAudioMusicVolumeGroup); + + assertEquals( + mAudioService.getLastAudibleVolumeForVolumeGroup(mAudioMusicVolumeGroup.getId()), + mAudioService.getLastAudibleStreamVolume(STREAM_MUSIC)); + } + + @Test + public void check_isVolumeGroupMuted() throws Exception { + assumeNotNull(mAudioMusicVolumeGroup); + + assertEquals(mAudioService.isVolumeGroupMuted(mAudioMusicVolumeGroup.getId()), + mAudioService.isStreamMute(STREAM_MUSIC)); + } + + /** ----------------- AudioDeviceVolumeManager ----------------- */ + @Test + public void setDeviceVolume_checkIndex() { + AudioManager am = mContext.getSystemService(AudioManager.class); + final int minIndex = am.getStreamMinVolume(STREAM_MUSIC); + final int maxIndex = am.getStreamMaxVolume(STREAM_MUSIC); + final int midIndex = (minIndex + maxIndex) / 2; + final VolumeInfo volMedia = new VolumeInfo.Builder(STREAM_MUSIC) + .setMinVolumeIndex(minIndex) + .setMaxVolumeIndex(maxIndex) + .build(); + final VolumeInfo volMin = new VolumeInfo.Builder(volMedia).setVolumeIndex(minIndex).build(); + final VolumeInfo volMid = new VolumeInfo.Builder(volMedia).setVolumeIndex(midIndex).build(); + final AudioDeviceAttributes usbDevice = new AudioDeviceAttributes( + /*native type*/ AudioSystem.DEVICE_OUT_USB_DEVICE, /*address*/ "bla"); + + mAudioService.setDeviceVolume(volMin, usbDevice, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + assertEquals(mAudioService.getDeviceVolume(volMin, usbDevice, + mContext.getOpPackageName()), volMin); + verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( + STREAM_MUSIC, minIndex, AudioSystem.DEVICE_OUT_USB_DEVICE); + + mAudioService.setDeviceVolume(volMid, usbDevice, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + assertEquals(mAudioService.getDeviceVolume(volMid, usbDevice, + mContext.getOpPackageName()), volMid); + verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( + STREAM_MUSIC, midIndex, AudioSystem.DEVICE_OUT_USB_DEVICE); + } + + @Test + @RequiresFlagsDisabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME) + public void configurablePreScaleAbsoluteVolume_checkIndex() throws Exception { + AudioManager am = mContext.getSystemService(AudioManager.class); + final int minIndex = am.getStreamMinVolume(STREAM_MUSIC); + final int maxIndex = am.getStreamMaxVolume(STREAM_MUSIC); + final VolumeInfo volMedia = new VolumeInfo.Builder(STREAM_MUSIC) + .setMinVolumeIndex(minIndex) + .setMaxVolumeIndex(maxIndex) + .build(); + final AudioDeviceAttributes bleDevice = new AudioDeviceAttributes( + /*native type*/ AudioSystem.DEVICE_OUT_BLE_HEADSET, /*address*/ "fake_ble"); + final int maxPreScaleIndex = 3; + final float[] preScale = new float[maxPreScaleIndex]; + preScale[0] = mContext.getResources().getFraction( + com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index1, + 1, 1); + preScale[1] = mContext.getResources().getFraction( + com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index2, + 1, 1); + preScale[2] = mContext.getResources().getFraction( + com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index3, + 1, 1); + + for (int i = 0; i < maxPreScaleIndex; i++) { + final int targetIndex = (int) (preScale[i] * maxIndex); + final VolumeInfo volCur = new VolumeInfo.Builder(volMedia) + .setVolumeIndex(i + 1).build(); + // Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:1~3) + mAudioService.setDeviceVolume(volCur, bleDevice, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + assertEquals( + mAudioService.getDeviceVolume(volCur, bleDevice, mContext.getOpPackageName()), + volCur); + // Stream volume changes + verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( + STREAM_MUSIC, targetIndex, + AudioSystem.DEVICE_OUT_BLE_HEADSET); + } + + // Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:4) + final VolumeInfo volIndex4 = new VolumeInfo.Builder(volMedia) + .setVolumeIndex(4).build(); + mAudioService.setDeviceVolume(volIndex4, bleDevice, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + assertEquals( + mAudioService.getDeviceVolume(volIndex4, bleDevice, mContext.getOpPackageName()), + volIndex4); + verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( + STREAM_MUSIC, maxIndex, + AudioSystem.DEVICE_OUT_BLE_HEADSET); + } + + @Test + @RequiresFlagsEnabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME) + public void disablePreScaleAbsoluteVolume_checkIndex() throws Exception { + AudioManager am = mContext.getSystemService(AudioManager.class); + final int minIndex = am.getStreamMinVolume(STREAM_MUSIC); + final int maxIndex = am.getStreamMaxVolume(STREAM_MUSIC); + final VolumeInfo volMedia = new VolumeInfo.Builder(STREAM_MUSIC) + .setMinVolumeIndex(minIndex) + .setMaxVolumeIndex(maxIndex) + .build(); + final AudioDeviceAttributes bleDevice = new AudioDeviceAttributes( + /*native type*/ AudioSystem.DEVICE_OUT_BLE_HEADSET, /*address*/ "bla"); + final int maxPreScaleIndex = 3; + + for (int i = 0; i < maxPreScaleIndex; i++) { + final VolumeInfo volCur = new VolumeInfo.Builder(volMedia) + .setVolumeIndex(i + 1).build(); + // Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:1~3) + mAudioService.setDeviceVolume(volCur, bleDevice, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + // Stream volume changes + verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( + STREAM_MUSIC, maxIndex, + AudioSystem.DEVICE_OUT_BLE_HEADSET); + } + + // Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:4) + final VolumeInfo volIndex4 = new VolumeInfo.Builder(volMedia) + .setVolumeIndex(4).build(); + mAudioService.setDeviceVolume(volIndex4, bleDevice, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( + STREAM_MUSIC, maxIndex, + AudioSystem.DEVICE_OUT_BLE_HEADSET); + } + + /** ---------------- DeviceVolumeBehaviorTest ---------------- */ + @Test + public void setDeviceVolumeBehavior_changesDeviceVolumeBehavior() { + mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + for (int behavior : BASIC_VOLUME_BEHAVIORS) { + mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT, behavior, + mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + int actualBehavior = mAudioService.getDeviceVolumeBehavior(DEVICE_SPEAKER_OUT); + + assertWithMessage("Expected volume behavior to be " + behavior + + " but was instead " + actualBehavior) + .that(actualBehavior).isEqualTo(behavior); + } + } + + @Test + public void setToNewBehavior_triggersDeviceVolumeBehaviorDispatcher() { + TestDeviceVolumeBehaviorDispatcherStub dispatcher = + new TestDeviceVolumeBehaviorDispatcherStub(); + mAudioService.registerDeviceVolumeBehaviorDispatcher(true, dispatcher); + + mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + for (int behavior : BASIC_VOLUME_BEHAVIORS) { + dispatcher.reset(); + mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT, behavior, + mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + assertThat(dispatcher.mTimesCalled).isEqualTo(1); + assertThat(dispatcher.mDevice).isEqualTo(DEVICE_SPEAKER_OUT); + assertWithMessage("Expected dispatched volume behavior to be " + behavior + + " but was instead " + dispatcher.mVolumeBehavior) + .that(dispatcher.mVolumeBehavior).isEqualTo(behavior); + } + } + + @Test + public void setToSameBehavior_doesNotTriggerDeviceVolumeBehaviorDispatcher() { + mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + TestDeviceVolumeBehaviorDispatcherStub dispatcher = + new TestDeviceVolumeBehaviorDispatcherStub(); + mAudioService.registerDeviceVolumeBehaviorDispatcher(true, dispatcher); + + mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + assertThat(dispatcher.mTimesCalled).isEqualTo(0); + } + + @Test + public void unregisterDeviceVolumeBehaviorDispatcher_noLongerTriggered() { + mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + TestDeviceVolumeBehaviorDispatcherStub dispatcher = + new TestDeviceVolumeBehaviorDispatcherStub(); + mAudioService.registerDeviceVolumeBehaviorDispatcher(true, dispatcher); + mAudioService.registerDeviceVolumeBehaviorDispatcher(false, dispatcher); + + mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + assertThat(dispatcher.mTimesCalled).isEqualTo(0); + } + + @Test + public void setDeviceVolumeBehavior_checkIsVolumeFixed() throws Exception { + when(mSpyAudioSystem.getDevicesForAttributes(any(), anyBoolean())).thenReturn( + new ArrayList<>(List.of(DEVICE_SPEAKER_OUT))); + + mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED, mContext.getOpPackageName()); + + assertTrue(mAudioService.isVolumeFixed()); + } + + + private int circularIncrementAndGetVolume(int streamType) throws Exception { + AudioManager am = mContext.getSystemService(AudioManager.class); + final int streamMinVolume = am.getStreamMinVolume(streamType); + final int streamMaxVolume = am.getStreamMaxVolume(streamType); + + int streamVolume = mAudioService.getStreamVolume(streamType); + if (streamVolume + 1 > streamMaxVolume) { + return streamMinVolume; + } + return streamVolume + 1; + } +} |