Merge "Muting notifications due to focus is conditional on recording" into main
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 6a38723..2e22071 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1882,6 +1882,7 @@
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 a5a69f9..4918289 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -21,6 +21,7 @@
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 @@
}
}
+ /**
+ * @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 5c268d4..2eec9b3 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -775,4 +775,8 @@
@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 a1b6f29..91d533c 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 @@
}
}
+
+ /**
+ * @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 0df0006..1376bde 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 @@
}
/**
+ * 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 a6f71c2..85c4ffe 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.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 @@
}
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 9ed3559..7aa7b7e 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_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 @@
}
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 64d3a20..1786ac5 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.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 @@
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 @@
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 boolean isAudioAttributesUsage(int usage) {
- return mAttributes != null && mAttributes.getUsage() == usage;
+ return mAttributes.getUsage() == usage;
}
/**
@@ -1172,7 +1173,7 @@
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 42ad73a..8622488 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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
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 1b77b99..bfd2df2d 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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
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);