summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/ExtServices/src/android/ext/services/notification/Assistant.java190
-rw-r--r--packages/ExtServices/src/android/ext/services/notification/ChannelImpressions.java49
-rw-r--r--packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java142
-rw-r--r--packages/ExtServices/tests/src/android/ext/services/notification/ChannelImpressionsTest.java26
-rw-r--r--services/tests/notification/src/com/android/server/notification/RankingHelperTest.java28
5 files changed, 392 insertions, 43 deletions
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
index f535368bd356..6fe8975577a4 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -23,16 +23,37 @@ import static android.service.notification.NotificationListenerService.Ranking
import android.app.INotificationManager;
import android.content.Context;
import android.ext.services.R;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Environment;
+import android.os.storage.StorageManager;
import android.service.notification.Adjustment;
import android.service.notification.NotificationAssistantService;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
+import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Map;
/**
* Notification assistant that provides guidance on notification channel blocking
@@ -41,19 +62,112 @@ public class Assistant extends NotificationAssistantService {
private static final String TAG = "ExtAssistant";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final ArrayList<Integer> DISMISS_WITH_PREJUDICE = new ArrayList<>();
+ private static final String TAG_ASSISTANT = "assistant";
+ private static final String TAG_IMPRESSION = "impression-set";
+ private static final String ATT_KEY = "key";
+ private static final int DB_VERSION = 1;
+ private static final String ATTR_VERSION = "version";
+
+ private static final ArrayList<Integer> PREJUDICAL_DISMISSALS = new ArrayList<>();
static {
- DISMISS_WITH_PREJUDICE.add(REASON_CANCEL);
- DISMISS_WITH_PREJUDICE.add(REASON_LISTENER_CANCEL);
+ PREJUDICAL_DISMISSALS.add(REASON_CANCEL);
+ PREJUDICAL_DISMISSALS.add(REASON_LISTENER_CANCEL);
}
// key : impressions tracker
- // TODO: persist across reboots
+ // TODO: prune deleted channels and apps
ArrayMap<String, ChannelImpressions> mkeyToImpressions = new ArrayMap<>();
// SBN key : channel id
ArrayMap<String, String> mLiveNotifications = new ArrayMap<>();
private Ranking mFakeRanking = null;
+ private AtomicFile mFile = null;
+
+ public Assistant() {
+ }
+
+ private void loadFile() {
+ if (DEBUG) Slog.d(TAG, "loadFile");
+ AsyncTask.execute(() -> {
+ InputStream infile = null;
+ try {
+ infile = mFile.openRead();
+ readXml(infile);
+ } catch (FileNotFoundException e) {
+ // No data yet
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to read channel impressions", e);
+ } catch (NumberFormatException | XmlPullParserException e) {
+ Log.e(TAG, "Unable to parse channel impressions", e);
+ } finally {
+ IoUtils.closeQuietly(infile);
+ }
+ });
+ }
+
+ protected void readXml(InputStream stream)
+ throws XmlPullParserException, NumberFormatException, IOException {
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, StandardCharsets.UTF_8.name());
+ final int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ if (!TAG_ASSISTANT.equals(parser.getName())) {
+ continue;
+ }
+ final int impressionOuterDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, impressionOuterDepth)) {
+ if (!TAG_IMPRESSION.equals(parser.getName())) {
+ continue;
+ }
+ String key = parser.getAttributeValue(null, ATT_KEY);
+ ChannelImpressions ci = new ChannelImpressions();
+ ci.populateFromXml(parser);
+ synchronized (mkeyToImpressions) {
+ ci.append(mkeyToImpressions.get(key));
+ mkeyToImpressions.put(key, ci);
+ }
+ }
+ }
+ }
+
+ private void saveFile() throws IOException {
+ AsyncTask.execute(() -> {
+ final FileOutputStream stream;
+ try {
+ stream = mFile.startWrite();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to save policy file", e);
+ return;
+ }
+ try {
+ final XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, StandardCharsets.UTF_8.name());
+ writeXml(out);
+ mFile.finishWrite(stream);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to save impressions file, restoring backup", e);
+ mFile.failWrite(stream);
+ }
+ });
+ }
+
+ protected void writeXml(XmlSerializer out) throws IOException {
+ out.startDocument(null, true);
+ out.startTag(null, TAG_ASSISTANT);
+ out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
+ synchronized (mkeyToImpressions) {
+ for (Map.Entry<String, ChannelImpressions> entry
+ : mkeyToImpressions.entrySet()) {
+ // TODO: ensure channel still exists
+ out.startTag(null, TAG_IMPRESSION);
+ out.attribute(null, ATT_KEY, entry.getKey());
+ entry.getValue().writeXml(out);
+ out.endTag(null, TAG_IMPRESSION);
+ }
+ }
+ out.endTag(null, TAG_ASSISTANT);
+ out.endDocument();
+ }
@Override
public Adjustment onNotificationEnqueued(StatusBarNotification sbn) {
@@ -87,26 +201,38 @@ public class Assistant extends NotificationAssistantService {
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
NotificationStats stats, int reason) {
try {
+ boolean updatedImpressions = false;
String channelId = mLiveNotifications.remove(sbn.getKey());
String key = getKey(sbn.getPackageName(), sbn.getUserId(), channelId);
- ChannelImpressions ci = mkeyToImpressions.getOrDefault(key, new ChannelImpressions());
- if (stats.hasSeen()) {
- ci.incrementViews();
+ synchronized (mkeyToImpressions) {
+ ChannelImpressions ci = mkeyToImpressions.getOrDefault(key,
+ new ChannelImpressions());
+ if (stats.hasSeen()) {
+ ci.incrementViews();
+ updatedImpressions = true;
+ }
+ if (PREJUDICAL_DISMISSALS.contains(reason)) {
+ if ((!sbn.isAppGroup() || sbn.getNotification().isGroupChild())
+ && !stats.hasInteracted()
+ && stats.getDismissalSurface() != NotificationStats.DISMISSAL_AOD
+ && stats.getDismissalSurface() != NotificationStats.DISMISSAL_PEEK
+ && stats.getDismissalSurface() != NotificationStats.DISMISSAL_OTHER) {
+ if (DEBUG) Log.i(TAG, "increment dismissals " + key);
+ ci.incrementDismissals();
+ updatedImpressions = true;
+ } else {
+ if (DEBUG) Slog.i(TAG, "reset streak " + key);
+ if (ci.getStreak() > 0) {
+ updatedImpressions = true;
+ }
+ ci.resetStreak();
+ }
+ }
+ mkeyToImpressions.put(key, ci);
}
- if (DISMISS_WITH_PREJUDICE.contains(reason)
- && !sbn.isAppGroup()
- && !sbn.getNotification().isGroupChild()
- && !stats.hasInteracted()
- && stats.getDismissalSurface() != NotificationStats.DISMISSAL_AOD
- && stats.getDismissalSurface() != NotificationStats.DISMISSAL_PEEK
- && stats.getDismissalSurface() != NotificationStats.DISMISSAL_OTHER) {
- if (DEBUG) Log.i(TAG, "increment dismissals");
- ci.incrementDismissals();
- } else {
- if (DEBUG) Slog.i(TAG, "reset streak");
- ci.resetStreak();
+ if (updatedImpressions) {
+ saveFile();
}
- mkeyToImpressions.put(key, ci);
} catch (Throwable e) {
Slog.e(TAG, "Error occurred processing removal", e);
}
@@ -121,6 +247,11 @@ public class Assistant extends NotificationAssistantService {
public void onListenerConnected() {
if (DEBUG) Log.i(TAG, "CONNECTED");
try {
+ mFile = new AtomicFile(new File(new File(
+ Environment.getDataUserCePackageDirectory(
+ StorageManager.UUID_PRIVATE_INTERNAL, getUserId(), getPackageName()),
+ "assistant"), "block_stats.xml"));
+ loadFile();
for (StatusBarNotification sbn : getActiveNotifications()) {
onNotificationPosted(sbn);
}
@@ -129,7 +260,7 @@ public class Assistant extends NotificationAssistantService {
}
}
- private String getKey(String pkg, int userId, String channelId) {
+ protected String getKey(String pkg, int userId, String channelId) {
return pkg + "|" + userId + "|" + channelId;
}
@@ -151,6 +282,11 @@ public class Assistant extends NotificationAssistantService {
}
// for testing
+
+ protected void setFile(AtomicFile file) {
+ mFile = file;
+ }
+
protected void setFakeRanking(Ranking ranking) {
mFakeRanking = ranking;
}
@@ -162,4 +298,16 @@ public class Assistant extends NotificationAssistantService {
protected void setContext(Context context) {
mSystemContext = context;
}
+
+ protected ChannelImpressions getImpressions(String key) {
+ synchronized (mkeyToImpressions) {
+ return mkeyToImpressions.get(key);
+ }
+ }
+
+ protected void insertImpressions(String key, ChannelImpressions ci) {
+ synchronized (mkeyToImpressions) {
+ mkeyToImpressions.put(key, ci);
+ }
+ }
} \ No newline at end of file
diff --git a/packages/ExtServices/src/android/ext/services/notification/ChannelImpressions.java b/packages/ExtServices/src/android/ext/services/notification/ChannelImpressions.java
index 30567ccdd074..4ad4b2415735 100644
--- a/packages/ExtServices/src/android/ext/services/notification/ChannelImpressions.java
+++ b/packages/ExtServices/src/android/ext/services/notification/ChannelImpressions.java
@@ -18,14 +18,23 @@ package android.ext.services.notification;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.Log;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+
public final class ChannelImpressions implements Parcelable {
private static final String TAG = "ExtAssistant.CI";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
static final double DISMISS_TO_VIEW_RATIO_LIMIT = .8;
static final int STREAK_LIMIT = 2;
+ static final String ATT_DISMISSALS = "dismisses";
+ static final String ATT_VIEWS = "views";
+ static final String ATT_STREAK = "streak";
private int mDismissals = 0;
private int mViews = 0;
@@ -62,6 +71,14 @@ public final class ChannelImpressions implements Parcelable {
mStreak++;
}
+ public void append(ChannelImpressions additionalImpressions) {
+ if (additionalImpressions != null) {
+ mViews += additionalImpressions.getViews();
+ mStreak += additionalImpressions.getStreak();
+ mDismissals += additionalImpressions.getDismissals();
+ }
+ }
+
public void incrementViews() {
mViews++;
}
@@ -134,4 +151,36 @@ public final class ChannelImpressions implements Parcelable {
sb.append('}');
return sb.toString();
}
+
+ protected void populateFromXml(XmlPullParser parser) {
+ mDismissals = safeInt(parser, ATT_DISMISSALS, 0);
+ mStreak = safeInt(parser, ATT_STREAK, 0);
+ mViews = safeInt(parser, ATT_VIEWS, 0);
+ }
+
+ protected void writeXml(XmlSerializer out) throws IOException {
+ if (mDismissals != 0) {
+ out.attribute(null, ATT_DISMISSALS, String.valueOf(mDismissals));
+ }
+ if (mStreak != 0) {
+ out.attribute(null, ATT_STREAK, String.valueOf(mStreak));
+ }
+ if (mViews != 0) {
+ out.attribute(null, ATT_VIEWS, String.valueOf(mViews));
+ }
+ }
+
+ private static int safeInt(XmlPullParser parser, String att, int defValue) {
+ final String val = parser.getAttributeValue(null, att);
+ return tryParseInt(val, defValue);
+ }
+
+ private static int tryParseInt(String value, int defValue) {
+ if (TextUtils.isEmpty(value)) return defValue;
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
+ }
}
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java
index 4e5e9f9d1e00..7c35b48310f2 100644
--- a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java
@@ -31,7 +31,6 @@ import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.content.Intent;
-import android.ext.services.R;
import android.os.UserHandle;
import android.service.notification.Adjustment;
import android.service.notification.NotificationListenerService;
@@ -42,6 +41,10 @@ import android.service.notification.StatusBarNotification;
import android.support.test.InstrumentationRegistry;
import android.test.ServiceTestCase;
import android.testing.TestableContext;
+import android.util.AtomicFile;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
import org.junit.Before;
import org.junit.Rule;
@@ -49,6 +52,14 @@ import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
public class AssistantTest extends ServiceTestCase<Assistant> {
@@ -67,6 +78,8 @@ public class AssistantTest extends ServiceTestCase<Assistant> {
new NotificationChannel("one", "", IMPORTANCE_LOW);
@Mock INotificationManager mNoMan;
+ @Mock
+ AtomicFile mFile;
Assistant mAssistant;
@@ -88,6 +101,8 @@ public class AssistantTest extends ServiceTestCase<Assistant> {
bindService(startIntent);
mAssistant = getService();
mAssistant.setNoMan(mNoMan);
+ mAssistant.setFile(mFile);
+ when(mFile.startWrite()).thenReturn(mock(FileOutputStream.class));
}
private StatusBarNotification generateSbn(String pkg, int uid, NotificationChannel channel,
@@ -170,18 +185,43 @@ public class AssistantTest extends ServiceTestCase<Assistant> {
}
@Test
- public void testGroupCannotTriggerAdjustment() throws Exception {
+ public void testGroupChildCanTriggerAdjustment() throws Exception {
almostBlockChannel(PKG1, UID1, P1C1);
StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C1, "no", "I HAVE A GROUP");
- mAssistant.setFakeRanking(mock(Ranking.class));
+ mAssistant.setFakeRanking(generateRanking(sbn, P1C1));
NotificationStats stats = new NotificationStats();
stats.setDismissalSurface(NotificationStats.DISMISSAL_SHADE);
stats.setSeen();
+ mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
mAssistant.onNotificationRemoved(
sbn, mock(RankingMap.class), stats, NotificationListenerService.REASON_CANCEL);
- sbn = generateSbn(PKG1, UID1, P1C1, "new one!", null);
+ sbn = generateSbn(PKG1, UID1, P1C1, "new one!", "group");
+ mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
+
+ ArgumentCaptor<Adjustment> captor = ArgumentCaptor.forClass(Adjustment.class);
+ verify(mNoMan, times(1)).applyAdjustmentFromAssistant(any(), captor.capture());
+ assertEquals(sbn.getKey(), captor.getValue().getKey());
+ assertEquals(Ranking.USER_SENTIMENT_NEGATIVE,
+ captor.getValue().getSignals().getInt(Adjustment.KEY_USER_SENTIMENT));
+ }
+
+ @Test
+ public void testGroupSummaryCannotTriggerAdjustment() throws Exception {
+ almostBlockChannel(PKG1, UID1, P1C1);
+
+ StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C1, "no", "I HAVE A GROUP");
+ sbn.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+ mAssistant.setFakeRanking(generateRanking(sbn, P1C1));
+ NotificationStats stats = new NotificationStats();
+ stats.setDismissalSurface(NotificationStats.DISMISSAL_SHADE);
+ stats.setSeen();
+ mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
+ mAssistant.onNotificationRemoved(
+ sbn, mock(RankingMap.class), stats, NotificationListenerService.REASON_CANCEL);
+
+ sbn = generateSbn(PKG1, UID1, P1C1, "new one!", "group");
mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
verify(mNoMan, never()).applyAdjustmentFromAssistant(any(), any());
@@ -192,10 +232,11 @@ public class AssistantTest extends ServiceTestCase<Assistant> {
almostBlockChannel(PKG1, UID1, P1C1);
StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C1, "no", null);
- mAssistant.setFakeRanking(mock(Ranking.class));
+ mAssistant.setFakeRanking(generateRanking(sbn, P1C1));
NotificationStats stats = new NotificationStats();
stats.setDismissalSurface(NotificationStats.DISMISSAL_AOD);
stats.setSeen();
+ mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
mAssistant.onNotificationRemoved(
sbn, mock(RankingMap.class), stats, NotificationListenerService.REASON_CANCEL);
@@ -208,13 +249,13 @@ public class AssistantTest extends ServiceTestCase<Assistant> {
@Test
public void testInteractedCannotTriggerAdjustment() throws Exception {
almostBlockChannel(PKG1, UID1, P1C1);
-
StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C1, "no", null);
- mAssistant.setFakeRanking(mock(Ranking.class));
+ mAssistant.setFakeRanking(generateRanking(sbn, P1C1));
NotificationStats stats = new NotificationStats();
stats.setDismissalSurface(NotificationStats.DISMISSAL_SHADE);
stats.setSeen();
stats.setExpanded();
+ mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
mAssistant.onNotificationRemoved(
sbn, mock(RankingMap.class), stats, NotificationListenerService.REASON_CANCEL);
@@ -229,10 +270,11 @@ public class AssistantTest extends ServiceTestCase<Assistant> {
almostBlockChannel(PKG1, UID1, P1C1);
StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C1, "no", null);
- mAssistant.setFakeRanking(mock(Ranking.class));
+ mAssistant.setFakeRanking(generateRanking(sbn, P1C1));
NotificationStats stats = new NotificationStats();
stats.setDismissalSurface(NotificationStats.DISMISSAL_SHADE);
stats.setSeen();
+ mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
mAssistant.onNotificationRemoved(
sbn, mock(RankingMap.class), stats, NotificationListenerService.REASON_APP_CANCEL);
@@ -265,4 +307,88 @@ public class AssistantTest extends ServiceTestCase<Assistant> {
verify(mNoMan, never()).applyAdjustmentFromAssistant(any(), any());
}
+
+ @Test
+ public void testReadXml() throws Exception {
+ String key1 = mAssistant.getKey("pkg1", 1, "channel1");
+ int streak1 = 2;
+ int views1 = 5;
+ int dismiss1 = 9;
+
+ int streak1a = 3;
+ int views1a = 10;
+ int dismiss1a = 99;
+ String key1a = mAssistant.getKey("pkg1", 1, "channel1a");
+
+ int streak2 = 7;
+ int views2 = 77;
+ int dismiss2 = 777;
+ String key2 = mAssistant.getKey("pkg2", 2, "channel2");
+
+ String xml = "<assistant version=\"1\">\n"
+ + "<impression-set key=\"" + key1 + "\" "
+ + "dismisses=\"" + dismiss1 + "\" views=\"" + views1
+ + "\" streak=\"" + streak1 + "\"/>\n"
+ + "<impression-set key=\"" + key1a + "\" "
+ + "dismisses=\"" + dismiss1a + "\" views=\"" + views1a
+ + "\" streak=\"" + streak1a + "\"/>\n"
+ + "<impression-set key=\"" + key2 + "\" "
+ + "dismisses=\"" + dismiss2 + "\" views=\"" + views2
+ + "\" streak=\"" + streak2 + "\"/>\n"
+ + "</assistant>\n";
+ mAssistant.readXml(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())));
+
+ ChannelImpressions c1 = mAssistant.getImpressions(key1);
+ assertEquals(2, c1.getStreak());
+ assertEquals(5, c1.getViews());
+ assertEquals(9, c1.getDismissals());
+
+ ChannelImpressions c1a = mAssistant.getImpressions(key1a);
+ assertEquals(3, c1a.getStreak());
+ assertEquals(10, c1a.getViews());
+ assertEquals(99, c1a.getDismissals());
+
+ ChannelImpressions c2 = mAssistant.getImpressions(key2);
+ assertEquals(7, c2.getStreak());
+ assertEquals(77, c2.getViews());
+ assertEquals(777, c2.getDismissals());
+ }
+
+ @Test
+ public void testRoundTripXml() throws Exception {
+ String key1 = mAssistant.getKey("pkg1", 1, "channel1");
+ ChannelImpressions ci1 = new ChannelImpressions(9, 10);
+ String key2 = mAssistant.getKey("pkg1", 1, "channel2");
+ ChannelImpressions ci2 = new ChannelImpressions();
+ for (int i = 0; i < 3; i++) {
+ ci2.incrementViews();
+ ci2.incrementDismissals();
+ }
+ ChannelImpressions ci3 = new ChannelImpressions();
+ String key3 = mAssistant.getKey("pkg3", 3, "channel2");
+ for (int i = 0; i < 9; i++) {
+ ci3.incrementViews();
+ if (i % 3 == 0) {
+ ci3.incrementDismissals();
+ }
+ }
+
+ mAssistant.insertImpressions(key1, ci1);
+ mAssistant.insertImpressions(key2, ci2);
+ mAssistant.insertImpressions(key3, ci3);
+
+
+ XmlSerializer serializer = new FastXmlSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ mAssistant.writeXml(serializer);
+
+ Assistant assistant = new Assistant();
+ assistant.readXml(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())));
+
+ assertEquals(ci1, assistant.getImpressions(key1));
+ assertEquals(ci2, assistant.getImpressions(key2));
+ assertEquals(ci3, assistant.getImpressions(key3));
+ }
+
}
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/ChannelImpressionsTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/ChannelImpressionsTest.java
index a8c9fa35e678..d28e2ac0cffe 100644
--- a/packages/ExtServices/tests/src/android/ext/services/notification/ChannelImpressionsTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/ChannelImpressionsTest.java
@@ -21,6 +21,8 @@ import static android.ext.services.notification.ChannelImpressions.STREAK_LIMIT;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+
import org.junit.Test;
public class ChannelImpressionsTest {
@@ -82,4 +84,28 @@ public class ChannelImpressionsTest {
assertFalse(ci.shouldTriggerBlock());
}
+
+ @Test
+ public void testAppend() {
+ ChannelImpressions ci = new ChannelImpressions();
+ ci.incrementViews();
+ ci.incrementDismissals();
+
+ ChannelImpressions ci2 = new ChannelImpressions();
+ ci2.incrementViews();
+ ci2.incrementDismissals();
+ ci2.incrementViews();
+
+ ci.append(ci2);
+ assertEquals(3, ci.getViews());
+ assertEquals(2, ci.getDismissals());
+ assertEquals(2, ci.getStreak());
+
+ assertEquals(2, ci2.getViews());
+ assertEquals(1, ci2.getDismissals());
+ assertEquals(1, ci2.getStreak());
+
+ // no crash
+ ci.append(null);
+ }
}
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index df989f7d6be5..3c02e23eed2a 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -25,6 +25,20 @@ import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.fail;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
@@ -74,20 +88,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RankingHelperTest extends NotificationTestCase {