From 7cf0dd9e036f4a84c17fc1a694c94e0d44ed9d1b Mon Sep 17 00:00:00 2001 From: Jeremy Sim Date: Fri, 24 Mar 2023 22:39:45 -0700 Subject: [PATCH] App Pairs (behind flag): Add new ItemInfo types and DB save functionality This is the second of several patches implementing the App Pairs feature behind a flag. This patch includes: - AppPairsController, a new controller that will handle creation, launching, and deletion of app pairs - ITEM_TYPE_APP_PAIR is a new type of FolderInfo (FolderInfo now can be either a folder or an app pair, and should probably be renamed to CollectionInfo or something more generic in future) - Necessary plumbing for these new types - Database code that handles saving a new app pair to the database with the correct schema Flag: ENABLE_APP_PAIRS (set to false) Bug: 274189428 Test: Not included in this CL, but will follow Change-Id: Ie3aefd4eb9171f471789f54876de742849d3013b --- .../quickstep/TaskShortcutFactory.java | 10 +- .../quickstep/util/AppPairsController.java | 104 ++++++++++++++++++ .../util/SplitSelectStateController.java | 6 + src/com/android/launcher3/Launcher.java | 8 ++ .../android/launcher3/LauncherSettings.java | 5 + .../LauncherAccessibilityDelegate.java | 8 ++ .../android/launcher3/model/BgDataModel.java | 13 ++- .../launcher3/model/data/FolderInfo.java | 11 ++ .../launcher3/model/data/ItemInfo.java | 1 + 9 files changed, 155 insertions(+), 11 deletions(-) create mode 100644 quickstep/src/com/android/quickstep/util/AppPairsController.java diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java index fd7b3434bd..813523888d 100644 --- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java +++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java @@ -128,19 +128,19 @@ public interface TaskShortcutFactory { * A menu item, "Save app pair", that allows the user to preserve the current app combination as * a single persistent icon on the Home screen, allowing for quick split screen initialization. */ - class SaveAppPairSystemShortcut extends SystemShortcut { - + class SaveAppPairSystemShortcut extends SystemShortcut { private final TaskView mTaskView; - public SaveAppPairSystemShortcut(BaseDraggingActivity target, TaskView taskView) { - super(R.drawable.ic_save_app_pair, R.string.save_app_pair, target, + public SaveAppPairSystemShortcut(BaseDraggingActivity activity, TaskView taskView) { + super(R.drawable.ic_save_app_pair, R.string.save_app_pair, activity, taskView.getItemInfo(), taskView); mTaskView = taskView; } @Override public void onClick(View view) { - // TODO (b/274189428): Call "saveAppPair" function in new AppPairController class + ((RecentsView) mTarget.getOverviewPanel()) + .getSplitSelectController().getAppPairsController().saveAppPair(mTaskView); } } diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java new file mode 100644 index 0000000000..cbde257003 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java @@ -0,0 +1,104 @@ +/* + * Copyright 2023 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.quickstep.util; + +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + +import android.content.Context; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; +import com.android.launcher3.icons.IconCache; +import com.android.launcher3.model.data.FolderInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.quickstep.views.TaskView; + +/** + * Mini controller class that handles app pair interactions: saving, modifying, deleting, etc. + */ +public class AppPairsController { + + private static final int POINT_THREE_RATIO = 0; + private static final int POINT_FIVE_RATIO = 1; + private static final int POINT_SEVEN_RATIO = 2; + /** + * Used to calculate {@link #complement(int)} + */ + private static final int FULL_RATIO = 2; + + private static final int LEFT_TOP = 0; + private static final int RIGHT_BOTTOM = 1 << 2; + + // TODO (jeremysim b/274189428): Support saving different ratios in future. + public int DEFAULT_RATIO = POINT_FIVE_RATIO; + + private final Context mContext; + private final SplitSelectStateController mSplitSelectStateController; + public AppPairsController(Context context, + SplitSelectStateController splitSelectStateController) { + mContext = context; + mSplitSelectStateController = splitSelectStateController; + } + + /** + * Creates a new app pair ItemInfo and adds it to the workspace + */ + public void saveAppPair(TaskView taskView) { + TaskView.TaskIdAttributeContainer[] attributes = taskView.getTaskIdAttributeContainers(); + WorkspaceItemInfo app1 = attributes[0].getItemInfo().clone(); + WorkspaceItemInfo app2 = attributes[1].getItemInfo().clone(); + app1.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; + app2.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; + app1.rank = DEFAULT_RATIO + LEFT_TOP; + app2.rank = complement(DEFAULT_RATIO) + RIGHT_BOTTOM; + FolderInfo newAppPair = FolderInfo.createAppPair(app1, app2); + // TODO (jeremysim b/274189428): Generate default title here. + newAppPair.title = "App pair 1"; + + IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache(); + MODEL_EXECUTOR.execute(() -> { + newAppPair.contents.forEach(member -> { + member.title = ""; + member.bitmap = iconCache.getDefaultIcon(newAppPair.user); + iconCache.getTitleAndIcon(member, member.usingLowResIcon()); + }); + MAIN_EXECUTOR.execute(() -> { + LauncherAccessibilityDelegate delegate = + Launcher.getLauncher(mContext).getAccessibilityDelegate(); + if (delegate != null) { + MAIN_EXECUTOR.execute(() -> delegate.addToWorkspace(newAppPair, true)); + } + }); + }); + + } + + /** + * Used to calculate the "opposite" side of the split ratio, so we can know how big the split + * apps are supposed to be. This math works because POINT_THREE_RATIO is internally represented + * by 0, POINT_FIVE_RATIO is represented by 1, and POINT_SEVEN_RATIO is represented by 2. There + * are no other supported ratios for now. + */ + private int complement(int ratio1) { + int ratio2 = FULL_RATIO - ratio1; + return ratio2; + } +} diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java index 11e1fbd391..723d8623ca 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java @@ -84,6 +84,7 @@ public class SplitSelectStateController { private final Handler mHandler; private final RecentsModel mRecentTasksModel; private final SplitAnimationController mSplitAnimationController; + private final AppPairsController mAppPairsController; private StatsLogManager mStatsLogManager; private final SystemUiProxy mSystemUiProxy; private final StateManager mStateManager; @@ -128,6 +129,7 @@ public class SplitSelectStateController { mDepthController = depthController; mRecentTasksModel = recentsModel; mSplitAnimationController = new SplitAnimationController(this); + mAppPairsController = new AppPairsController(context, this); } /** @@ -586,4 +588,8 @@ public class SplitSelectStateController { public FloatingTaskView getFirstFloatingTaskView() { return mFirstFloatingTaskView; } + + public AppPairsController getAppPairsController() { + return mAppPairsController; + } } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 4f7380a20e..0e155a24f5 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -63,6 +63,8 @@ import static com.android.launcher3.popup.SystemShortcut.INSTALL; import static com.android.launcher3.popup.SystemShortcut.WIDGETS; import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK; import static com.android.launcher3.states.RotationHelper.REQUEST_NONE; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.ItemInfoMatcher.forFolderMatch; import android.animation.Animator; @@ -2475,6 +2477,12 @@ public class Launcher extends StatefulActivity (FolderInfo) item); break; } + case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR: { + FolderInfo info = (FolderInfo) item; + // TODO (jeremysim b/274189428): Create app pair icon + view = null; + break; + } case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: { view = inflateAppWidget((LauncherAppWidgetInfo) item); diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index 1cd2a30e59..ad03221837 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -112,6 +112,10 @@ public class LauncherSettings { */ public static final int ITEM_TYPE_DEEP_SHORTCUT = 6; + /** + * The favorite is an app pair for launching split screen + */ + public static final int ITEM_TYPE_APP_PAIR = 10; // *** Below enum values are used for metrics purpose but not used in Favorites DB *** @@ -256,6 +260,7 @@ public class LauncherSettings { case ITEM_TYPE_DEEP_SHORTCUT: return "DEEPSHORTCUT"; case ITEM_TYPE_TASK: return "TASK"; case ITEM_TYPE_QSB: return "QSB"; + case ITEM_TYPE_APP_PAIR: return "APP_PAIR"; default: return String.valueOf(type); } } diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index 27119ae67b..a7a25f4dce 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -408,6 +408,14 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate { + mContext.getModelWriter().addItemToDatabase(member, fi.id, -1, -1, -1); + }); + mContext.bindItems(Collections.singletonList(fi), true, accessibility); } })); return true; diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index b0f6e13ae3..0e3b06c20f 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -190,14 +190,15 @@ public class BgDataModel { for (ItemInfo item : items) { switch (item.itemType) { case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: + case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR: folders.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. - String msg = "deleting a folder (" + item + ") which still " + - "contains items (" + info + ")"; + String msg = "deleting a collection (" + item + ") which still " + + "contains items (" + info + ")"; Log.e(TAG, msg); } } @@ -238,6 +239,7 @@ public class BgDataModel { itemsIdMap.put(item.id, item); switch (item.itemType) { case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: + case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR: folders.put(item.id, (FolderInfo) item); workspaceItems.add(item); break; @@ -250,15 +252,14 @@ public class BgDataModel { } else { if (newItem) { if (!folders.containsKey(item.container)) { - // Adding an item to a folder that doesn't exist. - String msg = "adding item: " + item + " to a folder that " + - " doesn't exist"; + // 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); } - } break; case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java index 524b769603..e5a0eb1996 100644 --- a/src/com/android/launcher3/model/data/FolderInfo.java +++ b/src/com/android/launcher3/model/data/FolderInfo.java @@ -113,6 +113,17 @@ public class FolderInfo extends ItemInfo { 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.contents.add(app1); + newAppPair.contents.add(app2); + return newAppPair; + } + /** * Add an app or shortcut * diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java index 41a603df6a..bfb80b322e 100644 --- a/src/com/android/launcher3/model/data/ItemInfo.java +++ b/src/com/android/launcher3/model/data/ItemInfo.java @@ -90,6 +90,7 @@ public class ItemInfo { * {@link Favorites#ITEM_TYPE_SHORTCUT}, * {@link Favorites#ITEM_TYPE_DEEP_SHORTCUT} * {@link Favorites#ITEM_TYPE_FOLDER}, + * {@link Favorites#ITEM_TYPE_APP_PAIR}, * {@link Favorites#ITEM_TYPE_APPWIDGET} or * {@link Favorites#ITEM_TYPE_CUSTOM_APPWIDGET}. */