Improve shortcut backup & restore.

From http://go/p-shortcut-restore :

Currently on pre-P builds, there are 6 cases where shortcuts won't be restored.

Launcher issue:
A: Launcher is downgraded
B: Launcher has allowBackup="false"
C: Launcher's signature changed.
A-C would result in not restoring any shortcuts from any apps.

App issue:
D: App is downgraded
E: App has allowBackup="false"
F: App's signature changed.
D-F would result in not restoring any shortcuts from individual apps.

In P, we'll restore all pinned shortcuts anyway except in case B and C.

The new behavior would be:
A: We'll restore all pinned shortcuts anyway even if the launcher is downgraded.
B: If the launcher doesn't support backup&restore, we still don't restore shortcuts.
C: If the launcher has a different signature, we still don't restore shortcuts.

D, E, F: All pinned shortcuts will be restored, but are disabled.

In case of D, E, F, shortcuts would be:
1. ShortcutInfo.isEnabled() will return false. They are not launchable.
2. If it's case D (downgrade), shortcuts will be re-enabled once a publisher app
is updated to the original version or higher.
3. getDisabledMessage() will return a special, canned message explaining why
they're disabled.
4. A new API getDisabledReason() will return one of the following values:
 - DISABLED_REASON_NOT_DISABLED (for non-disabled shortcuts)
 - DISABLED_REASON_BY_APP (for shortcuts disabled by apps)
 - DISABLED_REASON_APP_CHANGED (for shortcuts disabled when a manifest shortcut is gone.)
 - DISABLED_REASON_VERSION_LOWER (case D)
 - DISABLED_REASON_BACKUP_NOT_SUPPORTED (case E)
 - DISABLED_REASON_SIGNATURE_MISMATCH (case F)
 - (DISABLE_REASON_OTHER_RESTORE_ISSUE for future use)
Launcher can opt to use this API to show a custom error message, rather than
using the canned system message from getDisabledMessage().

5. This spec change is completely transparent to publisher apps.
In case D,E,F, the disabled shortcuts will only be visible to the launcher,
and from the publisher app's point of view, they still don't exist.
APIs such as getPinnedShortcuts() will not return them, and updateShortcuts()
will not affect them. The enableShortcut() will not enable them either.

Bug 62451035
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest1 -w com.android.frameworks.servicestests
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest2 -w com.android.frameworks.servicestests
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest3 -w com.android.frameworks.servicestests
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest4 -w com.android.frameworks.servicestests
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest5 -w com.android.frameworks.servicestests
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest6 -w com.android.frameworks.servicestests
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest7 -w com.android.frameworks.servicestests
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest8 -w com.android.frameworks.servicestests
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest9 -w com.android.frameworks.servicestests
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest10 -w com.android.frameworks.servicestests
Test: cts-tradefed run cts-dev --skip-device-info --skip-preconditions --skip-system-status-check com.android.compatibility.common.tradefed.targetprep.NetworkConnectivityChecker -a armeabi-v7a -l INFO -m CtsShortcutManagerTestCases
Test: cts-tradefed run cts-dev --skip-device-info --skip-preconditions --skip-system-status-check com.android.compatibility.common.tradefed.targetprep.NetworkConnectivityChecker -a armeabi-v7a -l INFO -m CtsShortcutHostTestCases

Change-Id: Ib4d73aa44a0395f5e37c8ad0c9cd041a9038fb66
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index f922ad1..cedf476 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -83,11 +83,16 @@
         return mOwnerUserId;
     }
 
+    @Override
+    protected boolean canRestoreAnyVersion() {
+        // Launcher's pinned shortcuts can be restored to an older version.
+        return true;
+    }
+
     /**
      * Called when the new package can't receive the backup, due to signature or version mismatch.
      */
-    @Override
-    protected void onRestoreBlocked() {
+    private void onRestoreBlocked() {
         final ArrayList<PackageWithUser> pinnedPackages =
                 new ArrayList<>(mPinnedShortcuts.keySet());
         mPinnedShortcuts.clear();
@@ -101,15 +106,21 @@
     }
 
     @Override
-    protected void onRestored() {
-        // Nothing to do.
+    protected void onRestored(int restoreBlockReason) {
+        // For launcher, possible reasons here are DISABLED_REASON_SIGNATURE_MISMATCH or
+        // DISABLED_REASON_BACKUP_NOT_SUPPORTED.
+        // DISABLED_REASON_VERSION_LOWER will NOT happen because we don't check version
+        // code for launchers.
+        if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+            onRestoreBlocked();
+        }
     }
 
     /**
      * Pin the given shortcuts, replacing the current pinned ones.
      */
     public void pinShortcuts(@UserIdInt int packageUserId,
-            @NonNull String packageName, @NonNull List<String> ids) {
+            @NonNull String packageName, @NonNull List<String> ids, boolean forPinRequest) {
         final ShortcutPackage packageShortcuts =
                 mShortcutUser.getPackageShortcutsIfExists(packageName);
         if (packageShortcuts == null) {
@@ -124,8 +135,12 @@
         } else {
             final ArraySet<String> prevSet = mPinnedShortcuts.get(pu);
 
-            // Pin shortcuts.  Make sure only pin the ones that were visible to the caller.
-            // i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here.
+            // Actually pin shortcuts.
+            // This logic here is to make sure a launcher cannot pin a shortcut that is floating
+            // (i.e. not dynamic nor manifest but is pinned) and pinned by another launcher.
+            // In this case, technically the shortcut doesn't exist to this launcher, so it can't
+            // pin it.
+            // (Maybe unnecessarily strict...)
 
             final ArraySet<String> newSet = new ArraySet<>();
 
@@ -135,8 +150,10 @@
                 if (si == null) {
                     continue;
                 }
-                if (si.isDynamic() || si.isManifestShortcut()
-                        || (prevSet != null && prevSet.contains(id))) {
+                if (si.isDynamic()
+                        || si.isManifestShortcut()
+                        || (prevSet != null && prevSet.contains(id))
+                        || forPinRequest) {
                     newSet.add(id);
                 }
             }
@@ -155,7 +172,7 @@
     }
 
     /**
-     * Return true if the given shortcut is pinned by this launcher.
+     * Return true if the given shortcut is pinned by this launcher.<code></code>
      */
     public boolean hasPinned(ShortcutInfo shortcut) {
         final ArraySet<String> pinned =
@@ -164,10 +181,10 @@
     }
 
     /**
-     * Additionally pin a shortcut. c.f. {@link #pinShortcuts(int, String, List)}
+     * Additionally pin a shortcut. c.f. {@link #pinShortcuts(int, String, List, boolean)}
      */
     public void addPinnedShortcut(@NonNull String packageName, @UserIdInt int packageUserId,
-            String id) {
+            String id, boolean forPinRequest) {
         final ArraySet<String> pinnedSet = getPinnedShortcutIds(packageName, packageUserId);
         final ArrayList<String> pinnedList;
         if (pinnedSet != null) {
@@ -178,21 +195,21 @@
         }
         pinnedList.add(id);
 
-        pinShortcuts(packageUserId, packageName, pinnedList);
+        pinShortcuts(packageUserId, packageName, pinnedList, forPinRequest);
     }
 
     boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) {
         return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null;
     }
 
-    public void ensureVersionInfo() {
+    public void ensurePackageInfo() {
         final PackageInfo pi = mShortcutUser.mService.getPackageInfoWithSignatures(
                 getPackageName(), getPackageUserId());
         if (pi == null) {
             Slog.w(TAG, "Package not found: " + getPackageName());
             return;
         }
-        getPackageInfo().updateVersionInfo(pi);
+        getPackageInfo().updateFromPackageInfo(pi);
     }
 
     /**
@@ -201,6 +218,10 @@
     @Override
     public void saveToXml(XmlSerializer out, boolean forBackup)
             throws IOException {
+        if (forBackup && !getPackageInfo().isBackupAllowed()) {
+            // If an launcher app doesn't support backup&restore, then nothing to do.
+            return;
+        }
         final int size = mPinnedShortcuts.size();
         if (size == 0) {
             return; // Nothing to write.
@@ -209,7 +230,7 @@
         out.startTag(null, TAG_ROOT);
         ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, getPackageName());
         ShortcutService.writeAttr(out, ATTR_LAUNCHER_USER_ID, getPackageUserId());
-        getPackageInfo().saveToXml(out);
+        getPackageInfo().saveToXml(out, forBackup);
 
         for (int i = 0; i < size; i++) {
             final PackageWithUser pu = mPinnedShortcuts.keyAt(i);