Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 28f578a2 authored by Danning Chen's avatar Danning Chen Committed by Beverly
Browse files

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
parent 59fb99b1
Loading
Loading
Loading
Loading
+9 −0
Original line number Original line Diff line number Diff line
@@ -46,6 +46,10 @@ message ConversationInfoProto {
  // The notification channel id of the conversation.
  // The notification channel id of the conversation.
  optional string notification_channel_id = 4 [(.android.privacy).dest = DEST_EXPLICIT];
  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.
  // Integer representation of shortcut bit flags.
  optional int32 shortcut_flags = 5;
  optional int32 shortcut_flags = 5;


@@ -54,6 +58,11 @@ message ConversationInfoProto {


  // The phone number of the contact.
  // The phone number of the contact.
  optional string contact_phone_number = 7 [(.android.privacy).dest = DEST_EXPLICIT];
  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.
// On disk data of events.
+7 −2
Original line number Original line Diff line number Diff line
@@ -40,7 +40,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemService;
import com.android.server.SystemService;
import com.android.server.people.data.DataManager;
import com.android.server.people.data.DataManager;


import java.util.ArrayList;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Consumer;
@@ -106,17 +105,23 @@ public class PeopleService extends SystemService {
        @Override
        @Override
        public ParceledListSlice<ConversationChannel> getRecentConversations() {
        public ParceledListSlice<ConversationChannel> getRecentConversations() {
            enforceSystemOrRoot("get recent conversations");
            enforceSystemOrRoot("get recent conversations");
            return new ParceledListSlice<>(new ArrayList<>());
            return new ParceledListSlice<>(
                    mDataManager.getRecentConversations(
                            Binder.getCallingUserHandle().getIdentifier()));
        }
        }


        @Override
        @Override
        public void removeRecentConversation(String packageName, int userId, String shortcutId) {
        public void removeRecentConversation(String packageName, int userId, String shortcutId) {
            enforceSystemOrRoot("remove a recent conversation");
            enforceSystemOrRoot("remove a recent conversation");
            mDataManager.removeRecentConversation(packageName, userId, shortcutId,
                    Binder.getCallingUserHandle().getIdentifier());
        }
        }


        @Override
        @Override
        public void removeAllRecentConversations() {
        public void removeAllRecentConversations() {
            enforceSystemOrRoot("remove all recent conversations");
            enforceSystemOrRoot("remove all recent conversations");
            mDataManager.removeAllRecentConversations(
                    Binder.getCallingUserHandle().getIdentifier());
        }
        }
    }
    }


+70 −3
Original line number Original line Diff line number Diff line
@@ -90,6 +90,11 @@ public class ConversationInfo {
    @Nullable
    @Nullable
    private String mNotificationChannelId;
    private String mNotificationChannelId;


    @Nullable
    private String mParentNotificationChannelId;

    private long mLastEventTimestamp;

    @ShortcutFlags
    @ShortcutFlags
    private int mShortcutFlags;
    private int mShortcutFlags;


@@ -102,6 +107,8 @@ public class ConversationInfo {
        mContactUri = builder.mContactUri;
        mContactUri = builder.mContactUri;
        mContactPhoneNumber = builder.mContactPhoneNumber;
        mContactPhoneNumber = builder.mContactPhoneNumber;
        mNotificationChannelId = builder.mNotificationChannelId;
        mNotificationChannelId = builder.mNotificationChannelId;
        mParentNotificationChannelId = builder.mParentNotificationChannelId;
        mLastEventTimestamp = builder.mLastEventTimestamp;
        mShortcutFlags = builder.mShortcutFlags;
        mShortcutFlags = builder.mShortcutFlags;
        mConversationFlags = builder.mConversationFlags;
        mConversationFlags = builder.mConversationFlags;
    }
    }
@@ -129,14 +136,32 @@ public class ConversationInfo {
    }
    }


    /**
    /**
     * ID of the {@link android.app.NotificationChannel} where the notifications for this
     * ID of the conversation-specific {@link android.app.NotificationChannel} where the
     * conversation are posted.
     * notifications for this conversation are posted.
     */
     */
    @Nullable
    @Nullable
    String getNotificationChannelId() {
    String getNotificationChannelId() {
        return mNotificationChannelId;
        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. */
    /** 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);
@@ -202,6 +227,8 @@ public class ConversationInfo {
                && Objects.equals(mContactUri, other.mContactUri)
                && Objects.equals(mContactUri, other.mContactUri)
                && Objects.equals(mContactPhoneNumber, other.mContactPhoneNumber)
                && Objects.equals(mContactPhoneNumber, other.mContactPhoneNumber)
                && Objects.equals(mNotificationChannelId, other.mNotificationChannelId)
                && Objects.equals(mNotificationChannelId, other.mNotificationChannelId)
                && Objects.equals(mParentNotificationChannelId, other.mParentNotificationChannelId)
                && Objects.equals(mLastEventTimestamp, other.mLastEventTimestamp)
                && mShortcutFlags == other.mShortcutFlags
                && mShortcutFlags == other.mShortcutFlags
                && mConversationFlags == other.mConversationFlags;
                && mConversationFlags == other.mConversationFlags;
    }
    }
@@ -209,7 +236,8 @@ public class ConversationInfo {
    @Override
    @Override
    public int hashCode() {
    public int hashCode() {
        return Objects.hash(mShortcutId, mLocusId, mContactUri, mContactPhoneNumber,
        return Objects.hash(mShortcutId, mLocusId, mContactUri, mContactPhoneNumber,
                mNotificationChannelId, mShortcutFlags, mConversationFlags);
                mNotificationChannelId, mParentNotificationChannelId, mLastEventTimestamp,
                mShortcutFlags, mConversationFlags);
    }
    }


    @Override
    @Override
@@ -221,6 +249,8 @@ public class ConversationInfo {
        sb.append(", contactUri=").append(mContactUri);
        sb.append(", contactUri=").append(mContactUri);
        sb.append(", phoneNumber=").append(mContactPhoneNumber);
        sb.append(", phoneNumber=").append(mContactPhoneNumber);
        sb.append(", notificationChannelId=").append(mNotificationChannelId);
        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(", shortcutFlags=0x").append(Integer.toHexString(mShortcutFlags));
        sb.append(" [");
        sb.append(" [");
        if (isShortcutLongLived()) {
        if (isShortcutLongLived()) {
@@ -280,6 +310,11 @@ public class ConversationInfo {
            protoOutputStream.write(ConversationInfoProto.NOTIFICATION_CHANNEL_ID,
            protoOutputStream.write(ConversationInfoProto.NOTIFICATION_CHANNEL_ID,
                    mNotificationChannelId);
                    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.SHORTCUT_FLAGS, mShortcutFlags);
        protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags);
        protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags);
        if (mContactPhoneNumber != null) {
        if (mContactPhoneNumber != null) {
@@ -300,6 +335,8 @@ public class ConversationInfo {
            out.writeInt(mShortcutFlags);
            out.writeInt(mShortcutFlags);
            out.writeInt(mConversationFlags);
            out.writeInt(mConversationFlags);
            out.writeUTF(mContactPhoneNumber != null ? mContactPhoneNumber : "");
            out.writeUTF(mContactPhoneNumber != null ? mContactPhoneNumber : "");
            out.writeUTF(mParentNotificationChannelId != null ? mParentNotificationChannelId : "");
            out.writeLong(mLastEventTimestamp);
        } 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);
            return null;
            return null;
@@ -338,6 +375,14 @@ public class ConversationInfo {
                    builder.setNotificationChannelId(protoInputStream.readString(
                    builder.setNotificationChannelId(protoInputStream.readString(
                            ConversationInfoProto.NOTIFICATION_CHANNEL_ID));
                            ConversationInfoProto.NOTIFICATION_CHANNEL_ID));
                    break;
                    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:
                case (int) ConversationInfoProto.SHORTCUT_FLAGS:
                    builder.setShortcutFlags(protoInputStream.readInt(
                    builder.setShortcutFlags(protoInputStream.readInt(
                            ConversationInfoProto.SHORTCUT_FLAGS));
                            ConversationInfoProto.SHORTCUT_FLAGS));
@@ -382,6 +427,11 @@ public class ConversationInfo {
            if (!TextUtils.isEmpty(contactPhoneNumber)) {
            if (!TextUtils.isEmpty(contactPhoneNumber)) {
                builder.setContactPhoneNumber(contactPhoneNumber);
                builder.setContactPhoneNumber(contactPhoneNumber);
            }
            }
            String parentNotificationChannelId = in.readUTF();
            if (!TextUtils.isEmpty(parentNotificationChannelId)) {
                builder.setParentNotificationChannelId(parentNotificationChannelId);
            }
            builder.setLastEventTimestamp(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;
@@ -408,6 +458,11 @@ public class ConversationInfo {
        @Nullable
        @Nullable
        private String mNotificationChannelId;
        private String mNotificationChannelId;


        @Nullable
        private String mParentNotificationChannelId;

        private long mLastEventTimestamp;

        @ShortcutFlags
        @ShortcutFlags
        private int mShortcutFlags;
        private int mShortcutFlags;


@@ -427,6 +482,8 @@ public class ConversationInfo {
            mContactUri = conversationInfo.mContactUri;
            mContactUri = conversationInfo.mContactUri;
            mContactPhoneNumber = conversationInfo.mContactPhoneNumber;
            mContactPhoneNumber = conversationInfo.mContactPhoneNumber;
            mNotificationChannelId = conversationInfo.mNotificationChannelId;
            mNotificationChannelId = conversationInfo.mNotificationChannelId;
            mParentNotificationChannelId = conversationInfo.mParentNotificationChannelId;
            mLastEventTimestamp = conversationInfo.mLastEventTimestamp;
            mShortcutFlags = conversationInfo.mShortcutFlags;
            mShortcutFlags = conversationInfo.mShortcutFlags;
            mConversationFlags = conversationInfo.mConversationFlags;
            mConversationFlags = conversationInfo.mConversationFlags;
        }
        }
@@ -456,6 +513,16 @@ public class ConversationInfo {
            return this;
            return this;
        }
        }


        Builder setParentNotificationChannelId(String parentNotificationChannelId) {
            mParentNotificationChannelId = parentNotificationChannelId;
            return this;
        }

        Builder setLastEventTimestamp(long lastEventTimestamp) {
            mLastEventTimestamp = lastEventTimestamp;
            return this;
        }

        Builder setShortcutFlags(@ShortcutFlags int shortcutFlags) {
        Builder setShortcutFlags(@ShortcutFlags int shortcutFlags) {
            mShortcutFlags = shortcutFlags;
            mShortcutFlags = shortcutFlags;
            return this;
            return this;
+155 −54
Original line number Original line Diff line number Diff line
@@ -24,6 +24,7 @@ import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.NotificationManager;
import android.app.Person;
import android.app.Person;
import android.app.people.ConversationChannel;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetEvent;
import android.app.usage.UsageEvents;
import android.app.usage.UsageEvents;
@@ -74,9 +75,11 @@ import com.android.server.notification.ShortcutHelper;


import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.HashSet;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
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 QUERY_EVENTS_MAX_AGE_MS = 5L * DateUtils.MINUTE_IN_MILLIS;
    private static final long USAGE_STATS_QUERY_INTERVAL_SEC = 120L;
    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 Context mContext;
    private final Injector mInjector;
    private final Injector mInjector;
@@ -209,6 +213,68 @@ public class DataManager {
                mContext.getPackageName(), intentFilter, callingUserId);
                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. */
    /** Reports the sharing related {@link AppTargetEvent} from App Prediction Manager. */
    public void reportShareTargetEvent(@NonNull AppTargetEvent event,
    public void reportShareTargetEvent(@NonNull AppTargetEvent event,
            @NonNull IntentFilter intentFilter) {
            @NonNull IntentFilter intentFilter) {
@@ -278,7 +344,6 @@ public class DataManager {
        }
        }
        pruneUninstalledPackageData(userData);
        pruneUninstalledPackageData(userData);


        final NotificationListener notificationListener = mNotificationListeners.get(userId);
        userData.forAllPackages(packageData -> {
        userData.forAllPackages(packageData -> {
            if (signal.isCanceled()) {
            if (signal.isCanceled()) {
                return;
                return;
@@ -291,20 +356,7 @@ public class DataManager {
                packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_SMS);
                packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_SMS);
            }
            }
            packageData.pruneOrphanEvents();
            packageData.pruneOrphanEvents();
            if (notificationListener != null) {
            cleanupCachedShortcuts(userId, MAX_CACHED_RECENT_SHORTCUTS);
                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);
                    }
                });
            }
        });
        });
    }
    }


@@ -467,7 +519,8 @@ public class DataManager {
            @NonNull String packageName, @UserIdInt int userId,
            @NonNull String packageName, @UserIdInt int userId,
            @Nullable List<String> shortcutIds) {
            @Nullable List<String> shortcutIds) {
        @ShortcutQuery.QueryFlags int queryFlags = ShortcutQuery.FLAG_MATCH_DYNAMIC
        @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(
        return mShortcutServiceInternal.getShortcuts(
                UserHandle.USER_SYSTEM, mContext.getPackageName(),
                UserHandle.USER_SYSTEM, mContext.getPackageName(),
                /*changedSince=*/ 0, packageName, shortcutIds, /*locusIds=*/ null,
                /*changedSince=*/ 0, packageName, shortcutIds, /*locusIds=*/ null,
@@ -527,6 +580,68 @@ public class DataManager {
        return packageData;
        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
    @VisibleForTesting
    @WorkerThread
    @WorkerThread
    void addOrUpdateConversationInfo(@NonNull ShortcutInfo shortcutInfo) {
    void addOrUpdateConversationInfo(@NonNull ShortcutInfo shortcutInfo) {
@@ -737,9 +852,21 @@ public class DataManager {
        public void onShortcutsAddedOrUpdated(@NonNull String packageName,
        public void onShortcutsAddedOrUpdated(@NonNull String packageName,
                @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
                @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
            mInjector.getBackgroundExecutor().execute(() -> {
            mInjector.getBackgroundExecutor().execute(() -> {
                PackageData packageData = getPackage(packageName, user.getIdentifier());
                for (ShortcutInfo shortcut : shortcuts) {
                for (ShortcutInfo shortcut : shortcuts) {
                    if (ShortcutHelper.isConversationShortcut(
                    if (ShortcutHelper.isConversationShortcut(
                            shortcut, mShortcutServiceInternal, user.getIdentifier())) {
                            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);
                        addOrUpdateConversationInfo(shortcut);
                    }
                    }
                }
                }
@@ -800,6 +927,16 @@ public class DataManager {
            });
            });


            if (packageData != null) {
            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(
                EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
                        EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
                        EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
                eventHistory.addEvent(new Event(sbn.getPostTime(), Event.TYPE_NOTIFICATION_POSTED));
                eventHistory.addEvent(new Event(sbn.getPostTime(), Event.TYPE_NOTIFICATION_POSTED));
@@ -820,16 +957,7 @@ public class DataManager {
                    int count = mActiveNotifCounts.getOrDefault(conversationKey, 0) - 1;
                    int count = mActiveNotifCounts.getOrDefault(conversationKey, 0) - 1;
                    if (count <= 0) {
                    if (count <= 0) {
                        mActiveNotifCounts.remove(conversationKey);
                        mActiveNotifCounts.remove(conversationKey);
                        // The shortcut was cached by Notification Manager synchronously when the
                        cleanupCachedShortcuts(mUserId, MAX_CACHED_RECENT_SHORTCUTS);
                        // 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);
                        }
                    } else {
                    } else {
                        mActiveNotifCounts.put(conversationKey, count);
                        mActiveNotifCounts.put(conversationKey, count);
                    }
                    }
@@ -885,24 +1013,6 @@ public class DataManager {
            conversationStore.addOrUpdate(builder.build());
            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) {
        synchronized boolean hasActiveNotifications(String packageName, String shortcutId) {
            return mActiveNotifCounts.containsKey(Pair.create(packageName, shortcutId));
            return mActiveNotifCounts.containsKey(Pair.create(packageName, shortcutId));
        }
        }
@@ -975,16 +1085,7 @@ public class DataManager {


        @Override
        @Override
        public void onReceive(Context context, Intent intent) {
        public void onReceive(Context context, Intent intent) {
            forAllUnlockedUsers(userData -> {
            forAllUnlockedUsers(userData -> userData.forAllPackages(PackageData::saveToDisk));
                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);
            });
        }
        }
    }
    }


+12 −0

File changed.

Preview size limit exceeded, changes collapsed.

Loading