diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 8c0a2d7abd..f3cc164aa9 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -2495,7 +2495,7 @@ public class Launcher extends StatefulActivity implements Launche * @param updated list of shortcuts which have changed. */ @Override - public void bindWorkspaceItemsChanged(ArrayList updated) { + public void bindWorkspaceItemsChanged(List updated) { if (!updated.isEmpty()) { mWorkspace.updateShortcuts(updated); } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index a6283ff253..15e0daa8ea 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -115,6 +115,7 @@ import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverla import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.function.Predicate; /** @@ -3087,7 +3088,7 @@ public class Workspace extends PagedView return false; } - void updateShortcuts(ArrayList shortcuts) { + void updateShortcuts(List shortcuts) { final HashSet updates = new HashSet<>(shortcuts); ItemOperator op = (info, v) -> { if (v instanceof BubbleTextView && updates.contains(info)) { diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java index 9013cba54f..d1e501788e 100644 --- a/src/com/android/launcher3/model/BaseModelUpdateTask.java +++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java @@ -22,7 +22,9 @@ import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherModel.CallbackTask; import com.android.launcher3.LauncherModel.ModelUpdateTask; import com.android.launcher3.model.BgDataModel.Callbacks; +import com.android.launcher3.model.BgDataModel.FixedContainerItems; import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.ItemInfoMatcher; @@ -30,7 +32,10 @@ import com.android.launcher3.widget.WidgetListRowEntry; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; +import java.util.stream.Collectors; /** * Extension of {@link ModelUpdateTask} with some utility methods @@ -88,11 +93,27 @@ public abstract class BaseModelUpdateTask implements ModelUpdateTask { return mModel.getWriter(false /* hasVerticalHotseat */, false /* verifyChanges */); } - - public void bindUpdatedWorkspaceItems(final ArrayList updatedShortcuts) { - if (!updatedShortcuts.isEmpty()) { - scheduleCallbackTask(c -> c.bindWorkspaceItemsChanged(updatedShortcuts)); + public void bindUpdatedWorkspaceItems(List allUpdates) { + // Bind workspace items + List workspaceUpdates = allUpdates.stream() + .filter(info -> info.id != ItemInfo.NO_ID) + .collect(Collectors.toList()); + if (!workspaceUpdates.isEmpty()) { + scheduleCallbackTask(c -> c.bindWorkspaceItemsChanged(workspaceUpdates)); } + + // Bind extra items if any + allUpdates.stream() + .mapToInt(info -> info.container) + .distinct() + .mapToObj(mDataModel.extraItems::get) + .filter(Objects::nonNull) + .forEach(this::bindExtraContainerItems); + } + + public void bindExtraContainerItems(FixedContainerItems item) { + FixedContainerItems copy = item.clone(); + scheduleCallbackTask(c -> c.bindExtraContainerItems(copy)); } public void bindDeepShortcuts(BgDataModel dataModel) { diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index 7524920ce8..dfdc1387ff 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -15,19 +15,25 @@ */ package com.android.launcher3.model; +import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY; + import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS; import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.mapping; + import android.content.Context; import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; import android.os.UserHandle; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Log; -import android.util.MutableInt; import com.android.launcher3.InstallShortcutReceiver; import com.android.launcher3.LauncherSettings; +import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.Workspace; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.data.AppInfo; @@ -36,8 +42,10 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.PromiseAppInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.pm.UserCache; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.shortcuts.ShortcutRequest; +import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; @@ -50,14 +58,16 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.function.BiConsumer; +import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * All the data stored in-memory and managed by the LauncherModel @@ -89,9 +99,9 @@ public class BgDataModel { public final IntSparseArrayMap folders = new IntSparseArrayMap<>(); /** - * Map of ShortcutKey to the number of times it is pinned. + * Extra container based items */ - public final Map pinnedShortcutCounts = new HashMap<>(); + public final IntSparseArrayMap extraItems = new IntSparseArrayMap<>(); /** * List of all cached predicted items visible on home screen @@ -128,8 +138,8 @@ public class BgDataModel { appWidgets.clear(); folders.clear(); itemsIdMap.clear(); - pinnedShortcutCounts.clear(); deepShortcutMap.clear(); + extraItems.clear(); } /** @@ -182,6 +192,7 @@ public class BgDataModel { } public synchronized void removeItem(Context context, Iterable items) { + ArraySet updatedDeepShortcuts = new ArraySet<>(); for (ItemInfo item : items) { switch (item.itemType) { case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: @@ -200,14 +211,7 @@ public class BgDataModel { workspaceItems.remove(item); break; case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: { - // Decrement pinned shortcut count - ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item); - MutableInt count = pinnedShortcutCounts.get(pinnedShortcut); - if ((count == null || --count.value == 0) - && !InstallShortcutReceiver.getPendingShortcuts(context) - .contains(pinnedShortcut)) { - unpinShortcut(context, pinnedShortcut); - } + updatedDeepShortcuts.add(item.user); // Fall through. } case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: @@ -221,6 +225,7 @@ public class BgDataModel { } itemsIdMap.remove(item.id); } + updatedDeepShortcuts.forEach(user -> updateShortcutPinnedState(context, user)); } public synchronized void addItem(Context context, ItemInfo item, boolean newItem) { @@ -230,23 +235,7 @@ public class BgDataModel { folders.put(item.id, (FolderInfo) item); workspaceItems.add(item); break; - case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: { - // Increment the count for the given shortcut - ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item); - MutableInt count = pinnedShortcutCounts.get(pinnedShortcut); - if (count == null) { - count = new MutableInt(1); - pinnedShortcutCounts.put(pinnedShortcut, count); - } else { - count.value++; - } - - // Since this is a new item, pin the shortcut in the system server. - if (newItem && count.value == 1) { - updatePinnedShortcuts(context, pinnedShortcut, List::add); - } - // Fall through - } + case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || @@ -271,36 +260,87 @@ public class BgDataModel { appWidgets.add((LauncherAppWidgetInfo) item); break; } + if (newItem && item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { + updateShortcutPinnedState(context, item.user); + } } /** - * Removes the given shortcut from the current list of pinned shortcuts. - * (Runs on background thread) + * Updates the deep shortucts state in system to match out internal model, pinning any missing + * shortcuts and unpinning any extra shortcuts. */ - public void unpinShortcut(Context context, ShortcutKey key) { - updatePinnedShortcuts(context, key, List::remove); + public void updateShortcutPinnedState(Context context) { + for (UserHandle user : UserCache.INSTANCE.get(context).getUserProfiles()) { + updateShortcutPinnedState(context, user); + } } - private void updatePinnedShortcuts(Context context, ShortcutKey key, - BiConsumer, String> idOp) { + /** + * Updates the deep shortucts state in system to match out internal model, pinning any missing + * shortcuts and unpinning any extra shortcuts. + */ + public synchronized void updateShortcutPinnedState(Context context, UserHandle user) { if (GO_DISABLE_WIDGETS) { return; } - String packageName = key.componentName.getPackageName(); - String id = key.getId(); - UserHandle user = key.user; - List pinnedIds = new ShortcutRequest(context, user) - .forPackage(packageName) - .query(PINNED) - .stream() - .map(ShortcutInfo::getId) - .collect(Collectors.toCollection(ArrayList::new)); - idOp.accept(pinnedIds, id); - try { - context.getSystemService(LauncherApps.class).pinShortcuts(packageName, pinnedIds, user); - } catch (SecurityException | IllegalStateException e) { - Log.w(TAG, "Failed to pin shortcut", e); + + // Collect all system shortcuts + QueryResult result = new ShortcutRequest(context, user) + .query(PINNED | FLAG_GET_KEY_FIELDS_ONLY); + if (!result.wasSuccess()) { + return; } + // Map of packageName to shortcutIds that are currently in the system + Map> systemMap = result.stream() + .collect(groupingBy(ShortcutInfo::getPackage, + mapping(ShortcutInfo::getId, Collectors.toSet()))); + + // Collect all model shortcuts + Stream.Builder itemStream = Stream.builder(); + forAllWorkspaceItemInfos(user, itemStream::accept); + // Map of packageName to shortcutIds that are currently in our model + Map> modelMap = Stream.concat( + // Model shortcuts + itemStream.build() + .filter(wi -> wi.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) + .map(ShortcutKey::fromItemInfo), + // Pending shortcuts + InstallShortcutReceiver.getPendingShortcuts(context) + .stream().filter(si -> si.user.equals(user))) + .collect(groupingBy(ShortcutKey::getPackageName, + mapping(ShortcutKey::getId, Collectors.toSet()))); + + // Check for diff + for (Map.Entry> entry : modelMap.entrySet()) { + Set modelShortcuts = entry.getValue(); + Set systemShortcuts = systemMap.remove(entry.getKey()); + if (systemShortcuts == null) { + systemShortcuts = Collections.emptySet(); + } + + // Do not use .equals as it can vary based on the type of set + if (systemShortcuts.size() != modelShortcuts.size() + || !systemShortcuts.containsAll(modelShortcuts)) { + // Update system state for this package + try { + context.getSystemService(LauncherApps.class).pinShortcuts( + entry.getKey(), new ArrayList<>(modelShortcuts), user); + } catch (SecurityException | IllegalStateException e) { + Log.w(TAG, "Failed to pin shortcut", e); + } + } + } + + // If there are any extra pinned shortcuts, remove them + systemMap.keySet().forEach(packageName -> { + // Update system state + try { + context.getSystemService(LauncherApps.class).pinShortcuts( + packageName, Collections.emptyList(), user); + } catch (SecurityException | IllegalStateException e) { + Log.w(TAG, "Failed to unpin shortcut", e); + } + }); } /** @@ -360,8 +400,40 @@ public class BgDataModel { op.accept((WorkspaceItemInfo) info); } } + + for (int i = extraItems.size() - 1; i >= 0; i--) { + for (ItemInfo info : extraItems.valueAt(i).items) { + if (info instanceof WorkspaceItemInfo && userHandle.equals(info.user)) { + op.accept((WorkspaceItemInfo) info); + } + } + } } + /** + * An object containing items corresponding to a fixed container + */ + public static class FixedContainerItems { + + public final int containerId; + public final List items; + + public FixedContainerItems(int containerId) { + this(containerId, new ArrayList<>()); + } + + public FixedContainerItems(int containerId, List items) { + this.containerId = containerId; + this.items = items; + } + + @Override + public FixedContainerItems clone() { + return new FixedContainerItems(containerId, Collections.unmodifiableList(items)); + } + } + + public interface Callbacks { // If the launcher has permission to access deep shortcuts. int FLAG_HAS_SHORTCUT_PERMISSION = 1 << 0; @@ -384,7 +456,7 @@ public class BgDataModel { void bindAppsAdded(IntArray newScreens, ArrayList addNotAnimated, ArrayList addAnimated); void bindPromiseAppProgressUpdated(PromiseAppInfo app); - void bindWorkspaceItemsChanged(ArrayList updated); + void bindWorkspaceItemsChanged(List updated); void bindWidgetsRestored(ArrayList widgets); void bindRestoreItemsChange(HashSet updates); void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher); @@ -393,6 +465,11 @@ public class BgDataModel { void executeOnNextDraw(ViewOnDrawExecutor executor); void bindDeepShortcutMap(HashMap deepShortcutMap); + /** + * Binds extra item provided any external source + */ + default void bindExtraContainerItems(FixedContainerItems item) { } + void bindAllApplications(AppInfo[] apps, int flags); /** diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 4a64522c65..bea00869ee 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -46,12 +46,10 @@ import android.os.UserManager; import android.text.TextUtils; import android.util.Log; import android.util.LongSparseArray; -import android.util.MutableInt; import android.util.TimingLogger; import androidx.annotation.WorkerThread; -import com.android.launcher3.InstallShortcutReceiver; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherSettings; @@ -94,7 +92,6 @@ import com.android.launcher3.widget.WidgetManagerHelper; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.CancellationException; @@ -794,17 +791,8 @@ public class LoaderTask implements Runnable { LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS); } - // Unpin shortcuts that don't exist on the workspace. - HashSet pendingShortcuts = - InstallShortcutReceiver.getPendingShortcuts(context); - for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) { - MutableInt numTimesPinned = mBgDataModel.pinnedShortcutCounts.get(key); - if ((numTimesPinned == null || numTimesPinned.value == 0) - && !pendingShortcuts.contains(key)) { - // Shortcut is pinned but doesn't exist on the workspace; unpin it. - mBgDataModel.unpinShortcut(context, key); - } - } + // Update pinned state of model shortcuts + mBgDataModel.updateShortcutPinnedState(context); // Sort the folder items, update ranks, and make sure all preview items are high res. FolderGridOrganizer verifier = diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java index a013312860..c996748a28 100644 --- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java +++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java @@ -222,7 +222,7 @@ public class SecondaryDisplayLauncher extends BaseDraggingActivity } @Override - public void bindWorkspaceItemsChanged(ArrayList updated) { } + public void bindWorkspaceItemsChanged(List updated) { } @Override public void bindWidgetsRestored(ArrayList widgets) { } diff --git a/src/com/android/launcher3/shortcuts/ShortcutKey.java b/src/com/android/launcher3/shortcuts/ShortcutKey.java index 3ca9490cc5..0c6d675afd 100644 --- a/src/com/android/launcher3/shortcuts/ShortcutKey.java +++ b/src/com/android/launcher3/shortcuts/ShortcutKey.java @@ -30,6 +30,10 @@ public class ShortcutKey extends ComponentKey { return componentName.getClassName(); } + public String getPackageName() { + return componentName.getPackageName(); + } + /** * Creates a {@link ShortcutRequest} for this key */