diff --git a/res/values/strings.xml b/res/values/strings.xml index f699fca9b5..ffa1e3f79e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -303,6 +303,17 @@ %1$s waiting to install + + + App update required + + The app for this icon isn\'t updated. You can update manually to re-enable this shortcut, or remove the icon. + + Update + + Remove + diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 86e88b7020..ed016609bb 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -50,7 +50,6 @@ import android.graphics.drawable.Drawable; 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.launcher3.widget.util.WidgetSizes; 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 class Workspace extends PagedView } } - 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 19345d7693..76a0c4d64c 100644 --- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java +++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java @@ -71,10 +71,6 @@ public abstract class ItemInfoWithIcon extends ItemInfo { */ 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. */ @@ -113,6 +109,16 @@ public abstract class ItemInfoWithIcon extends ItemInfo { public static final int FLAG_SHOW_DOWNLOAD_PROGRESS_MASK = FLAG_INSTALL_SESSION_ACTIVE | 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 a195979148..2b3da335c3 100644 --- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java +++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java @@ -180,12 +180,25 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { 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 8d57d695fa..cd00f15011 100644 --- a/src/com/android/launcher3/touch/ItemClickHandler.java +++ b/src/com/android/launcher3/touch/ItemClickHandler.java @@ -43,6 +43,7 @@ import android.widget.Toast; 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.LauncherAppWidgetInfo; 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.PendingAppWidgetHostView; 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 @@ public class ItemClickHandler { (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 class ItemClickHandler { 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 @@ public class ItemClickHandler { } } + 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. *