From b2ce7519b380a55358bed4b7128f7accddb14967 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Mon, 6 Jun 2022 16:40:14 +0000 Subject: [PATCH] AudioService: config for splitting notif and ring volumes Add support for ability to configure device with a choice of aliasing notification and ringtone volumes (default) or not (ringtone volume is separately controlled from notification volume): - add config_alias_ring_notif_stream_types property for per-device configuration - update voice stream aliases based on property - in audio dump: output aliases when present For testing: add ability to inject AppOpsManager in the AudioService constructor so a mock can be injected. Bug: 228180614 Test: atest AudioServiceTest#testRingNotifAlias Change-Id: I8b0ea86aecd398b76dbaa920e19727d0dd3d6eb0 Merged-In: I8b0ea86aecd398b76dbaa920e19727d0dd3d6eb0 --- core/res/res/values/config.xml | 4 ++ core/res/res/values/symbols.xml | 1 + .../android/server/audio/AudioService.java | 52 ++++++++++++++++++- .../server/audio/AudioServiceTest.java | 42 ++++++++++++++- 4 files changed, 96 insertions(+), 3 deletions(-) diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 46abd33453353..d97e2cc51708e 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2030,6 +2030,10 @@ STREAM_MUSIC as if it's on TV platform. --> false + + true + true diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index ad02128db613e..8258aa416cc0b 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -273,6 +273,7 @@ + diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 02648c4da76f6..43d77ab32371f 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -275,6 +275,25 @@ public class AudioService extends IAudioService.Stub // indicates whether the system maps all streams to a single stream. private final boolean mIsSingleVolume; + /** + * indicates whether STREAM_NOTIFICATION is aliased to STREAM_RING + * not final due to test method, see {@link #setNotifAliasRingForTest(boolean)}. + */ + private boolean mNotifAliasRing; + + /** + * Test method to temporarily override whether STREAM_NOTIFICATION is aliased to STREAM_RING, + * volumes will be updated in case of a change. + * @param alias if true, STREAM_NOTIFICATION is aliased to STREAM_RING + */ + /*package*/ void setNotifAliasRingForTest(boolean alias) { + boolean update = (mNotifAliasRing != alias); + mNotifAliasRing = alias; + if (update) { + updateStreamVolumeAlias(true, "AudioServiceTest"); + } + } + /*package*/ boolean isPlatformVoice() { return mPlatformType == AudioSystem.PLATFORM_VOICE; } @@ -942,10 +961,25 @@ public class AudioService extends IAudioService.Stub */ public AudioService(Context context, AudioSystemAdapter audioSystem, SystemServerAdapter systemServer, SettingsAdapter settings, @Nullable Looper looper) { + this (context, audioSystem, systemServer, settings, looper, + context.getSystemService(AppOpsManager.class)); + } + + /** + * @param context + * @param audioSystem Adapter for {@link AudioSystem} + * @param systemServer Adapter for privilieged functionality for system server components + * @param settings Adapter for {@link Settings} + * @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, @Nullable Looper looper, + AppOpsManager appOps) { sLifecycleLogger.log(new AudioEventLogger.StringEvent("AudioService()")); mContext = context; mContentResolver = context.getContentResolver(); - mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); + mAppOps = appOps; mAudioSystem = audioSystem; mSystemServer = systemServer; @@ -976,6 +1010,9 @@ public class AudioService extends IAudioService.Stub mUseVolumeGroupAliases = mContext.getResources().getBoolean( com.android.internal.R.bool.config_handleVolumeAliasesUsingVolumeGroups); + mNotifAliasRing = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_alias_ring_notif_stream_types); + // Initialize volume // Priority 1 - Android Property // Priority 2 - Audio Policy Service @@ -2008,7 +2045,13 @@ public class AudioService extends IAudioService.Stub pw.println("\nStream volumes (device: index)"); int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int i = 0; i < numStreamTypes; i++) { - pw.println("- " + AudioSystem.STREAM_NAMES[i] + ":"); + StringBuilder alias = new StringBuilder(); + if (mStreamVolumeAlias[i] != i) { + alias.append(" (aliased to: ") + .append(AudioSystem.STREAM_NAMES[mStreamVolumeAlias[i]]) + .append(")"); + } + pw.println("- " + AudioSystem.STREAM_NAMES[i] + alias + ":"); mStreamStates[i].dump(pw); pw.println(""); } @@ -2040,6 +2083,10 @@ public class AudioService extends IAudioService.Stub mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT; dtmfStreamAlias = AudioSystem.STREAM_MUSIC; } + if (!mNotifAliasRing) { + mStreamVolumeAlias[AudioSystem.STREAM_NOTIFICATION] = + AudioSystem.STREAM_NOTIFICATION; + } } if (mIsSingleVolume) { @@ -9888,6 +9935,7 @@ public class AudioService extends IAudioService.Stub pw.print(" mBtScoOnByApp="); pw.println(mBtScoOnByApp); pw.print(" mIsSingleVolume="); pw.println(mIsSingleVolume); pw.print(" mUseFixedVolume="); pw.println(mUseFixedVolume); + pw.print(" mNotifAliasRing="); pw.println(mNotifAliasRing); pw.print(" mFixedVolumeDevices="); pw.println(dumpDeviceTypes(mFixedVolumeDevices)); pw.print(" mFullVolumeDevices="); pw.println(dumpDeviceTypes(mFullVolumeDevices)); pw.print(" mAbsoluteVolumeDevices.keySet()="); pw.println(dumpDeviceTypes( 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 91c45b4f8b603..adcbe6bb12c0b 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java @@ -15,12 +15,18 @@ */ package com.android.server.audio; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.app.AppOpsManager; import android.content.Context; +import android.media.AudioSystem; import android.os.Looper; import android.os.UserHandle; import android.util.Log; @@ -33,6 +39,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.Spy; @MediumTest @@ -46,6 +53,7 @@ public class AudioServiceTest { private AudioSystemAdapter mAudioSystem; @Spy private SystemServerAdapter mSpySystemServer; private SettingsAdapter mSettingsAdapter; + @Mock private AppOpsManager mMockAppOpsManager; // the class being unit-tested here private AudioService mAudioService; @@ -61,8 +69,11 @@ public class AudioServiceTest { mAudioSystem = new NoOpAudioSystemAdapter(); mSpySystemServer = spy(new NoOpSystemServerAdapter()); mSettingsAdapter = new NoOpSettingsAdapter(); + mMockAppOpsManager = mock(AppOpsManager.class); + when(mMockAppOpsManager.noteOp(anyInt(), anyInt(), anyString(), anyString(), anyString())) + .thenReturn(AppOpsManager.MODE_ALLOWED); mAudioService = new AudioService(mContext, mAudioSystem, mSpySystemServer, - mSettingsAdapter, null); + mSettingsAdapter, null, mMockAppOpsManager); } /** @@ -113,4 +124,33 @@ public class AudioServiceTest { reset(mSpySystemServer); } } + + @Test + public void testRingNotifAlias() throws Exception { + Log.i(TAG, "running testRingNotifAlias"); + Assert.assertNotNull(mAudioService); + // TODO add initialization message that can be caught here instead of sleeping + Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS); // wait for full AudioService initialization + + // test with aliasing RING and NOTIFICATION + mAudioService.setNotifAliasRingForTest(true); + final int ringMaxVol = mAudioService.getStreamMaxVolume(AudioSystem.STREAM_RING); + final int ringMinVol = mAudioService.getStreamMinVolume(AudioSystem.STREAM_RING); + final int ringVol = ringMinVol + 1; + // set a value for NOTIFICATION so it's not at the target test value (ringMaxVol) + mAudioService.setStreamVolume(AudioSystem.STREAM_NOTIFICATION, + ringVol, 0, "bla"); + mAudioService.setStreamVolume(AudioSystem.STREAM_RING, ringMaxVol, 0, "bla"); + Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS); + Assert.assertEquals(ringMaxVol, + mAudioService.getStreamVolume(AudioSystem.STREAM_NOTIFICATION)); + + // test with no aliasing between RING and NOTIFICATION + mAudioService.setNotifAliasRingForTest(false); + mAudioService.setStreamVolume(AudioSystem.STREAM_RING, ringVol, 0, "bla"); + mAudioService.setStreamVolume(AudioSystem.STREAM_NOTIFICATION, ringMaxVol, 0, "bla"); + Assert.assertEquals(ringVol, mAudioService.getStreamVolume(AudioSystem.STREAM_RING)); + Assert.assertEquals(ringMaxVol, mAudioService.getStreamVolume( + AudioSystem.STREAM_NOTIFICATION)); + } }