Reparent folders and app pairs
Previously, app pairs and folders shared a common data model, FolderInfo. Now we need to separate them, so a new type, CollectionInfo, will serve as the parent of both types.
Bug: 315731527
Fixes: 326664798
Flag: ACONFIG com.android.wm.shell.enable_app_pairs TRUNKFOOD
Test: Manual, unit tests to follow
Change-Id: Ia8c429cf6e6a376f2554ae1866549ef0bcab2a22
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 1285aca..4369fc3 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -435,8 +435,7 @@
}
@UiThread
- @VisibleForTesting
- public void applyLabel(ItemInfoWithIcon info) {
+ public void applyLabel(ItemInfo info) {
CharSequence label = info.title;
if (label != null) {
mLastOriginalText = label;
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 9a5627a..58789fd 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -27,7 +27,7 @@
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.logging.StatsLogManager;
-import com.android.launcher3.model.data.FolderInfo;
+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;
@@ -75,7 +75,7 @@
}
return (info instanceof LauncherAppWidgetInfo)
- || (info instanceof FolderInfo);
+ || (info instanceof CollectionInfo);
}
@Override
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e7d2843..0d4dbeb 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -202,6 +202,8 @@
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.model.StringCache;
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;
@@ -798,13 +800,19 @@
@Override
public void invalidateParent(ItemInfo info) {
if (info.container >= 0) {
- View folderIcon = getWorkspace().getHomescreenIconByItemId(info.container);
- if (folderIcon instanceof FolderIcon && folderIcon.getTag() instanceof FolderInfo) {
+ View collectionIcon = getWorkspace().getHomescreenIconByItemId(info.container);
+ if (collectionIcon instanceof FolderIcon folderIcon
+ && collectionIcon.getTag() instanceof FolderInfo) {
if (new FolderGridOrganizer(getDeviceProfile())
.setFolderInfo((FolderInfo) folderIcon.getTag())
.isItemInPreview(info.rank)) {
folderIcon.invalidate();
}
+ } else if (collectionIcon instanceof AppPairIcon appPairIcon
+ && collectionIcon.getTag() instanceof AppPairInfo appPairInfo) {
+ if (appPairInfo.getContents().contains(info)) {
+ appPairIcon.getIconDrawableArea().redraw();
+ }
}
}
}
@@ -2003,24 +2011,26 @@
public boolean removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb,
@Nullable final String reason) {
if (itemInfo instanceof WorkspaceItemInfo) {
- // Remove the shortcut from the folder before removing it from launcher
- View folderIcon = mWorkspace.getHomescreenIconByItemId(itemInfo.container);
- if (folderIcon instanceof FolderIcon) {
- ((FolderInfo) folderIcon.getTag()).remove((WorkspaceItemInfo) itemInfo, true);
+ View collectionIcon = mWorkspace.getHomescreenIconByItemId(itemInfo.container);
+ if (collectionIcon instanceof FolderIcon) {
+ // Remove the shortcut from the folder before removing it from launcher
+ ((FolderInfo) collectionIcon.getTag()).remove((WorkspaceItemInfo) itemInfo, true);
+ } else if (collectionIcon instanceof AppPairIcon appPairIcon) {
+ removeItem(appPairIcon, appPairIcon.getInfo(), deleteFromDb,
+ "removing app pair because one of its member apps was removed");
} else {
mWorkspace.removeWorkspaceItem(v);
}
if (deleteFromDb) {
getModelWriter().deleteItemFromDatabase(itemInfo, reason);
}
- } else if (itemInfo instanceof FolderInfo) {
- final FolderInfo folderInfo = (FolderInfo) itemInfo;
+ } else if (itemInfo instanceof CollectionInfo ci) {
if (v instanceof FolderIcon) {
((FolderIcon) v).removeListeners();
}
mWorkspace.removeWorkspaceItem(v);
if (deleteFromDb) {
- getModelWriter().deleteFolderAndContentsFromDatabase(folderInfo);
+ getModelWriter().deleteCollectionAndContentsFromDatabase(ci);
}
} else if (itemInfo instanceof LauncherAppWidgetInfo) {
final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index ca34dd1..ce3c55a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -95,6 +95,7 @@
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
+import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -3313,7 +3314,7 @@
}
} else if (child instanceof FolderIcon) {
FolderInfo folderInfo = (FolderInfo) info;
- List<WorkspaceItemInfo> matches = folderInfo.contents.stream()
+ List<WorkspaceItemInfo> matches = folderInfo.getContents().stream()
.filter(matcher)
.collect(Collectors.toList());
if (!matches.isEmpty()) {
@@ -3322,6 +3323,11 @@
((FolderIcon) child).getFolder().close(false /* animate */);
}
}
+ } else if (info instanceof AppPairInfo api) {
+ // If an app pair's member apps are being removed, delete the whole app pair.
+ if (api.anyMatch(matcher)) {
+ mLauncher.removeItem(child, info, true);
+ }
}
}
}
@@ -3373,9 +3379,9 @@
}
} else if (info instanceof FolderInfo && v instanceof FolderIcon) {
FolderInfo fi = (FolderInfo) info;
- if (fi.contents.stream().anyMatch(matcher)) {
+ if (fi.anyMatch(matcher)) {
FolderDotInfo folderDotInfo = new FolderDotInfo();
- for (WorkspaceItemInfo si : fi.contents) {
+ for (WorkspaceItemInfo si : fi.getContents()) {
folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si));
}
((FolderIcon) v).setDotInfo(folderDotInfo);
diff --git a/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java
index 19d0421..29862ae 100644
--- a/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java
@@ -28,7 +28,7 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.model.data.FolderInfo;
+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;
@@ -45,6 +45,7 @@
public enum DragType {
ICON,
FOLDER,
+ APP_PAIR,
WIDGET
}
@@ -103,7 +104,7 @@
&& item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
}
return (item instanceof LauncherAppWidgetInfo)
- || (item instanceof FolderInfo);
+ || (item instanceof CollectionInfo);
}
@Override
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 66b8216..bb25b6d 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -39,6 +39,8 @@
import com.android.launcher3.folder.Folder;
import com.android.launcher3.keyboard.KeyboardDragAndDropView;
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;
@@ -317,6 +319,8 @@
mDragInfo.dragType = DragType.ICON;
if (info instanceof FolderInfo) {
mDragInfo.dragType = DragType.FOLDER;
+ } else if (info instanceof AppPairInfo) {
+ mDragInfo.dragType = DragType.APP_PAIR;
} else if (info instanceof LauncherAppWidgetInfo) {
mDragInfo.dragType = DragType.WIDGET;
}
@@ -430,16 +434,16 @@
LauncherSettings.Favorites.CONTAINER_DESKTOP,
screenId, coordinates[0], coordinates[1]);
bindItem(info, accessibility, finishCallback);
- } else if (item instanceof FolderInfo fi) {
+ } else if (item instanceof CollectionInfo ci) {
Workspace<?> workspace = mContext.getWorkspace();
workspace.snapToPage(workspace.getPageIndexForScreenId(screenId));
- mContext.getModelWriter().addItemToDatabase(fi,
+ mContext.getModelWriter().addItemToDatabase(ci,
LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, coordinates[0],
coordinates[1]);
- fi.contents.forEach(member -> {
- mContext.getModelWriter().addItemToDatabase(member, fi.id, -1, -1, -1);
- });
- bindItem(fi, accessibility, finishCallback);
+ ci.getContents().forEach(member ->
+ mContext.getModelWriter()
+ .addItemToDatabase(member, ci.id, -1, -1, -1));
+ bindItem(ci, accessibility, finishCallback);
}
}));
return true;
diff --git a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
index a8624dd..52073cc 100644
--- a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
@@ -149,7 +149,7 @@
// Find the first item in the folder.
FolderInfo folder = (FolderInfo) info;
WorkspaceItemInfo firstItem = null;
- for (WorkspaceItemInfo shortcut : folder.contents) {
+ for (WorkspaceItemInfo shortcut : folder.getContents()) {
if (firstItem == null || firstItem.rank > shortcut.rank) {
firstItem = shortcut;
}
diff --git a/src/com/android/launcher3/apppairs/AppPairIcon.java b/src/com/android/launcher3/apppairs/AppPairIcon.java
index bbeb341..9010f82 100644
--- a/src/com/android/launcher3/apppairs/AppPairIcon.java
+++ b/src/com/android/launcher3/apppairs/AppPairIcon.java
@@ -33,7 +33,7 @@
import com.android.launcher3.R;
import com.android.launcher3.Reorderable;
import com.android.launcher3.dragndrop.DraggableView;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.views.ActivityContext;
@@ -50,17 +50,12 @@
public class AppPairIcon extends FrameLayout implements DraggableView, Reorderable {
private static final String TAG = "AppPairIcon";
- /**
- * Indicates that the app pair is currently launchable on the current screen.
- */
- private boolean mIsLaunchableAtScreenSize = true;
-
// A view that holds the app pair icon graphic.
private AppPairIconGraphic mIconGraphic;
// A view that holds the app pair's title.
private BubbleTextView mAppPairName;
// The underlying ItemInfo that stores info about the app pair members, etc.
- private FolderInfo mInfo;
+ private AppPairInfo mInfo;
// The containing element that holds this icon: workspace, taskbar, folder, etc. Affects certain
// aspects of how the icon is drawn.
private int mContainer;
@@ -81,7 +76,7 @@
* Builds an AppPairIcon to be added to the Launcher.
*/
public static AppPairIcon inflateIcon(int resId, ActivityContext activity,
- @Nullable ViewGroup group, FolderInfo appPairInfo, int container) {
+ @Nullable ViewGroup group, AppPairInfo appPairInfo, int container) {
DeviceProfile grid = activity.getDeviceProfile();
LayoutInflater inflater = (group != null)
? LayoutInflater.from(group.getContext())
@@ -89,7 +84,7 @@
AppPairIcon icon = (AppPairIcon) inflater.inflate(resId, group, false);
// Sort contents, so that left-hand app comes first
- appPairInfo.contents.sort(Comparator.comparingInt(a -> a.rank));
+ appPairInfo.getContents().sort(Comparator.comparingInt(a -> a.rank));
icon.setTag(appPairInfo);
icon.setOnClickListener(activity.getItemOnClickListener());
@@ -100,8 +95,6 @@
icon.mIconGraphic = icon.findViewById(R.id.app_pair_icon_graphic);
icon.mIconGraphic.init(icon, container);
- icon.checkDisabledState();
-
// Set up app pair title
icon.mAppPairName = icon.findViewById(R.id.app_pair_icon_name);
FrameLayout.LayoutParams lp =
@@ -115,7 +108,7 @@
// For some reason, app icons have setIncludeFontPadding(false) inside folders, so we set it
// here to match that.
icon.mAppPairName.setIncludeFontPadding(container != DISPLAY_FOLDER);
- icon.mAppPairName.setText(appPairInfo.title);
+ icon.mAppPairName.applyLabel(appPairInfo);
// Set up accessibility
icon.setContentDescription(icon.getAccessibilityTitle(appPairInfo));
@@ -127,9 +120,9 @@
/**
* Returns a formatted accessibility title for app pairs.
*/
- public String getAccessibilityTitle(FolderInfo appPairInfo) {
- CharSequence app1 = appPairInfo.contents.get(0).title;
- CharSequence app2 = appPairInfo.contents.get(1).title;
+ public String getAccessibilityTitle(AppPairInfo appPairInfo) {
+ CharSequence app1 = appPairInfo.getFirstApp().title;
+ CharSequence app2 = appPairInfo.getSecondApp().title;
return getContext().getString(R.string.app_pair_name_format, app1, app2);
}
@@ -174,7 +167,7 @@
return mScaleForReorderBounce;
}
- public FolderInfo getInfo() {
+ public AppPairInfo getInfo() {
return mInfo;
}
@@ -186,41 +179,20 @@
return mIconGraphic;
}
- public boolean isLaunchableAtScreenSize() {
- return mIsLaunchableAtScreenSize;
- }
-
- /**
- * Updates the "disabled" state of the app pair in the current device configuration.
- * App pairs can be "disabled" in two ways:
- * 1) One of the member WorkspaceItemInfos is disabled (i.e. the app software itself is paused
- * by the user or can't be launched for some other reason).
- * 2) This specific instance of an app pair can't be launched due to screen size requirements.
- */
- public void checkDisabledState() {
- DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile();
- // If user is on a small screen, we can't launch if either of the apps is non-resizeable
- mIsLaunchableAtScreenSize =
- dp.isTablet || getInfo().contents.stream().noneMatch(
- wii -> wii.hasStatusFlag(WorkspaceItemInfo.FLAG_NON_RESIZEABLE));
- // Invalidate to update icons
- mIconGraphic.redraw();
- }
-
/**
* Called when WorkspaceItemInfos get updated, and the app pair icon may need to be redrawn.
*/
public void maybeRedrawForWorkspaceUpdate(Predicate<WorkspaceItemInfo> itemCheck) {
// If either of the app pair icons return true on the predicate (i.e. in the list of
// updated apps), redraw the icon graphic (icon background and both icons).
- if (getInfo().contents.stream().anyMatch(itemCheck)) {
- checkDisabledState();
+ if (getInfo().anyMatch(itemCheck)) {
+ mIconGraphic.redraw();
}
}
/**
* Inside folders, icons are vertically centered in their rows. See
- * {@link BubbleTextView#onMeasure(int, int)} for comparison.
+ * {@link BubbleTextView} for comparison.
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
diff --git a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
index 04050b0..a3a1cfc 100644
--- a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
+++ b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
@@ -23,12 +23,12 @@
import android.util.AttributeSet
import android.view.Gravity
import android.widget.FrameLayout
+import androidx.annotation.OpenForTesting
import com.android.launcher3.DeviceProfile
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener
import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter
-import com.android.launcher3.model.data.FolderInfo
-import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.model.data.AppPairInfo
import com.android.launcher3.util.Themes
import com.android.launcher3.views.ActivityContext
@@ -36,29 +36,32 @@
* A FrameLayout marking the area on an [AppPairIcon] where the visual icon will be drawn. One of
* two child UI elements on an [AppPairIcon], along with a BubbleTextView holding the text title.
*/
-class AppPairIconGraphic @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+@OpenForTesting
+open class AppPairIconGraphic
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null) :
FrameLayout(context, attrs), OnDeviceProfileChangeListener {
private val TAG = "AppPairIconGraphic"
companion object {
- /** Composes a drawable for this icon, consisting of a background and 2 app icons. */
+ /**
+ * Composes a drawable for this icon, consisting of a background and 2 app icons. The app
+ * pair will draw as "disabled" if either of the following is true:
+ * 1) One of the member WorkspaceItemInfos is disabled (i.e. the app software itself is
+ * paused or can't be launched for some other reason).
+ * 2) One of the member apps can't be launched due to screen size requirements.
+ */
@JvmStatic
- fun composeDrawable(appPairInfo: FolderInfo, p: AppPairIconDrawingParams): Drawable {
+ fun composeDrawable(appPairInfo: AppPairInfo, p: AppPairIconDrawingParams): Drawable {
// Generate new icons, using themed flag if needed.
val flags = if (Themes.isThemedIconEnabled(p.context)) BitmapInfo.FLAG_THEMED else 0
- val appIcon1 = appPairInfo.contents[0].newIcon(p.context, flags)
- val appIcon2 = appPairInfo.contents[1].newIcon(p.context, flags)
+ val appIcon1 = appPairInfo.getFirstApp().newIcon(p.context, flags)
+ val appIcon2 = appPairInfo.getSecondApp().newIcon(p.context, flags)
appIcon1.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt())
appIcon2.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt())
- // Check disabled status.
- val activity: ActivityContext = ActivityContext.lookupContext(p.context)
- val isLaunchableAtScreenSize =
- activity.deviceProfile.isTablet ||
- appPairInfo.contents.stream().noneMatch { wii: WorkspaceItemInfo ->
- wii.hasStatusFlag(WorkspaceItemInfo.FLAG_NON_RESIZEABLE)
- }
- val shouldDrawAsDisabled = appPairInfo.isDisabled || !isLaunchableAtScreenSize
+ val shouldDrawAsDisabled =
+ appPairInfo.isDisabled || !appPairInfo.isLaunchable(p.context)
// Set disabled status on icons.
appIcon1.setIsDisabled(shouldDrawAsDisabled)
@@ -124,7 +127,6 @@
*/
fun getIconBounds(outBounds: Rect) {
outBounds.set(0, 0, drawParams.backgroundSize.toInt(), drawParams.backgroundSize.toInt())
-
outBounds.offset(
// x-coordinate in parent's coordinate system
((parentIcon.width - drawParams.backgroundSize) / 2).toInt(),
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index b6e5977..bc5a164 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -33,6 +33,7 @@
import com.android.launcher3.DropTarget;
import com.android.launcher3.Flags;
import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -289,7 +290,8 @@
// Cancel the current drag if we are removing an app that we are dragging
if (mDragObject != null) {
ItemInfo dragInfo = mDragObject.dragInfo;
- if (dragInfo instanceof WorkspaceItemInfo && matcher.test(dragInfo)) {
+ if ((dragInfo instanceof WorkspaceItemInfo && matcher.test(dragInfo))
+ || (dragInfo instanceof AppPairInfo api && api.anyMatch(matcher))) {
cancelDrag();
}
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index c8c634a..aa3c5ba 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -493,7 +493,7 @@
mInfo = info;
mFromTitle = info.title;
mFromLabelState = info.getFromLabelState();
- ArrayList<WorkspaceItemInfo> children = info.contents;
+ ArrayList<WorkspaceItemInfo> children = info.getContents();
Collections.sort(children, ITEM_POS_COMPARATOR);
updateItemLocationsInDatabaseBatch(true);
@@ -626,7 +626,7 @@
// onDropComplete. Perform cleanup once drag-n-drop ends.
mDragController.addDragListener(this);
- ArrayList<WorkspaceItemInfo> items = new ArrayList<>(mInfo.contents);
+ ArrayList<WorkspaceItemInfo> items = new ArrayList<>(mInfo.getContents());
mEmptyCellRank = items.size();
items.add(null); // Add an empty spot at the end
@@ -639,7 +639,7 @@
* is played.
*/
public void animateOpen() {
- animateOpen(mInfo.contents, 0);
+ animateOpen(mInfo.getContents(), 0);
}
/**
@@ -1097,9 +1097,9 @@
mActivityContext.getDeviceProfile()).setFolderInfo(mInfo);
ArrayList<ItemInfo> items = new ArrayList<>();
- int total = mInfo.contents.size();
+ int total = mInfo.getContents().size();
for (int i = 0; i < total; i++) {
- WorkspaceItemInfo itemInfo = mInfo.contents.get(i);
+ WorkspaceItemInfo itemInfo = mInfo.getContents().get(i);
if (verifier.updateRankAndPos(itemInfo, i)) {
items.add(itemInfo);
}
@@ -1113,7 +1113,7 @@
FolderNameInfos nameInfos = new FolderNameInfos();
FolderNameProvider fnp = FolderNameProvider.newInstance(getContext());
fnp.getSuggestedFolderName(
- getContext(), mInfo.contents, nameInfos);
+ getContext(), mInfo.getContents(), nameInfos);
mInfo.suggestedFolderNames = nameInfos;
});
}
@@ -1217,7 +1217,7 @@
}
public int getItemCount() {
- return mInfo.contents.size();
+ return mInfo.getContents().size();
}
void replaceFolderWithFinalItem() {
diff --git a/src/com/android/launcher3/folder/FolderGridOrganizer.java b/src/com/android/launcher3/folder/FolderGridOrganizer.java
index cc24761..593673d 100644
--- a/src/com/android/launcher3/folder/FolderGridOrganizer.java
+++ b/src/com/android/launcher3/folder/FolderGridOrganizer.java
@@ -57,7 +57,7 @@
* Updates the organizer with the provided folder info
*/
public FolderGridOrganizer setFolderInfo(FolderInfo info) {
- return setContentSize(info.contents.size());
+ return setContentSize(info.getContents().size());
}
/**
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index ee0d5fc..62ce311 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -215,7 +215,7 @@
// Keep the notification dot up to date with the sum of all the content's dots.
FolderDotInfo folderDotInfo = new FolderDotInfo();
- for (WorkspaceItemInfo si : folderInfo.contents) {
+ for (WorkspaceItemInfo si : folderInfo.getContents()) {
folderDotInfo.addDotInfo(activity.getDotInfoForItem(si));
}
icon.setDotInfo(folderDotInfo);
@@ -422,7 +422,7 @@
FolderNameInfos nameInfos = new FolderNameInfos();
Executors.MODEL_EXECUTOR.post(() -> {
d.folderNameProvider.getSuggestedFolderName(
- getContext(), mInfo.contents, nameInfos);
+ getContext(), mInfo.getContents(), nameInfos);
postDelayed(() -> {
setLabelSuggestion(nameInfos, d.logInstanceId);
invalidate();
@@ -487,7 +487,7 @@
}
mFolder.notifyDrop();
onDrop(item, d, null, 1.0f,
- itemReturnedOnFailedDrop ? item.rank : mInfo.contents.size(),
+ itemReturnedOnFailedDrop ? item.rank : mInfo.getContents().size(),
itemReturnedOnFailedDrop
);
}
@@ -666,7 +666,7 @@
* Returns the list of items which should be visible in the preview
*/
public List<WorkspaceItemInfo> getPreviewItemsOnPage(int page) {
- return mPreviewVerifier.setFolderInfo(mInfo).previewItemsForPage(page, mInfo.contents);
+ return mPreviewVerifier.setFolderInfo(mInfo).previewItemsForPage(page, mInfo.getContents());
}
@Override
@@ -809,7 +809,7 @@
* Returns a formatted accessibility title for folder
*/
public String getAccessiblityTitle(CharSequence title) {
- int size = mInfo.contents.size();
+ int size = mInfo.getContents().size();
if (size < MAX_NUM_ITEMS_IN_PREVIEW) {
return getContext().getString(R.string.folder_name_format_exact, title, size);
} else {
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index bf59594..5d2bb3a 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -35,7 +35,7 @@
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.StringCache;
import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.Preconditions;
@@ -62,7 +62,7 @@
* name edit box can also be used to provide suggestion.
*/
public static final int SUGGEST_MAX = 4;
- protected IntSparseArrayMap<FolderInfo> mFolderInfos;
+ protected IntSparseArrayMap<CollectionInfo> mCollectionInfos;
protected List<AppInfo> mAppInfos;
/**
@@ -79,7 +79,7 @@
}
public static FolderNameProvider newInstance(Context context, List<AppInfo> appInfos,
- IntSparseArrayMap<FolderInfo> folderInfos) {
+ IntSparseArrayMap<CollectionInfo> folderInfos) {
Preconditions.assertWorkerThread();
FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
context.getApplicationContext(), R.string.folder_name_provider_class);
@@ -93,9 +93,9 @@
new FolderNameWorker());
}
- private void load(List<AppInfo> appInfos, IntSparseArrayMap<FolderInfo> folderInfos) {
+ private void load(List<AppInfo> appInfos, IntSparseArrayMap<CollectionInfo> folderInfos) {
mAppInfos = appInfos;
- mFolderInfos = folderInfos;
+ mCollectionInfos = folderInfos;
}
/**
@@ -195,7 +195,7 @@
@Override
public void execute(@NonNull final LauncherAppState app,
@NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
- mFolderInfos = dataModel.folders.clone();
+ mCollectionInfos = dataModel.collections.clone();
mAppInfos = Arrays.asList(apps.copyData());
}
}
diff --git a/src/com/android/launcher3/folder/LauncherDelegate.java b/src/com/android/launcher3/folder/LauncherDelegate.java
index 78298b3..33bcf21 100644
--- a/src/com/android/launcher3/folder/LauncherDelegate.java
+++ b/src/com/android/launcher3/folder/LauncherDelegate.java
@@ -93,7 +93,7 @@
// folder
CellLayout cellLayout = mLauncher.getCellLayout(info.container,
mLauncher.getCellPosMapper().mapModelToPresenter(info).screenId);
- finalItem = info.contents.remove(0);
+ finalItem = info.getContents().remove(0);
newIcon = mLauncher.getItemInflater().inflateItem(
finalItem, mLauncher.getModelWriter(), cellLayout);
mLauncher.getModelWriter().addOrMoveItemInDatabase(finalItem,
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 9aee379..6b3bb51 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -82,6 +82,8 @@
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.WidgetsModel;
+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;
@@ -388,16 +390,16 @@
addInScreenFromBind(icon, info);
}
- private void inflateAndAddCollectionIcon(FolderInfo info) {
+ private void inflateAndAddCollectionIcon(CollectionInfo info) {
boolean isOnDesktop = info.container == Favorites.CONTAINER_DESKTOP;
CellLayout screen = isOnDesktop
? mWorkspaceScreens.get(info.screenId)
: mHotseat;
- FrameLayout folderIcon = info.itemType == Favorites.ITEM_TYPE_FOLDER
- ? FolderIcon.inflateIcon(R.layout.folder_icon, this, screen, info)
- : AppPairIcon.inflateIcon(R.layout.app_pair_icon, this, screen, info,
+ FrameLayout collectionIcon = info.itemType == Favorites.ITEM_TYPE_FOLDER
+ ? FolderIcon.inflateIcon(R.layout.folder_icon, this, screen, (FolderInfo) info)
+ : AppPairIcon.inflateIcon(R.layout.app_pair_icon, this, screen, (AppPairInfo) info,
isOnDesktop ? DISPLAY_WORKSPACE : DISPLAY_TASKBAR);
- addInScreenFromBind(folderIcon, info);
+ addInScreenFromBind(collectionIcon, info);
}
private void inflateAndAddWidgets(
@@ -501,7 +503,7 @@
break;
case Favorites.ITEM_TYPE_FOLDER:
case Favorites.ITEM_TYPE_APP_PAIR:
- inflateAndAddCollectionIcon((FolderInfo) itemInfo);
+ inflateAndAddCollectionIcon((CollectionInfo) itemInfo);
break;
default:
break;
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 96a8da9..ce563b7 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -31,7 +31,7 @@
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -131,8 +131,8 @@
int screenId = coords[0];
ItemInfo itemInfo;
- if (item instanceof WorkspaceItemInfo || item instanceof FolderInfo ||
- item instanceof LauncherAppWidgetInfo) {
+ if (item instanceof WorkspaceItemInfo || item instanceof CollectionInfo
+ || item instanceof LauncherAppWidgetInfo) {
itemInfo = item;
} else if (item instanceof WorkspaceItemFactory) {
itemInfo = ((WorkspaceItemFactory) item).makeWorkspaceItem(app.getContext());
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 8579d1d..ee9ce7d 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -44,6 +44,7 @@
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
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.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -102,9 +103,9 @@
public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
/**
- * Map of id to FolderInfos of all the folders created by LauncherModel
+ * Map of id to CollectionInfos of all the folders or app pairs created by LauncherModel
*/
- public final IntSparseArrayMap<FolderInfo> folders = new IntSparseArrayMap<>();
+ public final IntSparseArrayMap<CollectionInfo> collections = new IntSparseArrayMap<>();
/**
* Extra container based items
@@ -144,7 +145,7 @@
public synchronized void clear() {
workspaceItems.clear();
appWidgets.clear();
- folders.clear();
+ collections.clear();
itemsIdMap.clear();
deepShortcutMap.clear();
extraItems.clear();
@@ -179,9 +180,9 @@
for (int i = 0; i < appWidgets.size(); i++) {
writer.println(prefix + '\t' + appWidgets.get(i).toString());
}
- writer.println(prefix + " ---- folder items ");
- for (int i = 0; i < folders.size(); i++) {
- writer.println(prefix + '\t' + folders.valueAt(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 + " ---- extra items ");
for (int i = 0; i < extraItems.size(); i++) {
@@ -211,12 +212,12 @@
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
- folders.remove(item.id);
+ collections.remove(item.id);
if (FeatureFlags.IS_STUDIO_BUILD) {
for (ItemInfo info : itemsIdMap) {
if (info.container == item.id) {
- // We are deleting a folder which still contains items that
- // think they are contained by that folder.
+ // 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);
@@ -259,7 +260,7 @@
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
- folders.put(item.id, (FolderInfo) item);
+ collections.put(item.id, (CollectionInfo) item);
workspaceItems.add(item);
break;
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
@@ -269,14 +270,14 @@
workspaceItems.add(item);
} else {
if (newItem) {
- if (!folders.containsKey(item.container)) {
+ 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((WorkspaceItemInfo) item, false);
+ findOrMakeFolder(item.container).add((WorkspaceItemInfo) item);
}
}
break;
@@ -371,15 +372,18 @@
* Return an existing FolderInfo object if we have encountered this ID previously,
* or make a new one.
*/
- public synchronized FolderInfo findOrMakeFolder(int id) {
+ public synchronized CollectionInfo findOrMakeFolder(int id) {
// See if a placeholder was created for us already
- FolderInfo folderInfo = folders.get(id);
- if (folderInfo == null) {
- // No placeholder -- create a new instance
- folderInfo = new FolderInfo();
- folders.put(id, folderInfo);
+ 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 folderInfo;
+ return collectionInfo;
}
/**
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java
index 9e91b9d..1deb665 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcast.java
+++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java
@@ -36,6 +36,7 @@
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherSettings;
+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;
@@ -67,7 +68,8 @@
private static final String ACTION_FIRST_SCREEN_ACTIVE_INSTALLS
= "com.android.launcher3.action.FIRST_SCREEN_ACTIVE_INSTALLS";
- private static final String FOLDER_ITEM_EXTRA = "folderItem";
+ // String retained as "folderItem" for back-compatibility reasons.
+ private static final String COLLECTION_ITEM_EXTRA = "folderItem";
private static final String WORKSPACE_ITEM_EXTRA = "workspaceItem";
private static final String HOTSEAT_ITEM_EXTRA = "hotseatItem";
private static final String WIDGET_ITEM_EXTRA = "widgetItem";
@@ -105,20 +107,19 @@
@WorkerThread
private void sendBroadcastToInstaller(Context context, String installerPackageName,
Set<String> packages, List<ItemInfo> firstScreenItems) {
- Set<String> folderItems = new HashSet<>();
+ Set<String> collectionItems = new HashSet<>();
Set<String> workspaceItems = new HashSet<>();
Set<String> hotseatItems = new HashSet<>();
Set<String> widgetItems = new HashSet<>();
for (ItemInfo info : firstScreenItems) {
- if (info instanceof FolderInfo) {
- FolderInfo folderInfo = (FolderInfo) info;
- String folderItemInfoPackage;
- for (ItemInfo folderItemInfo : cloneOnMainThread(folderInfo.contents)) {
- folderItemInfoPackage = getPackageName(folderItemInfo);
- if (folderItemInfoPackage != null
- && packages.contains(folderItemInfoPackage)) {
- folderItems.add(folderItemInfoPackage);
+ if (info instanceof CollectionInfo ci) {
+ String collectionItemInfoPackage;
+ for (ItemInfo collectionItemInfo : cloneOnMainThread(ci.getContents())) {
+ collectionItemInfoPackage = getPackageName(collectionItemInfo);
+ if (collectionItemInfoPackage != null
+ && packages.contains(collectionItemInfoPackage)) {
+ collectionItems.add(collectionItemInfoPackage);
}
}
}
@@ -137,13 +138,13 @@
}
if (DEBUG) {
- printList(installerPackageName, "Folder item", folderItems);
+ printList(installerPackageName, "Collection item", collectionItems);
printList(installerPackageName, "Workspace item", workspaceItems);
printList(installerPackageName, "Hotseat item", hotseatItems);
printList(installerPackageName, "Widget item", widgetItems);
}
- if (folderItems.isEmpty()
+ if (collectionItems.isEmpty()
&& workspaceItems.isEmpty()
&& hotseatItems.isEmpty()
&& widgetItems.isEmpty()) {
@@ -152,7 +153,7 @@
}
context.sendBroadcast(new Intent(ACTION_FIRST_SCREEN_ACTIVE_INSTALLS)
.setPackage(installerPackageName)
- .putStringArrayListExtra(FOLDER_ITEM_EXTRA, new ArrayList<>(folderItems))
+ .putStringArrayListExtra(COLLECTION_ITEM_EXTRA, new ArrayList<>(collectionItems))
.putStringArrayListExtra(WORKSPACE_ITEM_EXTRA, new ArrayList<>(workspaceItems))
.putStringArrayListExtra(HOTSEAT_ITEM_EXTRA, new ArrayList<>(hotseatItems))
.putStringArrayListExtra(WIDGET_ITEM_EXTRA, new ArrayList<>(widgetItems))
@@ -180,7 +181,7 @@
}
/**
- * Clone the provided list on UI thread. This is used for {@link FolderInfo#contents} which
+ * Clone the provided list on UI thread. This is used for {@link FolderInfo#getContents()} which
* is always modified on UI thread.
*/
@AnyThread
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 17cef90..1971b16 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -20,7 +20,6 @@
import static com.android.launcher3.Flags.enableLauncherBrMetricsFixed;
import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE;
import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
import static com.android.launcher3.config.FeatureFlags.SMARTSPACE_AS_A_WIDGET;
@@ -77,9 +76,12 @@
import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
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;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
@@ -99,7 +101,6 @@
import com.android.launcher3.widget.WidgetInflater;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -234,6 +235,7 @@
if (Objects.equals(mApp.getInvariantDeviceProfile().dbFile, mDbName)) {
verifyNotStopped();
sanitizeFolders(mItemsDeleted);
+ sanitizeAppPairs();
sanitizeWidgetsShortcutsAndPackages();
logASplit("sanitizeData");
}
@@ -482,14 +484,20 @@
}
/**
- * After all items have been processed and added to the BgDataModel, this method requests
- * high-res icons for the items that are part of an app pair
+ * After all items have been processed and added to the BgDataModel, this method sorts and
+ * requests high-res icons for the items that are part of an app pair.
*/
private void processAppPairItems() {
- mBgDataModel.workspaceItems.stream()
- .filter((itemInfo -> itemInfo.itemType == ITEM_TYPE_APP_PAIR))
- .forEach(fi -> ((FolderInfo) fi).contents.forEach(item ->
- mIconCache.getTitleAndIcon(item, false /*useLowResIcon*/)));
+ for (CollectionInfo collection : mBgDataModel.collections) {
+ if (!(collection instanceof AppPairInfo appPair)) {
+ continue;
+ }
+
+ appPair.getContents().sort(Folder.ITEM_POS_COMPARATOR);
+ // Fetch hi-res icons if needed.
+ appPair.getContents().stream().filter(ItemInfoWithIcon::usingLowResIcon)
+ .forEach(member -> mIconCache.getTitleAndIcon(member, false));
+ }
}
/**
@@ -545,20 +553,21 @@
// Sort the folder items, update ranks, and make sure all preview items are high res.
List<FolderGridOrganizer> verifiers = mApp.getInvariantDeviceProfile().supportedProfiles
.stream().map(FolderGridOrganizer::new).toList();
- for (FolderInfo folder : mBgDataModel.folders) {
- Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
+ for (CollectionInfo collection : mBgDataModel.collections) {
+ if (!(collection instanceof FolderInfo folder)) {
+ continue;
+ }
+
+ folder.getContents().sort(Folder.ITEM_POS_COMPARATOR);
verifiers.forEach(verifier -> verifier.setFolderInfo(folder));
- int size = folder.contents.size();
+ int size = folder.getContents().size();
// Update ranks here to ensure there are no gaps caused by removed folder items.
// Ranks are the source of truth for folder items, so cellX and cellY can be
// ignored for now. Database will be updated once user manually modifies folder.
for (int rank = 0; rank < size; ++rank) {
- WorkspaceItemInfo info = folder.contents.get(rank);
- // rank is used differently in app pairs, so don't reset
- if (folder.itemType != ITEM_TYPE_APP_PAIR) {
- info.rank = rank;
- }
+ WorkspaceItemInfo info = folder.getContents().get(rank);
+ info.rank = rank;
if (info.usingLowResIcon() && info.itemType == Favorites.ITEM_TYPE_APPLICATION
&& verifiers.stream().anyMatch(it -> it.isItemInPreview(info.rank))) {
@@ -611,14 +620,32 @@
IntArray deletedFolderIds = mApp.getModel().getModelDbController().deleteEmptyFolders();
synchronized (mBgDataModel) {
for (int folderId : deletedFolderIds) {
- mBgDataModel.workspaceItems.remove(mBgDataModel.folders.get(folderId));
- mBgDataModel.folders.remove(folderId);
+ mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(folderId));
+ mBgDataModel.collections.remove(folderId);
mBgDataModel.itemsIdMap.remove(folderId);
}
}
}
}
+ /** Cleans up app pairs if they don't have the right number of member apps (2). */
+ private void sanitizeAppPairs() {
+ IntArray deletedAppPairIds = mApp.getModel().getModelDbController().deleteBadAppPairs();
+ IntArray deletedAppIds = mApp.getModel().getModelDbController().deleteUnparentedApps();
+
+ IntArray deleted = new IntArray();
+ deleted.addAll(deletedAppPairIds);
+ deleted.addAll(deletedAppIds);
+
+ synchronized (mBgDataModel) {
+ for (int id : deleted) {
+ mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(id));
+ mBgDataModel.collections.remove(id);
+ mBgDataModel.itemsIdMap.remove(id);
+ }
+ }
+ }
+
private void sanitizeWidgetsShortcutsAndPackages() {
Context context = mApp.getContext();
@@ -754,16 +781,16 @@
private void loadFolderNames() {
FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext(),
- mBgAllAppsList.data, mBgDataModel.folders);
+ mBgAllAppsList.data, mBgDataModel.collections);
synchronized (mBgDataModel) {
- for (int i = 0; i < mBgDataModel.folders.size(); i++) {
+ for (int i = 0; i < mBgDataModel.collections.size(); i++) {
FolderNameInfos suggestionInfos = new FolderNameInfos();
- FolderInfo info = mBgDataModel.folders.valueAt(i);
- if (info.suggestedFolderNames == null) {
- provider.getSuggestedFolderName(mApp.getContext(), info.contents,
+ CollectionInfo info = mBgDataModel.collections.valueAt(i);
+ if (info instanceof FolderInfo fi && fi.suggestedFolderNames == null) {
+ provider.getSuggestedFolderName(mApp.getContext(), fi.getContents(),
suggestionInfos);
- info.suggestedFolderNames = suggestionInfos;
+ fi.suggestedFolderNames = suggestionInfos;
}
}
}
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index 8ed554a..7e1d40d 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -15,11 +15,15 @@
*/
package com.android.launcher3.model;
+import static android.provider.BaseColumns._ID;
import static android.util.Base64.NO_PADDING;
import static android.util.Base64.NO_WRAP;
import static com.android.launcher3.DefaultLayoutParser.RES_PARTNER_DEFAULT_LAYOUT;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_KEY;
import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL;
@@ -391,6 +395,68 @@
}
}
+ /**
+ * Deletes any app pair that doesn't contain 2 member apps from the DB.
+ * @return Ids of deleted app pairs.
+ */
+ @WorkerThread
+ public IntArray deleteBadAppPairs() {
+ createDbIfNotExists();
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ try (SQLiteTransaction t = new SQLiteTransaction(db)) {
+ // Select all entries with ITEM_TYPE = ITEM_TYPE_APP_PAIR whose id does not appear
+ // exactly twice in the CONTAINER column.
+ String selection =
+ ITEM_TYPE + " = " + ITEM_TYPE_APP_PAIR
+ + " AND " + _ID + " NOT IN"
+ + " (SELECT " + CONTAINER + " FROM " + TABLE_NAME
+ + " GROUP BY " + CONTAINER + " HAVING COUNT(*) = 2)";
+
+ IntArray appPairIds = LauncherDbUtils.queryIntArray(false, db, TABLE_NAME,
+ _ID, selection, null, null);
+ if (!appPairIds.isEmpty()) {
+ db.delete(TABLE_NAME, Utilities.createDbSelectionQuery(
+ _ID, appPairIds), null);
+ }
+ t.commit();
+ return appPairIds;
+ } catch (SQLException ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ return new IntArray();
+ }
+ }
+
+ /**
+ * Deletes any app with a container id that doesn't exist.
+ * @return Ids of deleted apps.
+ */
+ @WorkerThread
+ public IntArray deleteUnparentedApps() {
+ createDbIfNotExists();
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ try (SQLiteTransaction t = new SQLiteTransaction(db)) {
+ // Select all entries whose container id does not appear in the database.
+ String selection =
+ CONTAINER + " >= 0"
+ + " AND " + CONTAINER + " NOT IN"
+ + " (SELECT " + _ID + " FROM " + TABLE_NAME + ")";
+
+ IntArray appIds = LauncherDbUtils.queryIntArray(false, db, TABLE_NAME,
+ _ID, selection, null, null);
+ if (!appIds.isEmpty()) {
+ db.delete(TABLE_NAME, Utilities.createDbSelectionQuery(
+ _ID, appIds), null);
+ }
+ t.commit();
+ return appIds;
+ } catch (SQLException ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ return new IntArray();
+ }
+ }
+
private static void addModifiedTime(ContentValues values) {
values.put(LauncherSettings.Favorites.MODIFIED, System.currentTimeMillis());
}
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 55093a3..b477cb1 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -37,7 +37,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.model.data.FolderInfo;
+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;
@@ -275,7 +275,7 @@
public void deleteItemsFromDatabase(@NonNull final Predicate<ItemInfo> matcher,
@Nullable final String reason) {
deleteItemsFromDatabase(StreamSupport.stream(mBgDataModel.itemsIdMap.spliterator(), false)
- .filter(matcher).collect(Collectors.toList()), reason);
+ .filter(matcher).collect(Collectors.toList()), reason);
}
/**
@@ -302,15 +302,15 @@
/**
* Remove the specified folder and all its contents from the database.
*/
- public void deleteFolderAndContentsFromDatabase(final FolderInfo info) {
+ public void deleteCollectionAndContentsFromDatabase(final CollectionInfo info) {
ModelVerifier verifier = new ModelVerifier();
notifyDelete(Collections.singleton(info));
enqueueDeleteRunnable(newModelTask(() -> {
mModel.getModelDbController().delete(Favorites.TABLE_NAME,
Favorites.CONTAINER + "=" + info.id, null);
- mBgDataModel.removeItem(mContext, info.contents);
- info.contents.clear();
+ mBgDataModel.removeItem(mContext, info.getContents());
+ info.getContents().clear();
mModel.getModelDbController().delete(Favorites.TABLE_NAME,
Favorites._ID + "=" + info.id, null);
@@ -458,12 +458,12 @@
if (item.container != Favorites.CONTAINER_DESKTOP &&
item.container != Favorites.CONTAINER_HOTSEAT) {
- // Item is in a folder, make sure this folder exists
- if (!mBgDataModel.folders.containsKey(item.container)) {
+ // Item is in a collection, make sure this collection exists
+ if (!mBgDataModel.collections.containsKey(item.container)) {
// An items container is being set to a that of an item which is not in
// the list of Folders.
String msg = "item: " + item + " container being set to: " +
- item.container + ", not in the list of folders";
+ item.container + ", not in the list of collections";
Log.e(TAG, msg);
}
}
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 0ba468d..ea1ae2e 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -361,17 +361,10 @@
}
if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
- // This predicate is used to mark an ItemInfo for removal if its package or component
- // is marked for removal.
- Predicate<ItemInfo> removeAppMatch =
+ Predicate<ItemInfo> removeMatch =
ItemInfoMatcher.ofPackages(removedPackages, mUser)
.or(ItemInfoMatcher.ofComponents(removedComponents, mUser))
.and(ItemInfoMatcher.ofItemIds(forceKeepShortcuts).negate());
- // This predicate is used to mark an app pair for removal if it contains an app marked
- // for removal.
- Predicate<ItemInfo> removeAppPairMatch =
- ItemInfoMatcher.forAppPairMatch(removeAppMatch);
- Predicate<ItemInfo> removeMatch = removeAppMatch.or(removeAppPairMatch);
deleteAndBindComponentsRemoved(removeMatch,
"removed because the corresponding package or component is removed. "
+ "mOp=" + mOp + " removedPackages=" + removedPackages.stream().collect(
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index 22e5eb4..aa29290 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -32,6 +32,8 @@
import com.android.launcher3.Utilities
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError
import com.android.launcher3.logging.FileLog
+import com.android.launcher3.model.data.AppPairInfo
+import com.android.launcher3.model.data.FolderInfo
import com.android.launcher3.model.data.IconRequestInfo
import com.android.launcher3.model.data.ItemInfoWithIcon
import com.android.launcher3.model.data.LauncherAppWidgetInfo
@@ -360,25 +362,40 @@
}
/**
- * Loads the folder information from the database and formats it into a FolderInfo. Some of the
- * processing for folder content items is done in LoaderTask after all the items in the
- * workspace have been loaded. The loaded FolderInfos are stored in the BgDataModel.
+ * Loads CollectionInfo information from the database and formats it. This function runs while
+ * LoaderTask is still active; some of the processing for folder content items is done after all
+ * the items in the workspace have been loaded. The loaded and formatted CollectionInfo is then
+ * stored in the BgDataModel.
*/
private fun processFolderOrAppPair() {
- val folderInfo =
- bgDataModel.findOrMakeFolder(c.id).apply {
- c.applyCommonProperties(this)
- itemType = c.itemType
- // Do not trim the folder label, as is was set by the user.
- title = c.getString(c.mTitleIndex)
- spanX = 1
- spanY = 1
- options = c.options
- }
+ var collection = bgDataModel.findOrMakeFolder(c.id)
+ // 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.contents.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)
+ }
- // no special handling required for restored folders
+ c.applyCommonProperties(collection)
+ // Do not trim the folder label, as is was set by the user.
+ collection.title = c.getString(c.mTitleIndex)
+ collection.spanX = 1
+ collection.spanY = 1
+ if (collection is FolderInfo) {
+ collection.options = c.options
+ } else {
+ // An app pair may be inside another folder, so it needs to preserve rank information.
+ collection.rank = c.rank
+ }
+
c.markRestored()
- c.checkAndAddItem(folderInfo, bgDataModel, memoryLogger)
+ c.checkAndAddItem(collection, bgDataModel, memoryLogger)
}
/**
diff --git a/src/com/android/launcher3/model/data/AppPairInfo.kt b/src/com/android/launcher3/model/data/AppPairInfo.kt
new file mode 100644
index 0000000..4081316
--- /dev/null
+++ b/src/com/android/launcher3/model/data/AppPairInfo.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model.data
+
+import android.content.Context
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.logger.LauncherAtom
+import com.android.launcher3.views.ActivityContext
+
+/** A type of app collection that launches multiple apps into split screen. */
+class AppPairInfo() : CollectionInfo() {
+ init {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR
+ }
+
+ /** Convenience constructor, calls primary constructor and init block */
+ constructor(app1: WorkspaceItemInfo, app2: WorkspaceItemInfo) : this() {
+ add(app1)
+ add(app2)
+ }
+
+ /** Adds an element to the contents array. */
+ override fun add(item: WorkspaceItemInfo) {
+ contents.add(item)
+ }
+
+ /** Returns the first app in the pair. */
+ fun getFirstApp() = contents[0]
+
+ /** Returns the second app in the pair. */
+ fun getSecondApp() = contents[1]
+
+ /** Returns if either of the app pair members is currently disabled. */
+ override fun isDisabled() = anyMatch { it.isDisabled }
+
+ /** Checks if the app pair is launchable at the current screen size. */
+ fun isLaunchable(context: Context) =
+ (ActivityContext.lookupContext(context) as ActivityContext).getDeviceProfile().isTablet ||
+ noneMatch { it.hasStatusFlag(WorkspaceItemInfo.FLAG_NON_RESIZEABLE) }
+
+ /** Generates an ItemInfo for logging. */
+ override fun buildProto(cInfo: CollectionInfo?): LauncherAtom.ItemInfo {
+ val appPairIcon = LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size)
+ appPairIcon.setLabelInfo(title.toString())
+ return getDefaultItemInfoBuilder()
+ .setFolderIcon(appPairIcon)
+ .setRank(rank)
+ .setContainerInfo(getContainerInfo())
+ .build()
+ }
+}
diff --git a/src/com/android/launcher3/model/data/CollectionInfo.kt b/src/com/android/launcher3/model/data/CollectionInfo.kt
new file mode 100644
index 0000000..2b865a5
--- /dev/null
+++ b/src/com/android/launcher3/model/data/CollectionInfo.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model.data
+
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.logger.LauncherAtom
+import com.android.launcher3.util.ContentWriter
+import java.util.function.Predicate
+
+abstract class CollectionInfo : ItemInfo() {
+ var contents: ArrayList<WorkspaceItemInfo> = ArrayList()
+
+ abstract fun add(item: WorkspaceItemInfo)
+
+ /** Convenience function. Checks contents to see if any match a given predicate. */
+ fun anyMatch(matcher: Predicate<in WorkspaceItemInfo>): Boolean {
+ return contents.stream().anyMatch(matcher)
+ }
+
+ /** Convenience function. Returns true if none of the contents match a given predicate. */
+ fun noneMatch(matcher: Predicate<in WorkspaceItemInfo>): Boolean {
+ return contents.stream().noneMatch(matcher)
+ }
+
+ override fun onAddToDatabase(writer: ContentWriter) {
+ super.onAddToDatabase(writer)
+ writer.put(LauncherSettings.Favorites.TITLE, title)
+ }
+
+ /** Returns the collection wrapped as {@link LauncherAtom.ItemInfo} for logging. */
+ override fun buildProto(): LauncherAtom.ItemInfo {
+ return buildProto(null)
+ }
+}
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index 83ba2b3..1bbb2fe 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -24,8 +24,6 @@
import static com.android.launcher3.logger.LauncherAtom.Attribute.MANUAL_LABEL;
import static com.android.launcher3.logger.LauncherAtom.Attribute.SUGGESTED_LABEL;
-import android.os.Process;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -49,7 +47,7 @@
/**
* Represents a folder containing shortcuts or apps.
*/
-public class FolderInfo extends ItemInfo {
+public class FolderInfo extends CollectionInfo {
public static final int NO_FLAGS = 0x00000000;
@@ -100,27 +98,15 @@
public FolderNameInfos suggestedFolderNames;
- /**
- * The apps and shortcuts
- */
- public ArrayList<WorkspaceItemInfo> contents = new ArrayList<>();
-
private ArrayList<FolderListener> mListeners = new ArrayList<>();
public FolderInfo() {
itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
- user = Process.myUserHandle();
}
- /**
- * Create an app pair, a type of app collection that launches multiple apps into split screen
- */
- public static FolderInfo createAppPair(WorkspaceItemInfo app1, WorkspaceItemInfo app2) {
- FolderInfo newAppPair = new FolderInfo();
- newAppPair.itemType = LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
- newAppPair.add(app1, /* animate */ false);
- newAppPair.add(app2, /* animate */ false);
- return newAppPair;
+ /** Adds a app or shortcut to the contents array without animation. */
+ public void add(@NonNull WorkspaceItemInfo item) {
+ add(item, false /* animate */);
}
/**
@@ -129,15 +115,15 @@
* @param item
*/
public void add(WorkspaceItemInfo item, boolean animate) {
- add(item, contents.size(), animate);
+ add(item, getContents().size(), animate);
}
/**
* Add an app or shortcut for a specified rank.
*/
public void add(WorkspaceItemInfo item, int rank, boolean animate) {
- rank = Utilities.boundToRange(rank, 0, contents.size());
- contents.add(rank, item);
+ rank = Utilities.boundToRange(rank, 0, getContents().size());
+ getContents().add(rank, item);
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).onAdd(item, rank);
}
@@ -157,7 +143,7 @@
* Remove all matching app or shortcut. Does not change the DB.
*/
public void removeAll(List<WorkspaceItemInfo> items, boolean animate) {
- contents.removeAll(items);
+ getContents().removeAll(items);
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).onRemove(items);
}
@@ -167,8 +153,7 @@
@Override
public void onAddToDatabase(@NonNull ContentWriter writer) {
super.onAddToDatabase(writer);
- writer.put(LauncherSettings.Favorites.TITLE, title)
- .put(LauncherSettings.Favorites.OPTIONS, options);
+ writer.put(LauncherSettings.Favorites.OPTIONS, options);
}
public void addListener(FolderListener listener) {
@@ -219,9 +204,9 @@
@NonNull
@Override
- public LauncherAtom.ItemInfo buildProto(@Nullable FolderInfo fInfo) {
+ public LauncherAtom.ItemInfo buildProto(@Nullable CollectionInfo cInfo) {
FolderIcon.Builder folderIcon = FolderIcon.newBuilder()
- .setCardinality(contents.size());
+ .setCardinality(getContents().size());
if (LabelState.SUGGESTED.equals(getLabelState())) {
folderIcon.setLabelInfo(title.toString());
}
@@ -278,20 +263,11 @@
public ItemInfo makeShallowCopy() {
FolderInfo folderInfo = new FolderInfo();
folderInfo.copyFrom(this);
- folderInfo.contents = this.contents;
+ folderInfo.setContents(this.getContents());
return folderInfo;
}
/**
- * Returns {@link LauncherAtom.FolderIcon} wrapped as {@link LauncherAtom.ItemInfo} for logging.
- */
- @NonNull
- @Override
- public LauncherAtom.ItemInfo buildProto() {
- return buildProto(null);
- }
-
- /**
* Returns index of the accepted suggestion.
*/
public OptionalInt getAcceptedSuggestionIndex() {
@@ -371,13 +347,4 @@
}
return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
}
-
- @Override
- public boolean isDisabled() {
- if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR) {
- return contents.stream().anyMatch((WorkspaceItemInfo::isDisabled));
- }
-
- return super.isDisabled();
- }
}
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index f7cff78..8c3efd7 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -349,10 +349,9 @@
/**
* Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
- * @param fInfo
*/
@NonNull
- public LauncherAtom.ItemInfo buildProto(@Nullable final FolderInfo fInfo) {
+ public LauncherAtom.ItemInfo buildProto(@Nullable final CollectionInfo cInfo) {
LauncherAtom.ItemInfo.Builder itemBuilder = getDefaultItemInfoBuilder();
Optional<ComponentName> nullableComponent = Optional.ofNullable(getTargetComponent());
switch (itemType) {
@@ -398,21 +397,21 @@
default:
break;
}
- if (fInfo != null) {
+ if (cInfo != null) {
LauncherAtom.FolderContainer.Builder folderBuilder =
LauncherAtom.FolderContainer.newBuilder();
folderBuilder.setGridX(cellX).setGridY(cellY).setPageIndex(screenId);
- switch (fInfo.container) {
+ switch (cInfo.container) {
case CONTAINER_HOTSEAT:
case CONTAINER_HOTSEAT_PREDICTION:
folderBuilder.setHotseat(LauncherAtom.HotseatContainer.newBuilder()
- .setIndex(fInfo.screenId));
+ .setIndex(cInfo.screenId));
break;
case CONTAINER_DESKTOP:
folderBuilder.setWorkspace(LauncherAtom.WorkspaceContainer.newBuilder()
- .setPageIndex(fInfo.screenId)
- .setGridX(fInfo.cellX).setGridY(fInfo.cellY));
+ .setPageIndex(cInfo.screenId)
+ .setGridX(cInfo.cellX).setGridY(cInfo.cellY));
break;
}
itemBuilder.setContainerInfo(ContainerInfo.newBuilder().setFolder(folderBuilder));
diff --git a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
index 6fa8c54..f4dda55 100644
--- a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
@@ -271,8 +271,8 @@
@NonNull
@Override
- public LauncherAtom.ItemInfo buildProto(@Nullable FolderInfo folderInfo) {
- LauncherAtom.ItemInfo info = super.buildProto(folderInfo);
+ public LauncherAtom.ItemInfo buildProto(@Nullable CollectionInfo collectionInfo) {
+ LauncherAtom.ItemInfo info = super.buildProto(collectionInfo);
return info.toBuilder()
.setWidget(info.getWidget().toBuilder().setWidgetFeatures(widgetFeatures))
.addItemAttributes(getAttribute(sourceContainer))
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 911568c..0c25e96 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -53,6 +53,7 @@
import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -101,11 +102,9 @@
if (tag instanceof WorkspaceItemInfo) {
onClickAppShortcut(v, (WorkspaceItemInfo) tag, launcher);
} else if (tag instanceof FolderInfo) {
- if (v instanceof FolderIcon) {
- onClickFolderIcon(v);
- } else if (v instanceof AppPairIcon) {
- onClickAppPairIcon(v);
- }
+ onClickFolderIcon(v);
+ } else if (tag instanceof AppPairInfo) {
+ onClickAppPairIcon(v);
} else if (tag instanceof AppInfo) {
startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);
} else if (tag instanceof LauncherAppWidgetInfo) {
@@ -150,7 +149,7 @@
private static void onClickAppPairIcon(View v) {
Launcher launcher = Launcher.getLauncher(v.getContext());
AppPairIcon appPairIcon = (AppPairIcon) v;
- if (!appPairIcon.isLaunchableAtScreenSize()) {
+ if (!appPairIcon.getInfo().isLaunchable(launcher)) {
// Display a message for app pairs that are disabled due to screen size
boolean isFoldable = InvariantDeviceProfile.INSTANCE.get(launcher)
.supportedProfiles.stream().anyMatch(dp -> dp.isTwoPanels);
@@ -159,8 +158,8 @@
: R.string.app_pair_unlaunchable_at_screen_size,
Toast.LENGTH_SHORT).show();
} else if (appPairIcon.getInfo().isDisabled()) {
- WorkspaceItemInfo app1 = appPairIcon.getInfo().contents.get(0);
- WorkspaceItemInfo app2 = appPairIcon.getInfo().contents.get(1);
+ WorkspaceItemInfo app1 = appPairIcon.getInfo().getFirstApp();
+ WorkspaceItemInfo app2 = appPairIcon.getInfo().getSecondApp();
// Show the user why the app pair is disabled.
if (app1.isDisabled() && !handleDisabledItemClicked(app1, launcher)) {
// If handleDisabledItemClicked() did not handle the error message, we initiate an
diff --git a/src/com/android/launcher3/util/ItemInflater.kt b/src/com/android/launcher3/util/ItemInflater.kt
index 0f8311d..ebf4656 100644
--- a/src/com/android/launcher3/util/ItemInflater.kt
+++ b/src/com/android/launcher3/util/ItemInflater.kt
@@ -29,6 +29,7 @@
import com.android.launcher3.apppairs.AppPairIcon
import com.android.launcher3.folder.FolderIcon
import com.android.launcher3.model.ModelWriter
+import com.android.launcher3.model.data.AppPairInfo
import com.android.launcher3.model.data.FolderInfo
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.model.data.LauncherAppWidgetInfo
@@ -81,7 +82,7 @@
R.layout.app_pair_icon,
context,
parent,
- item as FolderInfo,
+ item as AppPairInfo,
BubbleTextView.DISPLAY_WORKSPACE
)
Favorites.ITEM_TYPE_APPWIDGET,
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index 3074111..063313a 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -65,20 +65,11 @@
* Returns a matcher for items within folders.
*/
public static Predicate<ItemInfo> forFolderMatch(Predicate<ItemInfo> childOperator) {
- return info -> info instanceof FolderInfo && ((FolderInfo) info).contents.stream()
+ return info -> info instanceof FolderInfo && ((FolderInfo) info).getContents().stream()
.anyMatch(childOperator);
}
/**
- * Returns a matcher for items within app pairs.
- */
- public static Predicate<ItemInfo> forAppPairMatch(Predicate<ItemInfo> childOperator) {
- Predicate<ItemInfo> isAppPair = info ->
- info instanceof FolderInfo fi && fi.itemType == Favorites.ITEM_TYPE_APP_PAIR;
- return isAppPair.and(forFolderMatch(childOperator));
- }
-
- /**
* Returns a matcher for items with provided ids
*/
public static Predicate<ItemInfo> ofItemIds(IntSet ids) {
diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
index 69786bb..02779ce 100644
--- a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
+++ b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
@@ -23,6 +23,7 @@
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -59,7 +60,7 @@
: null);
} else if (info instanceof FolderInfo && v instanceof FolderIcon) {
((FolderIcon) v).updatePreviewItems(updates::contains);
- } else if (info instanceof FolderInfo && v instanceof AppPairIcon appPairIcon) {
+ } else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) {
appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains);
}
@@ -89,7 +90,7 @@
((PendingAppWidgetHostView) v).applyState();
} else if (v instanceof FolderIcon && info instanceof FolderInfo) {
((FolderIcon) v).updatePreviewItems(updates::contains);
- } else if (info instanceof FolderInfo && v instanceof AppPairIcon appPairIcon) {
+ } else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) {
appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains);
}
// process all the shortcuts
diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
index a501960..a916252 100644
--- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
@@ -25,7 +25,7 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.logger.LauncherAtom;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.widget.picker.WidgetRecommendationCategory;
import com.android.launcher3.widget.util.WidgetSizes;
@@ -82,8 +82,8 @@
@NonNull
@Override
- public LauncherAtom.ItemInfo buildProto(@Nullable FolderInfo folderInfo) {
- LauncherAtom.ItemInfo info = super.buildProto(folderInfo);
+ public LauncherAtom.ItemInfo buildProto(@Nullable CollectionInfo collectionInfo) {
+ LauncherAtom.ItemInfo info = super.buildProto(collectionInfo);
return info.toBuilder()
.addItemAttributes(LauncherAppWidgetInfo.getAttribute(sourceContainer))
.build();