Do not scan system apps unless after OTA

Bug 30956231

Change-Id: I4af34cdd39cfaf0b8712b9ded1ef119efa3e04b0
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 827b88a..6f6fd7c 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -667,6 +667,12 @@
                 // - version code hasn't change
                 // - lastUpdateTime hasn't change
                 // - all target activities are still enabled.
+
+                // Note, system apps timestamps do *not* change after OTAs.  (But they do
+                // after an adb sync or a local flash.)
+                // This means if a system app's version code doesn't change on an OTA,
+                // we don't notice it's updated.  But that's fine since their version code *should*
+                // really change on OTAs.
                 if ((getPackageInfo().getVersionCode() == pi.versionCode)
                         && (getPackageInfo().getLastUpdateTime() == pi.lastUpdateTime)
                         && areAllActivitiesStillEnabled()) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index b80775b..adf19dc 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -54,6 +54,7 @@
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
@@ -2659,10 +2660,14 @@
             boolean forceRescan) {
         final ShortcutUser user = getUserShortcutsLocked(userId);
 
+        // Note after each OTA, we'll need to rescan all system apps, as their lastUpdateTime
+        // is not reliable.
         final long now = injectCurrentTimeMillis();
+        final boolean afterOta =
+                !injectBuildFingerprint().equals(user.getLastAppScanOsFingerprint());
 
         // Then for each installed app, publish manifest shortcuts when needed.
-        forUpdatedPackages(userId, lastScanTime, ai -> {
+        forUpdatedPackages(userId, lastScanTime, afterOta, ai -> {
             user.attemptToRestoreIfNeededAndSave(this, ai.packageName, userId);
             user.rescanPackageIfNeeded(ai.packageName, forceRescan);
         });
@@ -2670,6 +2675,7 @@
         // 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);
+        user.setLastAppScanOsFingerprint(injectBuildFingerprint());
         scheduleSaveUser(userId);
     }
 
@@ -2908,7 +2914,7 @@
         return parceledList.getList();
     }
 
-    private void forUpdatedPackages(@UserIdInt int userId, long lastScanTime,
+    private void forUpdatedPackages(@UserIdInt int userId, long lastScanTime, boolean afterOta,
             Consumer<ApplicationInfo> callback) {
         if (DEBUG) {
             Slog.d(TAG, "forUpdatedPackages for user " + userId + ", lastScanTime=" + lastScanTime);
@@ -2920,7 +2926,8 @@
             // If the package has been updated since the last scan time, then scan it.
             // Also if it's a system app with no update, lastUpdateTime is not reliable, so
             // just scan it.
-            if (pi.lastUpdateTime >= lastScanTime || isPureSystemApp(pi.applicationInfo)) {
+            if (pi.lastUpdateTime >= lastScanTime
+                    || (afterOta && isPureSystemApp(pi.applicationInfo))) {
                 if (DEBUG) {
                     Slog.d(TAG, "Found updated package " + pi.packageName);
                 }
@@ -3598,6 +3605,12 @@
         Binder.restoreCallingIdentity(token);
     }
 
+    // Injection point.
+    @VisibleForTesting
+    String injectBuildFingerprint() {
+        return Build.FINGERPRINT;
+    }
+
     final void wtf(String message) {
         wtf(message, /* exception= */ null);
     }
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index ce3ed9c..c05c66f 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -60,6 +60,7 @@
 
     // Suffix "2" was added to force rescan all packages after the next OTA.
     private static final String ATTR_LAST_APP_SCAN_TIME = "last-app-scan-time2";
+    private static final String ATTR_LAST_APP_SCAN_OS_FINGERPRINT = "last-app-scan-fp";
     private static final String KEY_USER_ID = "userId";
     private static final String KEY_LAUNCHERS = "launchers";
     private static final String KEY_PACKAGES = "packages";
@@ -125,6 +126,8 @@
 
     private long mLastAppScanTime;
 
+    private String mLastAppScanOsFingerprint;
+
     public ShortcutUser(ShortcutService service, int userId) {
         mService = service;
         mUserId = userId;
@@ -142,6 +145,14 @@
         mLastAppScanTime = lastAppScanTime;
     }
 
+    public String getLastAppScanOsFingerprint() {
+        return mLastAppScanOsFingerprint;
+    }
+
+    public void setLastAppScanOsFingerprint(String lastAppScanOsFingerprint) {
+        mLastAppScanOsFingerprint = lastAppScanOsFingerprint;
+    }
+
     // We don't expose this directly to non-test code because only ShortcutUser should add to/
     // remove from it.
     @VisibleForTesting
@@ -318,6 +329,8 @@
         ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales);
         ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
                 mLastAppScanTime);
+        ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_OS_FINGERPRINT,
+                mLastAppScanOsFingerprint);
 
         ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher);
 
@@ -364,7 +377,8 @@
                 ATTR_LAST_APP_SCAN_TIME);
         final long currentTime = s.injectCurrentTimeMillis();
         ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0;
-
+        ret.mLastAppScanOsFingerprint = ShortcutService.parseStringAttribute(parser,
+                ATTR_LAST_APP_SCAN_OS_FINGERPRINT);
         final int outerDepth = parser.getDepth();
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -457,6 +471,8 @@
         pw.print(mLastAppScanTime);
         pw.print("] ");
         pw.print(ShortcutService.formatTime(mLastAppScanTime));
+        pw.print("  Last app scan FP: ");
+        pw.print(mLastAppScanOsFingerprint);
         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 e96e97b..792f300 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -407,6 +407,11 @@
         }
 
         @Override
+        String injectBuildFingerprint() {
+            return mInjectedBuildFingerprint;
+        }
+
+        @Override
         void wtf(String message, Throwable th) {
             // During tests, WTF is fatal.
             fail(message + "  exception: " + th + "\n" + Log.getStackTraceString(th));
@@ -528,6 +533,7 @@
     protected Map<String, PackageInfo> mInjectedPackages;
 
     protected Set<PackageWithUser> mUninstalledPackages;
+    protected Set<String> mSystemPackages;
 
     protected PackageManager mMockPackageManager;
     protected PackageManagerInternal mMockPackageManagerInternal;
@@ -628,6 +634,8 @@
     protected static final String PACKAGE_FALLBACK_LAUNCHER_NAME = "fallback";
     protected static final int PACKAGE_FALLBACK_LAUNCHER_PRIORITY = -999;
 
+    protected String mInjectedBuildFingerprint = "build1";
+
     static {
         QUERY_ALL.setQueryFlags(
                 ShortcutQuery.FLAG_GET_ALL_KINDS);
@@ -677,6 +685,7 @@
                 pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
 
         mUninstalledPackages = new HashSet<>();
+        mSystemPackages = new HashSet<>();
 
         mInjectedFilePathRoot = new File(getTestContext().getCacheDir(), "test-files");
 
@@ -963,6 +972,9 @@
         if (mUninstalledPackages.contains(PackageWithUser.of(userId, packageName))) {
             ret.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED;
         }
+        if (mSystemPackages.contains(packageName)) {
+            ret.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        }
 
         if (getSignatures) {
             ret.signatures = pi.signatures;
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index ed4e391..46ac7ce 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -4347,6 +4347,128 @@
         });
     }
 
+    public void testHandlePackageUpdate_systemAppUpdate() {
+
+        // Package1 is a system app.  Package 2 is not a system app, so it's not scanned
+        // in this test at all.
+        mSystemPackages.add(CALLING_PACKAGE_1);
+
+        // Initial state: no shortcuts.
+        mService.checkPackageChanges(USER_0);
+
+        assertEquals(mInjectedCurrentTimeMillis,
+                mService.getUserShortcutsLocked(USER_0).getLastAppScanTime());
+        assertEquals(mInjectedBuildFingerprint,
+                mService.getUserShortcutsLocked(USER_0).getLastAppScanOsFingerprint());
+
+        // They have no shortcuts.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertWith(getCallerShortcuts())
+                    .isEmpty();
+        });
+
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertWith(getCallerShortcuts())
+                    .isEmpty();
+        });
+
+        // Next.
+        // Update the packages -- now they have 1 manifest shortcut.
+        // But checkPackageChanges() don't notice it, since their version code / timestamp haven't
+        // changed.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_1);
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
+                R.xml.shortcut_1);
+        mInjectedCurrentTimeMillis += 1000;
+        mService.checkPackageChanges(USER_0);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertWith(getCallerShortcuts())
+                    .isEmpty();
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertWith(getCallerShortcuts())
+                    .isEmpty();
+        });
+
+        // Next.
+        // Update the build finger print.  All system apps will be scanned now.
+        mInjectedBuildFingerprint = "update1";
+        mInjectedCurrentTimeMillis += 1000;
+        mService.checkPackageChanges(USER_0);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms1");
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertWith(getCallerShortcuts())
+                    .isEmpty();
+        });
+
+        // Next.
+        // Update manifest shortcuts.
+        mInjectedBuildFingerprint = "update2";
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_2);
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
+                R.xml.shortcut_2);
+        mInjectedCurrentTimeMillis += 1000;
+        mService.checkPackageChanges(USER_0);
+
+        // Fingerprint hasn't changed, so CALLING_PACKAGE_1 wasn't scanned.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms1");
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertWith(getCallerShortcuts())
+                    .isEmpty();
+        });
+
+        // Update the fingerprint, but CALLING_PACKAGE_1's version code hasn't changed, so
+        // still not scanned.
+        mInjectedBuildFingerprint = "update2";
+        mInjectedCurrentTimeMillis += 1000;
+        mService.checkPackageChanges(USER_0);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms1");
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertWith(getCallerShortcuts())
+                    .isEmpty();
+        });
+
+        // Now update the version code, so CALLING_PACKAGE_1 is scanned again.
+        mInjectedBuildFingerprint = "update3";
+        mInjectedCurrentTimeMillis += 1000;
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.checkPackageChanges(USER_0);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms1", "ms2");
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertWith(getCallerShortcuts())
+                    .isEmpty();
+        });
+
+        // Make sure getLastAppScanTime / getLastAppScanOsFingerprint are persisted.
+        initService();
+        assertEquals(mInjectedCurrentTimeMillis,
+                mService.getUserShortcutsLocked(USER_0).getLastAppScanTime());
+        assertEquals(mInjectedBuildFingerprint,
+                mService.getUserShortcutsLocked(USER_0).getLastAppScanOsFingerprint());
+    }
+
     public void testHandlePackageChanged() {
         final ComponentName ACTIVITY1 = new ComponentName(CALLING_PACKAGE_1, "act1");
         final ComponentName ACTIVITY2 = new ComponentName(CALLING_PACKAGE_1, "act2");