ShortcutManager: Optimize package scanning

SM needs to check all packages when a user is unlocked in case
any apps have been upgraded while the user was not running,
in which case it'll publish the manifest shortcuts.

Currently it actually scans all apps' manifests.

This CL optimizes it by remembering the last scan time (for each user)
and checks only the packages that have been updated since then.

Bug 29069154

Change-Id: Id10b6be8915fe7c1e26daccde7951ddbd01ea452
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 7835231..3565154 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -113,6 +113,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -316,7 +317,7 @@
         int LAUNCHER_PERMISSION_CHECK = 4;
         int CLEANUP_DANGLING_BITMAPS = 5;
         int GET_ACTIVITIES_WITH_METADATA = 6;
-        int GET_INSTALLED_APPLICATIONS = 7;
+        int GET_INSTALLED_PACKAGES = 7;
         int CHECK_PACKAGE_CHANGES = 8;
 
         int COUNT = CHECK_PACKAGE_CHANGES + 1;
@@ -2282,11 +2283,17 @@
                         cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId);
                     }
                 }
+                final long now = injectCurrentTimeMillis();
 
                 // Then for each installed app, publish manifest shortcuts when needed.
-                forInstalledApplications(ownerUserId, ai -> {
+                forUpdatedPackages(ownerUserId, user.getLastAppScanTime(), ai -> {
                     user.handlePackageAddedOrUpdated(ai.packageName);
                 });
+
+                // Write the time just before the scan, because there may be apps that have just
+                // been updated, and we want to catch them in the next time.
+                user.setLastAppScanTime(now);
+                scheduleSaveUser(ownerUserId);
             }
         } finally {
             logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start);
@@ -2424,12 +2431,12 @@
 
     @Nullable
     @VisibleForTesting
-    List<ApplicationInfo> injectInstalledApplications(@UserIdInt int userId) {
+    List<PackageInfo> injectInstalledPackages(@UserIdInt int userId) {
         final long start = injectElapsedRealtime();
         final long token = injectClearCallingIdentity();
         try {
-            final ParceledListSlice<ApplicationInfo> parceledList =
-                    mIPackageManager.getInstalledApplications(PACKAGE_MATCH_FLAGS, userId);
+            final ParceledListSlice<PackageInfo> parceledList =
+                    mIPackageManager.getInstalledPackages(PACKAGE_MATCH_FLAGS, userId);
             if (parceledList == null) {
                 return Collections.emptyList();
             }
@@ -2441,18 +2448,25 @@
         } finally {
             injectRestoreCallingIdentity(token);
 
-            logDurationStat(Stats.GET_INSTALLED_APPLICATIONS, start);
+            logDurationStat(Stats.GET_INSTALLED_PACKAGES, start);
         }
     }
 
-    private void forInstalledApplications(@UserIdInt int userId,
+    private void forUpdatedPackages(@UserIdInt int userId, long lastScanTime,
             Consumer<ApplicationInfo> callback) {
-        final List<ApplicationInfo> list = injectInstalledApplications(userId);
+        if (DEBUG) {
+            Slog.d(TAG, "forUpdatedPackages for user " + userId + ", lastScanTime=" + lastScanTime);
+        }
+        final List<PackageInfo> list = injectInstalledPackages(userId);
         for (int i = list.size() - 1; i >= 0; i--) {
-            final ApplicationInfo ai = list.get(i);
+            final PackageInfo pi = list.get(i);
 
-            if ((ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
-                callback.accept(ai);
+            if (((pi.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0)
+                    && (pi.lastUpdateTime >= lastScanTime)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Found updated package " + pi.packageName);
+                }
+                callback.accept(pi.applicationInfo);
             }
         }
     }
@@ -2612,7 +2626,7 @@
                 dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo");
                 dumpStatLS(pw, p, Stats.CLEANUP_DANGLING_BITMAPS, "cleanupDanglingBitmaps");
                 dumpStatLS(pw, p, Stats.GET_ACTIVITIES_WITH_METADATA, "getActivities+metadata");
-                dumpStatLS(pw, p, Stats.GET_INSTALLED_APPLICATIONS, "getInstalledApplications");
+                dumpStatLS(pw, p, Stats.GET_INSTALLED_PACKAGES, "getInstalledPackages");
                 dumpStatLS(pw, p, Stats.CHECK_PACKAGE_CHANGES, "checkPackageChanges");
             }
 
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 840c3df..f8ee325 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -52,6 +52,7 @@
 
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER = "locale-seq-no";
+    private static final String ATTR_LAST_APP_SCAN_TIME = "last-app-scan-time";
 
     static final class PackageWithUser {
         final int userId;
@@ -107,6 +108,8 @@
 
     private long mKnownLocaleChangeSequenceNumber;
 
+    private long mLastAppScanTime;
+
     public ShortcutUser(ShortcutService service, int userId) {
         mService = service;
         mUserId = userId;
@@ -116,6 +119,14 @@
         return mUserId;
     }
 
+    public long getLastAppScanTime() {
+        return mLastAppScanTime;
+    }
+
+    public void setLastAppScanTime(long lastAppScanTime) {
+        mLastAppScanTime = lastAppScanTime;
+    }
+
     // We don't expose this directly to non-test code because only ShortcutUser should add to/
     // remove from it.
     @VisibleForTesting
@@ -258,6 +269,8 @@
 
         ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER,
                 mKnownLocaleChangeSequenceNumber);
+        ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
+                mLastAppScanTime);
 
         ShortcutService.writeTagValue(out, TAG_LAUNCHER,
                 mDefaultLauncherComponent);
@@ -299,6 +312,13 @@
         ret.mKnownLocaleChangeSequenceNumber = ShortcutService.parseLongAttribute(parser,
                 ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER);
 
+        // If lastAppScanTime is in the future, that means the clock went backwards.
+        // Just scan all apps again.
+        final long lastAppScanTime = ShortcutService.parseLongAttribute(parser,
+                ATTR_LAST_APP_SCAN_TIME);
+        final long currentTime = s.injectCurrentTimeMillis();
+        ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0;
+
         final int outerDepth = parser.getDepth();
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -361,6 +381,8 @@
         pw.print(mUserId);
         pw.print("  Known locale seq#: ");
         pw.print(mKnownLocaleChangeSequenceNumber);
+        pw.print("  Last app scan: ");
+        pw.print(mLastAppScanTime);
         pw.println();
 
         prefix += prefix + "  ";
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index ed53b77..d33047b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -302,8 +302,8 @@
         }
 
         @Override
-        List<ApplicationInfo> injectInstalledApplications(@UserIdInt int userId) {
-            return getInstalledApplications(userId);
+        List<PackageInfo> injectInstalledPackages(@UserIdInt int userId) {
+            return getInstalledPackages(userId);
         }
 
         @Override
@@ -793,6 +793,27 @@
         return ret;
     }
 
+    private void addPackageInfo(PackageInfo pi, List<PackageInfo> list) {
+        if (pi != null) {
+            list.add(pi);
+        }
+    }
+
+    private List<PackageInfo> getInstalledPackages(int userId) {
+        final ArrayList<PackageInfo> ret = new ArrayList<>();
+
+        addPackageInfo(getInjectedPackageInfo(CALLING_PACKAGE_1, userId, false), ret);
+        addPackageInfo(getInjectedPackageInfo(CALLING_PACKAGE_2, userId, false), ret);
+        addPackageInfo(getInjectedPackageInfo(CALLING_PACKAGE_3, userId, false), ret);
+        addPackageInfo(getInjectedPackageInfo(CALLING_PACKAGE_4, userId, false), ret);
+        addPackageInfo(getInjectedPackageInfo(LAUNCHER_1, userId, false), ret);
+        addPackageInfo(getInjectedPackageInfo(LAUNCHER_2, userId, false), ret);
+        addPackageInfo(getInjectedPackageInfo(LAUNCHER_3, userId, false), ret);
+        addPackageInfo(getInjectedPackageInfo(LAUNCHER_4, userId, false), ret);
+
+        return ret;
+    }
+
     protected void addManifestShortcutResource(ComponentName activity, int resId) {
         final String packageName = activity.getPackageName();
         LinkedHashMap<ComponentName, Integer> map = mActivityMetadataResId.get(packageName);