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

Commit 9775cfa3 authored by Julia Reynolds's avatar Julia Reynolds Committed by Android (Google) Code Review
Browse files

Merge "Hold classifications for noisy notifications" into main

parents 9889cd23 25cf5547
Loading
Loading
Loading
Loading
+87 −19
Original line number Diff line number Diff line
@@ -20,8 +20,14 @@ import static android.service.notification.Adjustment.KEY_UNCLASSIFY;
import static android.service.notification.Flags.notificationForceGrouping;

import android.content.Context;
import android.util.ArraySet;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.List;

/**
 * Applies adjustments from the group helper and notification assistant
 */
@@ -30,6 +36,14 @@ public class NotificationAdjustmentExtractor implements NotificationSignalExtrac
    private static final boolean DBG = false;
    private GroupHelper mGroupHelper;

    /** Length of time (in milliseconds) that a noisy notification will stay in its non-bundled
     * classification.
     */
    @VisibleForTesting
    static final long HANG_TIME_MS = 30000;

    @VisibleForTesting
    InjectedTime mInjectedTimeMs = null;

    public void initialize(Context ctx, NotificationUsageStats usageStats) {
        if (DBG) Slog.d(TAG, "Initializing  " + getClass().getSimpleName() + ".");
@@ -43,12 +57,73 @@ public class NotificationAdjustmentExtractor implements NotificationSignalExtrac

        final boolean hasAdjustedClassification = record.hasAdjustment(KEY_TYPE);
        final boolean removedClassification = record.hasAdjustment(KEY_UNCLASSIFY);

        if (Flags.showNoisyBundledNotifications()
                && android.service.notification.Flags.notificationClassification()
                && hasAdjustedClassification && record.getLastAudiblyAlertedMs() > 0) {
            record.applyAdjustments(new ArraySet<>(new String[] {KEY_TYPE}));

            return getClassificationReconsideration(record);
        }

        record.applyAdjustments();

        if (notificationForceGrouping()
                && android.service.notification.Flags.notificationClassification()) {
            // Classification adjustments trigger regrouping
            if (mGroupHelper != null && (hasAdjustedClassification || removedClassification)) {
                return getRegroupReconsideration(
                        record, hasAdjustedClassification, removedClassification);
            }
        }

        return null;
    }

    @Override
    public void setConfig(RankingConfig config) {
        // config is not used
    }

    @Override
    public void setZenHelper(ZenModeHelper helper) {

    }

    @Override
    public void setGroupHelper(GroupHelper groupHelper) {
        mGroupHelper = groupHelper;
    }

    private long getCurrentTime() {
        if (mInjectedTimeMs != null) {
            return mInjectedTimeMs.getCurrentTimeMillis();
        }
        return System.currentTimeMillis();
    }

    private RankingReconsideration getClassificationReconsideration(NotificationRecord record) {
        return new RankingReconsideration(record.getKey(), HANG_TIME_MS) {
            @Override
            public void work() {
                // pass
            }

            @Override
            public void applyChangesLocked(NotificationRecord record) {
                if ((getCurrentTime() - record.getLastAudiblyAlertedMs()) >= HANG_TIME_MS) {
                    record.applyAdjustments();
                    getRegroupReconsideration(record, true, false).applyChangesLocked(record);
                }
            }
        };
    }

    // The notification channel of the record has changed such that it's now moving to a new
    // UI section. We need to change the record's grouping to make sure it's not in a group
    // for the wrong section
    private RankingReconsideration getRegroupReconsideration(NotificationRecord record,
            boolean hasAdjustedClassification, boolean removedClassification) {
        return new RankingReconsideration(record.getKey(), 0) {
            @Override
            public void work() {
@@ -69,23 +144,16 @@ public class NotificationAdjustmentExtractor implements NotificationSignalExtrac
            }
        };
    }
        }

        return null;
    }
    static class InjectedTime {
        private final long mCurrentTimeMillis;

    @Override
    public void setConfig(RankingConfig config) {
        // config is not used
        InjectedTime(long time) {
            mCurrentTimeMillis = time;
        }

    @Override
    public void setZenHelper(ZenModeHelper helper) {

        long getCurrentTimeMillis() {
            return mCurrentTimeMillis;
        }

    @Override
    public void setGroupHelper(GroupHelper groupHelper) {
        mGroupHelper = groupHelper;
    }
}
+128 −51
Original line number Diff line number Diff line
@@ -21,7 +21,20 @@ 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.IMPORTANCE_UNSPECIFIED;
import static android.service.notification.Adjustment.KEY_CONTEXTUAL_ACTIONS;
import static android.service.notification.Adjustment.KEY_GROUP_KEY;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_IMPORTANCE_PROPOSAL;
import static android.service.notification.Adjustment.KEY_NOT_CONVERSATION;
import static android.service.notification.Adjustment.KEY_PEOPLE;
import static android.service.notification.Adjustment.KEY_RANKING_SCORE;
import static android.service.notification.Adjustment.KEY_SENSITIVE_CONTENT;
import static android.service.notification.Adjustment.KEY_SNOOZE_CRITERIA;
import static android.service.notification.Adjustment.KEY_SUMMARIZATION;
import static android.service.notification.Adjustment.KEY_TEXT_REPLIES;
import static android.service.notification.Adjustment.KEY_TYPE;
import static android.service.notification.Adjustment.KEY_UNCLASSIFY;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;

@@ -708,6 +721,13 @@ public final class NotificationRecord {
                this.getSbn().getNotification());
    }

    @VisibleForTesting
    int getPendingAdjustmentCount() {
        synchronized (mAdjustments) {
            return mAdjustments.size();
        }
    }

    public boolean hasAdjustment(String key) {
        synchronized (mAdjustments) {
            for (Adjustment adjustment : mAdjustments) {
@@ -726,113 +746,160 @@ public final class NotificationRecord {
    }

    public void applyAdjustments() {
        long now = System.currentTimeMillis();
        applyAdjustments(new ArraySet<>());
    }

    public void applyAdjustments(@NonNull ArraySet<String> keysToSkip) {
        synchronized (mAdjustments) {
            for (Adjustment adjustment: mAdjustments) {
            for (int i = mAdjustments.size() - 1; i >= 0; i--) {
                Adjustment adjustment = mAdjustments.get(i);
                Bundle signals = adjustment.getSignals();
                if (signals.containsKey(Adjustment.KEY_PEOPLE)) {
                if (signals.containsKey(KEY_PEOPLE) && !keysToSkip.contains(KEY_PEOPLE)) {
                    final ArrayList<String> people =
                            adjustment.getSignals().getStringArrayList(Adjustment.KEY_PEOPLE);
                            adjustment.getSignals().getStringArrayList(KEY_PEOPLE);
                    setPeopleOverride(people);
                    EventLogTags.writeNotificationAdjusted(
                            getKey(), Adjustment.KEY_PEOPLE, people.toString());
                    EventLogTags.writeNotificationAdjusted(getKey(), KEY_PEOPLE, people.toString());
                    if (com.android.server.notification.Flags.showNoisyBundledNotifications()) {
                        signals.remove(KEY_PEOPLE);
                    }
                if (signals.containsKey(Adjustment.KEY_SNOOZE_CRITERIA)) {
                }
                if (signals.containsKey(KEY_SNOOZE_CRITERIA)
                        && !keysToSkip.contains(KEY_SNOOZE_CRITERIA)) {
                    final ArrayList<SnoozeCriterion> snoozeCriterionList =
                            adjustment.getSignals().getParcelableArrayList(
                                    Adjustment.KEY_SNOOZE_CRITERIA,
                                    KEY_SNOOZE_CRITERIA,
                                    android.service.notification.SnoozeCriterion.class);
                    setSnoozeCriteria(snoozeCriterionList);
                    EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_SNOOZE_CRITERIA,
                    EventLogTags.writeNotificationAdjusted(getKey(), KEY_SNOOZE_CRITERIA,
                            snoozeCriterionList.toString());
                    if (com.android.server.notification.Flags.showNoisyBundledNotifications()) {
                        signals.remove(KEY_SNOOZE_CRITERIA);
                    }
                }
                if (signals.containsKey(Adjustment.KEY_GROUP_KEY)) {
                if (signals.containsKey(KEY_GROUP_KEY) && !keysToSkip.contains(KEY_GROUP_KEY)) {
                    final String groupOverrideKey =
                            adjustment.getSignals().getString(Adjustment.KEY_GROUP_KEY);
                            adjustment.getSignals().getString(KEY_GROUP_KEY);
                    setOverrideGroupKey(groupOverrideKey);
                    EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_GROUP_KEY,
                    EventLogTags.writeNotificationAdjusted(getKey(), KEY_GROUP_KEY,
                            groupOverrideKey);
                    if (com.android.server.notification.Flags.showNoisyBundledNotifications()) {
                        signals.remove(KEY_GROUP_KEY);
                    }
                if (signals.containsKey(Adjustment.KEY_USER_SENTIMENT)) {
                }
                if (signals.containsKey(KEY_USER_SENTIMENT)
                        && !keysToSkip.contains(KEY_USER_SENTIMENT)) {
                    // Only allow user sentiment update from assistant if user hasn't already
                    // expressed a preference for this channel
                    if (!mIsAppImportanceLocked
                            && (getChannel().getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0) {
                        setUserSentiment(adjustment.getSignals().getInt(
                                Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEUTRAL));
                                KEY_USER_SENTIMENT, USER_SENTIMENT_NEUTRAL));
                        EventLogTags.writeNotificationAdjusted(getKey(),
                                Adjustment.KEY_USER_SENTIMENT,
                                KEY_USER_SENTIMENT,
                                Integer.toString(getUserSentiment()));
                        if (com.android.server.notification.Flags.showNoisyBundledNotifications()) {
                            signals.remove(KEY_USER_SENTIMENT);
                        }
                    }
                }
                if (signals.containsKey(Adjustment.KEY_CONTEXTUAL_ACTIONS)) {
                if (signals.containsKey(KEY_CONTEXTUAL_ACTIONS)
                        && !keysToSkip.contains(KEY_CONTEXTUAL_ACTIONS)) {
                    setSystemGeneratedSmartActions(
                            signals.getParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS,
                            signals.getParcelableArrayList(KEY_CONTEXTUAL_ACTIONS,
                                    android.app.Notification.Action.class));
                    EventLogTags.writeNotificationAdjusted(getKey(),
                            Adjustment.KEY_CONTEXTUAL_ACTIONS,
                            getSystemGeneratedSmartActions().toString());
                            KEY_CONTEXTUAL_ACTIONS, getSystemGeneratedSmartActions().toString());
                    if (com.android.server.notification.Flags.showNoisyBundledNotifications()) {
                        signals.remove(KEY_CONTEXTUAL_ACTIONS);
                    }
                }
                if (signals.containsKey(Adjustment.KEY_TEXT_REPLIES)) {
                    setSmartReplies(signals.getCharSequenceArrayList(Adjustment.KEY_TEXT_REPLIES));
                    EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_TEXT_REPLIES,
                if (signals.containsKey(KEY_TEXT_REPLIES)
                        && !keysToSkip.contains(KEY_TEXT_REPLIES)) {
                    setSmartReplies(signals.getCharSequenceArrayList(KEY_TEXT_REPLIES));
                    EventLogTags.writeNotificationAdjusted(getKey(), KEY_TEXT_REPLIES,
                            getSmartReplies().toString());
                    if (com.android.server.notification.Flags.showNoisyBundledNotifications()) {
                        signals.remove(KEY_TEXT_REPLIES);
                    }
                if (signals.containsKey(Adjustment.KEY_IMPORTANCE)) {
                    int importance = signals.getInt(Adjustment.KEY_IMPORTANCE);
                }
                if (signals.containsKey(KEY_IMPORTANCE) && !keysToSkip.contains(KEY_IMPORTANCE)) {
                    int importance = signals.getInt(KEY_IMPORTANCE);
                    importance = Math.max(IMPORTANCE_UNSPECIFIED, importance);
                    importance = Math.min(IMPORTANCE_HIGH, importance);
                    setAssistantImportance(importance);
                    EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_IMPORTANCE,
                    EventLogTags.writeNotificationAdjusted(getKey(), KEY_IMPORTANCE,
                            Integer.toString(importance));
                    if (com.android.server.notification.Flags.showNoisyBundledNotifications()) {
                        signals.remove(KEY_IMPORTANCE);
                    }
                }
                if (signals.containsKey(Adjustment.KEY_RANKING_SCORE)) {
                    mRankingScore = signals.getFloat(Adjustment.KEY_RANKING_SCORE);
                    EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_RANKING_SCORE,
                if (signals.containsKey(KEY_RANKING_SCORE)
                        && !keysToSkip.contains(KEY_RANKING_SCORE)) {
                    mRankingScore = signals.getFloat(KEY_RANKING_SCORE);
                    EventLogTags.writeNotificationAdjusted(getKey(), KEY_RANKING_SCORE,
                            Float.toString(mRankingScore));
                    if (com.android.server.notification.Flags.showNoisyBundledNotifications()) {
                        signals.remove(KEY_RANKING_SCORE);
                    }
                if (signals.containsKey(Adjustment.KEY_NOT_CONVERSATION)) {
                    mIsNotConversationOverride = signals.getBoolean(
                            Adjustment.KEY_NOT_CONVERSATION);
                }
                if (signals.containsKey(KEY_NOT_CONVERSATION)
                        && !keysToSkip.contains(KEY_NOT_CONVERSATION)) {
                    mIsNotConversationOverride = signals.getBoolean(KEY_NOT_CONVERSATION);
                    EventLogTags.writeNotificationAdjusted(getKey(),
                            Adjustment.KEY_NOT_CONVERSATION,
                            Boolean.toString(mIsNotConversationOverride));
                            KEY_NOT_CONVERSATION, Boolean.toString(mIsNotConversationOverride));
                    if (com.android.server.notification.Flags.showNoisyBundledNotifications()) {
                        signals.remove(KEY_NOT_CONVERSATION);
                    }
                }
                if (signals.containsKey(Adjustment.KEY_IMPORTANCE_PROPOSAL)) {
                    mProposedImportance = signals.getInt(Adjustment.KEY_IMPORTANCE_PROPOSAL);
                if (signals.containsKey(KEY_IMPORTANCE_PROPOSAL)
                        && !keysToSkip.contains(KEY_IMPORTANCE_PROPOSAL)) {
                    mProposedImportance = signals.getInt(KEY_IMPORTANCE_PROPOSAL);
                    EventLogTags.writeNotificationAdjusted(getKey(),
                            Adjustment.KEY_IMPORTANCE_PROPOSAL,
                            KEY_IMPORTANCE_PROPOSAL,
                            Integer.toString(mProposedImportance));
                    if (com.android.server.notification.Flags.showNoisyBundledNotifications()) {
                        signals.remove(KEY_IMPORTANCE_PROPOSAL);
                    }
                if (signals.containsKey(Adjustment.KEY_SENSITIVE_CONTENT)) {
                    mSensitiveContent = signals.getBoolean(Adjustment.KEY_SENSITIVE_CONTENT);
                }
                if (signals.containsKey(KEY_SENSITIVE_CONTENT)
                        && !keysToSkip.contains(KEY_SENSITIVE_CONTENT)) {
                    mSensitiveContent = signals.getBoolean(KEY_SENSITIVE_CONTENT);
                    EventLogTags.writeNotificationAdjusted(getKey(),
                            Adjustment.KEY_SENSITIVE_CONTENT,
                            Boolean.toString(mSensitiveContent));
                            KEY_SENSITIVE_CONTENT, Boolean.toString(mSensitiveContent));
                    if (com.android.server.notification.Flags.showNoisyBundledNotifications()) {
                        signals.remove(KEY_SENSITIVE_CONTENT);
                    }
                }
                if (android.service.notification.Flags.notificationClassification()) {
                    if (signals.containsKey(Adjustment.KEY_TYPE)) {
                    if (signals.containsKey(KEY_TYPE) && !keysToSkip.contains(KEY_TYPE)) {
                        // Store original channel visibility before re-assigning channel
                        if (!NotificationChannel.SYSTEM_RESERVED_IDS.contains(mChannel.getId())) {
                            setOriginalChannelVisibility(mChannel.getLockscreenVisibility());
                        }
                        updateNotificationChannel(signals.getParcelable(Adjustment.KEY_TYPE,
                        updateNotificationChannel(signals.getParcelable(KEY_TYPE,
                                NotificationChannel.class));
                        EventLogTags.writeNotificationAdjusted(getKey(),
                                Adjustment.KEY_TYPE,
                                mChannel.getId());
                        EventLogTags.writeNotificationAdjusted(
                                getKey(), KEY_TYPE, mChannel.getId());
                        if (com.android.server.notification.Flags.showNoisyBundledNotifications()) {
                            signals.remove(KEY_TYPE);
                        }
                    }
                    if (signals.containsKey(Adjustment.KEY_UNCLASSIFY)) {
                    if (signals.containsKey(KEY_UNCLASSIFY)
                            && !keysToSkip.contains(KEY_UNCLASSIFY)) {
                        // reset original channel visibility as we're returning to the original
                        setOriginalChannelVisibility(NotificationManager.VISIBILITY_NO_OVERRIDE);
                        updateNotificationChannel(signals.getParcelable(Adjustment.KEY_UNCLASSIFY,
                        updateNotificationChannel(signals.getParcelable(KEY_UNCLASSIFY,
                                NotificationChannel.class));
                        EventLogTags.writeNotificationAdjusted(getKey(),
                                Adjustment.KEY_UNCLASSIFY, mChannel.getId());
                                KEY_UNCLASSIFY, mChannel.getId());
                        if (com.android.server.notification.Flags.showNoisyBundledNotifications()) {
                            signals.remove(KEY_UNCLASSIFY);
                        }
                    }
                }
                if ((android.app.Flags.nmSummarizationUi() || android.app.Flags.nmSummarization())
                        && signals.containsKey(KEY_SUMMARIZATION)) {
                        && signals.containsKey(KEY_SUMMARIZATION)
                        && !keysToSkip.contains(KEY_SUMMARIZATION)) {
                    CharSequence summary = signals.getCharSequence(KEY_SUMMARIZATION,
                            signals.getString(KEY_SUMMARIZATION));
                    if (summary != null) {
@@ -842,15 +909,25 @@ public final class NotificationRecord {
                    }
                    EventLogTags.writeNotificationAdjusted(getKey(),
                            KEY_SUMMARIZATION, Boolean.toString(mSummarization != null));
                    if (com.android.server.notification.Flags.showNoisyBundledNotifications()) {
                        signals.remove(KEY_SUMMARIZATION);
                    }
                }
                if (!signals.isEmpty() && adjustment.getIssuer() != null) {
                    mAdjustmentIssuer = adjustment.getIssuer();
                }
                if (com.android.server.notification.Flags.showNoisyBundledNotifications()) {
                    if (adjustment.getSignals().isEmpty()) {
                        mAdjustments.remove(i);
                    }
            // We have now gotten all the information out of the adjustments and can forget them.
                }
            }
            if (!com.android.server.notification.Flags.showNoisyBundledNotifications()) {
                // We have now gotten all the information out of the adjustments and can forget them
                mAdjustments.clear();
            }
        }
    }

    String getAdjustmentIssuer() {
        return mAdjustmentIssuer;
+2 −3
Original line number Diff line number Diff line
@@ -20,9 +20,8 @@ import android.content.Context;
import com.android.internal.compat.IPlatformCompat;

/**
 * Extracts signals that will be useful to the {@link NotificationComparator} and caches them
 *  on the {@link NotificationRecord} object. These annotations will
 *  not be passed on to {@link android.service.notification.NotificationListenerService}s.
 * Extracts signals that will be useful to provide to notification listeners and caches them
 * on the {@link NotificationRecord} object.
 *
 *  If you add a new Extractor be sure to add it to R.array.config_notificationSignalExtractors.
 */
+0 −1
Original line number Diff line number Diff line
@@ -15,7 +15,6 @@
 */
package com.android.server.notification;

import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.text.TextUtils.formatSimple;

import android.annotation.NonNull;
+10 −0
Original line number Diff line number Diff line
@@ -230,3 +230,13 @@ flag {
    purpose: PURPOSE_BUGFIX
  }
}

flag {
  name: "show_noisy_bundled_notifications"
  namespace: "notifications"
  description: "If a classification arrived late and the notification made noise, show the notification as unclassified temporarily"
  bug: "430573835"
  metadata {
      purpose: PURPOSE_BUGFIX
    }
}
Loading