Merge "Fix unbounded number of uncached shortcuts" into tm-qpr-dev

This commit is contained in:
Jeff Nainaparampil
2022-08-17 15:41:48 +00:00
committed by Android (Google) Code Review
5 changed files with 250 additions and 12 deletions

View File

@@ -62,7 +62,10 @@ message ConversationInfoProto {
// The timestamp of the last event in millis. // The timestamp of the last event in millis.
optional int64 last_event_timestamp = 9; optional int64 last_event_timestamp = 9;
// Next tag: 10 // The timestamp this conversation was created in millis.
optional int64 creation_timestamp = 10;
// Next tag: 11
} }
// On disk data of events. // On disk data of events.

View File

@@ -26,6 +26,7 @@ import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutInfo.ShortcutFlags; import android.content.pm.ShortcutInfo.ShortcutFlags;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.util.Slog; import android.util.Slog;
import android.util.proto.ProtoInputStream; import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream; import android.util.proto.ProtoOutputStream;
@@ -37,6 +38,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@@ -50,6 +52,11 @@ import java.util.Objects;
* Represents a conversation that is provided by the app based on {@link ShortcutInfo}. * Represents a conversation that is provided by the app based on {@link ShortcutInfo}.
*/ */
public class ConversationInfo { public class ConversationInfo {
private static final boolean DEBUG = false;
// Schema version for the backup payload. Must be incremented whenever fields are added in
// backup payload.
private static final int VERSION = 1;
private static final String TAG = ConversationInfo.class.getSimpleName(); private static final String TAG = ConversationInfo.class.getSimpleName();
@@ -100,6 +107,8 @@ public class ConversationInfo {
private long mLastEventTimestamp; private long mLastEventTimestamp;
private long mCreationTimestamp;
@ShortcutFlags @ShortcutFlags
private int mShortcutFlags; private int mShortcutFlags;
@@ -116,6 +125,7 @@ public class ConversationInfo {
mNotificationChannelId = builder.mNotificationChannelId; mNotificationChannelId = builder.mNotificationChannelId;
mParentNotificationChannelId = builder.mParentNotificationChannelId; mParentNotificationChannelId = builder.mParentNotificationChannelId;
mLastEventTimestamp = builder.mLastEventTimestamp; mLastEventTimestamp = builder.mLastEventTimestamp;
mCreationTimestamp = builder.mCreationTimestamp;
mShortcutFlags = builder.mShortcutFlags; mShortcutFlags = builder.mShortcutFlags;
mConversationFlags = builder.mConversationFlags; mConversationFlags = builder.mConversationFlags;
mCurrStatuses = builder.mCurrStatuses; mCurrStatuses = builder.mCurrStatuses;
@@ -170,6 +180,13 @@ public class ConversationInfo {
return mLastEventTimestamp; return mLastEventTimestamp;
} }
/**
* Timestamp of the creation of the conversationInfo.
*/
long getCreationTimestamp() {
return mCreationTimestamp;
}
/** Whether the shortcut for this conversation is set long-lived by the app. */ /** Whether the shortcut for this conversation is set long-lived by the app. */
public boolean isShortcutLongLived() { public boolean isShortcutLongLived() {
return hasShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED); return hasShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED);
@@ -241,6 +258,7 @@ public class ConversationInfo {
&& Objects.equals(mNotificationChannelId, other.mNotificationChannelId) && Objects.equals(mNotificationChannelId, other.mNotificationChannelId)
&& Objects.equals(mParentNotificationChannelId, other.mParentNotificationChannelId) && Objects.equals(mParentNotificationChannelId, other.mParentNotificationChannelId)
&& Objects.equals(mLastEventTimestamp, other.mLastEventTimestamp) && Objects.equals(mLastEventTimestamp, other.mLastEventTimestamp)
&& mCreationTimestamp == other.mCreationTimestamp
&& mShortcutFlags == other.mShortcutFlags && mShortcutFlags == other.mShortcutFlags
&& mConversationFlags == other.mConversationFlags && mConversationFlags == other.mConversationFlags
&& Objects.equals(mCurrStatuses, other.mCurrStatuses); && Objects.equals(mCurrStatuses, other.mCurrStatuses);
@@ -250,7 +268,7 @@ public class ConversationInfo {
public int hashCode() { public int hashCode() {
return Objects.hash(mShortcutId, mLocusId, mContactUri, mContactPhoneNumber, return Objects.hash(mShortcutId, mLocusId, mContactUri, mContactPhoneNumber,
mNotificationChannelId, mParentNotificationChannelId, mLastEventTimestamp, mNotificationChannelId, mParentNotificationChannelId, mLastEventTimestamp,
mShortcutFlags, mConversationFlags, mCurrStatuses); mCreationTimestamp, mShortcutFlags, mConversationFlags, mCurrStatuses);
} }
@Override @Override
@@ -264,6 +282,7 @@ public class ConversationInfo {
sb.append(", notificationChannelId=").append(mNotificationChannelId); sb.append(", notificationChannelId=").append(mNotificationChannelId);
sb.append(", parentNotificationChannelId=").append(mParentNotificationChannelId); sb.append(", parentNotificationChannelId=").append(mParentNotificationChannelId);
sb.append(", lastEventTimestamp=").append(mLastEventTimestamp); sb.append(", lastEventTimestamp=").append(mLastEventTimestamp);
sb.append(", creationTimestamp=").append(mCreationTimestamp);
sb.append(", statuses=").append(mCurrStatuses); sb.append(", statuses=").append(mCurrStatuses);
sb.append(", shortcutFlags=0x").append(Integer.toHexString(mShortcutFlags)); sb.append(", shortcutFlags=0x").append(Integer.toHexString(mShortcutFlags));
sb.append(" ["); sb.append(" [");
@@ -329,6 +348,7 @@ public class ConversationInfo {
mParentNotificationChannelId); mParentNotificationChannelId);
} }
protoOutputStream.write(ConversationInfoProto.LAST_EVENT_TIMESTAMP, mLastEventTimestamp); protoOutputStream.write(ConversationInfoProto.LAST_EVENT_TIMESTAMP, mLastEventTimestamp);
protoOutputStream.write(ConversationInfoProto.CREATION_TIMESTAMP, mCreationTimestamp);
protoOutputStream.write(ConversationInfoProto.SHORTCUT_FLAGS, mShortcutFlags); protoOutputStream.write(ConversationInfoProto.SHORTCUT_FLAGS, mShortcutFlags);
protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags); protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags);
if (mContactPhoneNumber != null) { if (mContactPhoneNumber != null) {
@@ -352,6 +372,8 @@ public class ConversationInfo {
out.writeUTF(mContactPhoneNumber != null ? mContactPhoneNumber : ""); out.writeUTF(mContactPhoneNumber != null ? mContactPhoneNumber : "");
out.writeUTF(mParentNotificationChannelId != null ? mParentNotificationChannelId : ""); out.writeUTF(mParentNotificationChannelId != null ? mParentNotificationChannelId : "");
out.writeLong(mLastEventTimestamp); out.writeLong(mLastEventTimestamp);
out.writeInt(VERSION);
out.writeLong(mCreationTimestamp);
// ConversationStatus is a transient object and not persisted // ConversationStatus is a transient object and not persisted
} catch (IOException e) { } catch (IOException e) {
Slog.e(TAG, "Failed to write fields to backup payload.", e); Slog.e(TAG, "Failed to write fields to backup payload.", e);
@@ -399,6 +421,9 @@ public class ConversationInfo {
builder.setLastEventTimestamp(protoInputStream.readLong( builder.setLastEventTimestamp(protoInputStream.readLong(
ConversationInfoProto.LAST_EVENT_TIMESTAMP)); ConversationInfoProto.LAST_EVENT_TIMESTAMP));
break; break;
case (int) ConversationInfoProto.CREATION_TIMESTAMP:
builder.setCreationTimestamp(protoInputStream.readLong(
ConversationInfoProto.CREATION_TIMESTAMP));
case (int) ConversationInfoProto.SHORTCUT_FLAGS: case (int) ConversationInfoProto.SHORTCUT_FLAGS:
builder.setShortcutFlags(protoInputStream.readInt( builder.setShortcutFlags(protoInputStream.readInt(
ConversationInfoProto.SHORTCUT_FLAGS)); ConversationInfoProto.SHORTCUT_FLAGS));
@@ -448,6 +473,10 @@ public class ConversationInfo {
builder.setParentNotificationChannelId(parentNotificationChannelId); builder.setParentNotificationChannelId(parentNotificationChannelId);
} }
builder.setLastEventTimestamp(in.readLong()); builder.setLastEventTimestamp(in.readLong());
int payloadVersion = maybeReadVersion(in);
if (payloadVersion == 1) {
builder.setCreationTimestamp(in.readLong());
}
} catch (IOException e) { } catch (IOException e) {
Slog.e(TAG, "Failed to read conversation info fields from backup payload.", e); Slog.e(TAG, "Failed to read conversation info fields from backup payload.", e);
return null; return null;
@@ -455,6 +484,16 @@ public class ConversationInfo {
return builder.build(); return builder.build();
} }
private static int maybeReadVersion(DataInputStream in) throws IOException {
try {
return in.readInt();
} catch (EOFException eofException) {
// EOF is expected if using old backup payload protocol.
if (DEBUG) Log.d(TAG, "Eof reached for data stream, missing version number");
return 0;
}
}
/** /**
* Builder class for {@link ConversationInfo} objects. * Builder class for {@link ConversationInfo} objects.
*/ */
@@ -479,6 +518,8 @@ public class ConversationInfo {
private long mLastEventTimestamp; private long mLastEventTimestamp;
private long mCreationTimestamp;
@ShortcutFlags @ShortcutFlags
private int mShortcutFlags; private int mShortcutFlags;
@@ -502,6 +543,7 @@ public class ConversationInfo {
mNotificationChannelId = conversationInfo.mNotificationChannelId; mNotificationChannelId = conversationInfo.mNotificationChannelId;
mParentNotificationChannelId = conversationInfo.mParentNotificationChannelId; mParentNotificationChannelId = conversationInfo.mParentNotificationChannelId;
mLastEventTimestamp = conversationInfo.mLastEventTimestamp; mLastEventTimestamp = conversationInfo.mLastEventTimestamp;
mCreationTimestamp = conversationInfo.mCreationTimestamp;
mShortcutFlags = conversationInfo.mShortcutFlags; mShortcutFlags = conversationInfo.mShortcutFlags;
mConversationFlags = conversationInfo.mConversationFlags; mConversationFlags = conversationInfo.mConversationFlags;
mCurrStatuses = conversationInfo.mCurrStatuses; mCurrStatuses = conversationInfo.mCurrStatuses;
@@ -542,6 +584,11 @@ public class ConversationInfo {
return this; return this;
} }
Builder setCreationTimestamp(long creationTimestamp) {
mCreationTimestamp = creationTimestamp;
return this;
}
Builder setShortcutFlags(@ShortcutFlags int shortcutFlags) { Builder setShortcutFlags(@ShortcutFlags int shortcutFlags) {
mShortcutFlags = shortcutFlags; mShortcutFlags = shortcutFlags;
return this; return this;

View File

@@ -816,10 +816,18 @@ public class DataManager {
} }
private boolean isCachedRecentConversation(ConversationInfo conversationInfo) { private boolean isCachedRecentConversation(ConversationInfo conversationInfo) {
return isEligibleForCleanUp(conversationInfo)
&& conversationInfo.getLastEventTimestamp() > 0L;
}
/**
* Conversations that are cached and not customized are eligible for clean-up, even if they
* don't have an associated notification event with them.
*/
private boolean isEligibleForCleanUp(ConversationInfo conversationInfo) {
return conversationInfo.isShortcutCachedForNotification() return conversationInfo.isShortcutCachedForNotification()
&& Objects.equals(conversationInfo.getNotificationChannelId(), && Objects.equals(conversationInfo.getNotificationChannelId(),
conversationInfo.getParentNotificationChannelId()) conversationInfo.getParentNotificationChannelId());
&& conversationInfo.getLastEventTimestamp() > 0L;
} }
private boolean hasActiveNotifications(String packageName, @UserIdInt int userId, private boolean hasActiveNotifications(String packageName, @UserIdInt int userId,
@@ -842,14 +850,14 @@ public class DataManager {
} }
// pair of <package name, conversation info> // pair of <package name, conversation info>
List<Pair<String, ConversationInfo>> cachedConvos = new ArrayList<>(); List<Pair<String, ConversationInfo>> cachedConvos = new ArrayList<>();
userData.forAllPackages(packageData -> userData.forAllPackages(packageData -> {
packageData.forAllConversations(conversationInfo -> { packageData.forAllConversations(conversationInfo -> {
if (isCachedRecentConversation(conversationInfo)) { if (isEligibleForCleanUp(conversationInfo)) {
cachedConvos.add( cachedConvos.add(
Pair.create(packageData.getPackageName(), conversationInfo)); Pair.create(packageData.getPackageName(), conversationInfo));
} }
}) });
); });
if (cachedConvos.size() <= targetCachedCount) { if (cachedConvos.size() <= targetCachedCount) {
return; return;
} }
@@ -858,7 +866,9 @@ public class DataManager {
PriorityQueue<Pair<String, ConversationInfo>> maxHeap = new PriorityQueue<>( PriorityQueue<Pair<String, ConversationInfo>> maxHeap = new PriorityQueue<>(
numToUncache + 1, numToUncache + 1,
Comparator.comparingLong((Pair<String, ConversationInfo> pair) -> Comparator.comparingLong((Pair<String, ConversationInfo> pair) ->
pair.second.getLastEventTimestamp()).reversed()); Math.max(
pair.second.getLastEventTimestamp(),
pair.second.getCreationTimestamp())).reversed());
for (Pair<String, ConversationInfo> cached : cachedConvos) { for (Pair<String, ConversationInfo> cached : cachedConvos) {
if (hasActiveNotifications(cached.first, userId, cached.second.getShortcutId())) { if (hasActiveNotifications(cached.first, userId, cached.second.getShortcutId())) {
continue; continue;
@@ -893,7 +903,7 @@ public class DataManager {
} }
ConversationInfo.Builder builder = oldConversationInfo != null ConversationInfo.Builder builder = oldConversationInfo != null
? new ConversationInfo.Builder(oldConversationInfo) ? new ConversationInfo.Builder(oldConversationInfo)
: new ConversationInfo.Builder(); : new ConversationInfo.Builder().setCreationTimestamp(System.currentTimeMillis());
builder.setShortcutId(shortcutInfo.getId()); builder.setShortcutId(shortcutInfo.getId());
builder.setLocusId(shortcutInfo.getLocusId()); builder.setLocusId(shortcutInfo.getLocusId());
@@ -1326,7 +1336,8 @@ public class DataManager {
} }
} }
private void updateConversationStoreThenNotifyListeners(ConversationStore cs, @VisibleForTesting
void updateConversationStoreThenNotifyListeners(ConversationStore cs,
ConversationInfo modifiedConv, ConversationInfo modifiedConv,
String packageName, int userId) { String packageName, int userId) {
cs.addOrUpdate(modifiedConv); cs.addOrUpdate(modifiedConv);

View File

@@ -58,6 +58,7 @@ public final class ConversationInfoTest {
.setNotificationChannelId(NOTIFICATION_CHANNEL_ID) .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
.setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID) .setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
.setLastEventTimestamp(100L) .setLastEventTimestamp(100L)
.setCreationTimestamp(200L)
.setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED
| ShortcutInfo.FLAG_CACHED_NOTIFICATIONS) | ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)
.setImportant(true) .setImportant(true)
@@ -79,6 +80,7 @@ public final class ConversationInfoTest {
assertEquals(PARENT_NOTIFICATION_CHANNEL_ID, assertEquals(PARENT_NOTIFICATION_CHANNEL_ID,
conversationInfo.getParentNotificationChannelId()); conversationInfo.getParentNotificationChannelId());
assertEquals(100L, conversationInfo.getLastEventTimestamp()); assertEquals(100L, conversationInfo.getLastEventTimestamp());
assertEquals(200L, conversationInfo.getCreationTimestamp());
assertTrue(conversationInfo.isShortcutLongLived()); assertTrue(conversationInfo.isShortcutLongLived());
assertTrue(conversationInfo.isShortcutCachedForNotification()); assertTrue(conversationInfo.isShortcutCachedForNotification());
assertTrue(conversationInfo.isImportant()); assertTrue(conversationInfo.isImportant());
@@ -105,6 +107,7 @@ public final class ConversationInfoTest {
assertNull(conversationInfo.getNotificationChannelId()); assertNull(conversationInfo.getNotificationChannelId());
assertNull(conversationInfo.getParentNotificationChannelId()); assertNull(conversationInfo.getParentNotificationChannelId());
assertEquals(0L, conversationInfo.getLastEventTimestamp()); assertEquals(0L, conversationInfo.getLastEventTimestamp());
assertEquals(0L, conversationInfo.getCreationTimestamp());
assertFalse(conversationInfo.isShortcutLongLived()); assertFalse(conversationInfo.isShortcutLongLived());
assertFalse(conversationInfo.isShortcutCachedForNotification()); assertFalse(conversationInfo.isShortcutCachedForNotification());
assertFalse(conversationInfo.isImportant()); assertFalse(conversationInfo.isImportant());
@@ -131,6 +134,7 @@ public final class ConversationInfoTest {
.setNotificationChannelId(NOTIFICATION_CHANNEL_ID) .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
.setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID) .setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
.setLastEventTimestamp(100L) .setLastEventTimestamp(100L)
.setCreationTimestamp(200L)
.setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED) .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
.setImportant(true) .setImportant(true)
.setNotificationSilenced(true) .setNotificationSilenced(true)
@@ -154,6 +158,7 @@ public final class ConversationInfoTest {
assertEquals(NOTIFICATION_CHANNEL_ID, destination.getNotificationChannelId()); assertEquals(NOTIFICATION_CHANNEL_ID, destination.getNotificationChannelId());
assertEquals(PARENT_NOTIFICATION_CHANNEL_ID, destination.getParentNotificationChannelId()); assertEquals(PARENT_NOTIFICATION_CHANNEL_ID, destination.getParentNotificationChannelId());
assertEquals(100L, destination.getLastEventTimestamp()); assertEquals(100L, destination.getLastEventTimestamp());
assertEquals(200L, destination.getCreationTimestamp());
assertTrue(destination.isShortcutLongLived()); assertTrue(destination.isShortcutLongLived());
assertFalse(destination.isImportant()); assertFalse(destination.isImportant());
assertTrue(destination.isNotificationSilenced()); assertTrue(destination.isNotificationSilenced());
@@ -164,4 +169,105 @@ public final class ConversationInfoTest {
assertThat(destination.getStatuses()).contains(cs); assertThat(destination.getStatuses()).contains(cs);
assertThat(destination.getStatuses()).contains(cs2); assertThat(destination.getStatuses()).contains(cs2);
} }
@Test
public void testBuildFromAnotherConversation_identicalConversation() {
ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build();
ConversationStatus cs2 = new ConversationStatus.Builder("id2", ACTIVITY_GAME).build();
ConversationInfo source = new ConversationInfo.Builder()
.setShortcutId(SHORTCUT_ID)
.setLocusId(LOCUS_ID)
.setContactUri(CONTACT_URI)
.setContactPhoneNumber(PHONE_NUMBER)
.setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
.setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
.setLastEventTimestamp(100L)
.setCreationTimestamp(200L)
.setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
.setImportant(true)
.setNotificationSilenced(true)
.setBubbled(true)
.setPersonImportant(true)
.setPersonBot(true)
.setContactStarred(true)
.addOrUpdateStatus(cs)
.addOrUpdateStatus(cs2)
.build();
ConversationInfo destination = new ConversationInfo.Builder(source).build();
assertEquals(SHORTCUT_ID, destination.getShortcutId());
assertEquals(LOCUS_ID, destination.getLocusId());
assertEquals(CONTACT_URI, destination.getContactUri());
assertEquals(PHONE_NUMBER, destination.getContactPhoneNumber());
assertEquals(NOTIFICATION_CHANNEL_ID, destination.getNotificationChannelId());
assertEquals(PARENT_NOTIFICATION_CHANNEL_ID, destination.getParentNotificationChannelId());
assertEquals(100L, destination.getLastEventTimestamp());
assertEquals(200L, destination.getCreationTimestamp());
assertTrue(destination.isShortcutLongLived());
assertTrue(destination.isImportant());
assertTrue(destination.isNotificationSilenced());
assertTrue(destination.isBubbled());
assertTrue(destination.isPersonImportant());
assertTrue(destination.isPersonBot());
assertTrue(destination.isContactStarred());
assertThat(destination.getStatuses()).contains(cs);
assertThat(destination.getStatuses()).contains(cs2);
// Also check equals() implementation
assertTrue(source.equals(destination));
assertTrue(destination.equals(source));
}
@Test
public void testBuildFromBackupPayload() {
ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build();
ConversationStatus cs2 = new ConversationStatus.Builder("id2", ACTIVITY_GAME).build();
ConversationInfo conversationInfo = new ConversationInfo.Builder()
.setShortcutId(SHORTCUT_ID)
.setLocusId(LOCUS_ID)
.setContactUri(CONTACT_URI)
.setContactPhoneNumber(PHONE_NUMBER)
.setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
.setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
.setLastEventTimestamp(100L)
.setCreationTimestamp(200L)
.setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED
| ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)
.setImportant(true)
.setNotificationSilenced(true)
.setBubbled(true)
.setDemoted(true)
.setPersonImportant(true)
.setPersonBot(true)
.setContactStarred(true)
.addOrUpdateStatus(cs)
.addOrUpdateStatus(cs2)
.build();
ConversationInfo conversationInfoFromBackup =
ConversationInfo.readFromBackupPayload(conversationInfo.getBackupPayload());
assertEquals(SHORTCUT_ID, conversationInfoFromBackup.getShortcutId());
assertEquals(LOCUS_ID, conversationInfoFromBackup.getLocusId());
assertEquals(CONTACT_URI, conversationInfoFromBackup.getContactUri());
assertEquals(PHONE_NUMBER, conversationInfoFromBackup.getContactPhoneNumber());
assertEquals(
NOTIFICATION_CHANNEL_ID, conversationInfoFromBackup.getNotificationChannelId());
assertEquals(PARENT_NOTIFICATION_CHANNEL_ID,
conversationInfoFromBackup.getParentNotificationChannelId());
assertEquals(100L, conversationInfoFromBackup.getLastEventTimestamp());
assertEquals(200L, conversationInfoFromBackup.getCreationTimestamp());
assertTrue(conversationInfoFromBackup.isShortcutLongLived());
assertTrue(conversationInfoFromBackup.isShortcutCachedForNotification());
assertTrue(conversationInfoFromBackup.isImportant());
assertTrue(conversationInfoFromBackup.isNotificationSilenced());
assertTrue(conversationInfoFromBackup.isBubbled());
assertTrue(conversationInfoFromBackup.isDemoted());
assertTrue(conversationInfoFromBackup.isPersonImportant());
assertTrue(conversationInfoFromBackup.isPersonBot());
assertTrue(conversationInfoFromBackup.isContactStarred());
// ConversationStatus is a transient object and not persisted
}
} }

View File

@@ -291,7 +291,8 @@ public final class DataManagerTest {
mShortcutChangeCallbackCaptor.capture()); mShortcutChangeCallbackCaptor.capture());
mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue(); mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue();
verify(mContext, times(2)).registerReceiver(any(), any()); verify(mContext, times(1)).registerReceiver(any(), any());
verify(mContext, times(1)).registerReceiver(any(), any(), anyInt());
} }
@After @After
@@ -1162,6 +1163,76 @@ public final class DataManagerTest {
} }
} }
@Test
public void testUncacheOldestCachedShortcut_missingNotificationEvents() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
for (int i = 0; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
String shortcutId = TEST_SHORTCUT_ID + i;
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
buildPerson());
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mShortcutChangeCallback.onShortcutsAddedOrUpdated(
TEST_PKG_NAME,
Collections.singletonList(shortcut),
UserHandle.of(USER_ID_PRIMARY));
mLooper.dispatchAll();
}
// Only the shortcut #0 is uncached, all the others are not.
verify(mShortcutServiceInternal).uncacheShortcuts(
anyInt(), any(), eq(TEST_PKG_NAME),
eq(Collections.singletonList(TEST_SHORTCUT_ID + 0)), eq(USER_ID_PRIMARY),
eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
for (int i = 1; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
verify(mShortcutServiceInternal, never()).uncacheShortcuts(
anyInt(), anyString(), anyString(),
eq(Collections.singletonList(TEST_SHORTCUT_ID + i)), anyInt(),
eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
}
}
@Test
public void testUncacheOldestCachedShortcut_legacyConversation() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
// Add an extra conversation with a legacy type (no creationTime)
ConversationStore conversationStore = mDataManager
.getUserDataForTesting(USER_ID_PRIMARY)
.getOrCreatePackageData(TEST_PKG_NAME)
.getConversationStore();
ConversationInfo.Builder builder = new ConversationInfo.Builder();
builder.setShortcutId(TEST_SHORTCUT_ID + 0);
builder.setShortcutFlags(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mDataManager.updateConversationStoreThenNotifyListeners(
conversationStore,
builder.build(),
TEST_PKG_NAME, USER_ID_PRIMARY);
for (int i = 1; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
String shortcutId = TEST_SHORTCUT_ID + i;
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
buildPerson());
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mShortcutChangeCallback.onShortcutsAddedOrUpdated(
TEST_PKG_NAME,
Collections.singletonList(shortcut),
UserHandle.of(USER_ID_PRIMARY));
mLooper.dispatchAll();
}
// Only the shortcut #0 is uncached, all the others are not.
verify(mShortcutServiceInternal).uncacheShortcuts(
anyInt(), any(), eq(TEST_PKG_NAME),
eq(Collections.singletonList(TEST_SHORTCUT_ID + 0)), eq(USER_ID_PRIMARY),
eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
for (int i = 1; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
verify(mShortcutServiceInternal, never()).uncacheShortcuts(
anyInt(), anyString(), anyString(),
eq(Collections.singletonList(TEST_SHORTCUT_ID + i)), anyInt(),
eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
}
}
@Test @Test
public void testBackupAndRestoration() public void testBackupAndRestoration()
throws IntentFilter.MalformedMimeTypeException { throws IntentFilter.MalformedMimeTypeException {