Merge "Dedup smart reply updates for bubble flyout [DO NOT MERGE]" into qt-qpr1-dev

This commit is contained in:
TreeHugger Robot
2019-11-07 18:29:49 +00:00
committed by Android (Google) Code Review
9 changed files with 97 additions and 20 deletions

View File

@@ -1514,6 +1514,7 @@ public abstract class NotificationListenerService extends Service {
private ArrayList<Notification.Action> mSmartActions; private ArrayList<Notification.Action> mSmartActions;
private ArrayList<CharSequence> mSmartReplies; private ArrayList<CharSequence> mSmartReplies;
private boolean mCanBubble; private boolean mCanBubble;
private boolean mVisuallyInterruptive;
private static final int PARCEL_VERSION = 2; private static final int PARCEL_VERSION = 2;
@@ -1545,6 +1546,7 @@ public abstract class NotificationListenerService extends Service {
out.writeTypedList(mSmartActions, flags); out.writeTypedList(mSmartActions, flags);
out.writeCharSequenceList(mSmartReplies); out.writeCharSequenceList(mSmartReplies);
out.writeBoolean(mCanBubble); out.writeBoolean(mCanBubble);
out.writeBoolean(mVisuallyInterruptive);
} }
/** @hide */ /** @hide */
@@ -1577,6 +1579,7 @@ public abstract class NotificationListenerService extends Service {
mSmartActions = in.createTypedArrayList(Notification.Action.CREATOR); mSmartActions = in.createTypedArrayList(Notification.Action.CREATOR);
mSmartReplies = in.readCharSequenceList(); mSmartReplies = in.readCharSequenceList();
mCanBubble = in.readBoolean(); mCanBubble = in.readBoolean();
mVisuallyInterruptive = in.readBoolean();
} }
@@ -1763,6 +1766,11 @@ public abstract class NotificationListenerService extends Service {
return mCanBubble; return mCanBubble;
} }
/** @hide */
public boolean visuallyInterruptive() {
return mVisuallyInterruptive;
}
/** @hide */ /** @hide */
public boolean isNoisy() { public boolean isNoisy() {
return mNoisy; return mNoisy;
@@ -1779,7 +1787,8 @@ public abstract class NotificationListenerService extends Service {
ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge, ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge,
int userSentiment, boolean hidden, long lastAudiblyAlertedMs, int userSentiment, boolean hidden, long lastAudiblyAlertedMs,
boolean noisy, ArrayList<Notification.Action> smartActions, boolean noisy, ArrayList<Notification.Action> smartActions,
ArrayList<CharSequence> smartReplies, boolean canBubble) { ArrayList<CharSequence> smartReplies, boolean canBubble,
boolean visuallyInterruptive) {
mKey = key; mKey = key;
mRank = rank; mRank = rank;
mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW; mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1800,6 +1809,7 @@ public abstract class NotificationListenerService extends Service {
mSmartActions = smartActions; mSmartActions = smartActions;
mSmartReplies = smartReplies; mSmartReplies = smartReplies;
mCanBubble = canBubble; mCanBubble = canBubble;
mVisuallyInterruptive = visuallyInterruptive;
} }
/** /**
@@ -1824,7 +1834,8 @@ public abstract class NotificationListenerService extends Service {
other.mNoisy, other.mNoisy,
other.mSmartActions, other.mSmartActions,
other.mSmartReplies, other.mSmartReplies,
other.mCanBubble); other.mCanBubble,
other.mVisuallyInterruptive);
} }
/** /**

View File

@@ -184,6 +184,8 @@ public class BubbleData {
Log.d(TAG, "notificationEntryUpdated: " + entry); Log.d(TAG, "notificationEntryUpdated: " + entry);
} }
Bubble bubble = getBubbleWithKey(entry.key); Bubble bubble = getBubbleWithKey(entry.key);
suppressFlyout = !entry.isVisuallyInterruptive || suppressFlyout;
if (bubble == null) { if (bubble == null) {
// Create a new bubble // Create a new bubble
bubble = new Bubble(mContext, entry); bubble = new Bubble(mContext, entry);
@@ -193,8 +195,10 @@ public class BubbleData {
} else { } else {
// Updates an existing bubble // Updates an existing bubble
bubble.updateEntry(entry); bubble.updateEntry(entry);
bubble.setSuppressFlyout(suppressFlyout);
doUpdate(bubble); doUpdate(bubble);
} }
if (bubble.shouldAutoExpand()) { if (bubble.shouldAutoExpand()) {
setSelectedBubbleInternal(bubble); setSelectedBubbleInternal(bubble);
if (!mExpanded) { if (!mExpanded) {

View File

@@ -154,6 +154,12 @@ public final class NotificationEntry {
*/ */
public boolean canBubble; public boolean canBubble;
/**
* 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 * 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. * the lock screen/status bar and in the top section in the shade.
@@ -196,6 +202,7 @@ public final class NotificationEntry {
suppressedVisualEffects = ranking.getSuppressedVisualEffects(); suppressedVisualEffects = ranking.getSuppressedVisualEffects();
suspended = ranking.isSuspended(); suspended = ranking.isSuspended();
canBubble = ranking.canBubble(); canBubble = ranking.canBubble();
isVisuallyInterruptive = ranking.visuallyInterruptive();
} }
public void setInterruption() { public void setInterruption() {

View File

@@ -182,7 +182,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
0, 0,
NotificationManager.IMPORTANCE_DEFAULT, NotificationManager.IMPORTANCE_DEFAULT,
null, null, 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; return true;
}).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class)); }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
} }
@@ -201,7 +201,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
null, null, null, null,
null, null, null, true, null, null, null, true,
NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL, false, -1, NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL, false, -1,
false, smartActions, null, false); false, smartActions, null, false, false);
return true; return true;
}).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class)); }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
} }

View File

@@ -623,6 +623,7 @@ public class NotificationDataTest extends SysuiTestCase {
public static final String OVERRIDE_SMART_ACTIONS = "sa"; public static final String OVERRIDE_SMART_ACTIONS = "sa";
public static final String OVERRIDE_SMART_REPLIES = "sr"; public static final String OVERRIDE_SMART_REPLIES = "sr";
public static final String OVERRIDE_BUBBLE = "cb"; public static final String OVERRIDE_BUBBLE = "cb";
public static final String OVERRIDE_VISUALLY_INTERRUPTIVE = "vi";
public Map<String, Bundle> rankingOverrides = new HashMap<>(); public Map<String, Bundle> rankingOverrides = new HashMap<>();
@@ -683,7 +684,9 @@ public class NotificationDataTest extends SysuiTestCase {
overrides.containsKey(OVERRIDE_SMART_REPLIES) overrides.containsKey(OVERRIDE_SMART_REPLIES)
? overrides.getCharSequenceArrayList(OVERRIDE_SMART_REPLIES) ? overrides.getCharSequenceArrayList(OVERRIDE_SMART_REPLIES)
: currentReplies, : currentReplies,
overrides.getBoolean(OVERRIDE_BUBBLE, outRanking.canBubble())); overrides.getBoolean(OVERRIDE_BUBBLE, outRanking.canBubble()),
overrides.getBoolean(OVERRIDE_VISUALLY_INTERRUPTIVE,
outRanking.visuallyInterruptive()));
} }
return true; return true;
} }

View File

@@ -129,6 +129,12 @@ public class NotificationComparator
return -1 * Integer.compare(leftPriority, rightPriority); 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 // then break ties by time, most recent first
return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs()); return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs());
} }

View File

@@ -5543,7 +5543,9 @@ public class NotificationManagerService extends SystemService {
notification.flags |= notification.flags |=
old.getNotification().flags & FLAG_FOREGROUND_SERVICE; old.getNotification().flags & FLAG_FOREGROUND_SERVICE;
r.isUpdate = true; 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); mNotificationsByKey.put(n.getKey(), r);
@@ -5642,7 +5644,6 @@ public class NotificationManagerService extends SystemService {
Notification oldN = old.sbn.getNotification(); Notification oldN = old.sbn.getNotification();
Notification newN = r.sbn.getNotification(); Notification newN = r.sbn.getNotification();
if (oldN.extras == null || newN.extras == null) { if (oldN.extras == null || newN.extras == null) {
if (DEBUG_INTERRUPTIVENESS) { if (DEBUG_INTERRUPTIVENESS) {
Slog.v(TAG, "INTERRUPTIVENESS: " Slog.v(TAG, "INTERRUPTIVENESS: "
@@ -5674,6 +5675,7 @@ public class NotificationManagerService extends SystemService {
} }
return true; return true;
} }
// Do not compare Spannables (will always return false); compare unstyled Strings // Do not compare Spannables (will always return false); compare unstyled Strings
final String oldText = String.valueOf(oldN.extras.get(Notification.EXTRA_TEXT)); final String oldText = String.valueOf(oldN.extras.get(Notification.EXTRA_TEXT));
final String newText = String.valueOf(newN.extras.get(Notification.EXTRA_TEXT)); final String newText = String.valueOf(newN.extras.get(Notification.EXTRA_TEXT));
@@ -5688,6 +5690,7 @@ public class NotificationManagerService extends SystemService {
} }
return true; return true;
} }
if (oldN.hasCompletedProgress() != newN.hasCompletedProgress()) { if (oldN.hasCompletedProgress() != newN.hasCompletedProgress()) {
if (DEBUG_INTERRUPTIVENESS) { if (DEBUG_INTERRUPTIVENESS) {
Slog.v(TAG, "INTERRUPTIVENESS: " Slog.v(TAG, "INTERRUPTIVENESS: "
@@ -5695,6 +5698,16 @@ public class NotificationManagerService extends SystemService {
} }
return true; 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 // Actions
if (Notification.areActionsVisiblyDifferent(oldN, newN)) { if (Notification.areActionsVisiblyDifferent(oldN, newN)) {
if (DEBUG_INTERRUPTIVENESS) { if (DEBUG_INTERRUPTIVENESS) {
@@ -5728,7 +5741,6 @@ public class NotificationManagerService extends SystemService {
} catch (Exception e) { } catch (Exception e) {
Slog.w(TAG, "error recovering builder", e); Slog.w(TAG, "error recovering builder", e);
} }
return false; return false;
} }
@@ -5923,12 +5935,17 @@ public class NotificationManagerService extends SystemService {
Slog.v(TAG, "INTERRUPTIVENESS: " Slog.v(TAG, "INTERRUPTIVENESS: "
+ record.getKey() + " is not interruptive: summary"); + record.getKey() + " is not interruptive: summary");
} }
} else if (record.canBubble()) {
if (DEBUG_INTERRUPTIVENESS) {
Slog.v(TAG, "INTERRUPTIVENESS: "
+ record.getKey() + " is not interruptive: bubble");
}
} else { } else {
record.setInterruptive(true);
if (DEBUG_INTERRUPTIVENESS) { if (DEBUG_INTERRUPTIVENESS) {
Slog.v(TAG, "INTERRUPTIVENESS: " Slog.v(TAG, "INTERRUPTIVENESS: "
+ record.getKey() + " is interruptive: alerted"); + record.getKey() + " is interruptive: alerted");
} }
record.setInterruptive(true);
} }
MetricsLogger.action(record.getLogMaker() MetricsLogger.action(record.getLogMaker()
.setCategory(MetricsEvent.NOTIFICATION_ALERT) .setCategory(MetricsEvent.NOTIFICATION_ALERT)
@@ -6287,15 +6304,21 @@ public class NotificationManagerService extends SystemService {
int indexBefore = findNotificationRecordIndexLocked(record); int indexBefore = findNotificationRecordIndexLocked(record);
boolean interceptBefore = record.isIntercepted(); boolean interceptBefore = record.isIntercepted();
int visibilityBefore = record.getPackageVisibilityOverride(); int visibilityBefore = record.getPackageVisibilityOverride();
boolean interruptiveBefore = record.isInterruptive();
recon.applyChangesLocked(record); recon.applyChangesLocked(record);
applyZenModeLocked(record); applyZenModeLocked(record);
mRankingHelper.sort(mNotificationList); mRankingHelper.sort(mNotificationList);
int indexAfter = findNotificationRecordIndexLocked(record); boolean indexChanged = indexBefore != findNotificationRecordIndexLocked(record);
boolean interceptAfter = record.isIntercepted(); boolean interceptChanged = interceptBefore != record.isIntercepted();
int visibilityAfter = record.getPackageVisibilityOverride(); boolean visibilityChanged = visibilityBefore != record.getPackageVisibilityOverride();
changed = indexBefore != indexAfter || interceptBefore != interceptAfter
|| visibilityBefore != visibilityAfter; // Broadcast isInterruptive changes for bubbles.
if (interceptBefore && !interceptAfter boolean interruptiveChanged =
record.canBubble() && (interruptiveBefore != record.isInterruptive());
changed = indexChanged || interceptChanged || visibilityChanged || interruptiveChanged;
if (interceptBefore && !record.isIntercepted()
&& record.isNewEnoughForAlerting(System.currentTimeMillis())) { && record.isNewEnoughForAlerting(System.currentTimeMillis())) {
buzzBeepBlinkLocked(record); buzzBeepBlinkLocked(record);
} }
@@ -7426,7 +7449,8 @@ public class NotificationManagerService extends SystemService {
record.getSound() != null || record.getVibration() != null, record.getSound() != null || record.getVibration() != null,
record.getSystemGeneratedSmartActions(), record.getSystemGeneratedSmartActions(),
record.getSmartReplies(), record.getSmartReplies(),
record.canBubble() record.canBubble(),
record.isInterruptive()
); );
rankings.add(ranking); rankings.add(ranking);
} }

View File

@@ -68,6 +68,7 @@ public class NotificationComparatorTest extends UiServiceTestCase {
private final int uid2 = 1111111; private final int uid2 = 1111111;
private static final String TEST_CHANNEL_ID = "test_channel_id"; private static final String TEST_CHANNEL_ID = "test_channel_id";
private NotificationRecord mRecordMinCallNonInterruptive;
private NotificationRecord mRecordMinCall; private NotificationRecord mRecordMinCall;
private NotificationRecord mRecordHighCall; private NotificationRecord mRecordHighCall;
private NotificationRecord mRecordDefaultMedia; private NotificationRecord mRecordDefaultMedia;
@@ -105,6 +106,18 @@ public class NotificationComparatorTest extends UiServiceTestCase {
smsPkg = Settings.Secure.getString(mContext.getContentResolver(), smsPkg = Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.SMS_DEFAULT_APPLICATION); 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) Notification n1 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setCategory(Notification.CATEGORY_CALL) .setCategory(Notification.CATEGORY_CALL)
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
@@ -113,6 +126,7 @@ public class NotificationComparatorTest extends UiServiceTestCase {
callPkg, 1, "minCall", callUid, callUid, n1, callPkg, 1, "minCall", callUid, callUid, n1,
new UserHandle(userId), "", 2000), getDefaultChannel()); new UserHandle(userId), "", 2000), getDefaultChannel());
mRecordMinCall.setSystemImportance(NotificationManager.IMPORTANCE_MIN); mRecordMinCall.setSystemImportance(NotificationManager.IMPORTANCE_MIN);
mRecordMinCall.setInterruptive(true);
Notification n2 = new Notification.Builder(mContext, TEST_CHANNEL_ID) Notification n2 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setCategory(Notification.CATEGORY_CALL) .setCategory(Notification.CATEGORY_CALL)
@@ -245,6 +259,7 @@ public class NotificationComparatorTest extends UiServiceTestCase {
expected.add(mRecordCheater); expected.add(mRecordCheater);
expected.add(mRecordCheaterColorized); expected.add(mRecordCheaterColorized);
expected.add(mRecordMinCall); expected.add(mRecordMinCall);
expected.add(mRecordMinCallNonInterruptive);
List<NotificationRecord> actual = new ArrayList<>(); List<NotificationRecord> actual = new ArrayList<>();
actual.addAll(expected); actual.addAll(expected);

View File

@@ -51,6 +51,8 @@ import android.service.notification.NotificationRankingUpdate;
import android.service.notification.SnoozeCriterion; import android.service.notification.SnoozeCriterion;
import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.UiServiceTestCase; import com.android.server.UiServiceTestCase;
import org.junit.After; import org.junit.After;
@@ -61,8 +63,6 @@ import org.junit.runner.RunWith;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import androidx.test.runner.AndroidJUnit4;
@SmallTest @SmallTest
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class NotificationListenerServiceTest extends UiServiceTestCase { public class NotificationListenerServiceTest extends UiServiceTestCase {
@@ -116,6 +116,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions()); assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions());
assertEquals(getSmartReplies(key, i), ranking.getSmartReplies()); assertEquals(getSmartReplies(key, i), ranking.getSmartReplies());
assertEquals(canBubble(i), ranking.canBubble()); assertEquals(canBubble(i), ranking.canBubble());
assertEquals(visuallyInterruptive(i), ranking.visuallyInterruptive());
} }
} }
@@ -182,7 +183,8 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
tweak.isNoisy(), tweak.isNoisy(),
(ArrayList) tweak.getSmartActions(), (ArrayList) tweak.getSmartActions(),
(ArrayList) tweak.getSmartReplies(), (ArrayList) tweak.getSmartReplies(),
tweak.canBubble() tweak.canBubble(),
tweak.visuallyInterruptive()
); );
assertNotEquals(nru, nru2); assertNotEquals(nru, nru2);
} }
@@ -258,7 +260,8 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
getNoisy(i), getNoisy(i),
getSmartActions(key, i), getSmartActions(key, i),
getSmartReplies(key, i), getSmartReplies(key, i),
canBubble(i) canBubble(i),
visuallyInterruptive(i)
); );
rankings[i] = ranking; rankings[i] = ranking;
} }
@@ -363,6 +366,10 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
return index % 4 == 0; return index % 4 == 0;
} }
private boolean visuallyInterruptive(int index) {
return index % 4 == 0;
}
private void assertActionsEqual( private void assertActionsEqual(
List<Notification.Action> expecteds, List<Notification.Action> actuals) { List<Notification.Action> expecteds, List<Notification.Action> actuals) {
assertEquals(expecteds.size(), actuals.size()); assertEquals(expecteds.size(), actuals.size());