Implement the get/remove recent conversations APIs in PeopleService
A conversation is a cached recent conversation if all below conditions are met: 1) its shortcut is cached 2) its notification setting is not customized 3) it has recent notifications A recent conversation can only be removed if it does not have any active notifications. Bug: 162593584 Test: atest + manual test Change-Id: I0d820a6a2bb82ceb238c3f559e108829d1547291
This commit is contained in:
@@ -46,6 +46,10 @@ message ConversationInfoProto {
|
||||
// The notification channel id of the conversation.
|
||||
optional string notification_channel_id = 4 [(.android.privacy).dest = DEST_EXPLICIT];
|
||||
|
||||
// The parent notification channel ID of the conversation. This is the notification channel where
|
||||
// the notifications are posted before this conversation is customized by the user.
|
||||
optional string parent_notification_channel_id = 8 [(.android.privacy).dest = DEST_EXPLICIT];
|
||||
|
||||
// Integer representation of shortcut bit flags.
|
||||
optional int32 shortcut_flags = 5;
|
||||
|
||||
@@ -54,6 +58,11 @@ message ConversationInfoProto {
|
||||
|
||||
// The phone number of the contact.
|
||||
optional string contact_phone_number = 7 [(.android.privacy).dest = DEST_EXPLICIT];
|
||||
|
||||
// The timestamp of the last event in millis.
|
||||
optional int64 last_event_timestamp = 9;
|
||||
|
||||
// Next tag: 10
|
||||
}
|
||||
|
||||
// On disk data of events.
|
||||
|
||||
@@ -40,7 +40,6 @@ import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.people.data.DataManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
@@ -106,17 +105,23 @@ public class PeopleService extends SystemService {
|
||||
@Override
|
||||
public ParceledListSlice<ConversationChannel> getRecentConversations() {
|
||||
enforceSystemOrRoot("get recent conversations");
|
||||
return new ParceledListSlice<>(new ArrayList<>());
|
||||
return new ParceledListSlice<>(
|
||||
mDataManager.getRecentConversations(
|
||||
Binder.getCallingUserHandle().getIdentifier()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRecentConversation(String packageName, int userId, String shortcutId) {
|
||||
enforceSystemOrRoot("remove a recent conversation");
|
||||
mDataManager.removeRecentConversation(packageName, userId, shortcutId,
|
||||
Binder.getCallingUserHandle().getIdentifier());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAllRecentConversations() {
|
||||
enforceSystemOrRoot("remove all recent conversations");
|
||||
mDataManager.removeAllRecentConversations(
|
||||
Binder.getCallingUserHandle().getIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,11 @@ public class ConversationInfo {
|
||||
@Nullable
|
||||
private String mNotificationChannelId;
|
||||
|
||||
@Nullable
|
||||
private String mParentNotificationChannelId;
|
||||
|
||||
private long mLastEventTimestamp;
|
||||
|
||||
@ShortcutFlags
|
||||
private int mShortcutFlags;
|
||||
|
||||
@@ -102,6 +107,8 @@ public class ConversationInfo {
|
||||
mContactUri = builder.mContactUri;
|
||||
mContactPhoneNumber = builder.mContactPhoneNumber;
|
||||
mNotificationChannelId = builder.mNotificationChannelId;
|
||||
mParentNotificationChannelId = builder.mParentNotificationChannelId;
|
||||
mLastEventTimestamp = builder.mLastEventTimestamp;
|
||||
mShortcutFlags = builder.mShortcutFlags;
|
||||
mConversationFlags = builder.mConversationFlags;
|
||||
}
|
||||
@@ -129,14 +136,32 @@ public class ConversationInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the {@link android.app.NotificationChannel} where the notifications for this
|
||||
* conversation are posted.
|
||||
* ID of the conversation-specific {@link android.app.NotificationChannel} where the
|
||||
* notifications for this conversation are posted.
|
||||
*/
|
||||
@Nullable
|
||||
String getNotificationChannelId() {
|
||||
return mNotificationChannelId;
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the parent {@link android.app.NotificationChannel} for this conversation. This is the
|
||||
* notification channel where the notifications are posted before this conversation is
|
||||
* customized by the user.
|
||||
*/
|
||||
@Nullable
|
||||
String getParentNotificationChannelId() {
|
||||
return mParentNotificationChannelId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamp of the last event, {@code 0L} if there are no events. This timestamp is for
|
||||
* identifying and sorting the recent conversations. It may only count a subset of event types.
|
||||
*/
|
||||
long getLastEventTimestamp() {
|
||||
return mLastEventTimestamp;
|
||||
}
|
||||
|
||||
/** Whether the shortcut for this conversation is set long-lived by the app. */
|
||||
public boolean isShortcutLongLived() {
|
||||
return hasShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED);
|
||||
@@ -202,6 +227,8 @@ public class ConversationInfo {
|
||||
&& Objects.equals(mContactUri, other.mContactUri)
|
||||
&& Objects.equals(mContactPhoneNumber, other.mContactPhoneNumber)
|
||||
&& Objects.equals(mNotificationChannelId, other.mNotificationChannelId)
|
||||
&& Objects.equals(mParentNotificationChannelId, other.mParentNotificationChannelId)
|
||||
&& Objects.equals(mLastEventTimestamp, other.mLastEventTimestamp)
|
||||
&& mShortcutFlags == other.mShortcutFlags
|
||||
&& mConversationFlags == other.mConversationFlags;
|
||||
}
|
||||
@@ -209,7 +236,8 @@ public class ConversationInfo {
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mShortcutId, mLocusId, mContactUri, mContactPhoneNumber,
|
||||
mNotificationChannelId, mShortcutFlags, mConversationFlags);
|
||||
mNotificationChannelId, mParentNotificationChannelId, mLastEventTimestamp,
|
||||
mShortcutFlags, mConversationFlags);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -221,6 +249,8 @@ public class ConversationInfo {
|
||||
sb.append(", contactUri=").append(mContactUri);
|
||||
sb.append(", phoneNumber=").append(mContactPhoneNumber);
|
||||
sb.append(", notificationChannelId=").append(mNotificationChannelId);
|
||||
sb.append(", parentNotificationChannelId=").append(mParentNotificationChannelId);
|
||||
sb.append(", lastEventTimestamp=").append(mLastEventTimestamp);
|
||||
sb.append(", shortcutFlags=0x").append(Integer.toHexString(mShortcutFlags));
|
||||
sb.append(" [");
|
||||
if (isShortcutLongLived()) {
|
||||
@@ -280,6 +310,11 @@ public class ConversationInfo {
|
||||
protoOutputStream.write(ConversationInfoProto.NOTIFICATION_CHANNEL_ID,
|
||||
mNotificationChannelId);
|
||||
}
|
||||
if (mParentNotificationChannelId != null) {
|
||||
protoOutputStream.write(ConversationInfoProto.PARENT_NOTIFICATION_CHANNEL_ID,
|
||||
mParentNotificationChannelId);
|
||||
}
|
||||
protoOutputStream.write(ConversationInfoProto.LAST_EVENT_TIMESTAMP, mLastEventTimestamp);
|
||||
protoOutputStream.write(ConversationInfoProto.SHORTCUT_FLAGS, mShortcutFlags);
|
||||
protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags);
|
||||
if (mContactPhoneNumber != null) {
|
||||
@@ -300,6 +335,8 @@ public class ConversationInfo {
|
||||
out.writeInt(mShortcutFlags);
|
||||
out.writeInt(mConversationFlags);
|
||||
out.writeUTF(mContactPhoneNumber != null ? mContactPhoneNumber : "");
|
||||
out.writeUTF(mParentNotificationChannelId != null ? mParentNotificationChannelId : "");
|
||||
out.writeLong(mLastEventTimestamp);
|
||||
} catch (IOException e) {
|
||||
Slog.e(TAG, "Failed to write fields to backup payload.", e);
|
||||
return null;
|
||||
@@ -338,6 +375,14 @@ public class ConversationInfo {
|
||||
builder.setNotificationChannelId(protoInputStream.readString(
|
||||
ConversationInfoProto.NOTIFICATION_CHANNEL_ID));
|
||||
break;
|
||||
case (int) ConversationInfoProto.PARENT_NOTIFICATION_CHANNEL_ID:
|
||||
builder.setParentNotificationChannelId(protoInputStream.readString(
|
||||
ConversationInfoProto.PARENT_NOTIFICATION_CHANNEL_ID));
|
||||
break;
|
||||
case (int) ConversationInfoProto.LAST_EVENT_TIMESTAMP:
|
||||
builder.setLastEventTimestamp(protoInputStream.readLong(
|
||||
ConversationInfoProto.LAST_EVENT_TIMESTAMP));
|
||||
break;
|
||||
case (int) ConversationInfoProto.SHORTCUT_FLAGS:
|
||||
builder.setShortcutFlags(protoInputStream.readInt(
|
||||
ConversationInfoProto.SHORTCUT_FLAGS));
|
||||
@@ -382,6 +427,11 @@ public class ConversationInfo {
|
||||
if (!TextUtils.isEmpty(contactPhoneNumber)) {
|
||||
builder.setContactPhoneNumber(contactPhoneNumber);
|
||||
}
|
||||
String parentNotificationChannelId = in.readUTF();
|
||||
if (!TextUtils.isEmpty(parentNotificationChannelId)) {
|
||||
builder.setParentNotificationChannelId(parentNotificationChannelId);
|
||||
}
|
||||
builder.setLastEventTimestamp(in.readLong());
|
||||
} catch (IOException e) {
|
||||
Slog.e(TAG, "Failed to read conversation info fields from backup payload.", e);
|
||||
return null;
|
||||
@@ -408,6 +458,11 @@ public class ConversationInfo {
|
||||
@Nullable
|
||||
private String mNotificationChannelId;
|
||||
|
||||
@Nullable
|
||||
private String mParentNotificationChannelId;
|
||||
|
||||
private long mLastEventTimestamp;
|
||||
|
||||
@ShortcutFlags
|
||||
private int mShortcutFlags;
|
||||
|
||||
@@ -427,6 +482,8 @@ public class ConversationInfo {
|
||||
mContactUri = conversationInfo.mContactUri;
|
||||
mContactPhoneNumber = conversationInfo.mContactPhoneNumber;
|
||||
mNotificationChannelId = conversationInfo.mNotificationChannelId;
|
||||
mParentNotificationChannelId = conversationInfo.mParentNotificationChannelId;
|
||||
mLastEventTimestamp = conversationInfo.mLastEventTimestamp;
|
||||
mShortcutFlags = conversationInfo.mShortcutFlags;
|
||||
mConversationFlags = conversationInfo.mConversationFlags;
|
||||
}
|
||||
@@ -456,6 +513,16 @@ public class ConversationInfo {
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder setParentNotificationChannelId(String parentNotificationChannelId) {
|
||||
mParentNotificationChannelId = parentNotificationChannelId;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder setLastEventTimestamp(long lastEventTimestamp) {
|
||||
mLastEventTimestamp = lastEventTimestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder setShortcutFlags(@ShortcutFlags int shortcutFlags) {
|
||||
mShortcutFlags = shortcutFlags;
|
||||
return this;
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Person;
|
||||
import android.app.people.ConversationChannel;
|
||||
import android.app.prediction.AppTarget;
|
||||
import android.app.prediction.AppTargetEvent;
|
||||
import android.app.usage.UsageEvents;
|
||||
@@ -74,9 +75,11 @@ import com.android.server.notification.ShortcutHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
@@ -97,6 +100,7 @@ public class DataManager {
|
||||
|
||||
private static final long QUERY_EVENTS_MAX_AGE_MS = 5L * DateUtils.MINUTE_IN_MILLIS;
|
||||
private static final long USAGE_STATS_QUERY_INTERVAL_SEC = 120L;
|
||||
@VisibleForTesting static final int MAX_CACHED_RECENT_SHORTCUTS = 30;
|
||||
|
||||
private final Context mContext;
|
||||
private final Injector mInjector;
|
||||
@@ -209,6 +213,68 @@ public class DataManager {
|
||||
mContext.getPackageName(), intentFilter, callingUserId);
|
||||
}
|
||||
|
||||
/** Returns the cached non-customized recent conversations. */
|
||||
public List<ConversationChannel> getRecentConversations(@UserIdInt int callingUserId) {
|
||||
List<ConversationChannel> conversationChannels = new ArrayList<>();
|
||||
forPackagesInProfile(callingUserId, packageData -> {
|
||||
String packageName = packageData.getPackageName();
|
||||
int userId = packageData.getUserId();
|
||||
packageData.forAllConversations(conversationInfo -> {
|
||||
if (!isCachedRecentConversation(conversationInfo)) {
|
||||
return;
|
||||
}
|
||||
String shortcutId = conversationInfo.getShortcutId();
|
||||
ShortcutInfo shortcutInfo = getShortcut(packageName, userId, shortcutId);
|
||||
int uid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
|
||||
NotificationChannel parentChannel =
|
||||
mNotificationManagerInternal.getNotificationChannel(packageName, uid,
|
||||
conversationInfo.getParentNotificationChannelId());
|
||||
if (shortcutInfo == null || parentChannel == null) {
|
||||
return;
|
||||
}
|
||||
conversationChannels.add(
|
||||
new ConversationChannel(shortcutInfo, parentChannel,
|
||||
conversationInfo.getLastEventTimestamp(),
|
||||
hasActiveNotifications(packageName, userId, shortcutId)));
|
||||
});
|
||||
});
|
||||
return conversationChannels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uncaches the shortcut that's associated with the specified conversation so this conversation
|
||||
* will not show up in the recent conversations list.
|
||||
*/
|
||||
public void removeRecentConversation(String packageName, int userId, String shortcutId,
|
||||
@UserIdInt int callingUserId) {
|
||||
if (!hasActiveNotifications(packageName, userId, shortcutId)) {
|
||||
mShortcutServiceInternal.uncacheShortcuts(callingUserId, mContext.getPackageName(),
|
||||
packageName, Collections.singletonList(shortcutId), userId,
|
||||
ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uncaches the shortcuts for all the recent conversations that they don't have active
|
||||
* notifications.
|
||||
*/
|
||||
public void removeAllRecentConversations(@UserIdInt int callingUserId) {
|
||||
forPackagesInProfile(callingUserId, packageData -> {
|
||||
String packageName = packageData.getPackageName();
|
||||
int userId = packageData.getUserId();
|
||||
List<String> idsToUncache = new ArrayList<>();
|
||||
packageData.forAllConversations(conversationInfo -> {
|
||||
String shortcutId = conversationInfo.getShortcutId();
|
||||
if (isCachedRecentConversation(conversationInfo)
|
||||
&& !hasActiveNotifications(packageName, userId, shortcutId)) {
|
||||
idsToUncache.add(shortcutId);
|
||||
}
|
||||
});
|
||||
mShortcutServiceInternal.uncacheShortcuts(callingUserId, mContext.getPackageName(),
|
||||
packageName, idsToUncache, userId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
||||
});
|
||||
}
|
||||
|
||||
/** Reports the sharing related {@link AppTargetEvent} from App Prediction Manager. */
|
||||
public void reportShareTargetEvent(@NonNull AppTargetEvent event,
|
||||
@NonNull IntentFilter intentFilter) {
|
||||
@@ -278,7 +344,6 @@ public class DataManager {
|
||||
}
|
||||
pruneUninstalledPackageData(userData);
|
||||
|
||||
final NotificationListener notificationListener = mNotificationListeners.get(userId);
|
||||
userData.forAllPackages(packageData -> {
|
||||
if (signal.isCanceled()) {
|
||||
return;
|
||||
@@ -291,20 +356,7 @@ public class DataManager {
|
||||
packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_SMS);
|
||||
}
|
||||
packageData.pruneOrphanEvents();
|
||||
if (notificationListener != null) {
|
||||
String packageName = packageData.getPackageName();
|
||||
packageData.forAllConversations(conversationInfo -> {
|
||||
if (conversationInfo.isShortcutCachedForNotification()
|
||||
&& conversationInfo.getNotificationChannelId() == null
|
||||
&& !notificationListener.hasActiveNotifications(
|
||||
packageName, conversationInfo.getShortcutId())) {
|
||||
mShortcutServiceInternal.uncacheShortcuts(userId,
|
||||
mContext.getPackageName(), packageName,
|
||||
Collections.singletonList(conversationInfo.getShortcutId()),
|
||||
userId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
||||
}
|
||||
});
|
||||
}
|
||||
cleanupCachedShortcuts(userId, MAX_CACHED_RECENT_SHORTCUTS);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -467,7 +519,8 @@ public class DataManager {
|
||||
@NonNull String packageName, @UserIdInt int userId,
|
||||
@Nullable List<String> shortcutIds) {
|
||||
@ShortcutQuery.QueryFlags int queryFlags = ShortcutQuery.FLAG_MATCH_DYNAMIC
|
||||
| ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
|
||||
| ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER
|
||||
| ShortcutQuery.FLAG_MATCH_CACHED;
|
||||
return mShortcutServiceInternal.getShortcuts(
|
||||
UserHandle.USER_SYSTEM, mContext.getPackageName(),
|
||||
/*changedSince=*/ 0, packageName, shortcutIds, /*locusIds=*/ null,
|
||||
@@ -527,6 +580,68 @@ public class DataManager {
|
||||
return packageData;
|
||||
}
|
||||
|
||||
private boolean isCachedRecentConversation(ConversationInfo conversationInfo) {
|
||||
return conversationInfo.isShortcutCachedForNotification()
|
||||
&& conversationInfo.getNotificationChannelId() == null
|
||||
&& conversationInfo.getParentNotificationChannelId() != null
|
||||
&& conversationInfo.getLastEventTimestamp() > 0L;
|
||||
}
|
||||
|
||||
private boolean hasActiveNotifications(String packageName, @UserIdInt int userId,
|
||||
String shortcutId) {
|
||||
NotificationListener notificationListener = mNotificationListeners.get(userId);
|
||||
return notificationListener != null
|
||||
&& notificationListener.hasActiveNotifications(packageName, shortcutId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the oldest cached shortcuts that don't have active notifications for the recent
|
||||
* conversations. After the cleanup, normally, the total number of cached shortcuts will be
|
||||
* less than or equal to the target count. However, there are exception cases: e.g. when all
|
||||
* the existing cached shortcuts have active notifications.
|
||||
*/
|
||||
private void cleanupCachedShortcuts(@UserIdInt int userId, int targetCachedCount) {
|
||||
UserData userData = getUnlockedUserData(userId);
|
||||
if (userData == null) {
|
||||
return;
|
||||
}
|
||||
// pair of <package name, conversation info>
|
||||
List<Pair<String, ConversationInfo>> cachedConvos = new ArrayList<>();
|
||||
userData.forAllPackages(packageData ->
|
||||
packageData.forAllConversations(conversationInfo -> {
|
||||
if (isCachedRecentConversation(conversationInfo)) {
|
||||
cachedConvos.add(
|
||||
Pair.create(packageData.getPackageName(), conversationInfo));
|
||||
}
|
||||
})
|
||||
);
|
||||
if (cachedConvos.size() <= targetCachedCount) {
|
||||
return;
|
||||
}
|
||||
int numToUncache = cachedConvos.size() - targetCachedCount;
|
||||
// Max heap keeps the oldest cached conversations.
|
||||
PriorityQueue<Pair<String, ConversationInfo>> maxHeap = new PriorityQueue<>(
|
||||
numToUncache + 1,
|
||||
Comparator.comparingLong((Pair<String, ConversationInfo> pair) ->
|
||||
pair.second.getLastEventTimestamp()).reversed());
|
||||
for (Pair<String, ConversationInfo> cached : cachedConvos) {
|
||||
if (hasActiveNotifications(cached.first, userId, cached.second.getShortcutId())) {
|
||||
continue;
|
||||
}
|
||||
maxHeap.offer(cached);
|
||||
if (maxHeap.size() > numToUncache) {
|
||||
maxHeap.poll();
|
||||
}
|
||||
}
|
||||
while (!maxHeap.isEmpty()) {
|
||||
Pair<String, ConversationInfo> toUncache = maxHeap.poll();
|
||||
mShortcutServiceInternal.uncacheShortcuts(userId,
|
||||
mContext.getPackageName(), toUncache.first,
|
||||
Collections.singletonList(toUncache.second.getShortcutId()),
|
||||
userId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@WorkerThread
|
||||
void addOrUpdateConversationInfo(@NonNull ShortcutInfo shortcutInfo) {
|
||||
@@ -737,9 +852,21 @@ public class DataManager {
|
||||
public void onShortcutsAddedOrUpdated(@NonNull String packageName,
|
||||
@NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
|
||||
mInjector.getBackgroundExecutor().execute(() -> {
|
||||
PackageData packageData = getPackage(packageName, user.getIdentifier());
|
||||
for (ShortcutInfo shortcut : shortcuts) {
|
||||
if (ShortcutHelper.isConversationShortcut(
|
||||
shortcut, mShortcutServiceInternal, user.getIdentifier())) {
|
||||
if (shortcut.isCached()) {
|
||||
ConversationInfo conversationInfo = packageData != null
|
||||
? packageData.getConversationInfo(shortcut.getId()) : null;
|
||||
if (conversationInfo == null
|
||||
|| !conversationInfo.isShortcutCachedForNotification()) {
|
||||
// This is a newly cached shortcut. Clean up the existing cached
|
||||
// shortcuts to ensure the cache size is under the limit.
|
||||
cleanupCachedShortcuts(user.getIdentifier(),
|
||||
MAX_CACHED_RECENT_SHORTCUTS - 1);
|
||||
}
|
||||
}
|
||||
addOrUpdateConversationInfo(shortcut);
|
||||
}
|
||||
}
|
||||
@@ -800,6 +927,16 @@ public class DataManager {
|
||||
});
|
||||
|
||||
if (packageData != null) {
|
||||
ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId);
|
||||
if (conversationInfo == null) {
|
||||
return;
|
||||
}
|
||||
ConversationInfo updated = new ConversationInfo.Builder(conversationInfo)
|
||||
.setLastEventTimestamp(sbn.getPostTime())
|
||||
.setParentNotificationChannelId(sbn.getNotification().getChannelId())
|
||||
.build();
|
||||
packageData.getConversationStore().addOrUpdate(updated);
|
||||
|
||||
EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
|
||||
EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
|
||||
eventHistory.addEvent(new Event(sbn.getPostTime(), Event.TYPE_NOTIFICATION_POSTED));
|
||||
@@ -820,16 +957,7 @@ public class DataManager {
|
||||
int count = mActiveNotifCounts.getOrDefault(conversationKey, 0) - 1;
|
||||
if (count <= 0) {
|
||||
mActiveNotifCounts.remove(conversationKey);
|
||||
// The shortcut was cached by Notification Manager synchronously when the
|
||||
// associated notification was posted. Uncache it here when all the
|
||||
// associated notifications are removed.
|
||||
if (conversationInfo.isShortcutCachedForNotification()
|
||||
&& conversationInfo.getNotificationChannelId() == null) {
|
||||
mShortcutServiceInternal.uncacheShortcuts(mUserId,
|
||||
mContext.getPackageName(), sbn.getPackageName(),
|
||||
Collections.singletonList(conversationInfo.getShortcutId()),
|
||||
mUserId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
||||
}
|
||||
cleanupCachedShortcuts(mUserId, MAX_CACHED_RECENT_SHORTCUTS);
|
||||
} else {
|
||||
mActiveNotifCounts.put(conversationKey, count);
|
||||
}
|
||||
@@ -885,24 +1013,6 @@ public class DataManager {
|
||||
conversationStore.addOrUpdate(builder.build());
|
||||
}
|
||||
|
||||
synchronized void cleanupCachedShortcuts() {
|
||||
for (Pair<String, String> conversationKey : mActiveNotifCounts.keySet()) {
|
||||
String packageName = conversationKey.first;
|
||||
String shortcutId = conversationKey.second;
|
||||
PackageData packageData = getPackage(packageName, mUserId);
|
||||
ConversationInfo conversationInfo =
|
||||
packageData != null ? packageData.getConversationInfo(shortcutId) : null;
|
||||
if (conversationInfo != null
|
||||
&& conversationInfo.isShortcutCachedForNotification()
|
||||
&& conversationInfo.getNotificationChannelId() == null) {
|
||||
mShortcutServiceInternal.uncacheShortcuts(mUserId,
|
||||
mContext.getPackageName(), packageName,
|
||||
Collections.singletonList(shortcutId),
|
||||
mUserId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized boolean hasActiveNotifications(String packageName, String shortcutId) {
|
||||
return mActiveNotifCounts.containsKey(Pair.create(packageName, shortcutId));
|
||||
}
|
||||
@@ -975,16 +1085,7 @@ public class DataManager {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
forAllUnlockedUsers(userData -> {
|
||||
NotificationListener listener = mNotificationListeners.get(userData.getUserId());
|
||||
// Clean up the cached shortcuts because all the notifications are cleared after
|
||||
// system shutdown. The associated shortcuts need to be uncached to keep in sync
|
||||
// unless the settings are changed by the user.
|
||||
if (listener != null) {
|
||||
listener.cleanupCachedShortcuts();
|
||||
}
|
||||
userData.forAllPackages(PackageData::saveToDisk);
|
||||
});
|
||||
forAllUnlockedUsers(userData -> userData.forAllPackages(PackageData::saveToDisk));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ public final class ConversationInfoTest {
|
||||
private static final Uri CONTACT_URI = Uri.parse("tel:+1234567890");
|
||||
private static final String PHONE_NUMBER = "+1234567890";
|
||||
private static final String NOTIFICATION_CHANNEL_ID = "test : abc";
|
||||
private static final String PARENT_NOTIFICATION_CHANNEL_ID = "test";
|
||||
|
||||
@Test
|
||||
public void testBuild() {
|
||||
@@ -46,6 +47,8 @@ public final class ConversationInfoTest {
|
||||
.setContactUri(CONTACT_URI)
|
||||
.setContactPhoneNumber(PHONE_NUMBER)
|
||||
.setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
|
||||
.setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
|
||||
.setLastEventTimestamp(100L)
|
||||
.setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED
|
||||
| ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)
|
||||
.setImportant(true)
|
||||
@@ -62,6 +65,9 @@ public final class ConversationInfoTest {
|
||||
assertEquals(CONTACT_URI, conversationInfo.getContactUri());
|
||||
assertEquals(PHONE_NUMBER, conversationInfo.getContactPhoneNumber());
|
||||
assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId());
|
||||
assertEquals(PARENT_NOTIFICATION_CHANNEL_ID,
|
||||
conversationInfo.getParentNotificationChannelId());
|
||||
assertEquals(100L, conversationInfo.getLastEventTimestamp());
|
||||
assertTrue(conversationInfo.isShortcutLongLived());
|
||||
assertTrue(conversationInfo.isShortcutCachedForNotification());
|
||||
assertTrue(conversationInfo.isImportant());
|
||||
@@ -84,6 +90,8 @@ public final class ConversationInfoTest {
|
||||
assertNull(conversationInfo.getContactUri());
|
||||
assertNull(conversationInfo.getContactPhoneNumber());
|
||||
assertNull(conversationInfo.getNotificationChannelId());
|
||||
assertNull(conversationInfo.getParentNotificationChannelId());
|
||||
assertEquals(0L, conversationInfo.getLastEventTimestamp());
|
||||
assertFalse(conversationInfo.isShortcutLongLived());
|
||||
assertFalse(conversationInfo.isShortcutCachedForNotification());
|
||||
assertFalse(conversationInfo.isImportant());
|
||||
@@ -103,6 +111,8 @@ public final class ConversationInfoTest {
|
||||
.setContactUri(CONTACT_URI)
|
||||
.setContactPhoneNumber(PHONE_NUMBER)
|
||||
.setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
|
||||
.setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
|
||||
.setLastEventTimestamp(100L)
|
||||
.setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
|
||||
.setImportant(true)
|
||||
.setNotificationSilenced(true)
|
||||
@@ -122,6 +132,8 @@ public final class ConversationInfoTest {
|
||||
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());
|
||||
assertTrue(destination.isShortcutLongLived());
|
||||
assertFalse(destination.isImportant());
|
||||
assertTrue(destination.isNotificationSilenced());
|
||||
|
||||
@@ -29,6 +29,7 @@ import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
@@ -45,6 +46,7 @@ import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Person;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.app.people.ConversationChannel;
|
||||
import android.app.prediction.AppTarget;
|
||||
import android.app.prediction.AppTargetEvent;
|
||||
import android.app.prediction.AppTargetId;
|
||||
@@ -112,6 +114,7 @@ public final class DataManagerTest {
|
||||
private static final String CONTACT_URI = "content://com.android.contacts/contacts/lookup/123";
|
||||
private static final String PHONE_NUMBER = "+1234567890";
|
||||
private static final String NOTIFICATION_CHANNEL_ID = "test : sc";
|
||||
private static final String PARENT_NOTIFICATION_CHANNEL_ID = "test";
|
||||
private static final long MILLIS_PER_MINUTE = 1000L * 60L;
|
||||
|
||||
@Mock private Context mContext;
|
||||
@@ -133,10 +136,12 @@ public final class DataManagerTest {
|
||||
|
||||
private ScheduledExecutorService mExecutorService;
|
||||
private NotificationChannel mNotificationChannel;
|
||||
private NotificationChannel mParentNotificationChannel;
|
||||
private DataManager mDataManager;
|
||||
private CancellationSignal mCancellationSignal;
|
||||
private ShortcutChangeCallback mShortcutChangeCallback;
|
||||
private BroadcastReceiver mShutdownBroadcastReceiver;
|
||||
private ShortcutInfo mShortcutInfo;
|
||||
private TestInjector mInjector;
|
||||
|
||||
@Before
|
||||
@@ -157,6 +162,11 @@ public final class DataManagerTest {
|
||||
}).when(mPackageManagerInternal).forEachInstalledPackage(any(Consumer.class), anyInt());
|
||||
|
||||
addLocalServiceMock(NotificationManagerInternal.class, mNotificationManagerInternal);
|
||||
mParentNotificationChannel = new NotificationChannel(
|
||||
PARENT_NOTIFICATION_CHANNEL_ID, "test channel",
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
when(mNotificationManagerInternal.getNotificationChannel(anyString(), anyInt(),
|
||||
anyString())).thenReturn(mParentNotificationChannel);
|
||||
|
||||
when(mContext.getContentResolver()).thenReturn(mContentResolver);
|
||||
when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
|
||||
@@ -199,6 +209,7 @@ public final class DataManagerTest {
|
||||
when(mStatusBarNotification.getUser()).thenReturn(UserHandle.of(USER_ID_PRIMARY));
|
||||
when(mStatusBarNotification.getPostTime()).thenReturn(System.currentTimeMillis());
|
||||
when(mNotification.getShortcutId()).thenReturn(TEST_SHORTCUT_ID);
|
||||
when(mNotification.getChannelId()).thenReturn(PARENT_NOTIFICATION_CHANNEL_ID);
|
||||
|
||||
mNotificationChannel = new NotificationChannel(
|
||||
NOTIFICATION_CHANNEL_ID, "test channel", NotificationManager.IMPORTANCE_DEFAULT);
|
||||
@@ -212,6 +223,13 @@ public final class DataManagerTest {
|
||||
|
||||
when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
|
||||
anyString(), anyInt(), any())).thenReturn(true);
|
||||
|
||||
mShortcutInfo = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
|
||||
buildPerson());
|
||||
when(mShortcutServiceInternal.getShortcuts(
|
||||
anyInt(), anyString(), anyLong(), anyString(), anyList(), any(), any(),
|
||||
anyInt(), anyInt(), anyInt(), anyInt()))
|
||||
.thenReturn(Collections.singletonList(mShortcutInfo));
|
||||
verify(mShortcutServiceInternal).addShortcutChangeCallback(
|
||||
mShortcutChangeCallbackCaptor.capture());
|
||||
mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue();
|
||||
@@ -417,29 +435,28 @@ public final class DataManagerTest {
|
||||
List<Range<Long>> activeNotificationOpenTimeSlots = getActiveSlotsForTestShortcut(
|
||||
Event.NOTIFICATION_EVENT_TYPES);
|
||||
assertEquals(1, activeNotificationOpenTimeSlots.size());
|
||||
verify(mShortcutServiceInternal).uncacheShortcuts(
|
||||
anyInt(), any(), eq(TEST_PKG_NAME),
|
||||
eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY),
|
||||
eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotificationDismissed() {
|
||||
public void testUncacheShortcutsWhenNotificationsDismissed() {
|
||||
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||
|
||||
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
|
||||
buildPerson());
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut);
|
||||
|
||||
NotificationListenerService listenerService =
|
||||
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
|
||||
|
||||
// Post one notification.
|
||||
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut);
|
||||
listenerService.onNotificationPosted(mStatusBarNotification);
|
||||
// The cached conversations are above the limit because every conversation has active
|
||||
// notifications. To uncache one of them, the notifications for that conversation need to
|
||||
// be dismissed.
|
||||
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);
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut);
|
||||
when(mNotification.getShortcutId()).thenReturn(shortcutId);
|
||||
listenerService.onNotificationPosted(mStatusBarNotification);
|
||||
}
|
||||
|
||||
// Post another notification.
|
||||
// Post another notification for the last conversation.
|
||||
listenerService.onNotificationPosted(mStatusBarNotification);
|
||||
|
||||
// Removing one of the two notifications does not un-cache the shortcut.
|
||||
@@ -452,13 +469,12 @@ public final class DataManagerTest {
|
||||
listenerService.onNotificationRemoved(mStatusBarNotification, null,
|
||||
NotificationListenerService.REASON_CANCEL_ALL);
|
||||
verify(mShortcutServiceInternal).uncacheShortcuts(
|
||||
anyInt(), any(), eq(TEST_PKG_NAME),
|
||||
eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY),
|
||||
anyInt(), any(), eq(TEST_PKG_NAME), anyList(), eq(USER_ID_PRIMARY),
|
||||
eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShortcutNotUncachedIfNotificationChannelCreated() {
|
||||
public void testConversationIsNotRecentIfCustomized() {
|
||||
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||
|
||||
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
|
||||
@@ -472,15 +488,12 @@ public final class DataManagerTest {
|
||||
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut);
|
||||
|
||||
assertEquals(1, mDataManager.getRecentConversations(USER_ID_PRIMARY).size());
|
||||
|
||||
listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
|
||||
mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
|
||||
|
||||
listenerService.onNotificationRemoved(mStatusBarNotification, null,
|
||||
NotificationListenerService.REASON_CANCEL_ALL);
|
||||
verify(mShortcutServiceInternal, never()).uncacheShortcuts(
|
||||
anyInt(), any(), eq(TEST_PKG_NAME),
|
||||
eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY),
|
||||
eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
|
||||
assertTrue(mDataManager.getRecentConversations(USER_ID_PRIMARY).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -560,53 +573,6 @@ public final class DataManagerTest {
|
||||
assertFalse(conversationInfo.isDemoted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUncacheShortcutWhenShutdown() {
|
||||
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||
|
||||
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
|
||||
buildPerson());
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut);
|
||||
|
||||
NotificationListenerService listenerService =
|
||||
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
|
||||
|
||||
listenerService.onNotificationPosted(mStatusBarNotification);
|
||||
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut);
|
||||
|
||||
mShutdownBroadcastReceiver.onReceive(mContext, new Intent());
|
||||
verify(mShortcutServiceInternal).uncacheShortcuts(
|
||||
anyInt(), any(), eq(TEST_PKG_NAME),
|
||||
eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY),
|
||||
eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoNotUncacheShortcutWhenShutdownIfNotificationChannelCreated() {
|
||||
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||
|
||||
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
|
||||
buildPerson());
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut);
|
||||
|
||||
NotificationListenerService listenerService =
|
||||
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
|
||||
|
||||
listenerService.onNotificationPosted(mStatusBarNotification);
|
||||
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut);
|
||||
|
||||
listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
|
||||
mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
|
||||
|
||||
mShutdownBroadcastReceiver.onReceive(mContext, new Intent());
|
||||
verify(mShortcutServiceInternal, never()).uncacheShortcuts(
|
||||
anyInt(), any(), eq(TEST_PKG_NAME),
|
||||
eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY),
|
||||
eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShortcutAddedOrUpdated() {
|
||||
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||
@@ -769,20 +735,57 @@ public final class DataManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPruneInactiveCachedShortcuts() {
|
||||
public void testDoNotUncacheShortcutWithActiveNotifications() {
|
||||
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||
NotificationListenerService listenerService =
|
||||
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
|
||||
|
||||
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
|
||||
buildPerson());
|
||||
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut);
|
||||
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);
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut);
|
||||
when(mNotification.getShortcutId()).thenReturn(shortcutId);
|
||||
listenerService.onNotificationPosted(mStatusBarNotification);
|
||||
}
|
||||
|
||||
mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal);
|
||||
|
||||
verify(mShortcutServiceInternal, never()).uncacheShortcuts(
|
||||
anyInt(), anyString(), anyString(), anyList(), anyInt(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUncacheOldestCachedShortcut() {
|
||||
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||
NotificationListenerService listenerService =
|
||||
mDataManager.getNotificationListenerServiceForTesting(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);
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut);
|
||||
when(mNotification.getShortcutId()).thenReturn(shortcutId);
|
||||
when(mStatusBarNotification.getPostTime()).thenReturn(100L + i);
|
||||
listenerService.onNotificationPosted(mStatusBarNotification);
|
||||
listenerService.onNotificationRemoved(mStatusBarNotification, null,
|
||||
NotificationListenerService.REASON_CANCEL);
|
||||
}
|
||||
|
||||
// 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)), eq(USER_ID_PRIMARY),
|
||||
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
|
||||
@@ -812,6 +815,124 @@ public final class DataManagerTest {
|
||||
assertEquals(conversationInfo.getShortcutId(), TEST_SHORTCUT_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRecentConversations() {
|
||||
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||
|
||||
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
|
||||
buildPerson());
|
||||
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut);
|
||||
|
||||
NotificationListenerService listenerService =
|
||||
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
|
||||
listenerService.onNotificationPosted(mStatusBarNotification);
|
||||
|
||||
List<ConversationChannel> result = mDataManager.getRecentConversations(USER_ID_PRIMARY);
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(shortcut.getId(), result.get(0).getShortcutInfo().getId());
|
||||
assertEquals(mParentNotificationChannel.getId(),
|
||||
result.get(0).getParentNotificationChannel().getId());
|
||||
assertEquals(mStatusBarNotification.getPostTime(), result.get(0).getLastEventTimestamp());
|
||||
assertTrue(result.get(0).hasActiveNotifications());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonCachedShortcutNotInRecentList() {
|
||||
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||
|
||||
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY_MANAGED,
|
||||
TEST_SHORTCUT_ID, buildPerson());
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut);
|
||||
|
||||
NotificationListenerService listenerService =
|
||||
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
|
||||
listenerService.onNotificationPosted(mStatusBarNotification);
|
||||
|
||||
List<ConversationChannel> result = mDataManager.getRecentConversations(USER_ID_PRIMARY);
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomizedConversationNotInRecentList() {
|
||||
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||
|
||||
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
|
||||
buildPerson());
|
||||
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut);
|
||||
|
||||
// Post a notification and customize the notification settings.
|
||||
NotificationListenerService listenerService =
|
||||
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
|
||||
listenerService.onNotificationPosted(mStatusBarNotification);
|
||||
listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
|
||||
mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
|
||||
|
||||
List<ConversationChannel> result = mDataManager.getRecentConversations(USER_ID_PRIMARY);
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveRecentConversation() {
|
||||
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||
|
||||
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
|
||||
buildPerson());
|
||||
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut);
|
||||
|
||||
NotificationListenerService listenerService =
|
||||
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
|
||||
listenerService.onNotificationPosted(mStatusBarNotification);
|
||||
listenerService.onNotificationRemoved(mStatusBarNotification, null,
|
||||
NotificationListenerService.REASON_CANCEL);
|
||||
mDataManager.removeRecentConversation(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
|
||||
USER_ID_PRIMARY);
|
||||
|
||||
verify(mShortcutServiceInternal).uncacheShortcuts(
|
||||
anyInt(), any(), eq(TEST_PKG_NAME), eq(Collections.singletonList(TEST_SHORTCUT_ID)),
|
||||
eq(USER_ID_PRIMARY), eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveAllRecentConversations() {
|
||||
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||
|
||||
ShortcutInfo shortcut1 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "1",
|
||||
buildPerson());
|
||||
shortcut1.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut1);
|
||||
|
||||
ShortcutInfo shortcut2 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "2",
|
||||
buildPerson());
|
||||
shortcut2.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut2);
|
||||
|
||||
NotificationListenerService listenerService =
|
||||
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
|
||||
|
||||
// Post a notification and then dismiss it for conversation #1.
|
||||
when(mNotification.getShortcutId()).thenReturn("1");
|
||||
listenerService.onNotificationPosted(mStatusBarNotification);
|
||||
listenerService.onNotificationRemoved(mStatusBarNotification, null,
|
||||
NotificationListenerService.REASON_CANCEL);
|
||||
|
||||
// Post a notification for conversation #2, but don't dismiss it. Its shortcut won't be
|
||||
// uncached when removeAllRecentConversations() is called.
|
||||
when(mNotification.getShortcutId()).thenReturn("2");
|
||||
listenerService.onNotificationPosted(mStatusBarNotification);
|
||||
|
||||
mDataManager.removeAllRecentConversations(USER_ID_PRIMARY);
|
||||
|
||||
verify(mShortcutServiceInternal).uncacheShortcuts(
|
||||
anyInt(), any(), eq(TEST_PKG_NAME), eq(Collections.singletonList("1")),
|
||||
eq(USER_ID_PRIMARY), eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
|
||||
verify(mShortcutServiceInternal, never()).uncacheShortcuts(
|
||||
anyInt(), any(), eq(TEST_PKG_NAME), eq(Collections.singletonList("2")),
|
||||
eq(USER_ID_PRIMARY), eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
|
||||
}
|
||||
|
||||
private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
|
||||
LocalServices.removeServiceForTest(clazz);
|
||||
LocalServices.addService(clazz, mock);
|
||||
|
||||
Reference in New Issue
Block a user