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

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

Merge "Notification avalanche auditory update" into main

parents fe7d3ac5 de69af8e
Loading
Loading
Loading
Loading
+110 −13
Original line number Diff line number Diff line
@@ -514,12 +514,16 @@ public final class NotificationAttentionHelper {
            EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0,
                    getPolitenessState(record));
        }
        record.setAudiblyAlerted(buzz || beep);
        if (Flags.politeNotifications()) {
            // Update last alert time
            if (buzz || beep) {
                mStrategy.setLastNotificationUpdateTimeMs(record, System.currentTimeMillis());
            }

            record.setAudiblyAlerted((buzz || beep)
                    && getPolitenessState(record) != PolitenessStrategy.POLITE_STATE_MUTED);
        } else {
            record.setAudiblyAlerted(buzz || beep);
        }
        return buzzBeepBlinkLoggingCode;
    }
@@ -678,7 +682,7 @@ public final class NotificationAttentionHelper {

        // The user can choose to apply cooldown for all apps/conversations only from the
        // Settings app
        if (!mNotificationCooldownApplyToAll && record.getChannel().getConversationId() == null) {
        if (!mNotificationCooldownApplyToAll && !record.isConversation()) {
            return false;
        }

@@ -1203,7 +1207,7 @@ public final class NotificationAttentionHelper {
            setLastNotificationUpdateTimeMs(record, 0);
        }

        public final @PolitenessState int getPolitenessState(final NotificationRecord record) {
        public @PolitenessState int getPolitenessState(final NotificationRecord record) {
            return mVolumeStates.getOrDefault(getChannelKey(record), POLITE_STATE_DEFAULT);
        }

@@ -1364,7 +1368,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);
                }

                if (DEBUG) {
                    Log.i(TAG,
@@ -1379,6 +1388,26 @@ public final class NotificationAttentionHelper {
            mAppStrategy.onNotificationPosted(record);
        }

        @PolitenessState int getNextState(@PolitenessState final int currState,
                final long timeSinceLastNotif, final NotificationRecord record) {
            // Mute all except priority conversations
            if (!isAvalancheExempted(record)) {
                return POLITE_STATE_MUTED;
            }
            if (isAvalancheExemptedFullVolume(record)) {
                return POLITE_STATE_DEFAULT;
            }
            return getNextState(currState, timeSinceLastNotif);
        }

        public @PolitenessState int getPolitenessState(final NotificationRecord record) {
            if (isAvalancheActive()) {
                return super.getPolitenessState(record);
            } else {
                return mAppStrategy.getPolitenessState(record);
            }
        }

        @Override
        public float getSoundVolume(final NotificationRecord record) {
            if (isAvalancheActive()) {
@@ -1396,6 +1425,16 @@ public final class NotificationAttentionHelper {

        @Override
        String getChannelKey(final NotificationRecord record) {
            if (isAvalancheActive()) {
                if (Flags.politeNotificationsAttnUpdate()) {
                    // Treat high importance conversations independently
                    if (isAvalancheExempted(record)) {
                        return super.getChannelKey(record);
                    } else {
                        // Use one global key per user
                        return record.getSbn().getNormalizedUserId() + ":" + COMMON_KEY;
                    }
                } else {
                    // If the user explicitly changed the channel notification sound:
                    // handle as a separate channel
                    if (record.getChannel().hasUserSetSound()) {
@@ -1405,6 +1444,10 @@ public final class NotificationAttentionHelper {
                        return record.getSbn().getNormalizedUserId() + ":" + COMMON_KEY;
                    }
                }
            } else {
                return mAppStrategy.getChannelKey(record);
            }
        }

        @Override
        public void setLastNotificationUpdateTimeMs(NotificationRecord record,
@@ -1415,12 +1458,21 @@ public final class NotificationAttentionHelper {
        }

        long getLastNotificationUpdateTimeMs(final NotificationRecord record) {
            if (Flags.politeNotificationsAttnUpdate()) {
                // Mute all except priority conversations
                if (isAvalancheExempted(record)) {
                    return super.getLastNotificationUpdateTimeMs(record);
                } else {
                    return mLastNotificationTimestamp;
                }
            } else {
                if (record.getChannel().hasUserSetSound()) {
                    return super.getLastNotificationUpdateTimeMs(record);
                } else {
                    return mLastNotificationTimestamp;
                }
            }
        }

        @Override
        void setApplyCooldownPerPackage(boolean applyPerPackage) {
@@ -1445,6 +1497,51 @@ public final class NotificationAttentionHelper {
        void setTriggerTimeMs(long timestamp) {
            mLastAvalancheTriggerTimestamp = timestamp;
        }

        private boolean isAvalancheExemptedFullVolume(final NotificationRecord record) {
            // important conversation
            if (record.isConversation()
                    && (record.getImportance() > NotificationManager.IMPORTANCE_DEFAULT
                    || record.getChannel().isImportantConversation())) {
                return true;
            }

            // call notification
            if (record.getNotification().isStyle(Notification.CallStyle.class)) {
                return true;
            }

            // alarm/reminder
            final String category = record.getNotification().category;
            if (Notification.CATEGORY_REMINDER.equals(category)
                    || Notification.CATEGORY_EVENT.equals(category)) {
                return true;
            }

            return false;
        }

        private boolean isAvalancheExempted(final NotificationRecord record) {
            if (isAvalancheExemptedFullVolume(record)) {
                return true;
            }

            // recent conversation
            if (record.isConversation()
                    && record.getNotification().when > mLastAvalancheTriggerTimestamp) {
                return true;
            }

            if (record.getNotification().fullScreenIntent != null) {
                return true;
            }

            if (record.getNotification().isColorized()) {
                return true;
            }

            return false;
        }
    }

    //======================  Observers  =============================
+8 −1
Original line number Diff line number Diff line
@@ -79,3 +79,10 @@ flag {
    purpose: PURPOSE_BUGFIX
  }
}

flag {
  name: "polite_notifications_attn_update"
  namespace: "systemui"
  description: "This flag controls the polite notification attention behavior updates as per UXR feedback"
  bug: "270456865"
}
+159 −8
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.UserInfo;
import android.graphics.Color;
import android.graphics.drawable.Icon;
@@ -212,7 +213,9 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
        // TODO (b/291907312): remove feature flag
        // Disable feature flags by default. Tests should enable as needed.
        mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS,
                Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS, Flags.FLAG_VIBRATE_WHILE_UNLOCKED);
                Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS,
                Flags.FLAG_VIBRATE_WHILE_UNLOCKED,
                Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);

        mService = spy(new NotificationManagerService(getContext(), mNotificationRecordLogger,
            mNotificationInstanceIdSequence));
@@ -410,8 +413,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
            boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
            boolean isLeanback, UserHandle userHandle) {
        return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, defaultVibration,
                defaultSound, defaultLights, groupKey, groupAlertBehavior, isLeanback, userHandle,
                mPkg);
                defaultSound, defaultLights, groupKey, groupAlertBehavior, isLeanback, false,
                userHandle, mPkg);
    }

    private NotificationRecord getNotificationRecord(int id,
@@ -419,6 +422,16 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
            boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
            boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
            boolean isLeanback, UserHandle userHandle, String packageName) {
        return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, defaultVibration,
                defaultSound, defaultLights, groupKey, groupAlertBehavior, isLeanback, false,
                userHandle, packageName);
    }

    private NotificationRecord getNotificationRecord(int id,
            boolean insistent, boolean once,
            boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
            boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
            boolean isLeanback, boolean isConversation, UserHandle userHandle, String packageName) {

        final Builder builder = new Builder(getContext())
            .setContentTitle("foo")
@@ -426,6 +439,10 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
            .setPriority(Notification.PRIORITY_HIGH)
            .setOnlyAlertOnce(once);

        if (isConversation) {
            builder.setStyle(new Notification.MessagingStyle("test user"));
        }

        int defaults = 0;
        if (noisy) {
            if (defaultSound) {
@@ -485,6 +502,19 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
        return r;
    }

    private NotificationRecord getConversationNotificationRecord(int id,
            boolean insistent, boolean once,
            boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
            boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
            boolean isLeanback, UserHandle userHandle, String packageName, String shortcutId) {
        NotificationRecord r = getNotificationRecord(id, insistent, once, noisy, buzzy, lights,
                defaultVibration, defaultSound, defaultLights, groupKey, groupAlertBehavior,
                isLeanback, true, userHandle, packageName);
        ShortcutInfo.Builder sb = new ShortcutInfo.Builder(getContext());
        r.setShortcutInfo(sb.setId(shortcutId).build());
        return r;
    }

    //
    // Convenience functions for interacting with mocks
    //
@@ -2057,12 +2087,14 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {

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

        // update should beep at 50% volume
        r.isUpdate = true;
        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
        verifyBeepVolume(0.5f);
        assertNotEquals(-1, r.getLastAudiblyAlertedMs());

        // 2nd update should beep at 0% volume
        Mockito.reset(mRingtonePlayer);
@@ -2070,7 +2102,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
        verifyBeepVolume(0.0f);

        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
        assertEquals(-1, r.getLastAudiblyAlertedMs());
    }

    @Test
@@ -2091,6 +2123,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {

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

        // Use different package for next notifications
@@ -2101,6 +2134,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
        // update should beep at 50% volume
        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
        verifyBeepVolume(0.5f);
        assertNotEquals(-1, r2.getLastAudiblyAlertedMs());

        // Use different package for next notifications
        NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
@@ -2113,7 +2147,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
        verifyBeepVolume(0.0f);

        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
        assertEquals(-1, r3.getLastAudiblyAlertedMs());
    }

    @Test
@@ -2157,6 +2191,117 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
    }

    @Test
    public void testBeepVolume_politeNotif_AvalancheStrategy_AttnUpdate() 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();

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

        // Use different package for next notifications
        NotificationRecord r2 = getNotificationRecord(mId, false /* insistent */, false /* once */,
                true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
                false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");

        // update should beep at 0% volume
        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
        assertEquals(-1, r2.getLastAudiblyAlertedMs());
        verifyBeepVolume(0.0f);

        // Use different package for next notifications
        NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
                true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
                false, null, Notification.GROUP_ALERT_ALL, false, mUser, "yetAnotherPkg");

        // 2nd update should beep at 0% volume
        Mockito.reset(mRingtonePlayer);
        mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
        verifyBeepVolume(0.0f);

        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
        assertEquals(-1, r3.getLastAudiblyAlertedMs());
    }

    @Test
    public void testBeepVolume_politeNotif_AvalancheStrategy_exempt_AttnUpdate()
            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();
        r.getNotification().category = Notification.CATEGORY_EVENT;

        // Should beep at 100% volume
        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
        verifyBeepVolume(1.0f);
        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
        Mockito.reset(mRingtonePlayer);

        // Use different package for next notifications
        NotificationRecord r2 = getConversationNotificationRecord(mId, false /* insistent */,
                false /* once */, true /* noisy */, false /* buzzy*/, false /* lights */, true,
                true, false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg",
                "shortcut");

        // Should beep at 100% volume
        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
        assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
        verifyBeepVolume(1.0f);

        // Use different package for next notifications
        mChannel = new NotificationChannel("test3", "test3", IMPORTANCE_DEFAULT);
        NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
                true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
                false, null, Notification.GROUP_ALERT_ALL, false, mUser, "yetAnotherPkg");

        r3.getNotification().category = Notification.CATEGORY_REMINDER;

        // Should beep at 100% volume
        Mockito.reset(mRingtonePlayer);
        mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
        assertNotEquals(-1, r3.getLastAudiblyAlertedMs());
        verifyBeepVolume(1.0f);

        // Same package as r3 for next notifications
        NotificationRecord r4 = getConversationNotificationRecord(mId, false /* insistent */,
                false /* once */, true /* noisy */, false /* buzzy*/, false /* lights */, true,
                true, false, null, Notification.GROUP_ALERT_ALL, false, mUser, "yetAnotherPkg",
                "shortcut");

        // 2nd update should beep at 50% volume
        Mockito.reset(mRingtonePlayer);
        mAttentionHelper.buzzBeepBlinkLocked(r4, DEFAULT_SIGNALS);
        verifyBeepVolume(0.5f);

        verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
        assertNotEquals(-1, r4.getLastAudiblyAlertedMs());
    }

    @Test
    public void testBeepVolume_politeNotif_applyPerApp() throws Exception {
        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
@@ -2181,11 +2326,13 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
        // update should beep at 50% volume
        NotificationRecord r2 = getBeepyNotification();
        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
        assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
        verifyBeepVolume(0.5f);

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

        // Use different package for next notifications
@@ -2199,7 +2346,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
        verifyBeepVolume(1.0f);

        verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
        assertNotEquals(-1, r3.getLastAudiblyAlertedMs());
    }

    @Test
@@ -2227,11 +2374,13 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
        // 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 50% volume
        Mockito.reset(mRingtonePlayer);
        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
        assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
        verifyBeepVolume(0.5f);

        // Use different package for next notifications
@@ -2246,7 +2395,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
        verifyBeepVolume(1.0f);

        verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
        assertNotEquals(-1, r3.getLastAudiblyAlertedMs());
    }

    @Test
@@ -2355,12 +2504,14 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {

        // set up internal state
        mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
        Mockito.reset(mRingtonePlayer);

        // update should beep at 50% volume
        r.isUpdate = true;
        mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
        verifyBeepVolume(0.5f);
        assertNotEquals(-1, r.getLastAudiblyAlertedMs());

        // 2nd update should beep at 0% volume
        Mockito.reset(mRingtonePlayer);
@@ -2368,7 +2519,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
        verifyBeepVolume(0.0f);

        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
        assertEquals(-1, r.getLastAudiblyAlertedMs());
    }

    @Test