DO NOT MERGE Allow NAS to suggest importances for notifications
(In addition to the existing 'change importance' adjustment). Fixes: 262578968 Test: NotificationListenerServiceTest Test: NotificationRecordExtractorDataTest Test: NotificationRecordTest TesT: CTS in same topic Change-Id: I510414cba256cfb9fb19fa659947e7737f6fd4e1
This commit is contained in:
@@ -52,7 +52,7 @@ public final class Adjustment implements Parcelable {
|
|||||||
/** @hide */
|
/** @hide */
|
||||||
@StringDef (prefix = { "KEY_" }, value = {
|
@StringDef (prefix = { "KEY_" }, value = {
|
||||||
KEY_CONTEXTUAL_ACTIONS, KEY_GROUP_KEY, KEY_IMPORTANCE, KEY_PEOPLE, KEY_SNOOZE_CRITERIA,
|
KEY_CONTEXTUAL_ACTIONS, KEY_GROUP_KEY, KEY_IMPORTANCE, KEY_PEOPLE, KEY_SNOOZE_CRITERIA,
|
||||||
KEY_TEXT_REPLIES, KEY_USER_SENTIMENT
|
KEY_TEXT_REPLIES, KEY_USER_SENTIMENT, KEY_IMPORTANCE_PROPOSAL
|
||||||
})
|
})
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
public @interface Keys {}
|
public @interface Keys {}
|
||||||
@@ -121,6 +121,19 @@ public final class Adjustment implements Parcelable {
|
|||||||
*/
|
*/
|
||||||
public static final String KEY_IMPORTANCE = "key_importance";
|
public static final String KEY_IMPORTANCE = "key_importance";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Weaker than {@link #KEY_IMPORTANCE}, this adjustment suggests an importance rather than
|
||||||
|
* mandates an importance change.
|
||||||
|
*
|
||||||
|
* A notification listener can interpet this suggestion to show the user a prompt to change
|
||||||
|
* notification importance for the notification (or type, or app) moving forward.
|
||||||
|
*
|
||||||
|
* Data type: int, one of importance values e.g.
|
||||||
|
* {@link android.app.NotificationManager#IMPORTANCE_MIN}.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static final String KEY_IMPORTANCE_PROPOSAL = "key_importance_proposal";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data type: float, a ranking score from 0 (lowest) to 1 (highest).
|
* Data type: float, a ranking score from 0 (lowest) to 1 (highest).
|
||||||
* Used to rank notifications inside that fall under the same classification (i.e. alerting,
|
* Used to rank notifications inside that fall under the same classification (i.e. alerting,
|
||||||
|
|||||||
@@ -1711,6 +1711,8 @@ public abstract class NotificationListenerService extends Service {
|
|||||||
private ShortcutInfo mShortcutInfo;
|
private ShortcutInfo mShortcutInfo;
|
||||||
private @RankingAdjustment int mRankingAdjustment;
|
private @RankingAdjustment int mRankingAdjustment;
|
||||||
private boolean mIsBubble;
|
private boolean mIsBubble;
|
||||||
|
// Notification assistant importance suggestion
|
||||||
|
private int mProposedImportance;
|
||||||
|
|
||||||
private static final int PARCEL_VERSION = 2;
|
private static final int PARCEL_VERSION = 2;
|
||||||
|
|
||||||
@@ -1748,6 +1750,7 @@ public abstract class NotificationListenerService extends Service {
|
|||||||
out.writeParcelable(mShortcutInfo, flags);
|
out.writeParcelable(mShortcutInfo, flags);
|
||||||
out.writeInt(mRankingAdjustment);
|
out.writeInt(mRankingAdjustment);
|
||||||
out.writeBoolean(mIsBubble);
|
out.writeBoolean(mIsBubble);
|
||||||
|
out.writeInt(mProposedImportance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
@@ -1786,6 +1789,7 @@ public abstract class NotificationListenerService extends Service {
|
|||||||
mShortcutInfo = in.readParcelable(cl, android.content.pm.ShortcutInfo.class);
|
mShortcutInfo = in.readParcelable(cl, android.content.pm.ShortcutInfo.class);
|
||||||
mRankingAdjustment = in.readInt();
|
mRankingAdjustment = in.readInt();
|
||||||
mIsBubble = in.readBoolean();
|
mIsBubble = in.readBoolean();
|
||||||
|
mProposedImportance = in.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1877,6 +1881,22 @@ public abstract class NotificationListenerService extends Service {
|
|||||||
return mRankingScore;
|
return mRankingScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the proposed importance provided by the {@link NotificationAssistantService}.
|
||||||
|
*
|
||||||
|
* This can be used to suggest that the user change the importance of this type of
|
||||||
|
* notification moving forward. A value of
|
||||||
|
* {@link NotificationManager#IMPORTANCE_UNSPECIFIED} means that the NAS has not recommended
|
||||||
|
* a change to the importance, and no UI should be shown to the user. See
|
||||||
|
* {@link Adjustment#KEY_IMPORTANCE_PROPOSAL}.
|
||||||
|
*
|
||||||
|
* @return the importance of the notification
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public @NotificationManager.Importance int getProposedImportance() {
|
||||||
|
return mProposedImportance;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the system has overridden the group key, then this will be non-null, and this
|
* If the system has overridden the group key, then this will be non-null, and this
|
||||||
* key should be used to bundle notifications.
|
* key should be used to bundle notifications.
|
||||||
@@ -2041,7 +2061,7 @@ public abstract class NotificationListenerService extends Service {
|
|||||||
boolean noisy, ArrayList<Notification.Action> smartActions,
|
boolean noisy, ArrayList<Notification.Action> smartActions,
|
||||||
ArrayList<CharSequence> smartReplies, boolean canBubble,
|
ArrayList<CharSequence> smartReplies, boolean canBubble,
|
||||||
boolean isTextChanged, boolean isConversation, ShortcutInfo shortcutInfo,
|
boolean isTextChanged, boolean isConversation, ShortcutInfo shortcutInfo,
|
||||||
int rankingAdjustment, boolean isBubble) {
|
int rankingAdjustment, boolean isBubble, int proposedImportance) {
|
||||||
mKey = key;
|
mKey = key;
|
||||||
mRank = rank;
|
mRank = rank;
|
||||||
mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
|
mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
|
||||||
@@ -2067,6 +2087,7 @@ public abstract class NotificationListenerService extends Service {
|
|||||||
mShortcutInfo = shortcutInfo;
|
mShortcutInfo = shortcutInfo;
|
||||||
mRankingAdjustment = rankingAdjustment;
|
mRankingAdjustment = rankingAdjustment;
|
||||||
mIsBubble = isBubble;
|
mIsBubble = isBubble;
|
||||||
|
mProposedImportance = proposedImportance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2107,7 +2128,8 @@ public abstract class NotificationListenerService extends Service {
|
|||||||
other.mIsConversation,
|
other.mIsConversation,
|
||||||
other.mShortcutInfo,
|
other.mShortcutInfo,
|
||||||
other.mRankingAdjustment,
|
other.mRankingAdjustment,
|
||||||
other.mIsBubble);
|
other.mIsBubble,
|
||||||
|
other.mProposedImportance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2166,7 +2188,8 @@ public abstract class NotificationListenerService extends Service {
|
|||||||
&& Objects.equals((mShortcutInfo == null ? 0 : mShortcutInfo.getId()),
|
&& Objects.equals((mShortcutInfo == null ? 0 : mShortcutInfo.getId()),
|
||||||
(other.mShortcutInfo == null ? 0 : other.mShortcutInfo.getId()))
|
(other.mShortcutInfo == null ? 0 : other.mShortcutInfo.getId()))
|
||||||
&& Objects.equals(mRankingAdjustment, other.mRankingAdjustment)
|
&& Objects.equals(mRankingAdjustment, other.mRankingAdjustment)
|
||||||
&& Objects.equals(mIsBubble, other.mIsBubble);
|
&& Objects.equals(mIsBubble, other.mIsBubble)
|
||||||
|
&& Objects.equals(mProposedImportance, other.mProposedImportance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -290,7 +290,8 @@ public class NotificationListener extends NotificationListenerWithPlugins implem
|
|||||||
false,
|
false,
|
||||||
null,
|
null,
|
||||||
0,
|
0,
|
||||||
false
|
false,
|
||||||
|
0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return ranking;
|
return ranking;
|
||||||
|
|||||||
@@ -127,6 +127,6 @@ class TargetSdkResolverTest : SysuiTestCase() {
|
|||||||
NotificationManager.IMPORTANCE_DEFAULT,
|
NotificationManager.IMPORTANCE_DEFAULT,
|
||||||
null, null,
|
null, null,
|
||||||
null, null, null, true, 0, false, -1, false, null, null, false, false,
|
null, null, null, true, 0, false, -1, false, null, null, false, false,
|
||||||
false, null, 0, false)
|
false, null, 0, false, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.systemui.statusbar;
|
package com.android.systemui.statusbar;
|
||||||
|
|
||||||
|
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
@@ -57,6 +59,7 @@ public class RankingBuilder {
|
|||||||
private ShortcutInfo mShortcutInfo = null;
|
private ShortcutInfo mShortcutInfo = null;
|
||||||
private int mRankingAdjustment = 0;
|
private int mRankingAdjustment = 0;
|
||||||
private boolean mIsBubble = false;
|
private boolean mIsBubble = false;
|
||||||
|
private int mProposedImportance = IMPORTANCE_UNSPECIFIED;
|
||||||
|
|
||||||
public RankingBuilder() {
|
public RankingBuilder() {
|
||||||
}
|
}
|
||||||
@@ -86,6 +89,7 @@ public class RankingBuilder {
|
|||||||
mShortcutInfo = ranking.getConversationShortcutInfo();
|
mShortcutInfo = ranking.getConversationShortcutInfo();
|
||||||
mRankingAdjustment = ranking.getRankingAdjustment();
|
mRankingAdjustment = ranking.getRankingAdjustment();
|
||||||
mIsBubble = ranking.isBubble();
|
mIsBubble = ranking.isBubble();
|
||||||
|
mProposedImportance = ranking.getProposedImportance();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Ranking build() {
|
public Ranking build() {
|
||||||
@@ -114,7 +118,8 @@ public class RankingBuilder {
|
|||||||
mIsConversation,
|
mIsConversation,
|
||||||
mShortcutInfo,
|
mShortcutInfo,
|
||||||
mRankingAdjustment,
|
mRankingAdjustment,
|
||||||
mIsBubble);
|
mIsBubble,
|
||||||
|
mProposedImportance);
|
||||||
return ranking;
|
return ranking;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,6 +219,11 @@ public class RankingBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RankingBuilder setProposedImportance(@Importance int importance) {
|
||||||
|
mProposedImportance = importance;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public RankingBuilder setUserSentiment(int userSentiment) {
|
public RankingBuilder setUserSentiment(int userSentiment) {
|
||||||
mUserSentiment = userSentiment;
|
mUserSentiment = userSentiment;
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -239,7 +239,6 @@ import android.service.notification.NotificationRankingUpdate;
|
|||||||
import android.service.notification.NotificationRecordProto;
|
import android.service.notification.NotificationRecordProto;
|
||||||
import android.service.notification.NotificationServiceDumpProto;
|
import android.service.notification.NotificationServiceDumpProto;
|
||||||
import android.service.notification.NotificationStats;
|
import android.service.notification.NotificationStats;
|
||||||
import android.service.notification.SnoozeCriterion;
|
|
||||||
import android.service.notification.StatusBarNotification;
|
import android.service.notification.StatusBarNotification;
|
||||||
import android.service.notification.ZenModeConfig;
|
import android.service.notification.ZenModeConfig;
|
||||||
import android.service.notification.ZenModeProto;
|
import android.service.notification.ZenModeProto;
|
||||||
@@ -8526,95 +8525,6 @@ public class NotificationManagerService extends SystemService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class NotificationRecordExtractorData {
|
|
||||||
// Class that stores any field in a NotificationRecord that can change via an extractor.
|
|
||||||
// Used to cache previous data used in a sort.
|
|
||||||
int mPosition;
|
|
||||||
int mVisibility;
|
|
||||||
boolean mShowBadge;
|
|
||||||
boolean mAllowBubble;
|
|
||||||
boolean mIsBubble;
|
|
||||||
NotificationChannel mChannel;
|
|
||||||
String mGroupKey;
|
|
||||||
ArrayList<String> mOverridePeople;
|
|
||||||
ArrayList<SnoozeCriterion> mSnoozeCriteria;
|
|
||||||
Integer mUserSentiment;
|
|
||||||
Integer mSuppressVisually;
|
|
||||||
ArrayList<Notification.Action> mSystemSmartActions;
|
|
||||||
ArrayList<CharSequence> mSmartReplies;
|
|
||||||
int mImportance;
|
|
||||||
|
|
||||||
// These fields may not trigger a reranking but diffs here may be logged.
|
|
||||||
float mRankingScore;
|
|
||||||
boolean mIsConversation;
|
|
||||||
|
|
||||||
NotificationRecordExtractorData(int position, int visibility, boolean showBadge,
|
|
||||||
boolean allowBubble, boolean isBubble, NotificationChannel channel, String groupKey,
|
|
||||||
ArrayList<String> overridePeople, ArrayList<SnoozeCriterion> snoozeCriteria,
|
|
||||||
Integer userSentiment, Integer suppressVisually,
|
|
||||||
ArrayList<Notification.Action> systemSmartActions,
|
|
||||||
ArrayList<CharSequence> smartReplies, int importance, float rankingScore,
|
|
||||||
boolean isConversation) {
|
|
||||||
mPosition = position;
|
|
||||||
mVisibility = visibility;
|
|
||||||
mShowBadge = showBadge;
|
|
||||||
mAllowBubble = allowBubble;
|
|
||||||
mIsBubble = isBubble;
|
|
||||||
mChannel = channel;
|
|
||||||
mGroupKey = groupKey;
|
|
||||||
mOverridePeople = overridePeople;
|
|
||||||
mSnoozeCriteria = snoozeCriteria;
|
|
||||||
mUserSentiment = userSentiment;
|
|
||||||
mSuppressVisually = suppressVisually;
|
|
||||||
mSystemSmartActions = systemSmartActions;
|
|
||||||
mSmartReplies = smartReplies;
|
|
||||||
mImportance = importance;
|
|
||||||
mRankingScore = rankingScore;
|
|
||||||
mIsConversation = isConversation;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns whether the provided NotificationRecord differs from the cached data in any way.
|
|
||||||
// Should be guarded by mNotificationLock; not annotated here as this class is static.
|
|
||||||
boolean hasDiffForRankingLocked(NotificationRecord r, int newPosition) {
|
|
||||||
return mPosition != newPosition
|
|
||||||
|| mVisibility != r.getPackageVisibilityOverride()
|
|
||||||
|| mShowBadge != r.canShowBadge()
|
|
||||||
|| mAllowBubble != r.canBubble()
|
|
||||||
|| mIsBubble != r.getNotification().isBubbleNotification()
|
|
||||||
|| !Objects.equals(mChannel, r.getChannel())
|
|
||||||
|| !Objects.equals(mGroupKey, r.getGroupKey())
|
|
||||||
|| !Objects.equals(mOverridePeople, r.getPeopleOverride())
|
|
||||||
|| !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria())
|
|
||||||
|| !Objects.equals(mUserSentiment, r.getUserSentiment())
|
|
||||||
|| !Objects.equals(mSuppressVisually, r.getSuppressedVisualEffects())
|
|
||||||
|| !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions())
|
|
||||||
|| !Objects.equals(mSmartReplies, r.getSmartReplies())
|
|
||||||
|| mImportance != r.getImportance();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns whether the NotificationRecord has a change from this data for which we should
|
|
||||||
// log an update. This method specifically targets fields that may be changed via
|
|
||||||
// adjustments from the assistant.
|
|
||||||
//
|
|
||||||
// Fields here are the union of things in NotificationRecordLogger.shouldLogReported
|
|
||||||
// and NotificationRecord.applyAdjustments.
|
|
||||||
//
|
|
||||||
// Should be guarded by mNotificationLock; not annotated here as this class is static.
|
|
||||||
boolean hasDiffForLoggingLocked(NotificationRecord r, int newPosition) {
|
|
||||||
return mPosition != newPosition
|
|
||||||
|| !Objects.equals(mChannel, r.getChannel())
|
|
||||||
|| !Objects.equals(mGroupKey, r.getGroupKey())
|
|
||||||
|| !Objects.equals(mOverridePeople, r.getPeopleOverride())
|
|
||||||
|| !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria())
|
|
||||||
|| !Objects.equals(mUserSentiment, r.getUserSentiment())
|
|
||||||
|| !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions())
|
|
||||||
|| !Objects.equals(mSmartReplies, r.getSmartReplies())
|
|
||||||
|| mImportance != r.getImportance()
|
|
||||||
|| !r.rankingScoreMatches(mRankingScore)
|
|
||||||
|| mIsConversation != r.isConversation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleRankingSort() {
|
void handleRankingSort() {
|
||||||
if (mRankingHelper == null) return;
|
if (mRankingHelper == null) return;
|
||||||
synchronized (mNotificationLock) {
|
synchronized (mNotificationLock) {
|
||||||
@@ -8640,7 +8550,8 @@ public class NotificationManagerService extends SystemService {
|
|||||||
r.getSmartReplies(),
|
r.getSmartReplies(),
|
||||||
r.getImportance(),
|
r.getImportance(),
|
||||||
r.getRankingScore(),
|
r.getRankingScore(),
|
||||||
r.isConversation());
|
r.isConversation(),
|
||||||
|
r.getProposedImportance());
|
||||||
extractorDataBefore.put(r.getKey(), extractorData);
|
extractorDataBefore.put(r.getKey(), extractorData);
|
||||||
mRankingHelper.extractSignals(r);
|
mRankingHelper.extractSignals(r);
|
||||||
}
|
}
|
||||||
@@ -9935,7 +9846,8 @@ public class NotificationManagerService extends SystemService {
|
|||||||
record.getRankingScore() == 0
|
record.getRankingScore() == 0
|
||||||
? RANKING_UNCHANGED
|
? RANKING_UNCHANGED
|
||||||
: (record.getRankingScore() > 0 ? RANKING_PROMOTED : RANKING_DEMOTED),
|
: (record.getRankingScore() > 0 ? RANKING_PROMOTED : RANKING_DEMOTED),
|
||||||
record.getNotification().isBubbleNotification()
|
record.getNotification().isBubbleNotification(),
|
||||||
|
record.getProposedImportance()
|
||||||
);
|
);
|
||||||
rankings.add(ranking);
|
rankings.add(ranking);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ public final class NotificationRecord {
|
|||||||
// Whether this notification record should have an update logged the next time notifications
|
// Whether this notification record should have an update logged the next time notifications
|
||||||
// are sorted.
|
// are sorted.
|
||||||
private boolean mPendingLogUpdate = false;
|
private boolean mPendingLogUpdate = false;
|
||||||
|
private int mProposedImportance = IMPORTANCE_UNSPECIFIED;
|
||||||
|
|
||||||
public NotificationRecord(Context context, StatusBarNotification sbn,
|
public NotificationRecord(Context context, StatusBarNotification sbn,
|
||||||
NotificationChannel channel) {
|
NotificationChannel channel) {
|
||||||
@@ -499,6 +500,8 @@ public final class NotificationRecord {
|
|||||||
pw.println(prefix + "mImportance="
|
pw.println(prefix + "mImportance="
|
||||||
+ NotificationListenerService.Ranking.importanceToString(mImportance));
|
+ NotificationListenerService.Ranking.importanceToString(mImportance));
|
||||||
pw.println(prefix + "mImportanceExplanation=" + getImportanceExplanation());
|
pw.println(prefix + "mImportanceExplanation=" + getImportanceExplanation());
|
||||||
|
pw.println(prefix + "mProposedImportance="
|
||||||
|
+ NotificationListenerService.Ranking.importanceToString(mProposedImportance));
|
||||||
pw.println(prefix + "mIsAppImportanceLocked=" + mIsAppImportanceLocked);
|
pw.println(prefix + "mIsAppImportanceLocked=" + mIsAppImportanceLocked);
|
||||||
pw.println(prefix + "mIntercept=" + mIntercept);
|
pw.println(prefix + "mIntercept=" + mIntercept);
|
||||||
pw.println(prefix + "mHidden==" + mHidden);
|
pw.println(prefix + "mHidden==" + mHidden);
|
||||||
@@ -738,6 +741,12 @@ public final class NotificationRecord {
|
|||||||
Adjustment.KEY_NOT_CONVERSATION,
|
Adjustment.KEY_NOT_CONVERSATION,
|
||||||
Boolean.toString(mIsNotConversationOverride));
|
Boolean.toString(mIsNotConversationOverride));
|
||||||
}
|
}
|
||||||
|
if (signals.containsKey(Adjustment.KEY_IMPORTANCE_PROPOSAL)) {
|
||||||
|
mProposedImportance = signals.getInt(Adjustment.KEY_IMPORTANCE_PROPOSAL);
|
||||||
|
EventLogTags.writeNotificationAdjusted(getKey(),
|
||||||
|
Adjustment.KEY_IMPORTANCE_PROPOSAL,
|
||||||
|
Integer.toString(mProposedImportance));
|
||||||
|
}
|
||||||
if (!signals.isEmpty() && adjustment.getIssuer() != null) {
|
if (!signals.isEmpty() && adjustment.getIssuer() != null) {
|
||||||
mAdjustmentIssuer = adjustment.getIssuer();
|
mAdjustmentIssuer = adjustment.getIssuer();
|
||||||
}
|
}
|
||||||
@@ -870,6 +879,10 @@ public final class NotificationRecord {
|
|||||||
return stats.naturalImportance;
|
return stats.naturalImportance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getProposedImportance() {
|
||||||
|
return mProposedImportance;
|
||||||
|
}
|
||||||
|
|
||||||
public float getRankingScore() {
|
public float getRankingScore() {
|
||||||
return mRankingScore;
|
return mRankingScore;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.android.server.notification;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.service.notification.SnoozeCriterion;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that stores any field in a NotificationRecord that can change via an extractor.
|
||||||
|
* Used to cache previous data used in a sort.
|
||||||
|
*/
|
||||||
|
public final class NotificationRecordExtractorData {
|
||||||
|
private final int mPosition;
|
||||||
|
private final int mVisibility;
|
||||||
|
private final boolean mShowBadge;
|
||||||
|
private final boolean mAllowBubble;
|
||||||
|
private final boolean mIsBubble;
|
||||||
|
private final NotificationChannel mChannel;
|
||||||
|
private final String mGroupKey;
|
||||||
|
private final ArrayList<String> mOverridePeople;
|
||||||
|
private final ArrayList<SnoozeCriterion> mSnoozeCriteria;
|
||||||
|
private final Integer mUserSentiment;
|
||||||
|
private final Integer mSuppressVisually;
|
||||||
|
private final ArrayList<Notification.Action> mSystemSmartActions;
|
||||||
|
private final ArrayList<CharSequence> mSmartReplies;
|
||||||
|
private final int mImportance;
|
||||||
|
|
||||||
|
// These fields may not trigger a reranking but diffs here may be logged.
|
||||||
|
private final float mRankingScore;
|
||||||
|
private final boolean mIsConversation;
|
||||||
|
private final int mProposedImportance;
|
||||||
|
|
||||||
|
NotificationRecordExtractorData(int position, int visibility, boolean showBadge,
|
||||||
|
boolean allowBubble, boolean isBubble, NotificationChannel channel, String groupKey,
|
||||||
|
ArrayList<String> overridePeople, ArrayList<SnoozeCriterion> snoozeCriteria,
|
||||||
|
Integer userSentiment, Integer suppressVisually,
|
||||||
|
ArrayList<Notification.Action> systemSmartActions,
|
||||||
|
ArrayList<CharSequence> smartReplies, int importance, float rankingScore,
|
||||||
|
boolean isConversation, int proposedImportance) {
|
||||||
|
mPosition = position;
|
||||||
|
mVisibility = visibility;
|
||||||
|
mShowBadge = showBadge;
|
||||||
|
mAllowBubble = allowBubble;
|
||||||
|
mIsBubble = isBubble;
|
||||||
|
mChannel = channel;
|
||||||
|
mGroupKey = groupKey;
|
||||||
|
mOverridePeople = overridePeople;
|
||||||
|
mSnoozeCriteria = snoozeCriteria;
|
||||||
|
mUserSentiment = userSentiment;
|
||||||
|
mSuppressVisually = suppressVisually;
|
||||||
|
mSystemSmartActions = systemSmartActions;
|
||||||
|
mSmartReplies = smartReplies;
|
||||||
|
mImportance = importance;
|
||||||
|
mRankingScore = rankingScore;
|
||||||
|
mIsConversation = isConversation;
|
||||||
|
mProposedImportance = proposedImportance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns whether the provided NotificationRecord differs from the cached data in any way.
|
||||||
|
// Should be guarded by mNotificationLock; not annotated here as this class is static.
|
||||||
|
boolean hasDiffForRankingLocked(NotificationRecord r, int newPosition) {
|
||||||
|
return mPosition != newPosition
|
||||||
|
|| mVisibility != r.getPackageVisibilityOverride()
|
||||||
|
|| mShowBadge != r.canShowBadge()
|
||||||
|
|| mAllowBubble != r.canBubble()
|
||||||
|
|| mIsBubble != r.getNotification().isBubbleNotification()
|
||||||
|
|| !Objects.equals(mChannel, r.getChannel())
|
||||||
|
|| !Objects.equals(mGroupKey, r.getGroupKey())
|
||||||
|
|| !Objects.equals(mOverridePeople, r.getPeopleOverride())
|
||||||
|
|| !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria())
|
||||||
|
|| !Objects.equals(mUserSentiment, r.getUserSentiment())
|
||||||
|
|| !Objects.equals(mSuppressVisually, r.getSuppressedVisualEffects())
|
||||||
|
|| !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions())
|
||||||
|
|| !Objects.equals(mSmartReplies, r.getSmartReplies())
|
||||||
|
|| mImportance != r.getImportance()
|
||||||
|
|| mProposedImportance != r.getProposedImportance();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns whether the NotificationRecord has a change from this data for which we should
|
||||||
|
// log an update. This method specifically targets fields that may be changed via
|
||||||
|
// adjustments from the assistant.
|
||||||
|
//
|
||||||
|
// Fields here are the union of things in NotificationRecordLogger.shouldLogReported
|
||||||
|
// and NotificationRecord.applyAdjustments.
|
||||||
|
//
|
||||||
|
// Should be guarded by mNotificationLock; not annotated here as this class is static.
|
||||||
|
boolean hasDiffForLoggingLocked(NotificationRecord r, int newPosition) {
|
||||||
|
return mPosition != newPosition
|
||||||
|
|| !Objects.equals(mChannel, r.getChannel())
|
||||||
|
|| !Objects.equals(mGroupKey, r.getGroupKey())
|
||||||
|
|| !Objects.equals(mOverridePeople, r.getPeopleOverride())
|
||||||
|
|| !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria())
|
||||||
|
|| !Objects.equals(mUserSentiment, r.getUserSentiment())
|
||||||
|
|| !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions())
|
||||||
|
|| !Objects.equals(mSmartReplies, r.getSmartReplies())
|
||||||
|
|| mImportance != r.getImportance()
|
||||||
|
|| !r.rankingScoreMatches(mRankingScore)
|
||||||
|
|| mIsConversation != r.isConversation()
|
||||||
|
|| mProposedImportance != r.getProposedImportance();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1678,7 +1678,7 @@ public final class DataManagerTest {
|
|||||||
mParentNotificationChannel.getImportance(),
|
mParentNotificationChannel.getImportance(),
|
||||||
null, null,
|
null, null,
|
||||||
mParentNotificationChannel, null, null, true, 0, false, -1, false, null, null,
|
mParentNotificationChannel, null, null, true, 0, false, -1, false, null, null,
|
||||||
false, false, false, null, 0, false);
|
false, false, false, null, 0, false, 0);
|
||||||
return true;
|
return true;
|
||||||
}).when(mRankingMap).getRanking(eq(GENERIC_KEY),
|
}).when(mRankingMap).getRanking(eq(GENERIC_KEY),
|
||||||
any(NotificationListenerService.Ranking.class));
|
any(NotificationListenerService.Ranking.class));
|
||||||
@@ -1704,7 +1704,7 @@ public final class DataManagerTest {
|
|||||||
mNotificationChannel.getImportance(),
|
mNotificationChannel.getImportance(),
|
||||||
null, null,
|
null, null,
|
||||||
mNotificationChannel, null, null, true, 0, false, -1, false, null, null, false,
|
mNotificationChannel, null, null, true, 0, false, -1, false, null, null, false,
|
||||||
false, false, null, 0, false);
|
false, false, null, 0, false, 0);
|
||||||
return true;
|
return true;
|
||||||
}).when(mRankingMap).getRanking(eq(CUSTOM_KEY),
|
}).when(mRankingMap).getRanking(eq(CUSTOM_KEY),
|
||||||
any(NotificationListenerService.Ranking.class));
|
any(NotificationListenerService.Ranking.class));
|
||||||
|
|||||||
@@ -193,7 +193,8 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
|
|||||||
tweak.isConversation(),
|
tweak.isConversation(),
|
||||||
tweak.getConversationShortcutInfo(),
|
tweak.getConversationShortcutInfo(),
|
||||||
tweak.getRankingAdjustment(),
|
tweak.getRankingAdjustment(),
|
||||||
tweak.isBubble()
|
tweak.isBubble(),
|
||||||
|
tweak.getProposedImportance()
|
||||||
);
|
);
|
||||||
assertNotEquals(nru, nru2);
|
assertNotEquals(nru, nru2);
|
||||||
}
|
}
|
||||||
@@ -274,7 +275,8 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
|
|||||||
isConversation(i),
|
isConversation(i),
|
||||||
getShortcutInfo(i),
|
getShortcutInfo(i),
|
||||||
getRankingAdjustment(i),
|
getRankingAdjustment(i),
|
||||||
isBubble(i)
|
isBubble(i),
|
||||||
|
getProposedImportance(i)
|
||||||
);
|
);
|
||||||
rankings[i] = ranking;
|
rankings[i] = ranking;
|
||||||
}
|
}
|
||||||
@@ -402,6 +404,10 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
|
|||||||
return index % 3 - 1;
|
return index % 3 - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getProposedImportance(int index) {
|
||||||
|
return index % 5 - 1;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isBubble(int index) {
|
private boolean isBubble(int index) {
|
||||||
return index % 4 == 0;
|
return index % 4 == 0;
|
||||||
}
|
}
|
||||||
@@ -443,6 +449,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
|
|||||||
assertEquals(comment, a.getConversationShortcutInfo().getId(),
|
assertEquals(comment, a.getConversationShortcutInfo().getId(),
|
||||||
b.getConversationShortcutInfo().getId());
|
b.getConversationShortcutInfo().getId());
|
||||||
assertActionsEqual(a.getSmartActions(), b.getSmartActions());
|
assertActionsEqual(a.getSmartActions(), b.getSmartActions());
|
||||||
|
assertEquals(a.getProposedImportance(), b.getProposedImportance());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void detailedAssertEquals(RankingMap a, RankingMap b) {
|
private void detailedAssertEquals(RankingMap a, RankingMap b) {
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.server.notification;
|
||||||
|
|
||||||
|
import static android.app.NotificationManager.IMPORTANCE_HIGH;
|
||||||
|
import static android.app.NotificationManager.IMPORTANCE_LOW;
|
||||||
|
|
||||||
|
import static junit.framework.Assert.assertEquals;
|
||||||
|
import static junit.framework.Assert.assertFalse;
|
||||||
|
import static junit.framework.Assert.assertNull;
|
||||||
|
import static junit.framework.Assert.assertTrue;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.drawable.Icon;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import android.service.notification.Adjustment;
|
||||||
|
import android.service.notification.SnoozeCriterion;
|
||||||
|
import android.service.notification.StatusBarNotification;
|
||||||
|
|
||||||
|
import com.android.server.UiServiceTestCase;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class NotificationRecordExtractorDataTest extends UiServiceTestCase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHasDiffs_noDiffs() {
|
||||||
|
NotificationRecord r = generateRecord();
|
||||||
|
|
||||||
|
NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData(
|
||||||
|
1,
|
||||||
|
r.getPackageVisibilityOverride(),
|
||||||
|
r.canShowBadge(),
|
||||||
|
r.canBubble(),
|
||||||
|
r.getNotification().isBubbleNotification(),
|
||||||
|
r.getChannel(),
|
||||||
|
r.getGroupKey(),
|
||||||
|
r.getPeopleOverride(),
|
||||||
|
r.getSnoozeCriteria(),
|
||||||
|
r.getUserSentiment(),
|
||||||
|
r.getSuppressedVisualEffects(),
|
||||||
|
r.getSystemGeneratedSmartActions(),
|
||||||
|
r.getSmartReplies(),
|
||||||
|
r.getImportance(),
|
||||||
|
r.getRankingScore(),
|
||||||
|
r.isConversation(),
|
||||||
|
r.getProposedImportance());
|
||||||
|
|
||||||
|
assertFalse(extractorData.hasDiffForRankingLocked(r, 1));
|
||||||
|
assertFalse(extractorData.hasDiffForLoggingLocked(r, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHasDiffs_proposedImportanceChange() {
|
||||||
|
NotificationRecord r = generateRecord();
|
||||||
|
|
||||||
|
NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData(
|
||||||
|
1,
|
||||||
|
r.getPackageVisibilityOverride(),
|
||||||
|
r.canShowBadge(),
|
||||||
|
r.canBubble(),
|
||||||
|
r.getNotification().isBubbleNotification(),
|
||||||
|
r.getChannel(),
|
||||||
|
r.getGroupKey(),
|
||||||
|
r.getPeopleOverride(),
|
||||||
|
r.getSnoozeCriteria(),
|
||||||
|
r.getUserSentiment(),
|
||||||
|
r.getSuppressedVisualEffects(),
|
||||||
|
r.getSystemGeneratedSmartActions(),
|
||||||
|
r.getSmartReplies(),
|
||||||
|
r.getImportance(),
|
||||||
|
r.getRankingScore(),
|
||||||
|
r.isConversation(),
|
||||||
|
r.getProposedImportance());
|
||||||
|
|
||||||
|
Bundle signals = new Bundle();
|
||||||
|
signals.putInt(Adjustment.KEY_IMPORTANCE_PROPOSAL, IMPORTANCE_HIGH);
|
||||||
|
Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0);
|
||||||
|
r.addAdjustment(adjustment);
|
||||||
|
r.applyAdjustments();
|
||||||
|
|
||||||
|
assertTrue(extractorData.hasDiffForRankingLocked(r, 1));
|
||||||
|
assertTrue(extractorData.hasDiffForLoggingLocked(r, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private NotificationRecord generateRecord() {
|
||||||
|
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
|
||||||
|
final Notification.Builder builder = new Notification.Builder(getContext())
|
||||||
|
.setContentTitle("foo")
|
||||||
|
.setSmallIcon(android.R.drawable.sym_def_app_icon);
|
||||||
|
Notification n = builder.build();
|
||||||
|
StatusBarNotification sbn = new StatusBarNotification("", "", 0, "", 0,
|
||||||
|
0, n, UserHandle.ALL, null, System.currentTimeMillis());
|
||||||
|
return new NotificationRecord(getContext(), sbn, channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
|
|||||||
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||||
import static android.app.NotificationManager.IMPORTANCE_HIGH;
|
import static android.app.NotificationManager.IMPORTANCE_HIGH;
|
||||||
import static android.app.NotificationManager.IMPORTANCE_LOW;
|
import static android.app.NotificationManager.IMPORTANCE_LOW;
|
||||||
|
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
|
||||||
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
|
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
|
||||||
import static android.service.notification.Adjustment.KEY_NOT_CONVERSATION;
|
import static android.service.notification.Adjustment.KEY_NOT_CONVERSATION;
|
||||||
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
|
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
|
||||||
@@ -754,6 +755,24 @@ public class NotificationRecordTest extends UiServiceTestCase {
|
|||||||
assertEquals(USER_SENTIMENT_POSITIVE, record.getUserSentiment());
|
assertEquals(USER_SENTIMENT_POSITIVE, record.getUserSentiment());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProposedImportance() {
|
||||||
|
StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
|
||||||
|
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
|
||||||
|
false /* lights */, false /* defaultLights */, groupId /* group */);
|
||||||
|
NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
|
||||||
|
|
||||||
|
assertEquals(IMPORTANCE_UNSPECIFIED, record.getProposedImportance());
|
||||||
|
|
||||||
|
Bundle signals = new Bundle();
|
||||||
|
signals.putInt(Adjustment.KEY_IMPORTANCE_PROPOSAL, IMPORTANCE_DEFAULT);
|
||||||
|
record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId()));
|
||||||
|
|
||||||
|
record.applyAdjustments();
|
||||||
|
|
||||||
|
assertEquals(IMPORTANCE_DEFAULT, record.getProposedImportance());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAppImportance_returnsCorrectly() {
|
public void testAppImportance_returnsCorrectly() {
|
||||||
StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
|
StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
|
||||||
|
|||||||
Reference in New Issue
Block a user