diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java index 56945ba0a6..70868c5ab2 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java @@ -17,7 +17,6 @@ package com.android.launcher3.hybridhotseat; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; import static com.android.launcher3.model.PredictionHelper.getAppTargetFromItemInfo; -import static com.android.launcher3.model.PredictionHelper.isTrackedForHotseatPrediction; import static com.android.launcher3.model.PredictionHelper.wrapAppTargetWithItemLocation; import android.app.prediction.AppTarget; @@ -27,9 +26,12 @@ import android.os.Bundle; import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.BgDataModel.FixedContainerItems; +import com.android.launcher3.model.PredictionHelper; import com.android.launcher3.model.data.ItemInfo; import java.util.ArrayList; +import java.util.Objects; +import java.util.stream.Collectors; /** * Model helper for app predictions in workspace @@ -43,13 +45,18 @@ public class HotseatPredictionModel { */ public static Bundle convertDataModelToAppTargetBundle(Context context, BgDataModel dataModel) { Bundle bundle = new Bundle(); - ArrayList events = new ArrayList<>(); - ArrayList workspaceItems = dataModel.getAllWorkspaceItems(); - for (ItemInfo item : workspaceItems) { - AppTarget target = getAppTargetFromItemInfo(context, item); - if (target != null && !isTrackedForHotseatPrediction(item)) continue; - events.add(wrapAppTargetWithItemLocation(target, AppTargetEvent.ACTION_PIN, item)); - } + ArrayList events = dataModel.itemsIdMap + .stream() + .filter(PredictionHelper::isTrackedForHotseatPrediction) + .map(item -> { + AppTarget target = getAppTargetFromItemInfo(context, item); + return target != null + ? wrapAppTargetWithItemLocation(target, AppTargetEvent.ACTION_PIN, item) + : null; + }) + .filter(Objects::nonNull) + .collect(Collectors.toCollection(ArrayList::new)); + ArrayList currentTargets = new ArrayList<>(); FixedContainerItems hotseatItems = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION); if (hotseatItems != null) { diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java index 25e181312d..40e8fc2484 100644 --- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java +++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java @@ -462,7 +462,7 @@ public class QuickstepModelDelegate extends ModelDelegate { private Bundle getBundleForWidgetsOnWorkspace(Context context, BgDataModel dataModel) { Bundle bundle = new Bundle(); ArrayList widgetEvents = - dataModel.getAllWorkspaceItems().stream() + dataModel.itemsIdMap.stream() .filter(PredictionHelper::isTrackedForWidgetPrediction) .map(item -> { AppTarget target = getAppTargetFromItemInfo(context, item); diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java index 9d9054eda9..40e1c10d1c 100644 --- a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java +++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java @@ -18,6 +18,7 @@ package com.android.launcher3.model; import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions; import static com.android.launcher3.Flags.enableTieredWidgetsByDefaultInPicker; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION; +import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toMap; @@ -69,9 +70,11 @@ public final class WidgetsPredictionUpdateTask implements ModelUpdateTask { @NonNull AllAppsList apps) { Predicate predictedWidgetsFilter = enableTieredWidgetsByDefaultInPicker() ? dataModel.widgetsModel.getPredictedWidgetsFilter() : null; - Set widgetsInWorkspace = dataModel.appWidgets.stream().map( - widget -> new ComponentKey(widget.providerName, widget.user)).collect( - Collectors.toSet()); + Set widgetsInWorkspace = dataModel.itemsIdMap + .stream() + .filter(WIDGET_FILTER) + .map(item -> new ComponentKey(item.getTargetComponent(), item.user)) + .collect(Collectors.toSet()); // Widgets (excluding shortcuts & already added widgets) that belong to apps eligible for // being in predictions. diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java index 9bfe71ff95..b2a30ca56f 100644 --- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java +++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java @@ -31,15 +31,15 @@ import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerC import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_SNAPSHOT; import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_0; -import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_90; import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_180; import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_270; +import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_90; import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__ALLAPPS; import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__BACKGROUND; import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME; import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__OVERVIEW; -import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__PORTRAIT; import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__LANDSCAPE; +import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__PORTRAIT; import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__SEASCAPE; import android.content.Context; @@ -69,6 +69,7 @@ import com.android.launcher3.logger.LauncherAtomExtensions.DeviceSearchResultCon import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.StatsLogManager; +import com.android.launcher3.model.data.CollectionInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.Executors; @@ -386,7 +387,8 @@ public class StatsLogCompatManager extends StatsLogManager { // and then write to StatsLog. app.getModel().enqueueModelUpdateTask((taskController, dataModel, apps) -> write(event, applyOverwrites(mItemInfo.buildProto( - dataModel.collections.get(mItemInfo.container), mContext)))); + (CollectionInfo) dataModel.itemsIdMap.get(mItemInfo.container), + mContext)))); })) { // Write log on the model thread so that logs do not go out of order // (for eg: drop comes after drag) diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 7563493dcc..d93c07fb17 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -659,7 +659,8 @@ public final class Utilities { // Only fetch badge if the icon is on workspace if (info.id != ItemInfo.NO_ID && badge == null) { badge = appState.getIconCache().getShortcutInfoBadge(si) - .newIcon(context, FLAG_THEMED); + .newIcon(context, ThemeManager.INSTANCE.get(context) + .isMonoThemeEnabled() ? FLAG_THEMED : 0); } } } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java index be5f8f76ef..8a1f96d2dd 100644 --- a/src/com/android/launcher3/folder/FolderNameProvider.java +++ b/src/com/android/launcher3/folder/FolderNameProvider.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.folder; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER; + import android.annotation.SuppressLint; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; @@ -37,6 +39,7 @@ import com.android.launcher3.model.ModelTaskController; import com.android.launcher3.model.StringCache; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.CollectionInfo; +import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.Preconditions; @@ -197,9 +200,18 @@ public class FolderNameProvider implements ResourceBasedOverride { @Override public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel, @NonNull AllAppsList apps) { - mCollectionInfos = dataModel.collections.clone(); + mCollectionInfos = getCollectionForSuggestions(dataModel); mAppInfos = Arrays.asList(apps.copyData()); } } + public static IntSparseArrayMap getCollectionForSuggestions( + BgDataModel dataModel) { + IntSparseArrayMap result = new IntSparseArrayMap<>(); + dataModel.itemsIdMap.stream() + .filter(item -> item.itemType == ITEM_TYPE_FOLDER) + .forEach(item -> result.put(item.id, (FolderInfo) item)); + return result; + } + } diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index f0e4fc444c..a0b73ae184 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -24,10 +24,10 @@ import static com.android.launcher3.BubbleTextView.DISPLAY_TASKBAR; import static com.android.launcher3.BubbleTextView.DISPLAY_WORKSPACE; import static com.android.launcher3.DeviceProfile.DEFAULT_SCALE; import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_PREVIEW_RENDERER; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET; -import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems; -import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks; +import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter; import android.app.Fragment; import android.app.WallpaperColors; @@ -105,7 +105,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * Utility class for generating the preview of Launcher for a given InvariantDeviceProfile. @@ -456,54 +458,48 @@ public class LauncherPreviewRenderer extends ContextWrapper private void populate(BgDataModel dataModel, Map widgetProviderInfoMap) { - // Separate the items that are on the current screen, and the other remaining items. - ArrayList currentWorkspaceItems = new ArrayList<>(); - ArrayList otherWorkspaceItems = new ArrayList<>(); - ArrayList currentAppWidgets = new ArrayList<>(); - ArrayList otherAppWidgets = new ArrayList<>(); + IntSet missingHotseatRank = new IntSet(); + IntStream.range(0, mDp.numShownHotseatIcons).forEach(missingHotseatRank::add); - IntSet currentScreenIds = IntSet.wrap(mWorkspaceScreens.keySet()); - filterCurrentWorkspaceItems(currentScreenIds, dataModel.workspaceItems, - currentWorkspaceItems, otherWorkspaceItems); - filterCurrentWorkspaceItems(currentScreenIds, dataModel.appWidgets, currentAppWidgets, - otherAppWidgets); - for (ItemInfo itemInfo : currentWorkspaceItems) { - switch (itemInfo.itemType) { - case Favorites.ITEM_TYPE_APPLICATION: - case Favorites.ITEM_TYPE_DEEP_SHORTCUT: - inflateAndAddIcon((WorkspaceItemInfo) itemInfo); - break; - case Favorites.ITEM_TYPE_FOLDER: - case Favorites.ITEM_TYPE_APP_PAIR: - inflateAndAddCollectionIcon((CollectionInfo) itemInfo); - break; - default: - break; - } - } - Map widgetsMap = widgetProviderInfoMap; - for (ItemInfo itemInfo : currentAppWidgets) { - switch (itemInfo.itemType) { - case Favorites.ITEM_TYPE_APPWIDGET: - case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: - if (widgetsMap == null) { - widgetsMap = dataModel.widgetsModel.getWidgetsByComponentKey() - .entrySet() - .stream() - .filter(entry -> entry.getValue().widgetInfo != null) - .collect(Collectors.toMap( - Map.Entry::getKey, - entry -> entry.getValue().widgetInfo - )); + Map[] widgetsMap = new Map[] { widgetProviderInfoMap}; + + // Separate the items that are on the current screen, and the other remaining items. + dataModel.itemsIdMap.stream() + .filter(currentScreenContentFilter(IntSet.wrap(mWorkspaceScreens.keySet()))) + .forEach(itemInfo -> { + switch (itemInfo.itemType) { + case Favorites.ITEM_TYPE_APPLICATION: + case Favorites.ITEM_TYPE_DEEP_SHORTCUT: + inflateAndAddIcon((WorkspaceItemInfo) itemInfo); + break; + case Favorites.ITEM_TYPE_FOLDER: + case Favorites.ITEM_TYPE_APP_PAIR: + inflateAndAddCollectionIcon((CollectionInfo) itemInfo); + break; + case Favorites.ITEM_TYPE_APPWIDGET: + case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: + if (widgetsMap[0] == null) { + widgetsMap[0] = dataModel.widgetsModel.getWidgetsByComponentKey() + .entrySet() + .stream() + .filter(entry -> entry.getValue().widgetInfo != null) + .collect(Collectors.toMap( + Entry::getKey, + entry -> entry.getValue().widgetInfo + )); + } + inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, widgetsMap[0]); + break; + default: + break; } - inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, widgetsMap); - break; - default: - break; - } - } - IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems, - mDp.numShownHotseatIcons); + + if (itemInfo.container == CONTAINER_HOTSEAT) { + missingHotseatRank.remove(itemInfo.screenId); + } + }); + + IntArray ranks = missingHotseatRank.getArray(); FixedContainerItems hotseatPredictions = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION); List predictions = hotseatPredictions == null diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java index c251114185..de74ae8db7 100644 --- a/src/com/android/launcher3/model/BaseLauncherBinder.java +++ b/src/com/android/launcher3/model/BaseLauncherBinder.java @@ -19,8 +19,10 @@ package com.android.launcher3.model; import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED; import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle; import static com.android.launcher3.Flags.enableWorkspaceInflation; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING; -import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems; +import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER; +import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; @@ -44,11 +46,11 @@ 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.LauncherAppWidgetInfo; import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; +import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.ItemInflater; import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.LooperIdleLock; @@ -102,14 +104,12 @@ public class BaseLauncherBinder { Trace.beginSection("BaseLauncherBinder#bindWorkspace"); try { // Save a copy of all the bg-thread collections - ArrayList workspaceItems = new ArrayList<>(); - ArrayList appWidgets = new ArrayList<>(); + IntSparseArrayMap itemsIdMap; final IntArray orderedScreenIds = new IntArray(); ArrayList extraItems = new ArrayList<>(); final int workspaceItemCount; synchronized (mBgDataModel) { - workspaceItems.addAll(mBgDataModel.workspaceItems); - appWidgets.addAll(mBgDataModel.appWidgets); + itemsIdMap = mBgDataModel.itemsIdMap.clone(); orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens()); mBgDataModel.extraItems.forEach(extraItems::add); if (incrementBindId) { @@ -122,7 +122,7 @@ public class BaseLauncherBinder { for (Callbacks cb : mCallbacksList) { new UnifiedWorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId, - workspaceItems, appWidgets, extraItems, orderedScreenIds) + itemsIdMap, extraItems, orderedScreenIds) .bind(isBindSync, workspaceItemCount); } } finally { @@ -258,8 +258,7 @@ public class BaseLauncherBinder { private final BgDataModel mBgDataModel; private final int mMyBindingId; - private final ArrayList mWorkspaceItems; - private final ArrayList mAppWidgets; + private final IntSparseArrayMap mItemIdMap; private final IntArray mOrderedScreenIds; private final ArrayList mExtraItems; @@ -268,8 +267,7 @@ public class BaseLauncherBinder { LauncherAppState app, BgDataModel bgDataModel, int myBindingId, - ArrayList workspaceItems, - ArrayList appWidgets, + IntSparseArrayMap itemIdMap, ArrayList extraItems, IntArray orderedScreenIds) { mCallbacks = callbacks; @@ -277,8 +275,7 @@ public class BaseLauncherBinder { mApp = app; mBgDataModel = bgDataModel; mMyBindingId = myBindingId; - mWorkspaceItems = workspaceItems; - mAppWidgets = appWidgets; + mItemIdMap = itemIdMap; mExtraItems = extraItems; mOrderedScreenIds = orderedScreenIds; } @@ -294,10 +291,15 @@ public class BaseLauncherBinder { ArrayList currentAppWidgets = new ArrayList<>(); ArrayList otherAppWidgets = new ArrayList<>(); - filterCurrentWorkspaceItems(currentScreenIds, mWorkspaceItems, currentWorkspaceItems, - otherWorkspaceItems); - filterCurrentWorkspaceItems(currentScreenIds, mAppWidgets, currentAppWidgets, - otherAppWidgets); + Predicate currentScreenCheck = currentScreenContentFilter(currentScreenIds); + mItemIdMap.forEach(item -> { + if (currentScreenCheck.test(item)) { + (WIDGET_FILTER.test(item) ? currentAppWidgets : currentWorkspaceItems) + .add(item); + } else if (item.container == CONTAINER_DESKTOP) { + (WIDGET_FILTER.test(item) ? otherAppWidgets : otherWorkspaceItems).add(item); + } + }); final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile(); sortWorkspaceItemsSpatially(idp, currentWorkspaceItems); sortWorkspaceItemsSpatially(idp, otherWorkspaceItems); diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index b9b1e98ab9..a04cbfb27b 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -20,6 +20,11 @@ import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ import static com.android.launcher3.BuildConfig.QSB_ON_FIRST_SCREEN; import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED; import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER; import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET; import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED; @@ -31,7 +36,6 @@ 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.Pair; import android.view.View; @@ -39,14 +43,11 @@ import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.launcher3.LauncherSettings; -import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.BuildConfig; import com.android.launcher3.Workspace; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.data.AppInfo; -import com.android.launcher3.model.data.AppPairInfo; import com.android.launcher3.model.data.CollectionInfo; -import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -92,22 +93,6 @@ public class BgDataModel { */ public final IntSparseArrayMap itemsIdMap = new IntSparseArrayMap<>(); - /** - * List of all the folders and shortcuts directly on the home screen (no widgets - * or shortcuts within folders). - */ - public final ArrayList workspaceItems = new ArrayList<>(); - - /** - * All LauncherAppWidgetInfo created by LauncherModel. - */ - public final ArrayList appWidgets = new ArrayList<>(); - - /** - * Map of id to CollectionInfos of all the folders or app pairs created by LauncherModel - */ - public final IntSparseArrayMap collections = new IntSparseArrayMap<>(); - /** * Extra container based items */ @@ -144,9 +129,6 @@ public class BgDataModel { * Clears all the data */ public synchronized void clear() { - workspaceItems.clear(); - appWidgets.clear(); - collections.clear(); itemsIdMap.clear(); deepShortcutMap.clear(); extraItems.clear(); @@ -158,7 +140,7 @@ public class BgDataModel { public synchronized IntArray collectWorkspaceScreens() { IntSet screenSet = new IntSet(); for (ItemInfo item: itemsIdMap) { - if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { + if (item.container == CONTAINER_DESKTOP) { screenSet.add(item.screenId); } } @@ -173,26 +155,14 @@ public class BgDataModel { public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { writer.println(prefix + "Data Model:"); - writer.println(prefix + " ---- workspace items "); - for (int i = 0; i < workspaceItems.size(); i++) { - writer.println(prefix + '\t' + workspaceItems.get(i).toString()); - } - writer.println(prefix + " ---- appwidget items "); - for (int i = 0; i < appWidgets.size(); i++) { - writer.println(prefix + '\t' + appWidgets.get(i).toString()); - } - writer.println(prefix + " ---- collection items "); - for (int i = 0; i < collections.size(); i++) { - writer.println(prefix + '\t' + collections.valueAt(i).toString()); + writer.println(prefix + " ---- items id map "); + for (int i = 0; i < itemsIdMap.size(); i++) { + writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString()); } writer.println(prefix + " ---- extra items "); for (int i = 0; i < extraItems.size(); i++) { writer.println(prefix + '\t' + extraItems.valueAt(i).toString()); } - writer.println(prefix + " ---- items id map "); - for (int i = 0; i < itemsIdMap.size(); i++) { - writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString()); - } if (args.length > 0 && TextUtils.equals(args[0], "--all")) { writer.println(prefix + "shortcut counts "); @@ -207,94 +177,38 @@ public class BgDataModel { removeItem(context, Arrays.asList(items)); } - 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: - case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR: - collections.remove(item.id); - if (FeatureFlags.IS_STUDIO_BUILD) { - for (ItemInfo info : itemsIdMap) { - if (info.container == item.id) { - // We are deleting a collection which still contains items that - // think they are contained by that collection. - String msg = "deleting a collection (" + item + ") which still " - + "contains items (" + info + ")"; - Log.e(TAG, msg); - } - } - } - workspaceItems.remove(item); - break; - case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: { - updatedDeepShortcuts.add(item.user); - // Fall through. - } - case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: - workspaceItems.remove(item); - break; - case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: - case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: - appWidgets.remove(item); - break; - } - itemsIdMap.remove(item.id); + public synchronized void removeItem(Context context, List items) { + if (BuildConfig.IS_STUDIO_BUILD) { + items.stream() + .filter(item -> item.itemType == ITEM_TYPE_FOLDER + || item.itemType == ITEM_TYPE_APP_PAIR) + .forEach(item -> itemsIdMap.stream() + .filter(info -> info.container == item.id) + // We are deleting a collection which still contains items that + // think they are contained by that collection. + .forEach(info -> Log.e(TAG, + "deleting a collection (" + item + ") which still contains" + + " items (" + info + ")"))); } - updatedDeepShortcuts.forEach(user -> updateShortcutPinnedState(context, user)); + + items.forEach(item -> itemsIdMap.remove(item.id)); + items.stream().map(info -> info.user).distinct().forEach( + user -> updateShortcutPinnedState(context, user)); } public synchronized void addItem(Context context, ItemInfo item, boolean newItem) { - addItem(context, item, newItem, null); - } - - public synchronized void addItem( - Context context, ItemInfo item, boolean newItem, @Nullable LoaderMemoryLogger logger) { - if (logger != null) { - logger.addLog( - Log.DEBUG, - TAG, - String.format("Adding item to ID map: %s", item.toString()), - /* stackTrace= */ null); - } itemsIdMap.put(item.id, item); - switch (item.itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: - collections.put(item.id, (FolderInfo) item); - workspaceItems.add(item); - break; - case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR: - collections.put(item.id, (AppPairInfo) item); - // Fall through here. App pairs are both containers (like folders) and containable - // items (can be placed in folders). So we need to add app pairs to the folders - // array (above) but also verify the existence of their container, like regular - // apps (below). - case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: - case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: - if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || - item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - workspaceItems.add(item); - } else { - if (newItem) { - if (!collections.containsKey(item.container)) { - // Adding an item to a nonexistent collection. - String msg = "attempted to add item: " + item + " to a nonexistent app" - + " collection"; - Log.e(TAG, msg); - } - } else { - findOrMakeFolder(item.container).add(item); - } - } - break; - case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: - case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: - appWidgets.add((LauncherAppWidgetInfo) item); - break; - } - if (newItem && item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { + if (newItem && item.itemType == ITEM_TYPE_DEEP_SHORTCUT) { updateShortcutPinnedState(context, item.user); } + if (BuildConfig.IS_DEBUG_DEVICE + && newItem + && item.container != CONTAINER_DESKTOP + && item.container != CONTAINER_HOTSEAT + && !(itemsIdMap.get(item.container) instanceof CollectionInfo)) { + // Adding an item to a nonexistent collection. + Log.e(TAG, "attempted to add item: " + item + " to a nonexistent app collection"); + } } /** @@ -334,7 +248,7 @@ public class BgDataModel { Map> modelMap = Stream.concat( // Model shortcuts itemStream.build() - .filter(wi -> wi.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) + .filter(wi -> wi.itemType == ITEM_TYPE_DEEP_SHORTCUT) .map(ShortcutKey::fromItemInfo), // Pending shortcuts ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts(user)) @@ -374,24 +288,6 @@ public class BgDataModel { }); } - /** - * Return an existing FolderInfo object if we have encountered this ID previously, - * or make a new one. - */ - public synchronized CollectionInfo findOrMakeFolder(int id) { - // See if a placeholder was created for us already - CollectionInfo collectionInfo = collections.get(id); - if (collectionInfo == null) { - // No placeholder -- create a new blank folder instance. At this point, we don't know - // if the desired container is supposed to be a folder or an app pair. In the case that - // it is an app pair, the blank folder will be replaced by a blank app pair when the app - // pair is getting processed, in WorkspaceItemProcessor.processFolderOrAppPair(). - collectionInfo = new FolderInfo(); - collections.put(id, collectionInfo); - } - return collectionInfo; - } - /** * Clear all the deep shortcut counts for the given package, and re-add the new shortcut counts. */ @@ -423,16 +319,6 @@ public class BgDataModel { } } - /** - * Returns a list containing all workspace items including widgets. - */ - public synchronized ArrayList getAllWorkspaceItems() { - ArrayList items = new ArrayList<>(workspaceItems.size() + appWidgets.size()); - items.addAll(workspaceItems); - items.addAll(appWidgets); - return items; - } - /** * Calls the provided {@code op} for all workspaceItems in the in-memory model (both persisted * items and dynamic/predicted items for the provided {@code userHandle}. diff --git a/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt b/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt index aa62c32e33..6ad52ea92b 100644 --- a/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt +++ b/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt @@ -30,7 +30,6 @@ import com.android.launcher3.model.data.CollectionInfo import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.model.data.LauncherAppWidgetInfo import com.android.launcher3.model.data.WorkspaceItemInfo -import com.android.launcher3.pm.InstallSessionHelper import com.android.launcher3.util.Executors import com.android.launcher3.util.PackageManagerHelper import com.android.launcher3.util.PackageUserKey @@ -80,21 +79,22 @@ object FirstScreenBroadcastHelper { packageManagerHelper: PackageManagerHelper, firstScreenItems: List, userKeyToSessionMap: Map, - allWidgets: List + allWidgets: List, ): List { // installers for installing items - val pendingItemInstallerMap: Map> = + val pendingItemInstallerMap: Map> = createPendingItemsMap(userKeyToSessionMap) + val installingPackages = pendingItemInstallerMap.values.flatten().toSet() // installers for installed items on first screen - val installedItemInstallerMap: Map> = + val installedItemInstallerMap: Map> = createInstalledItemsMap(firstScreenItems, installingPackages, packageManagerHelper) // installers for widgets on all screens - val allInstalledWidgetsMap: Map> = - createAllInstalledWidgetsMap(allWidgets, installingPackages, packageManagerHelper) + val allInstalledWidgetsMap: Map> = + createInstalledItemsMap(allWidgets, installingPackages, packageManagerHelper) val allInstallers: Set = pendingItemInstallerMap.keys + @@ -131,39 +131,39 @@ object FirstScreenBroadcastHelper { context, 0 /* requestCode */, Intent(), - PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE - ) + PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE, + ), ) .putStringArrayListExtra( PENDING_COLLECTION_ITEM_EXTRA, - ArrayList(model.pendingCollectionItems) + ArrayList(model.pendingCollectionItems), ) .putStringArrayListExtra( PENDING_WORKSPACE_ITEM_EXTRA, - ArrayList(model.pendingWorkspaceItems) + ArrayList(model.pendingWorkspaceItems), ) .putStringArrayListExtra( PENDING_HOTSEAT_ITEM_EXTRA, - ArrayList(model.pendingHotseatItems) + ArrayList(model.pendingHotseatItems), ) .putStringArrayListExtra( PENDING_WIDGET_ITEM_EXTRA, - ArrayList(model.pendingWidgetItems) + ArrayList(model.pendingWidgetItems), ) .putStringArrayListExtra( INSTALLED_WORKSPACE_ITEMS_EXTRA, - ArrayList(model.installedWorkspaceItems) + ArrayList(model.installedWorkspaceItems), ) .putStringArrayListExtra( INSTALLED_HOTSEAT_ITEMS_EXTRA, - ArrayList(model.installedHotseatItems) + ArrayList(model.installedHotseatItems), ) .putStringArrayListExtra( ALL_INSTALLED_WIDGETS_ITEM_EXTRA, ArrayList( model.firstScreenInstalledWidgets + model.secondaryScreenInstalledWidgets - ) + ), ) context.sendBroadcast(intent) } @@ -172,66 +172,46 @@ object FirstScreenBroadcastHelper { /** Maps Installer packages to Set of app packages from install sessions */ private fun createPendingItemsMap( userKeyToSessionMap: Map - ): Map> { + ): Map> { val myUser = Process.myUserHandle() - val result = mutableMapOf>() - userKeyToSessionMap.forEach { entry -> - if (!myUser.equals(InstallSessionHelper.getUserHandle(entry.value))) return@forEach - val installer = entry.value.installerPackageName - val appPackage = entry.value.appPackageName - if (installer.isNullOrEmpty() || appPackage.isNullOrEmpty()) return@forEach - result.getOrPut(installer) { mutableSetOf() }.add(appPackage) - } - return result - } - - /** - * Maps Installer packages to Set of ItemInfo from first screen. Filter out installing packages. - */ - private fun createInstalledItemsMap( - firstScreenItems: List, - installingPackages: Set, - packageManagerHelper: PackageManagerHelper - ): Map> { - val result = mutableMapOf>() - firstScreenItems.forEach { item -> - val appPackage = getPackageName(item) ?: return@forEach - if (installingPackages.contains(appPackage)) return@forEach - val installer = packageManagerHelper.getAppInstallerPackage(appPackage) - if (installer.isNullOrEmpty()) return@forEach - result.getOrPut(installer) { mutableSetOf() }.add(item) - } - return result - } - - /** - * Maps Installer packages to Set of AppWidget packages installed on all screens. Filter out - * installing packages. - */ - private fun createAllInstalledWidgetsMap( - allWidgets: List, - installingPackages: Set, - packageManagerHelper: PackageManagerHelper - ): Map> { - val result = mutableMapOf>() - allWidgets - .sortedBy { widget -> widget.screenId } - .forEach { widget -> - val appPackage = getPackageName(widget) ?: return@forEach - if (installingPackages.contains(appPackage)) return@forEach - val installer = packageManagerHelper.getAppInstallerPackage(appPackage) - if (installer.isNullOrEmpty()) return@forEach - result.getOrPut(installer) { mutableSetOf() }.add(widget) + return userKeyToSessionMap.values + .filter { + it.user == myUser && + !it.installerPackageName.isNullOrEmpty() && + !it.appPackageName.isNullOrEmpty() } - return result + .groupBy( + keySelector = { it.installerPackageName }, + valueTransform = { it.appPackageName }, + ) + .mapValues { it.value.filterNotNull().toSet() } as Map> } + /** Maps Installer packages to Set of ItemInfos. Filter out installing packages. */ + private fun createInstalledItemsMap( + allItems: Iterable, + installingPackages: Set, + packageManagerHelper: PackageManagerHelper, + ): Map> = + allItems + .sortedBy { it.screenId } + .groupByTo(mutableMapOf()) { + getPackageName(it)?.let { pkg -> + if (installingPackages.contains(pkg)) { + null + } else { + packageManagerHelper.getAppInstallerPackage(pkg) + } + } + } + .apply { remove(null) } as Map> + /** * Add first screen Pending Items from Map to [FirstScreenBroadcastModel] for given installer */ private fun FirstScreenBroadcastModel.addPendingItems( installingItems: Set?, - firstScreenItems: List + firstScreenItems: List, ) { if (installingItems == null) return for (info in firstScreenItems) { @@ -251,7 +231,7 @@ object FirstScreenBroadcastHelper { */ private fun FirstScreenBroadcastModel.addInstalledItems( installer: String, - installedItemInstallerMap: Map>, + installedItemInstallerMap: Map>, ) { installedItemInstallerMap[installer]?.forEach { info -> val packageName: String = getPackageName(info) ?: return@forEach @@ -265,7 +245,7 @@ object FirstScreenBroadcastHelper { /** Add Widgets on every screen from Map to [FirstScreenBroadcastModel] for given installer */ private fun FirstScreenBroadcastModel.addAllScreenWidgets( installer: String, - allInstalledWidgetsMap: Map> + allInstalledWidgetsMap: Map>, ) { allInstalledWidgetsMap[installer]?.forEach { widget -> val packageName: String = getPackageName(widget) ?: return@forEach @@ -279,7 +259,7 @@ object FirstScreenBroadcastHelper { private fun FirstScreenBroadcastModel.addCollectionItems( info: ItemInfo, - installingPackages: Set + installingPackages: Set, ) { if (info !is CollectionInfo) return pendingCollectionItems.addAll( @@ -336,7 +316,7 @@ object FirstScreenBroadcastHelper { Log.d( TAG, "Sending First Screen Broadcast for installer=$installerPackage" + - ", total packages=${getTotalItemCount()}" + ", total packages=${getTotalItemCount()}", ) pendingCollectionItems.forEach { Log.d(TAG, "$installerPackage:Pending Collection item:$it") @@ -361,15 +341,7 @@ object FirstScreenBroadcastHelper { } } - private fun getPackageName(info: ItemInfo): String? { - var packageName: String? = null - if (info is LauncherAppWidgetInfo) { - info.providerName?.let { packageName = info.providerName.packageName } - } else if (info.targetComponent != null) { - packageName = info.targetComponent?.packageName - } - return packageName - } + private fun getPackageName(info: ItemInfo): String? = info.targetComponent?.packageName /** * Clone the provided list on UI thread. This is used for [FolderInfo.getContents] which is diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java index 536d4c9cd3..16238819e1 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -16,6 +16,11 @@ package com.android.launcher3.model; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME; import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET; import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG; @@ -48,6 +53,8 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.IconCache; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.model.data.CollectionInfo; +import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.IconRequestInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -84,6 +91,11 @@ public class LoaderCursor extends CursorWrapper { private final IntArray mRestoredRows = new IntArray(); private final IntSparseArrayMap mOccupied = new IntSparseArrayMap<>(); + // CollectionInfo objects, which have not yet been loaded from the DB, but are expected to + // found eventually as the loading progresses + private final IntSparseArrayMap mPendingCollectionInfo = + new IntSparseArrayMap<>(); + private final int mIconIndex; public final int mTitleIndex; @@ -479,8 +491,26 @@ public class LoaderCursor extends CursorWrapper { info.cellY = getInt(mCellYIndex); } - public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) { - checkAndAddItem(info, dataModel, null); + /** + * Return an existing FolderInfo object if we have encountered this ID previously, + * or make a new one. + */ + public CollectionInfo findOrMakeFolder(int id, BgDataModel dataModel) { + // See if a placeholder was created for us already + ItemInfo info = dataModel.itemsIdMap.get(id); + if (info instanceof CollectionInfo c) return c; + + CollectionInfo pending = mPendingCollectionInfo.get(id); + if (pending != null) return pending; + + // No placeholder -- create a new blank folder instance. At this point, we don't know + // if the desired container is supposed to be a folder or an app pair. In the case that + // it is an app pair, the blank folder will be replaced by a blank app pair when the app + // pair is getting processed, in WorkspaceItemProcessor.processFolderOrAppPair(). + pending = new FolderInfo(); + pending.id = id; + mPendingCollectionInfo.put(id, pending); + return pending; } /** @@ -495,7 +525,21 @@ public class LoaderCursor extends CursorWrapper { ShortcutKey.fromItemInfo(info); } if (checkItemPlacement(info, dataModel.isFirstPagePinnedItemEnabled)) { - dataModel.addItem(mContext, info, false, logger); + if (logger != null) { + logger.addLog( + Log.DEBUG, + TAG, + String.format("Adding item to ID map: %s", info), + /* stackTrace= */ null); + } + dataModel.addItem(mContext, info, false); + if ((info.itemType == ITEM_TYPE_APP_PAIR + || info.itemType == ITEM_TYPE_DEEP_SHORTCUT + || info.itemType == ITEM_TYPE_APPLICATION) + && info.container != CONTAINER_DESKTOP + && info.container != CONTAINER_HOTSEAT) { + findOrMakeFolder(info.container, dataModel).add(info); + } if (mRestoreEventLogger != null) { mRestoreEventLogger.logSingleFavoritesItemRestored(itemType); } diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 44b7e8ba50..fee9696b12 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -31,7 +31,8 @@ import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PRO import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED; -import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems; +import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER; +import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter; import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission; @@ -82,7 +83,6 @@ import com.android.launcher3.icons.cache.LauncherActivityCachingLogic; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.AppPairInfo; -import com.android.launcher3.model.data.CollectionInfo; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.IconRequestInfo; import com.android.launcher3.model.data.ItemInfo; @@ -210,10 +210,10 @@ public class LoaderTask implements Runnable { final int firstScreen = allScreens.get(0); IntSet firstScreens = IntSet.wrap(firstScreen); - ArrayList allItems = mBgDataModel.getAllWorkspaceItems(); - ArrayList firstScreenItems = new ArrayList<>(); - filterCurrentWorkspaceItems(firstScreens, allItems, firstScreenItems, - new ArrayList<>() /* otherScreenItems are ignored */); + List firstScreenItems = + mBgDataModel.itemsIdMap.stream() + .filter(currentScreenContentFilter(firstScreens)) + .toList(); final int disableArchivingLauncherBroadcast = Settings.Secure.getInt( mApp.getContext().getContentResolver(), "disable_launcher_broadcast_installed_apps", @@ -227,7 +227,7 @@ public class LoaderTask implements Runnable { mPmHelper, firstScreenItems, mInstallingPkgsCached, - mBgDataModel.appWidgets + mBgDataModel.itemsIdMap.stream().filter(WIDGET_FILTER).toList() ); logASplit("Sending first screen broadcast with additional archiving Extras"); FirstScreenBroadcastHelper.sendBroadcastsForModels(mApp.getContext(), broadcastModels); @@ -523,14 +523,13 @@ public class LoaderTask implements Runnable { * requests high-res icons for the items that are part of an app pair. */ private void processAppPairItems() { - for (CollectionInfo collection : mBgDataModel.collections) { - if (!(collection instanceof AppPairInfo appPair)) { - continue; - } - - appPair.getContents().sort(Folder.ITEM_POS_COMPARATOR); - appPair.fetchHiResIconsIfNeeded(mIconCache); - } + mBgDataModel.itemsIdMap.stream() + .filter(item -> item instanceof AppPairInfo) + .forEach(item -> { + AppPairInfo appPair = (AppPairInfo) item; + appPair.getContents().sort(Folder.ITEM_POS_COMPARATOR); + appPair.fetchHiResIconsIfNeeded(mIconCache); + }); } /** @@ -586,8 +585,8 @@ public class LoaderTask implements Runnable { // Sort the folder items, update ranks, and make sure all preview items are high res. List verifiers = mApp.getInvariantDeviceProfile().supportedProfiles .stream().map(FolderGridOrganizer::createFolderGridOrganizer).toList(); - for (CollectionInfo collection : mBgDataModel.collections) { - if (!(collection instanceof FolderInfo folder)) { + for (ItemInfo itemInfo : mBgDataModel.itemsIdMap) { + if (!(itemInfo instanceof FolderInfo folder)) { continue; } @@ -657,8 +656,6 @@ public class LoaderTask implements Runnable { IntArray deletedFolderIds = mApp.getModel().getModelDbController().deleteEmptyFolders(); synchronized (mBgDataModel) { for (int folderId : deletedFolderIds) { - mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(folderId)); - mBgDataModel.collections.remove(folderId); mBgDataModel.itemsIdMap.remove(folderId); } } @@ -676,8 +673,6 @@ public class LoaderTask implements Runnable { synchronized (mBgDataModel) { for (int id : deleted) { - mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(id)); - mBgDataModel.collections.remove(id); mBgDataModel.itemsIdMap.remove(id); } } @@ -819,18 +814,19 @@ public class LoaderTask implements Runnable { private void loadFolderNames() { FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext(), - mBgAllAppsList.data, mBgDataModel.collections); + mBgAllAppsList.data, FolderNameProvider.getCollectionForSuggestions(mBgDataModel)); synchronized (mBgDataModel) { - for (int i = 0; i < mBgDataModel.collections.size(); i++) { - FolderNameInfos suggestionInfos = new FolderNameInfos(); - CollectionInfo info = mBgDataModel.collections.valueAt(i); - if (info instanceof FolderInfo fi && fi.suggestedFolderNames == null) { - provider.getSuggestedFolderName(mApp.getContext(), fi.getAppContents(), - suggestionInfos); - fi.suggestedFolderNames = suggestionInfos; - } - } + mBgDataModel.itemsIdMap.stream() + .filter(item -> + item instanceof FolderInfo fi && fi.suggestedFolderNames == null) + .forEach(info -> { + FolderInfo fi = (FolderInfo) info; + FolderNameInfos suggestionInfos = new FolderNameInfos(); + provider.getSuggestedFolderName(mApp.getContext(), fi.getAppContents(), + suggestionInfos); + fi.suggestedFolderNames = suggestionInfos; + }); } } diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java index 9e72e2823e..da79982e00 100644 --- a/src/com/android/launcher3/model/ModelUtils.java +++ b/src/com/android/launcher3/model/ModelUtils.java @@ -15,15 +15,15 @@ */ package com.android.launcher3.model; -import com.android.launcher3.LauncherSettings; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; + import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.stream.IntStream; +import java.util.function.Predicate; /** * Utils class for {@link com.android.launcher3.LauncherModel}. @@ -31,54 +31,17 @@ import java.util.stream.IntStream; public class ModelUtils { /** - * Filters the set of items who are directly or indirectly (via another container) on the - * specified screen. + * Returns a filter for items on hotseat or current screens */ - public static void filterCurrentWorkspaceItems( - final IntSet currentScreenIds, - List allWorkspaceItems, - List currentScreenItems, - List otherScreenItems) { - // Purge any null ItemInfos - allWorkspaceItems.removeIf(Objects::isNull); - // Order the set of items by their containers first, this allows use to walk through the - // list sequentially, build up a list of containers that are in the specified screen, - // as well as all items in those containers. - IntSet itemsOnScreen = new IntSet(); - Collections.sort(allWorkspaceItems, - (lhs, rhs) -> Integer.compare(lhs.container, rhs.container)); - for (T info : allWorkspaceItems) { - if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { - if (currentScreenIds.contains(info.screenId)) { - currentScreenItems.add(info); - itemsOnScreen.add(info.id); - } else { - otherScreenItems.add(info); - } - } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - currentScreenItems.add(info); - itemsOnScreen.add(info.id); - } else { - if (itemsOnScreen.contains(info.container)) { - currentScreenItems.add(info); - itemsOnScreen.add(info.id); - } else { - otherScreenItems.add(info); - } - } - } + public static Predicate currentScreenContentFilter(IntSet currentScreenIds) { + return item -> item.container == CONTAINER_HOTSEAT + || (item.container == CONTAINER_DESKTOP + && currentScreenIds.contains(item.screenId)); } /** - * Iterates though current workspace items and returns available hotseat ranks for prediction. + * Returns a filter for widget items */ - public static IntArray getMissingHotseatRanks(List items, int len) { - IntSet seen = new IntSet(); - items.stream().filter( - info -> info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) - .forEach(i -> seen.add(i.screenId)); - IntArray result = new IntArray(len); - IntStream.range(0, len).filter(i -> !seen.contains(i)).forEach(result::add); - return result; - } + public static final Predicate WIDGET_FILTER = item -> + item.itemType == ITEM_TYPE_APPWIDGET || item.itemType == ITEM_TYPE_CUSTOM_APPWIDGET; } diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java index b477cb1e06..0332775224 100644 --- a/src/com/android/launcher3/model/ModelWriter.java +++ b/src/com/android/launcher3/model/ModelWriter.java @@ -459,37 +459,14 @@ public class ModelWriter { if (item.container != Favorites.CONTAINER_DESKTOP && item.container != Favorites.CONTAINER_HOTSEAT) { // Item is in a collection, make sure this collection exists - if (!mBgDataModel.collections.containsKey(item.container)) { + if (!(mBgDataModel.itemsIdMap.get(item.container) instanceof CollectionInfo)) { // An items container is being set to a that of an item which is not in - // the list of Folders. + // the list of collections. String msg = "item: " + item + " container being set to: " + item.container + ", not in the list of collections"; Log.e(TAG, msg); } } - - // Items are added/removed from the corresponding FolderInfo elsewhere, such - // as in Workspace.onDrop. Here, we just add/remove them from the list of items - // that are on the desktop, as appropriate - ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId); - if (modelItem != null && - (modelItem.container == Favorites.CONTAINER_DESKTOP || - modelItem.container == Favorites.CONTAINER_HOTSEAT)) { - switch (modelItem.itemType) { - case Favorites.ITEM_TYPE_APPLICATION: - case Favorites.ITEM_TYPE_DEEP_SHORTCUT: - case Favorites.ITEM_TYPE_FOLDER: - case Favorites.ITEM_TYPE_APP_PAIR: - if (!mBgDataModel.workspaceItems.contains(modelItem)) { - mBgDataModel.workspaceItems.add(modelItem); - } - break; - default: - break; - } - } else { - mBgDataModel.workspaceItems.remove(modelItem); - } mVerifier.verifyModel(); } } diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java index d2382132b3..4103937445 100644 --- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java +++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.model; +import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER; + import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -85,12 +87,16 @@ public class PackageInstallStateChangedTask implements ModelUpdateTask { } }); - for (LauncherAppWidgetInfo widget : dataModel.appWidgets) { - if (widget.providerName.getPackageName().equals(mInstallInfo.packageName)) { - widget.installProgress = mInstallInfo.progress; - updates.add(widget); - } - } + dataModel.itemsIdMap.stream() + .filter(WIDGET_FILTER) + .filter(item -> mInstallInfo.user.equals(item.user)) + .map(item -> (LauncherAppWidgetInfo) item) + .filter(widget -> widget.providerName.getPackageName() + .equals(mInstallInfo.packageName)) + .forEach(widget -> { + widget.installProgress = mInstallInfo.progress; + updates.add(widget); + }); if (!updates.isEmpty()) { taskController.scheduleCallbackTask( diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index d619965aa8..1153f487a0 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -18,7 +18,9 @@ package com.android.launcher3.model; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED; +import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER; import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED; +import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY; import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON; import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORED_ICON; @@ -347,24 +349,25 @@ public class PackageUpdatedTask implements ModelUpdateTask { } }); - for (LauncherAppWidgetInfo widgetInfo : dataModel.appWidgets) { - if (mUser.equals(widgetInfo.user) - && widgetInfo.hasRestoreFlag( - LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) - && packageSet.contains(widgetInfo.providerName.getPackageName())) { - widgetInfo.restoreStatus &= - ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY - & ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; + dataModel.itemsIdMap.stream() + .filter(WIDGET_FILTER) + .filter(item -> mUser.equals(item.user)) + .map(item -> (LauncherAppWidgetInfo) item) + .filter(widget -> widget.hasRestoreFlag(FLAG_PROVIDER_NOT_READY) + && packageSet.contains(widget.providerName.getPackageName())) + .forEach(widgetInfo -> { + widgetInfo.restoreStatus &= + ~FLAG_PROVIDER_NOT_READY + & ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; - // adding this flag ensures that launcher shows 'click to setup' - // if the widget has a config activity. In case there is no config - // activity, it will be marked as 'restored' during bind. - widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY; + // adding this flag ensures that launcher shows 'click to setup' + // if the widget has a config activity. In case there is no config + // activity, it will be marked as 'restored' during bind. + widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY; - widgets.add(widgetInfo); - taskController.getModelWriter().updateItemInDatabase(widgetInfo); - } - } + widgets.add(widgetInfo); + taskController.getModelWriter().updateItemInDatabase(widgetInfo); + }); } taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItems); diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt index dad78dd4d9..de1df2e2de 100644 --- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt +++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt @@ -404,18 +404,14 @@ class WorkspaceItemProcessor( * stored in the BgDataModel. */ private fun processFolderOrAppPair() { - var collection = bgDataModel.findOrMakeFolder(c.id) + var collection = c.findOrMakeFolder(c.id, bgDataModel) // If we generated a placeholder Folder before this point, it may need to be replaced with // an app pair. if (c.itemType == Favorites.ITEM_TYPE_APP_PAIR && collection is FolderInfo) { - val folderInfo: FolderInfo = collection val newAppPair = AppPairInfo() // Move the placeholder's contents over to the new app pair. - folderInfo.getContents().forEach(newAppPair::add) + collection.getContents().forEach(newAppPair::add) collection = newAppPair - // Remove the placeholder and add the app pair into the data model. - bgDataModel.collections.remove(c.id) - bgDataModel.collections.put(c.id, collection) } c.applyCommonProperties(collection) @@ -569,7 +565,7 @@ class WorkspaceItemProcessor( logWidgetInfo(app.invariantDeviceProfile, lapi) } } - c.checkAndAddItem(appWidgetInfo, bgDataModel) + c.checkAndAddItem(appWidgetInfo, bgDataModel, memoryLogger) } companion object { diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java index 772ea7fe88..7fb01522f8 100644 --- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java +++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java @@ -16,6 +16,8 @@ package com.android.launcher3.model.data; +import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED; + import android.content.Context; import android.content.Intent; import android.os.Process; @@ -23,6 +25,7 @@ import android.os.Process; import androidx.annotation.Nullable; import com.android.launcher3.Flags; +import com.android.launcher3.graphics.ThemeManager; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.BitmapInfo.DrawableCreationFlags; import com.android.launcher3.icons.FastBitmapDrawable; @@ -320,6 +323,9 @@ public abstract class ItemInfoWithIcon extends ItemInfo { * Returns a FastBitmapDrawable with the icon and context theme applied */ public FastBitmapDrawable newIcon(Context context, @DrawableCreationFlags int creationFlags) { + if (!ThemeManager.INSTANCE.get(context).isMonoThemeEnabled()) { + creationFlags &= ~FLAG_THEMED; + } FastBitmapDrawable drawable = bitmap.newIcon(context, creationFlags); drawable.setIsDisabled(isDisabled()); return drawable; diff --git a/src/com/android/launcher3/util/IntSparseArrayMap.java b/src/com/android/launcher3/util/IntSparseArrayMap.java index 9d5391be0e..70f74e36f5 100644 --- a/src/com/android/launcher3/util/IntSparseArrayMap.java +++ b/src/com/android/launcher3/util/IntSparseArrayMap.java @@ -19,6 +19,8 @@ package com.android.launcher3.util; import android.util.SparseArray; import java.util.Iterator; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; /** * Extension of {@link SparseArray} with some utility methods. @@ -43,6 +45,10 @@ public class IntSparseArrayMap extends SparseArray implements Iterable return new ValueIterator(); } + public Stream stream() { + return StreamSupport.stream(spliterator(), false); + } + @Thunk class ValueIterator implements Iterator { private int mNextIndex = 0; diff --git a/src/com/android/launcher3/util/LayoutImportExportHelper.kt b/src/com/android/launcher3/util/LayoutImportExportHelper.kt index 0df9dae50b..8559f3b4b1 100644 --- a/src/com/android/launcher3/util/LayoutImportExportHelper.kt +++ b/src/com/android/launcher3/util/LayoutImportExportHelper.kt @@ -56,7 +56,7 @@ object LayoutImportExportHelper { model.enqueueModelUpdateTask { _, dataModel, _ -> val builder = LauncherLayoutBuilder() - dataModel.workspaceItems.forEach { info -> + dataModel.itemsIdMap.forEach { info -> val loc = when (info.container) { CONTAINER_DESKTOP -> @@ -67,9 +67,6 @@ object LayoutImportExportHelper { } loc.addItem(context, info) } - dataModel.appWidgets.forEach { info -> - builder.atWorkspace(info.cellX, info.cellY, info.screenId).addItem(context, info) - } val layoutXml = builder.build() callback(layoutXml) diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt index 553d08c3bb..15accbda2a 100644 --- a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt @@ -17,11 +17,14 @@ package com.android.launcher3.folder import android.R -import android.graphics.Bitmap import android.os.Process import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.launcher3.LauncherAppState +import com.android.launcher3.LauncherPrefs +import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER +import com.android.launcher3.dagger.LauncherAppComponent +import com.android.launcher3.dagger.LauncherAppSingleton import com.android.launcher3.graphics.PreloadIconDrawable import com.android.launcher3.graphics.ThemeManager import com.android.launcher3.icons.BitmapInfo @@ -30,13 +33,14 @@ import com.android.launcher3.icons.IconCache import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver import com.android.launcher3.icons.PlaceHolderIconDrawable import com.android.launcher3.icons.UserBadgeDrawable -import com.android.launcher3.icons.mono.MonoThemedBitmap import com.android.launcher3.model.data.FolderInfo import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE import com.android.launcher3.model.data.WorkspaceItemInfo import com.android.launcher3.util.ActivityContextWrapper +import com.android.launcher3.util.AllModulesForTest import com.android.launcher3.util.Executors +import com.android.launcher3.util.FakePrefsModule import com.android.launcher3.util.FlagOp import com.android.launcher3.util.LauncherLayoutBuilder import com.android.launcher3.util.LauncherModelHelper @@ -44,10 +48,19 @@ import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext import com.android.launcher3.util.TestUtil import com.android.launcher3.util.UserIconInfo import com.google.common.truth.Truth.assertThat +import dagger.Component +import kotlin.annotation.AnnotationRetention.RUNTIME +import kotlin.annotation.AnnotationTarget.FUNCTION +import kotlin.annotation.AnnotationTarget.PROPERTY_GETTER +import kotlin.annotation.AnnotationTarget.PROPERTY_SETTER import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.Description import org.junit.runner.RunWith +import org.junit.runners.model.Statement import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doReturn @@ -61,6 +74,8 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class PreviewItemManagerTest { + @get:Rule val theseStateRule = ThemeStateRule() + private lateinit var previewItemManager: PreviewItemManager private lateinit var context: SandboxModelContext private lateinit var folderItems: ArrayList @@ -68,15 +83,14 @@ class PreviewItemManagerTest { private lateinit var folderIcon: FolderIcon private lateinit var iconCache: IconCache - private var defaultThemedIcons = false - - private val themeManager: ThemeManager - get() = ThemeManager.INSTANCE.get(context) - @Before fun setup() { modelHelper = LauncherModelHelper() context = modelHelper.sandboxContext + context.initDaggerComponent(DaggerPreviewItemManagerTestComponent.builder()) + theseStateRule.themeState?.let { + LauncherPrefs.get(context).putSync(ThemeManager.THEMED_ICONS.to(it)) + } folderIcon = FolderIcon(ActivityContextWrapper(context)) val app = spy(LauncherAppState.getInstance(context)) @@ -99,27 +113,16 @@ class PreviewItemManagerTest { ) .loadModelSync() + folderIcon.mInfo = + modelHelper.bgDataModel.itemsIdMap.find { it.itemType == ITEM_TYPE_FOLDER } + as FolderInfo // Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps - folderItems = modelHelper.bgDataModel.collections.valueAt(0).getAppContents() - folderIcon.mInfo = modelHelper.bgDataModel.collections.valueAt(0) as FolderInfo - folderIcon.mInfo.getContents().addAll(folderItems) - - // Set first icon to be themed. - folderItems[0].bitmap.themedBitmap = - MonoThemedBitmap( - folderItems[0].bitmap.icon, - Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888), - ) + folderItems = folderIcon.mInfo.getAppContents() // Set second icon to be non-themed. folderItems[1].bitmap.themedBitmap = null // Set third icon to be themed with badge. - folderItems[2].bitmap.themedBitmap = - MonoThemedBitmap( - folderItems[2].bitmap.icon, - Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888), - ) folderItems[2].bitmap = folderItems[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK)) @@ -127,20 +130,17 @@ class PreviewItemManagerTest { folderItems[3].bitmap = folderItems[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK)) folderItems[3].bitmap.themedBitmap = null - - defaultThemedIcons = themeManager.isMonoThemeEnabled } @After @Throws(Exception::class) fun tearDown() { - themeManager.isMonoThemeEnabled = defaultThemedIcons modelHelper.destroy() } @Test + @MonoThemeEnabled(true) fun checkThemedIconWithThemingOn_iconShouldBeThemed() { - themeManager.isMonoThemeEnabled = true val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) previewItemManager.setDrawable(drawingParams, folderItems[0]) @@ -149,8 +149,8 @@ class PreviewItemManagerTest { } @Test + @MonoThemeEnabled(false) fun checkThemedIconWithThemingOff_iconShouldNotBeThemed() { - themeManager.isMonoThemeEnabled = false val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) previewItemManager.setDrawable(drawingParams, folderItems[0]) @@ -159,8 +159,8 @@ class PreviewItemManagerTest { } @Test + @MonoThemeEnabled(true) fun checkUnthemedIconWithThemingOn_iconShouldNotBeThemed() { - themeManager.isMonoThemeEnabled = true val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) previewItemManager.setDrawable(drawingParams, folderItems[1]) @@ -169,8 +169,8 @@ class PreviewItemManagerTest { } @Test + @MonoThemeEnabled(false) fun checkUnthemedIconWithThemingOff_iconShouldNotBeThemed() { - themeManager.isMonoThemeEnabled = false val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) previewItemManager.setDrawable(drawingParams, folderItems[1]) @@ -179,8 +179,8 @@ class PreviewItemManagerTest { } @Test + @MonoThemeEnabled(true) fun checkThemedIconWithBadgeWithThemingOn_iconAndBadgeShouldBeThemed() { - themeManager.isMonoThemeEnabled = true val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) previewItemManager.setDrawable(drawingParams, folderItems[2]) @@ -192,8 +192,8 @@ class PreviewItemManagerTest { } @Test + @MonoThemeEnabled(true) fun checkUnthemedIconWithBadgeWithThemingOn_badgeShouldBeThemed() { - themeManager.isMonoThemeEnabled = true val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) previewItemManager.setDrawable(drawingParams, folderItems[3]) @@ -205,8 +205,8 @@ class PreviewItemManagerTest { } @Test + @MonoThemeEnabled(false) fun checkUnthemedIconWithBadgeWithThemingOff_iconAndBadgeShouldNotBeThemed() { - themeManager.isMonoThemeEnabled = false val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) previewItemManager.setDrawable(drawingParams, folderItems[3]) @@ -278,3 +278,28 @@ class PreviewItemManagerTest { private fun profileFlagOp(type: Int) = UserIconInfo(Process.myUserHandle(), type).applyBitmapInfoFlags(FlagOp.NO_OP) } + +class ThemeStateRule : TestRule { + + var themeState: Boolean? = null + + override fun apply(base: Statement, description: Description): Statement { + themeState = description.getAnnotation(MonoThemeEnabled::class.java)?.value + return base + } +} + +// Annotation for tests that need to be run with quickstep enabled and disabled. +@Retention(RUNTIME) +@Target(FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER) +annotation class MonoThemeEnabled(val value: Boolean = false) + +@LauncherAppSingleton +@Component(modules = [AllModulesForTest::class, FakePrefsModule::class]) +interface PreviewItemManagerTestComponent : LauncherAppComponent { + + @Component.Builder + interface Builder : LauncherAppComponent.Builder { + override fun build(): PreviewItemManagerTestComponent + } +} diff --git a/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java index 1e2431fea8..0ae4d00e94 100644 --- a/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java +++ b/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java @@ -16,6 +16,8 @@ package com.android.launcher3.model; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY; import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE; @@ -42,6 +44,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.List; + /** * Tests for layout parser for remote layout */ @@ -63,14 +67,23 @@ public class DefaultLayoutProviderTest { mModelHelper.destroy(); } + private List getWorkspaceItems() { + return mModelHelper + .getBgDataModel() + .itemsIdMap + .stream() + .filter(i -> i.container == CONTAINER_DESKTOP || i.container == CONTAINER_HOTSEAT) + .toList(); + } + @Test public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception { writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0) .putApp(TEST_PACKAGE, TEST_ACTIVITY)); // Verify one item in hotseat - assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size()); - ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0); + assertEquals(1, getWorkspaceItems().size()); + ItemInfo info = getWorkspaceItems().get(0); assertEquals(LauncherSettings.Favorites.CONTAINER_HOTSEAT, info.container); assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPLICATION, info.itemType); } @@ -84,8 +97,8 @@ public class DefaultLayoutProviderTest { .build()); // Verify folder - assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size()); - ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0); + assertEquals(1, getWorkspaceItems().size()); + ItemInfo info = getWorkspaceItems().get(0); assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType); assertEquals(3, ((FolderInfo) info).getContents().size()); } @@ -99,8 +112,8 @@ public class DefaultLayoutProviderTest { .build()); // Verify folder - assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size()); - ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0); + assertEquals(1, getWorkspaceItems().size()); + ItemInfo info = getWorkspaceItems().get(0); assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType); assertEquals(3, ((FolderInfo) info).getContents().size()); assertEquals("CustomFolder", info.title.toString()); @@ -124,8 +137,8 @@ public class DefaultLayoutProviderTest { .putWidget(pendingAppPkg, "PlaceholderWidget", 2, 2)); // Verify widget - assertEquals(1, mModelHelper.getBgDataModel().appWidgets.size()); - ItemInfo info = mModelHelper.getBgDataModel().appWidgets.get(0); + assertEquals(1, getWorkspaceItems().size()); + ItemInfo info = getWorkspaceItems().get(0); assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET, info.itemType); assertEquals(2, info.spanX); assertEquals(2, info.spanY); @@ -138,8 +151,8 @@ public class DefaultLayoutProviderTest { .putShortcut(TEST_PACKAGE, "shortcut2")); // Verify one item in hotseat - assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size()); - ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0); + assertEquals(1, getWorkspaceItems().size()); + ItemInfo info = getWorkspaceItems().get(0); assertEquals(LauncherSettings.Favorites.CONTAINER_HOTSEAT, info.container); assertEquals(LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT, info.itemType); } @@ -154,8 +167,8 @@ public class DefaultLayoutProviderTest { .build()); // Verify folder - assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size()); - FolderInfo info = (FolderInfo) mModelHelper.getBgDataModel().workspaceItems.get(0); + assertEquals(1, getWorkspaceItems().size()); + FolderInfo info = (FolderInfo) getWorkspaceItems().get(0); assertEquals(3, info.getContents().size()); // Verify last icon diff --git a/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt index e8f778fde7..f357487ae2 100644 --- a/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt @@ -18,8 +18,10 @@ package com.android.launcher3.model import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.launcher3.LauncherAppState +import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER import com.android.launcher3.icons.BitmapInfo import com.android.launcher3.icons.waitForUpdateHandlerToFinish +import com.android.launcher3.model.data.FolderInfo import com.android.launcher3.model.data.WorkspaceItemInfo import com.android.launcher3.util.Executors import com.android.launcher3.util.LauncherLayoutBuilder @@ -149,11 +151,13 @@ class FolderIconLoadTest { // Reload again with correct icon state app.model.forceReload() modelHelper.loadModelSync() - val collections = modelHelper.getBgDataModel().collections - - assertThat(collections.size()).isEqualTo(1) - assertThat(collections.valueAt(0).getAppContents().size).isEqualTo(itemCount) - return collections.valueAt(0).getAppContents() + val collections = + modelHelper.bgDataModel.itemsIdMap + .filter { it.itemType == ITEM_TYPE_FOLDER } + .map { it as FolderInfo } + assertThat(collections.size).isEqualTo(1) + assertThat(collections[0].getAppContents().size).isEqualTo(itemCount) + return collections[0].getAppContents() } private fun verifyHighRes(items: ArrayList, vararg indices: Int) { diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt index d699eeea02..da87dfc1ea 100644 --- a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt @@ -494,8 +494,7 @@ class WorkspaceItemProcessorTest { @Test fun `When processing Folder then create FolderInfo and mark restored`() { val actualFolderInfo = FolderInfo() - mockBgDataModel = - mock().apply { whenever(findOrMakeFolder(1)).thenReturn(actualFolderInfo) } + mockBgDataModel = mock() mockCursor = mock().apply { user = UserHandle(0) @@ -509,6 +508,7 @@ class WorkspaceItemProcessorTest { whenever(getColumnIndex(Favorites.TITLE)).thenReturn(4) whenever(getString(4)).thenReturn("title") whenever(options).thenReturn(5) + whenever(findOrMakeFolder(eq(1), any())).thenReturn(actualFolderInfo) } val expectedFolderInfo = FolderInfo().apply { @@ -600,7 +600,8 @@ class WorkspaceItemProcessorTest { // Then val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java) - verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel)) + verify(mockCursor) + .checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel), anyOrNull()) val actualWidgetInfo = widgetInfoCaptor.value with(actualWidgetInfo) { assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName) @@ -655,7 +656,7 @@ class WorkspaceItemProcessorTest { itemProcessorUnderTest.processItem() // Then - verify(mockCursor).checkAndAddItem(any(), any()) + verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull()) } @Test diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt index d2229c4f97..f04688dd97 100644 --- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt +++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt @@ -19,6 +19,10 @@ import com.android.launcher3.LauncherModel.LoaderTransaction import com.android.launcher3.LauncherPrefs import com.android.launcher3.LauncherPrefs.Companion.IS_FIRST_LOAD_AFTER_RESTORE import com.android.launcher3.LauncherPrefs.Companion.RESTORE_DEVICE +import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP +import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT +import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR +import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER import com.android.launcher3.icons.IconCache import com.android.launcher3.icons.cache.CachingLogic import com.android.launcher3.icons.cache.IconCacheUpdateHandler @@ -155,9 +159,24 @@ class LoaderTaskTest { widgetsFilterDataProvider, ) .runSyncOnBackgroundThread() - Truth.assertThat(workspaceItems.size).isAtLeast(25) - Truth.assertThat(appWidgets.size).isAtLeast(7) - Truth.assertThat(collections.size()).isAtLeast(8) + Truth.assertThat( + itemsIdMap + .filter { + it.container == CONTAINER_DESKTOP || it.container == CONTAINER_HOTSEAT + } + .size + ) + .isAtLeast(32) + Truth.assertThat(itemsIdMap.filter { ModelUtils.WIDGET_FILTER.test(it) }.size) + .isAtLeast(7) + Truth.assertThat( + itemsIdMap + .filter { + it.itemType == ITEM_TYPE_FOLDER || it.itemType == ITEM_TYPE_APP_PAIR + } + .size + ) + .isAtLeast(8) Truth.assertThat(itemsIdMap.size()).isAtLeast(40) Truth.assertThat(widgetsModel.defaultWidgetsFilter).isNotNull() } diff --git a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt index d553f470d0..8db049c492 100644 --- a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt +++ b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt @@ -58,6 +58,7 @@ import org.mockito.Mockito import org.mockito.Mockito.RETURNS_DEEP_STUBS import org.mockito.Mockito.verify import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.doAnswer import org.mockito.kotlin.eq import org.mockito.kotlin.mock @@ -220,7 +221,8 @@ class WorkspaceItemProcessorExtraTest { ) .commit() val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java) - verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel)) + verify(mockCursor) + .checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel), anyOrNull()) val actualWidgetInfo = widgetInfoCaptor.value with(actualWidgetInfo) { assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName) @@ -271,7 +273,7 @@ class WorkspaceItemProcessorExtraTest { itemProcessorUnderTest.processItem() // Then - verify(mockCursor).checkAndAddItem(any(), any()) + verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull()) } private fun createWorkspaceItemProcessorUnderTest(