Merge "Dedup smart reply updates for bubble flyout"
This commit is contained in:
committed by
Android (Google) Code Review
commit
04bdd93ea1
@@ -1522,6 +1522,7 @@ public abstract class NotificationListenerService extends Service {
|
||||
private ArrayList<Notification.Action> mSmartActions;
|
||||
private ArrayList<CharSequence> mSmartReplies;
|
||||
private boolean mCanBubble;
|
||||
private boolean mVisuallyInterruptive;
|
||||
|
||||
private static final int PARCEL_VERSION = 2;
|
||||
|
||||
@@ -1553,6 +1554,7 @@ public abstract class NotificationListenerService extends Service {
|
||||
out.writeTypedList(mSmartActions, flags);
|
||||
out.writeCharSequenceList(mSmartReplies);
|
||||
out.writeBoolean(mCanBubble);
|
||||
out.writeBoolean(mVisuallyInterruptive);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@@ -1585,6 +1587,7 @@ public abstract class NotificationListenerService extends Service {
|
||||
mSmartActions = in.createTypedArrayList(Notification.Action.CREATOR);
|
||||
mSmartReplies = in.readCharSequenceList();
|
||||
mCanBubble = in.readBoolean();
|
||||
mVisuallyInterruptive = in.readBoolean();
|
||||
}
|
||||
|
||||
|
||||
@@ -1771,6 +1774,11 @@ public abstract class NotificationListenerService extends Service {
|
||||
return mCanBubble;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean visuallyInterruptive() {
|
||||
return mVisuallyInterruptive;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean isNoisy() {
|
||||
return mNoisy;
|
||||
@@ -1787,7 +1795,8 @@ public abstract class NotificationListenerService extends Service {
|
||||
ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge,
|
||||
int userSentiment, boolean hidden, long lastAudiblyAlertedMs,
|
||||
boolean noisy, ArrayList<Notification.Action> smartActions,
|
||||
ArrayList<CharSequence> smartReplies, boolean canBubble) {
|
||||
ArrayList<CharSequence> smartReplies, boolean canBubble,
|
||||
boolean visuallyInterruptive) {
|
||||
mKey = key;
|
||||
mRank = rank;
|
||||
mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
|
||||
@@ -1808,6 +1817,7 @@ public abstract class NotificationListenerService extends Service {
|
||||
mSmartActions = smartActions;
|
||||
mSmartReplies = smartReplies;
|
||||
mCanBubble = canBubble;
|
||||
mVisuallyInterruptive = visuallyInterruptive;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1832,7 +1842,8 @@ public abstract class NotificationListenerService extends Service {
|
||||
other.mNoisy,
|
||||
other.mSmartActions,
|
||||
other.mSmartReplies,
|
||||
other.mCanBubble);
|
||||
other.mCanBubble,
|
||||
other.mVisuallyInterruptive);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -184,6 +184,8 @@ public class BubbleData {
|
||||
Log.d(TAG, "notificationEntryUpdated: " + entry);
|
||||
}
|
||||
Bubble bubble = getBubbleWithKey(entry.key);
|
||||
suppressFlyout = !entry.isVisuallyInterruptive || suppressFlyout;
|
||||
|
||||
if (bubble == null) {
|
||||
// Create a new bubble
|
||||
bubble = new Bubble(mContext, entry);
|
||||
@@ -193,8 +195,10 @@ public class BubbleData {
|
||||
} else {
|
||||
// Updates an existing bubble
|
||||
bubble.updateEntry(entry);
|
||||
bubble.setSuppressFlyout(suppressFlyout);
|
||||
doUpdate(bubble);
|
||||
}
|
||||
|
||||
if (bubble.shouldAutoExpand()) {
|
||||
setSelectedBubbleInternal(bubble);
|
||||
if (!mExpanded) {
|
||||
|
||||
@@ -140,6 +140,12 @@ public final class NotificationEntry {
|
||||
*/
|
||||
private boolean hasSentReply;
|
||||
|
||||
/**
|
||||
* Whether this notification has changed in visual appearance since the previous post.
|
||||
* New notifications are interruptive by default.
|
||||
*/
|
||||
public boolean isVisuallyInterruptive;
|
||||
|
||||
/**
|
||||
* Whether this notification is shown to the user as a high priority notification: visible on
|
||||
* the lock screen/status bar and in the top section in the shade.
|
||||
@@ -205,6 +211,7 @@ public final class NotificationEntry {
|
||||
+ " doesn't match existing key " + key);
|
||||
}
|
||||
mRanking = ranking;
|
||||
isVisuallyInterruptive = ranking.visuallyInterruptive();
|
||||
}
|
||||
|
||||
public NotificationChannel getChannel() {
|
||||
@@ -244,6 +251,7 @@ public final class NotificationEntry {
|
||||
return mRanking.canBubble();
|
||||
}
|
||||
|
||||
|
||||
public @NonNull List<Notification.Action> getSmartActions() {
|
||||
return mRanking.getSmartActions();
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ public class RankingBuilder {
|
||||
private ArrayList<Notification.Action> mSmartActions = new ArrayList<>();
|
||||
private ArrayList<CharSequence> mSmartReplies = new ArrayList<>();
|
||||
private boolean mCanBubble = false;
|
||||
private boolean mIsVisuallyInterruptive = false;
|
||||
|
||||
public RankingBuilder() {
|
||||
}
|
||||
@@ -98,7 +99,8 @@ public class RankingBuilder {
|
||||
mNoisy,
|
||||
mSmartActions,
|
||||
mSmartReplies,
|
||||
mCanBubble);
|
||||
mCanBubble,
|
||||
mIsVisuallyInterruptive);
|
||||
return ranking;
|
||||
}
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
|
||||
0,
|
||||
NotificationManager.IMPORTANCE_DEFAULT,
|
||||
null, null,
|
||||
null, null, null, true, sentiment, false, -1, false, null, null, false);
|
||||
null, null, null, true, sentiment, false, -1, false, null, null, false, false);
|
||||
return true;
|
||||
}).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
|
||||
}
|
||||
@@ -202,7 +202,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
|
||||
null, null,
|
||||
null, null, null, true,
|
||||
NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL, false, -1,
|
||||
false, smartActions, null, false);
|
||||
false, smartActions, null, false, false);
|
||||
return true;
|
||||
}).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
|
||||
}
|
||||
|
||||
@@ -653,6 +653,7 @@ public class NotificationDataTest extends SysuiTestCase {
|
||||
public static final String OVERRIDE_SMART_ACTIONS = "sa";
|
||||
public static final String OVERRIDE_SMART_REPLIES = "sr";
|
||||
public static final String OVERRIDE_BUBBLE = "cb";
|
||||
public static final String OVERRIDE_VISUALLY_INTERRUPTIVE = "vi";
|
||||
|
||||
public Map<String, Bundle> rankingOverrides = new HashMap<>();
|
||||
|
||||
@@ -713,7 +714,9 @@ public class NotificationDataTest extends SysuiTestCase {
|
||||
overrides.containsKey(OVERRIDE_SMART_REPLIES)
|
||||
? overrides.getCharSequenceArrayList(OVERRIDE_SMART_REPLIES)
|
||||
: currentReplies,
|
||||
overrides.getBoolean(OVERRIDE_BUBBLE, outRanking.canBubble()));
|
||||
overrides.getBoolean(OVERRIDE_BUBBLE, outRanking.canBubble()),
|
||||
overrides.getBoolean(OVERRIDE_VISUALLY_INTERRUPTIVE,
|
||||
outRanking.visuallyInterruptive()));
|
||||
} else {
|
||||
outRanking.populate(
|
||||
new RankingBuilder()
|
||||
|
||||
@@ -129,6 +129,12 @@ public class NotificationComparator
|
||||
return -1 * Integer.compare(leftPriority, rightPriority);
|
||||
}
|
||||
|
||||
final boolean leftInterruptive = left.isInterruptive();
|
||||
final boolean rightInterruptive = right.isInterruptive();
|
||||
if (leftInterruptive != rightInterruptive) {
|
||||
return -1 * Boolean.compare(leftInterruptive, rightInterruptive);
|
||||
}
|
||||
|
||||
// then break ties by time, most recent first
|
||||
return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs());
|
||||
}
|
||||
|
||||
@@ -5759,7 +5759,9 @@ public class NotificationManagerService extends SystemService {
|
||||
notification.flags |=
|
||||
old.getNotification().flags & FLAG_FOREGROUND_SERVICE;
|
||||
r.isUpdate = true;
|
||||
r.setTextChanged(isVisuallyInterruptive(old, r));
|
||||
final boolean isInterruptive = isVisuallyInterruptive(old, r);
|
||||
r.setTextChanged(isInterruptive);
|
||||
r.setInterruptive(isInterruptive);
|
||||
}
|
||||
|
||||
mNotificationsByKey.put(n.getKey(), r);
|
||||
@@ -5858,7 +5860,6 @@ public class NotificationManagerService extends SystemService {
|
||||
|
||||
Notification oldN = old.sbn.getNotification();
|
||||
Notification newN = r.sbn.getNotification();
|
||||
|
||||
if (oldN.extras == null || newN.extras == null) {
|
||||
if (DEBUG_INTERRUPTIVENESS) {
|
||||
Slog.v(TAG, "INTERRUPTIVENESS: "
|
||||
@@ -5890,6 +5891,7 @@ public class NotificationManagerService extends SystemService {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Do not compare Spannables (will always return false); compare unstyled Strings
|
||||
final String oldText = String.valueOf(oldN.extras.get(Notification.EXTRA_TEXT));
|
||||
final String newText = String.valueOf(newN.extras.get(Notification.EXTRA_TEXT));
|
||||
@@ -5904,6 +5906,7 @@ public class NotificationManagerService extends SystemService {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (oldN.hasCompletedProgress() != newN.hasCompletedProgress()) {
|
||||
if (DEBUG_INTERRUPTIVENESS) {
|
||||
Slog.v(TAG, "INTERRUPTIVENESS: "
|
||||
@@ -5911,6 +5914,16 @@ public class NotificationManagerService extends SystemService {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fields below are invisible to bubbles.
|
||||
if (r.canBubble()) {
|
||||
if (DEBUG_INTERRUPTIVENESS) {
|
||||
Slog.v(TAG, "INTERRUPTIVENESS: "
|
||||
+ r.getKey() + " is not interruptive: bubble");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Actions
|
||||
if (Notification.areActionsVisiblyDifferent(oldN, newN)) {
|
||||
if (DEBUG_INTERRUPTIVENESS) {
|
||||
@@ -5944,7 +5957,6 @@ public class NotificationManagerService extends SystemService {
|
||||
} catch (Exception e) {
|
||||
Slog.w(TAG, "error recovering builder", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -6139,12 +6151,17 @@ public class NotificationManagerService extends SystemService {
|
||||
Slog.v(TAG, "INTERRUPTIVENESS: "
|
||||
+ record.getKey() + " is not interruptive: summary");
|
||||
}
|
||||
} else if (record.canBubble()) {
|
||||
if (DEBUG_INTERRUPTIVENESS) {
|
||||
Slog.v(TAG, "INTERRUPTIVENESS: "
|
||||
+ record.getKey() + " is not interruptive: bubble");
|
||||
}
|
||||
} else {
|
||||
record.setInterruptive(true);
|
||||
if (DEBUG_INTERRUPTIVENESS) {
|
||||
Slog.v(TAG, "INTERRUPTIVENESS: "
|
||||
+ record.getKey() + " is interruptive: alerted");
|
||||
}
|
||||
record.setInterruptive(true);
|
||||
}
|
||||
MetricsLogger.action(record.getLogMaker()
|
||||
.setCategory(MetricsEvent.NOTIFICATION_ALERT)
|
||||
@@ -6503,15 +6520,21 @@ public class NotificationManagerService extends SystemService {
|
||||
int indexBefore = findNotificationRecordIndexLocked(record);
|
||||
boolean interceptBefore = record.isIntercepted();
|
||||
int visibilityBefore = record.getPackageVisibilityOverride();
|
||||
boolean interruptiveBefore = record.isInterruptive();
|
||||
|
||||
recon.applyChangesLocked(record);
|
||||
applyZenModeLocked(record);
|
||||
mRankingHelper.sort(mNotificationList);
|
||||
int indexAfter = findNotificationRecordIndexLocked(record);
|
||||
boolean interceptAfter = record.isIntercepted();
|
||||
int visibilityAfter = record.getPackageVisibilityOverride();
|
||||
changed = indexBefore != indexAfter || interceptBefore != interceptAfter
|
||||
|| visibilityBefore != visibilityAfter;
|
||||
if (interceptBefore && !interceptAfter
|
||||
boolean indexChanged = indexBefore != findNotificationRecordIndexLocked(record);
|
||||
boolean interceptChanged = interceptBefore != record.isIntercepted();
|
||||
boolean visibilityChanged = visibilityBefore != record.getPackageVisibilityOverride();
|
||||
|
||||
// Broadcast isInterruptive changes for bubbles.
|
||||
boolean interruptiveChanged =
|
||||
record.canBubble() && (interruptiveBefore != record.isInterruptive());
|
||||
|
||||
changed = indexChanged || interceptChanged || visibilityChanged || interruptiveChanged;
|
||||
if (interceptBefore && !record.isIntercepted()
|
||||
&& record.isNewEnoughForAlerting(System.currentTimeMillis())) {
|
||||
buzzBeepBlinkLocked(record);
|
||||
}
|
||||
@@ -7661,7 +7684,8 @@ public class NotificationManagerService extends SystemService {
|
||||
record.getSound() != null || record.getVibration() != null,
|
||||
record.getSystemGeneratedSmartActions(),
|
||||
record.getSmartReplies(),
|
||||
record.canBubble()
|
||||
record.canBubble(),
|
||||
record.isInterruptive()
|
||||
);
|
||||
rankings.add(ranking);
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ public class NotificationComparatorTest extends UiServiceTestCase {
|
||||
private final int uid2 = 1111111;
|
||||
private static final String TEST_CHANNEL_ID = "test_channel_id";
|
||||
|
||||
private NotificationRecord mRecordMinCallNonInterruptive;
|
||||
private NotificationRecord mRecordMinCall;
|
||||
private NotificationRecord mRecordHighCall;
|
||||
private NotificationRecord mRecordDefaultMedia;
|
||||
@@ -105,6 +106,18 @@ public class NotificationComparatorTest extends UiServiceTestCase {
|
||||
smsPkg = Settings.Secure.getString(mContext.getContentResolver(),
|
||||
Settings.Secure.SMS_DEFAULT_APPLICATION);
|
||||
|
||||
Notification nonInterruptiveNotif = new Notification.Builder(mContext, TEST_CHANNEL_ID)
|
||||
.setCategory(Notification.CATEGORY_CALL)
|
||||
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
|
||||
.build();
|
||||
mRecordMinCallNonInterruptive = new NotificationRecord(mContext,
|
||||
new StatusBarNotification(callPkg,
|
||||
callPkg, 1, "mRecordMinCallNonInterruptive", callUid, callUid,
|
||||
nonInterruptiveNotif,
|
||||
new UserHandle(userId), "", 2000), getDefaultChannel());
|
||||
mRecordMinCallNonInterruptive.setSystemImportance(NotificationManager.IMPORTANCE_MIN);
|
||||
mRecordMinCallNonInterruptive.setInterruptive(false);
|
||||
|
||||
Notification n1 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
|
||||
.setCategory(Notification.CATEGORY_CALL)
|
||||
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
|
||||
@@ -113,6 +126,7 @@ public class NotificationComparatorTest extends UiServiceTestCase {
|
||||
callPkg, 1, "minCall", callUid, callUid, n1,
|
||||
new UserHandle(userId), "", 2000), getDefaultChannel());
|
||||
mRecordMinCall.setSystemImportance(NotificationManager.IMPORTANCE_MIN);
|
||||
mRecordMinCall.setInterruptive(true);
|
||||
|
||||
Notification n2 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
|
||||
.setCategory(Notification.CATEGORY_CALL)
|
||||
@@ -245,6 +259,7 @@ public class NotificationComparatorTest extends UiServiceTestCase {
|
||||
expected.add(mRecordCheater);
|
||||
expected.add(mRecordCheaterColorized);
|
||||
expected.add(mRecordMinCall);
|
||||
expected.add(mRecordMinCallNonInterruptive);
|
||||
|
||||
List<NotificationRecord> actual = new ArrayList<>();
|
||||
actual.addAll(expected);
|
||||
|
||||
@@ -51,6 +51,8 @@ import android.service.notification.NotificationRankingUpdate;
|
||||
import android.service.notification.SnoozeCriterion;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.After;
|
||||
@@ -61,8 +63,6 @@ import org.junit.runner.RunWith;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class NotificationListenerServiceTest extends UiServiceTestCase {
|
||||
@@ -116,6 +116,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
|
||||
assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions());
|
||||
assertEquals(getSmartReplies(key, i), ranking.getSmartReplies());
|
||||
assertEquals(canBubble(i), ranking.canBubble());
|
||||
assertEquals(visuallyInterruptive(i), ranking.visuallyInterruptive());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +183,8 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
|
||||
tweak.isNoisy(),
|
||||
(ArrayList) tweak.getSmartActions(),
|
||||
(ArrayList) tweak.getSmartReplies(),
|
||||
tweak.canBubble()
|
||||
tweak.canBubble(),
|
||||
tweak.visuallyInterruptive()
|
||||
);
|
||||
assertNotEquals(nru, nru2);
|
||||
}
|
||||
@@ -258,7 +260,8 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
|
||||
getNoisy(i),
|
||||
getSmartActions(key, i),
|
||||
getSmartReplies(key, i),
|
||||
canBubble(i)
|
||||
canBubble(i),
|
||||
visuallyInterruptive(i)
|
||||
);
|
||||
rankings[i] = ranking;
|
||||
}
|
||||
@@ -363,6 +366,10 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
|
||||
return index % 4 == 0;
|
||||
}
|
||||
|
||||
private boolean visuallyInterruptive(int index) {
|
||||
return index % 4 == 0;
|
||||
}
|
||||
|
||||
private void assertActionsEqual(
|
||||
List<Notification.Action> expecteds, List<Notification.Action> actuals) {
|
||||
assertEquals(expecteds.size(), actuals.size());
|
||||
|
||||
Reference in New Issue
Block a user