diff options
10 files changed, 174 insertions, 42 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 6a3872326e0b..2e22071d72ad 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1882,6 +1882,7 @@ package android.media { method public void setRampingRingerEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void setRs2Value(float); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setTestDeviceConnectionState(@NonNull android.media.AudioDeviceAttributes, boolean); + method @FlaggedApi("android.media.audio.focus_exclusive_with_recording") @RequiresPermission(android.Manifest.permission.QUERY_AUDIO_STATE) public boolean shouldNotificationSoundPlay(@NonNull android.media.AudioAttributes); } public static final class AudioRecord.MetricsConstants { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index a5a69f987113..4918289e8b5c 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -21,6 +21,7 @@ import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO; import static android.content.Context.DEVICE_ID_DEFAULT; import static android.media.audio.Flags.autoPublicVolumeApiHardening; import static android.media.audio.Flags.automaticBtDeviceType; +import static android.media.audio.Flags.FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING; import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API; import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION; @@ -10081,6 +10082,28 @@ public class AudioManager { } } + /** + * @hide + * Checks whether a notification sound should be played or not, as reported by the state + * of the audio framework. Querying whether playback should proceed is favored over + * playing and letting the sound be muted or not. + * @param aa the {@link AudioAttributes} of the notification about to maybe play + * @return true if the audio framework state is such that the notification should be played + * because at time of checking, and the notification will be heard, + * false otherwise + */ + @TestApi + @FlaggedApi(FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING) + @RequiresPermission(android.Manifest.permission.QUERY_AUDIO_STATE) + public boolean shouldNotificationSoundPlay(@NonNull final AudioAttributes aa) { + final IAudioService service = getService(); + try { + return service.shouldNotificationSoundPlay(Objects.requireNonNull(aa)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + //==================================================================== // Mute await connection diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 5c268d4ab652..2eec9b3d4a09 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -775,4 +775,8 @@ interface IAudioService { @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)") FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss(); + + @EnforcePermission("QUERY_AUDIO_STATE") + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.QUERY_AUDIO_STATE)") + boolean shouldNotificationSoundPlay(in AudioAttributes aa); } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index a1b6f297f287..91d533c73b5d 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -13591,6 +13591,46 @@ public class AudioService extends IAudioService.Stub } } + + /** + * @see AudioManager#shouldNotificationSoundPlay(AudioAttributes) + */ + @android.annotation.EnforcePermission( + android.Manifest.permission.QUERY_AUDIO_STATE) + public boolean shouldNotificationSoundPlay(@NonNull final AudioAttributes aa) { + super.shouldNotificationSoundPlay_enforcePermission(); + Objects.requireNonNull(aa); + + // don't play notifications if the stream volume associated with the + // AudioAttributes of the notification record is 0 (non-zero volume implies + // not silenced by SILENT or VIBRATE ringer mode) + final int stream = AudioAttributes.toLegacyStreamType(aa); + final boolean mutingFromVolume = getStreamVolume(stream) == 0; + if (mutingFromVolume) { + if (DEBUG_VOL) { + Slog.d(TAG, "notification should not play due to muted stream " + stream); + } + return false; + } + + // don't play notifications if there is a user of GAIN_TRANSIENT_EXCLUSIVE audio focus + // and the focus owner is recording + final int uid = mMediaFocusControl.getExclusiveFocusOwnerUid(); + if (uid == -1) { // return value is -1 if focus isn't GAIN_TRANSIENT_EXCLUSIVE + return true; + } + // is the owner of GAIN_TRANSIENT_EXCLUSIVE focus also recording? + final boolean mutingFromFocusAndRecording = mRecordMonitor.isRecordingActiveForUid(uid); + if (mutingFromFocusAndRecording) { + if (DEBUG_VOL) { + Slog.d(TAG, "notification should not play due to exclusive focus owner recording " + + " uid:" + uid); + } + return false; + } + return true; + } + //====================== // Audioserver state dispatch //====================== diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index 0df0006c7be3..1376bde2fb71 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -297,6 +297,23 @@ public class MediaFocusControl implements PlayerFocusEnforcer { } /** + * Return the UID of the focus owner that has focus with exclusive focus gain + * @return -1 if nobody has exclusive focus, the UID of the owner otherwise + */ + protected int getExclusiveFocusOwnerUid() { + synchronized (mAudioFocusLock) { + if (mFocusStack.empty()) { + return -1; + } + final FocusRequester owner = mFocusStack.peek(); + if (owner.getGainRequest() != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) { + return -1; + } + return owner.getClientUid(); + } + } + + /** * Send AUDIOFOCUS_LOSS to a specific stack entry. * Note this method is supporting an external API, and is restricted to LOSS in order to * prevent allowing the stack to be in an invalid state (e.g. entry inside stack has focus) diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java index a6f71c29b380..85c4ffe6ac67 100644 --- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java +++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java @@ -22,6 +22,7 @@ import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; +import static android.media.audio.Flags.focusExclusiveWithRecording; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS; @@ -588,30 +589,41 @@ public final class NotificationAttentionHelper { } private boolean playSound(final NotificationRecord record, Uri soundUri) { + final boolean shouldPlay; + if (focusExclusiveWithRecording()) { + // flagged path + shouldPlay = mAudioManager.shouldNotificationSoundPlay(record.getAudioAttributes()); + } else { + // legacy path + // play notifications if there is no user of exclusive audio focus + // and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or + // VIBRATE ringer mode) + shouldPlay = !mAudioManager.isAudioFocusExclusive() + && (mAudioManager.getStreamVolume( + AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0); + } + if (!shouldPlay) { + if (DEBUG) Slog.v(TAG, "Not playing sound " + soundUri + " due to focus/volume"); + return false; + } + boolean looping = (record.getNotification().flags & FLAG_INSISTENT) != 0; - // play notifications if there is no user of exclusive audio focus - // and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or - // VIBRATE ringer mode) - if (!mAudioManager.isAudioFocusExclusive() - && (mAudioManager.getStreamVolume( - AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0)) { - final long identity = Binder.clearCallingIdentity(); - try { - final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); - if (player != null) { - if (DEBUG) { - Slog.v(TAG, "Playing sound " + soundUri + " with attributes " - + record.getAudioAttributes()); - } - player.playAsync(soundUri, record.getSbn().getUser(), looping, - record.getAudioAttributes(), getSoundVolume(record)); - return true; + final long identity = Binder.clearCallingIdentity(); + try { + final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); + if (player != null) { + if (DEBUG) { + Slog.v(TAG, "Playing sound " + soundUri + " with attributes " + + record.getAudioAttributes()); } - } catch (RemoteException e) { - Log.e(TAG, "Failed playSound: " + e); - } finally { - Binder.restoreCallingIdentity(identity); + player.playAsync(soundUri, record.getSbn().getUser(), looping, + record.getAudioAttributes(), getSoundVolume(record)); + return true; } + } catch (RemoteException e) { + Log.e(TAG, "Failed playSound: " + e); + } finally { + Binder.restoreCallingIdentity(identity); } return false; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 9ed3559c1389..7aa7b7e1bfc1 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -83,6 +83,7 @@ import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.media.audio.Flags.focusExclusiveWithRecording; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; import static android.os.Flags.allowPrivateProfile; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; @@ -9104,27 +9105,40 @@ public class NotificationManagerService extends SystemService { } private boolean playSound(final NotificationRecord record, Uri soundUri) { + final boolean shouldPlay; + if (focusExclusiveWithRecording()) { + // flagged path + shouldPlay = mAudioManager.shouldNotificationSoundPlay(record.getAudioAttributes()); + } else { + // legacy path + // play notifications if there is no user of exclusive audio focus + // and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or + // VIBRATE ringer mode) + shouldPlay = !mAudioManager.isAudioFocusExclusive() + && (mAudioManager.getStreamVolume( + AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0); + } + if (!shouldPlay) { + if (DBG) Slog.v(TAG, "Not playing sound " + soundUri + " due to focus/volume"); + return false; + } + boolean looping = (record.getNotification().flags & FLAG_INSISTENT) != 0; - // play notifications if there is no user of exclusive audio focus - // and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or - // VIBRATE ringer mode) - if (!mAudioManager.isAudioFocusExclusive() - && (mAudioManager.getStreamVolume( - AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0)) { - final long identity = Binder.clearCallingIdentity(); - try { - final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); - if (player != null) { - if (DBG) Slog.v(TAG, "Playing sound " + soundUri + final long identity = Binder.clearCallingIdentity(); + try { + final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); + if (player != null) { + if (DBG) { + Slog.v(TAG, "Playing sound " + soundUri + " with attributes " + record.getAudioAttributes()); - player.playAsync(soundUri, record.getSbn().getUser(), looping, - record.getAudioAttributes(), 1.0f); - return true; } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(identity); + player.playAsync(soundUri, record.getSbn().getUser(), looping, + record.getAudioAttributes(), 1.0f); + return true; } + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(identity); } return false; } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 64d3a20e9281..1786ac53eeab 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -25,6 +25,7 @@ import static android.service.notification.NotificationListenerService.Ranking.U import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; import android.annotation.FlaggedApi; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Flags; import android.app.KeyguardManager; @@ -167,7 +168,7 @@ public final class NotificationRecord { private boolean mPreChannelsNotification = true; private Uri mSound; private VibrationEffect mVibration; - private AudioAttributes mAttributes; + private @NonNull AudioAttributes mAttributes; private NotificationChannel mChannel; private ArrayList<String> mPeopleOverride; private ArrayList<SnoozeCriterion> mSnoozeCriteria; @@ -334,7 +335,7 @@ public final class NotificationRecord { return vibration; } - private AudioAttributes calculateAttributes() { + private @NonNull AudioAttributes calculateAttributes() { final Notification n = getSbn().getNotification(); AudioAttributes attributes = getChannel().getAudioAttributes(); if (attributes == null) { @@ -1003,7 +1004,7 @@ public final class NotificationRecord { } public boolean isAudioAttributesUsage(int usage) { - return mAttributes != null && mAttributes.getUsage() == usage; + return mAttributes.getUsage() == usage; } /** @@ -1172,7 +1173,7 @@ public final class NotificationRecord { return mVibration; } - public AudioAttributes getAudioAttributes() { + public @NonNull AudioAttributes getAudioAttributes() { return mAttributes; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index 42ad73a23f0e..8622488f820e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -158,6 +158,9 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { when(mAudioManager.isAudioFocusExclusive()).thenReturn(false); when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10); + // consistent with focus not exclusive and volume not muted + when(mAudioManager.shouldNotificationSoundPlay(any(AudioAttributes.class))) + .thenReturn(true); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); when(mAudioManager.getFocusRampTimeMs(anyInt(), any(AudioAttributes.class))).thenReturn(50); when(mUsageStats.isAlertRateLimited(any())).thenReturn(false); @@ -869,6 +872,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false); mService.buzzBeepBlinkLocked(r); @@ -886,6 +890,8 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(1); + // all streams at 1 means no muting from audio framework + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(true); mService.buzzBeepBlinkLocked(r); @@ -904,6 +910,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false); mService.buzzBeepBlinkLocked(r); @@ -924,6 +931,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false); mService.buzzBeepBlinkLocked(r); @@ -1195,6 +1203,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false); mService.buzzBeepBlinkLocked(r); verifyDelayedVibrate(mService.getVibratorHelper().createFallbackVibration(false)); @@ -1923,6 +1932,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { NotificationRecord r = getBuzzyBeepyNotification(); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false); mService.buzzBeepBlinkLocked(r); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java index 1b77b99e7d3e..bfd2df2d2b7d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java @@ -182,6 +182,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); when(mAudioManager.getFocusRampTimeMs(anyInt(), any(AudioAttributes.class))).thenReturn(50); + when(mAudioManager.shouldNotificationSoundPlay(any(AudioAttributes.class))) + .thenReturn(true); when(mUsageStats.isAlertRateLimited(any())).thenReturn(false); when(mVibrator.hasFrequencyControl()).thenReturn(false); when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(false); @@ -930,6 +932,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + when(mAudioManager.shouldNotificationSoundPlay(any(AudioAttributes.class))) + .thenReturn(false); mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); @@ -947,6 +951,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(1); + // all streams at 1 means no muting from audio framework + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(true); mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); @@ -965,6 +971,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false); mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); @@ -986,6 +993,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false); mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); @@ -1258,6 +1266,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false); mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); verifyDelayedVibrate(mAttentionHelper.getVibratorHelper().createFallbackVibration(false)); @@ -1988,6 +1997,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { NotificationRecord r = getBuzzyBeepyNotification(); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false); mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); |