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:
Julia Reynolds
2022-12-22 10:54:19 -05:00
parent b56558ee8d
commit 4ca6d026ba
12 changed files with 336 additions and 103 deletions

View File

@@ -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,

View File

@@ -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);
} }
} }

View File

@@ -290,7 +290,8 @@ public class NotificationListener extends NotificationListenerWithPlugins implem
false, false,
null, null,
0, 0,
false false,
0
); );
} }
return ranking; return ranking;

View File

@@ -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)
} }
} }

View File

@@ -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;

View File

@@ -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);
} }

View File

@@ -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;
} }

View File

@@ -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();
}
}

View File

@@ -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));

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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 */,