Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fb5b718f authored by Iavor-Valentin Iftime's avatar Iavor-Valentin Iftime Committed by Android (Google) Code Review
Browse files

Merge "Exempt emergency notifications from attentuation" into main

parents c2a13bcd 587fae97
Loading
Loading
Loading
Loading
+40 −10
Original line number Diff line number Diff line
@@ -22,12 +22,14 @@ import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
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.content.pm.PackageManager.PERMISSION_GRANTED;
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;

import android.Manifest.permission;
import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.KeyguardManager;
@@ -223,7 +225,10 @@ public final class NotificationAttentionHelper {
                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET),
                    record -> mPackageManager.checkPermission(
                            permission.RECEIVE_EMERGENCY_BROADCAST,
                            record.getSbn().getPackageName()) == PERMISSION_GRANTED);

            return new StrategyAvalanche(
                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
@@ -231,14 +236,17 @@ public final class NotificationAttentionHelper {
                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT),
                    appStrategy);
                    appStrategy, appStrategy.mExemptionProvider);
        } else {
            return new StrategyPerApp(
                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET),
                    record -> mPackageManager.checkPermission(
                            permission.RECEIVE_EMERGENCY_BROADCAST,
                            record.getSbn().getPackageName()) == PERMISSION_GRANTED);
        }
    }

@@ -1098,6 +1106,11 @@ public final class NotificationAttentionHelper {
        }
    }

    // Returns true if a notification should be exempted from attenuation
    private interface ExemptionProvider {
        boolean isExempted(NotificationRecord record);
    }

    @VisibleForTesting
    abstract static class PolitenessStrategy {
        static final int POLITE_STATE_DEFAULT = 0;
@@ -1128,8 +1141,10 @@ public final class NotificationAttentionHelper {

        protected boolean mIsActive = true;

        protected final ExemptionProvider mExemptionProvider;

        public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite,
                int volumeMuted) {
                int volumeMuted, ExemptionProvider exemptionProvider) {
            mVolumeStates = new HashMap<>();
            mLastUpdatedTimestampByPackage = new HashMap<>();

@@ -1137,6 +1152,7 @@ public final class NotificationAttentionHelper {
            this.mTimeoutMuted = timeoutMuted;
            this.mVolumePolite = volumePolite / 100.0f;
            this.mVolumeMuted = volumeMuted / 100.0f;
            this.mExemptionProvider = exemptionProvider;
        }

        abstract void onNotificationPosted(NotificationRecord record);
@@ -1294,8 +1310,8 @@ public final class NotificationAttentionHelper {
        private final int mMaxPostedForReset;

        public StrategyPerApp(int timeoutPolite, int timeoutMuted, int volumePolite,
                int volumeMuted, int maxPosted) {
            super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
                int volumeMuted, int maxPosted, ExemptionProvider exemptionProvider) {
            super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted, exemptionProvider);

            mNumPosted = new HashMap<>();
            mMaxPostedForReset = maxPosted;
@@ -1316,7 +1332,12 @@ public final class NotificationAttentionHelper {

            final String key = getChannelKey(record);
            @PolitenessState final int currState = getPolitenessState(record);
            @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
            @PolitenessState int nextState;
            if (Flags.politeNotificationsAttnUpdate()) {
                nextState = getNextState(currState, timeSinceLastNotif, record);
            } else {
                nextState = getNextState(currState, timeSinceLastNotif);
            }

            // Reset to default state if number of posted notifications exceed this value when muted
            int numPosted = mNumPosted.getOrDefault(key, 0) + 1;
@@ -1334,6 +1355,14 @@ public final class NotificationAttentionHelper {
            mVolumeStates.put(key, nextState);
        }

        @PolitenessState int getNextState(@PolitenessState final int currState,
                final long timeSinceLastNotif, final NotificationRecord record) {
            if (mExemptionProvider.isExempted(record)) {
                return POLITE_STATE_DEFAULT;
            }
            return getNextState(currState, timeSinceLastNotif);
        }

        @Override
        public void onUserInteraction(final NotificationRecord record) {
            super.onUserInteraction(record);
@@ -1354,8 +1383,9 @@ public final class NotificationAttentionHelper {
        private long mLastAvalancheTriggerTimestamp = 0;

        StrategyAvalanche(int timeoutPolite, int timeoutMuted, int volumePolite,
                    int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy) {
            super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
                    int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy,
                    ExemptionProvider exemptionProvider) {
            super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted, exemptionProvider);

            mTimeoutAvalanche = timeoutAvalanche;
            mAppStrategy = appStrategy;
@@ -1528,7 +1558,7 @@ public final class NotificationAttentionHelper {
                return true;
            }

            return false;
            return mExemptionProvider.isExempted(record);
        }

        private boolean isAvalancheExempted(final NotificationRecord record) {
+71 −0
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;

@@ -52,6 +54,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.Manifest.permission;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.KeyguardManager;
@@ -190,6 +193,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
        getContext().addMockSystemService(Vibrator.class, mVibrator);
        getContext().addMockSystemService(PackageManager.class, mPackageManager);
        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(false);
        when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST),
                anyString())).thenReturn(PERMISSION_DENIED);

        when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
        when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
@@ -2362,6 +2367,72 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
        assertNotEquals(-1, r4.getLastAudiblyAlertedMs());
    }

    @Test
    public void testBeepVolume_politeNotif_Avalanche_exemptEmergency() throws Exception {
        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
        mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
        TestableFlagResolver flagResolver = new TestableFlagResolver();
        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
        initAttentionHelper(flagResolver);

        // Trigger avalanche trigger intent
        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
        intent.putExtra("state", false);
        mAvalancheBroadcastReceiver.onReceive(getContext(), intent);

        NotificationRecord r = getBeepyNotification();

        // Grant RECEIVE_EMERGENCY_BROADCAST to notification's package
        when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST),
                eq(r.getSbn().getPackageName()))).thenReturn(PERMISSION_GRANTED);

        // Should beep at 100% volume
        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
        verifyBeepVolume(1.0f);
        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
        verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
    }

    @Test
    public void testBeepVolume_politeNotif_exemptEmergency() throws Exception {
        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
        mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
        TestableFlagResolver flagResolver = new TestableFlagResolver();
        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
        // NOTIFICATION_COOLDOWN_ALL setting is enabled
        Settings.System.putInt(getContext().getContentResolver(),
                Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
        initAttentionHelper(flagResolver);

        NotificationRecord r = getBeepyNotification();

        // Grant RECEIVE_EMERGENCY_BROADCAST to notification's package
        when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST),
                eq(r.getSbn().getPackageName()))).thenReturn(PERMISSION_GRANTED);

        // set up internal state
        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
        Mockito.reset(mRingtonePlayer);

        // update should beep at 100% volume
        NotificationRecord r2 = getBeepyNotification();
        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
        assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
        verifyBeepVolume(1.0f);

        // 2nd update should beep at 100% volume
        Mockito.reset(mRingtonePlayer);
        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
        assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
        verifyBeepVolume(1.0f);

        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
    }

    @Test
    public void testBeepVolume_politeNotif_applyPerApp() throws Exception {
        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);