Showing alert dialog when shortcut version higher than the App

Creating an AlertDialog when the disabled reason of the shortcut is DISABLED_REASON_VERSION_LOWER, which happens when the backed-up shortcut was created from an application that has a version higher than the one installed on the device. The AlertDialog will direct the user to the play store and update the application.

Test: Manual
Fix: 224796975
Change-Id: I0125fada60b48176775de6782ba03ee6790904aa
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f699fca..ffa1e3f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -303,6 +303,17 @@
     <!-- Title for an app whose download has been started. -->
     <string name="app_waiting_download_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> waiting to install</string>
 
+
+    <!-- Title shown on the alert dialog prompting the user to update the application in market
+     in order to re-enable the disabled shortcuts -->
+    <string name="dialog_update_title">App update required</string>
+    <!-- Message shown on the alert dialog prompting the user to update the application -->
+    <string name="dialog_update_message">The app for this icon isn\'t updated. You can update manually to re-enable this shortcut, or remove the icon.</string>
+    <!-- Message for the update button in the alert dialog, will bring the user to market -->
+    <string name="dialog_update">Update</string>
+    <!-- Message for the remove button in the alert dialog -->
+    <string name="dialog_remove">Remove</string>
+
     <!-- Strings for widgets & more in the popup container/bottom sheet -->
 
     <!-- Accessibility title for the popup containing a list of widgets. [CHAR_LIMIT=50] -->
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 86e88b7..ed01660 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -50,7 +50,6 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.Parcelable;
-import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
@@ -123,7 +122,6 @@
 import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.function.Consumer;
@@ -3297,9 +3295,12 @@
         }
     }
 
-    public void removeAbandonedPromise(String packageName, UserHandle user) {
-        ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(
-                Collections.singleton(packageName), user);
+    /**
+     * Remove workspace icons & widget information related to items in matcher.
+     *
+     * @param matcher  the matcher generated by the caller.
+     */
+    public void persistRemoveItemsByMatcher(ItemInfoMatcher matcher) {
         mLauncher.getModelWriter().deleteItemsFromDatabase(matcher);
         removeItemsByMatcher(matcher);
     }
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 19345d7..76a0c4d 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -71,10 +71,6 @@
      */
     public static final int FLAG_DISABLED_LOCKED_USER = 1 << 5;
 
-    public static final int FLAG_DISABLED_MASK = FLAG_DISABLED_SAFEMODE
-            | FLAG_DISABLED_NOT_AVAILABLE | FLAG_DISABLED_SUSPENDED
-            | FLAG_DISABLED_QUIET_USER | FLAG_DISABLED_BY_PUBLISHER | FLAG_DISABLED_LOCKED_USER;
-
     /**
      * The item points to a system app.
      */
@@ -114,6 +110,16 @@
             | FLAG_INCREMENTAL_DOWNLOAD_ACTIVE;
 
     /**
+     * Indicates that the icon is a disabled shortcut and application updates are required.
+     */
+    public static final int FLAG_DISABLED_VERSION_LOWER = 1 << 12;
+
+    public static final int FLAG_DISABLED_MASK = FLAG_DISABLED_SAFEMODE
+            | FLAG_DISABLED_NOT_AVAILABLE | FLAG_DISABLED_SUSPENDED
+            | FLAG_DISABLED_QUIET_USER | FLAG_DISABLED_BY_PUBLISHER | FLAG_DISABLED_LOCKED_USER
+            | FLAG_DISABLED_VERSION_LOWER;
+
+    /**
      * Status associated with the system state of the underlying item. This is calculated every
      * time a new info is created and not persisted on the disk.
      */
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index a195979..2b3da33 100644
--- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -180,12 +180,25 @@
             runtimeStatusFlags |= FLAG_DISABLED_BY_PUBLISHER;
         }
         disabledMessage = shortcutInfo.getDisabledMessage();
+        if (Utilities.ATLEAST_P
+                && shortcutInfo.getDisabledReason() == ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
+            runtimeStatusFlags |= FLAG_DISABLED_VERSION_LOWER;
+        } else {
+            runtimeStatusFlags &= ~FLAG_DISABLED_VERSION_LOWER;
+        }
 
         Person[] persons = ApiWrapper.getPersons(shortcutInfo);
         personKeys = persons.length == 0 ? Utilities.EMPTY_STRING_ARRAY
             : Arrays.stream(persons).map(Person::getKey).sorted().toArray(String[]::new);
     }
 
+    /**
+     * {@code true} if the shortcut is disabled due to its app being a lower version.
+     */
+    public boolean isDisabledVersionLower() {
+        return (runtimeStatusFlags & FLAG_DISABLED_VERSION_LOWER) != 0;
+    }
+
     /** Returns the WorkspaceItemInfo id associated with the deep shortcut. */
     public String getDeepShortcutId() {
         return itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 8d57d69..cd00f15 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -43,6 +43,7 @@
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.folder.Folder;
@@ -58,8 +59,10 @@
 import com.android.launcher3.model.data.SearchActionItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -67,6 +70,8 @@
 import com.android.launcher3.widget.WidgetAddFlowHandler;
 import com.android.launcher3.widget.WidgetManagerHelper;
 
+import java.util.Collections;
+
 /**
  * Class for handling clicks on workspace and all-apps items
  */
@@ -171,7 +176,8 @@
                         (d, i) -> startMarketIntentForPackage(v, launcher, packageName))
                 .setNeutralButton(R.string.abandoned_clean_this,
                         (d, i) -> launcher.getWorkspace()
-                                .removeAbandonedPromise(packageName, user))
+                                .persistRemoveItemsByMatcher(ItemInfoMatcher.ofPackages(
+                                        Collections.singleton(packageName), user)))
                 .create().show();
     }
 
@@ -205,6 +211,12 @@
     public static boolean handleDisabledItemClicked(WorkspaceItemInfo shortcut, Context context) {
         final int disabledFlags = shortcut.runtimeStatusFlags
                 & WorkspaceItemInfo.FLAG_DISABLED_MASK;
+        // Handle the case where the disabled reason is DISABLED_REASON_VERSION_LOWER.
+        // Show an AlertDialog for the user to choose either updating the app or cancel the launch.
+        if (maybeCreateAlertDialogForShortcut(shortcut, context)) {
+            return true;
+        }
+
         if ((disabledFlags
                 & ~FLAG_DISABLED_SUSPENDED
                 & ~FLAG_DISABLED_QUIET_USER) == 0) {
@@ -230,6 +242,37 @@
         }
     }
 
+    private static boolean maybeCreateAlertDialogForShortcut(final WorkspaceItemInfo shortcut,
+            Context context) {
+        try {
+            final Launcher launcher = Launcher.getLauncher(context);
+            if (shortcut.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+                    && shortcut.isDisabledVersionLower()) {
+
+                new AlertDialog.Builder(context)
+                        .setTitle(R.string.dialog_update_title)
+                        .setMessage(R.string.dialog_update_message)
+                        .setPositiveButton(R.string.dialog_update, (d, i) -> {
+                            // Direct the user to the play store to update the app
+                            context.startActivity(shortcut.getMarketIntent(context));
+                        })
+                        .setNeutralButton(R.string.dialog_remove, (d, i) -> {
+                            // Remove the icon if launcher is successfully initialized
+                            launcher.getWorkspace().persistRemoveItemsByMatcher(ItemInfoMatcher
+                                    .ofShortcutKeys(Collections.singleton(ShortcutKey
+                                            .fromItemInfo(shortcut))));
+                        })
+                        .create()
+                        .show();
+                return true;
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Error creating alert dialog", e);
+        }
+
+        return false;
+    }
+
     /**
      * Event handler for an app shortcut click.
      *