From 711c596c86625a94a4da5ef4accb60015ce8231d Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 23 Jun 2021 12:36:18 -0700 Subject: [PATCH] Binding Taskbar directly from Launcher model This allows taskbar to be loaded even in case of 3P Launchers and removes dependency on LauncherActivity lifecycle Bug: 187353581 Bug: 188788621 Test: Manual Change-Id: I5a0988e0697b41677d4c58f0213aef14ec0c0972 --- .../HotseatPredictionController.java | 4 - .../taskbar/LauncherTaskbarUIController.java | 12 -- .../launcher3/taskbar/TaskbarControllers.java | 1 + .../taskbar/TaskbarHotseatController.java | 91 --------- .../taskbar/TaskbarModelCallbacks.java | 174 ++++++++++++++++++ .../taskbar/TaskbarViewController.java | 9 + .../uioverrides/QuickstepLauncher.java | 12 -- src/com/android/launcher3/CellLayout.java | 11 +- src/com/android/launcher3/Launcher.java | 10 +- src/com/android/launcher3/LauncherModel.java | 27 ++- src/com/android/launcher3/Workspace.java | 77 +------- src/com/android/launcher3/folder/Folder.java | 5 +- .../android/launcher3/folder/FolderIcon.java | 2 + .../launcher3/folder/FolderPagedView.java | 2 +- .../launcher3/folder/LauncherDelegate.java | 9 +- .../launcher3/model/BaseLoaderResults.java | 13 +- .../launcher3/model/BaseModelUpdateTask.java | 2 +- .../android/launcher3/model/BgDataModel.java | 5 + .../android/launcher3/model/LoaderTask.java | 2 +- .../android/launcher3/model/ModelWriter.java | 64 +++++-- .../launcher3/util/ItemInfoMatcher.java | 10 + .../util/LauncherBindableItemsContainer.java | 113 ++++++++++++ .../launcher3/compat/PromiseIconUiTest.java | 6 +- .../ui/widget/AddConfigWidgetTest.java | 4 +- .../ui/widget/RequestPinItemTest.java | 2 +- .../util/rule/LauncherActivityRule.java | 2 +- 26 files changed, 427 insertions(+), 242 deletions(-) delete mode 100644 quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java create mode 100644 quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java create mode 100644 src/com/android/launcher3/util/LauncherBindableItemsContainer.java diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index 85e5ab0a9b..b40a1d57a8 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -262,10 +262,6 @@ public class HotseatPredictionController implements DragController.DragListener, } else { removeOutlineDrawings(); } - - if (mLauncher.getTaskbarUIController() != null) { - mLauncher.getTaskbarUIController().onHotseatUpdated(); - } } private void removeOutlineDrawings() { diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 7340559ad4..8b11154636 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -48,7 +48,6 @@ public class LauncherTaskbarUIController extends TaskbarUIController { private final BaseQuickstepLauncher mLauncher; private final TaskbarStateHandler mTaskbarStateHandler; - private final TaskbarHotseatController mHotseatController; private final TaskbarActivityContext mContext; private final TaskbarDragLayer mTaskbarDragLayer; @@ -77,8 +76,6 @@ public class LauncherTaskbarUIController extends TaskbarUIController { mLauncher = launcher; mTaskbarStateHandler = mLauncher.getTaskbarStateHandler(); - mHotseatController = new TaskbarHotseatController( - mLauncher, mTaskbarView::updateHotseatItems); } @Override @@ -91,7 +88,6 @@ public class LauncherTaskbarUIController extends TaskbarUIController { MultiValueAlpha taskbarIconAlpha = mControllers.taskbarViewController.getTaskbarIconAlpha(); mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME); - mHotseatController.init(); mLauncher.setTaskbarUIController(this); mKeyguardController = taskbarControllers.taskbarKeyguardController; @@ -106,7 +102,6 @@ public class LauncherTaskbarUIController extends TaskbarUIController { mIconAlignmentForResumedState.finishAnimation(); mIconAlignmentForGestureState.finishAnimation(); - mHotseatController.cleanup(); mLauncher.getHotseat().setIconsAlpha(1f); mLauncher.setTaskbarUIController(null); } @@ -243,13 +238,6 @@ public class LauncherTaskbarUIController extends TaskbarUIController { return mControllers.taskbarStashController.onLongPressToUnstashTaskbar(); } - /** - * Should be called when one or more items in the Hotseat have changed. - */ - public void onHotseatUpdated() { - mHotseatController.onHotseatUpdated(); - } - /** * @param ev MotionEvent in screen coordinates. * @return Whether any Taskbar item could handle the given MotionEvent if given the chance. diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java index 8279a470f0..be26913554 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java @@ -85,5 +85,6 @@ public class TaskbarControllers { rotationButtonController.onDestroy(); taskbarDragLayerController.onDestroy(); taskbarKeyguardController.onDestroy(); + taskbarViewController.onDestroy(); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java deleted file mode 100644 index 91cf7efab5..0000000000 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2021 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.taskbar; - -import android.view.View; - -import com.android.launcher3.BaseQuickstepLauncher; -import com.android.launcher3.CellLayout; -import com.android.launcher3.DropTarget; -import com.android.launcher3.Hotseat; -import com.android.launcher3.ShortcutAndWidgetContainer; -import com.android.launcher3.dragndrop.DragController; -import com.android.launcher3.dragndrop.DragOptions; -import com.android.launcher3.model.data.ItemInfo; - -import java.util.function.Consumer; - -/** - * Works with TaskbarController to update the TaskbarView's Hotseat items. - */ -public class TaskbarHotseatController { - - private final BaseQuickstepLauncher mLauncher; - private final Hotseat mHotseat; - private final Consumer mTaskbarCallbacks; - private final int mNumHotseatIcons; - - private final DragController.DragListener mDragListener = new DragController.DragListener() { - @Override - public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { } - - @Override - public void onDragEnd() { - onHotseatUpdated(); - } - }; - - public TaskbarHotseatController( - BaseQuickstepLauncher launcher, Consumer taskbarCallbacks) { - mLauncher = launcher; - mHotseat = mLauncher.getHotseat(); - mTaskbarCallbacks = taskbarCallbacks; - mNumHotseatIcons = mLauncher.getDeviceProfile().numShownHotseatIcons; - } - - protected void init() { - mLauncher.getDragController().addDragListener(mDragListener); - onHotseatUpdated(); - } - - protected void cleanup() { - mLauncher.getDragController().removeDragListener(mDragListener); - } - - /** - * Called when any Hotseat item changes, and reports the new list of items to TaskbarController. - */ - protected void onHotseatUpdated() { - ShortcutAndWidgetContainer shortcutsAndWidgets = mHotseat.getShortcutsAndWidgets(); - ItemInfo[] hotseatItemInfos = new ItemInfo[mNumHotseatIcons]; - for (int i = 0; i < shortcutsAndWidgets.getChildCount(); i++) { - View child = shortcutsAndWidgets.getChildAt(i); - Object tag = shortcutsAndWidgets.getChildAt(i).getTag(); - if (tag instanceof ItemInfo) { - ItemInfo itemInfo = (ItemInfo) tag; - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); - // Since the hotseat might be laid out vertically or horizontally, use whichever - // index is higher. - int index = Math.max(lp.cellX, lp.cellY); - if (0 <= index && index < hotseatItemInfos.length) { - hotseatItemInfos[index] = itemInfo; - } - } - } - - mTaskbarCallbacks.accept(hotseatItemInfos); - } -} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java new file mode 100644 index 0000000000..fc5abd000c --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2021 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.taskbar; + +import android.util.SparseArray; +import android.view.View; + +import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.model.BgDataModel; +import com.android.launcher3.model.BgDataModel.FixedContainerItems; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.IntSet; +import com.android.launcher3.util.ItemInfoMatcher; +import com.android.launcher3.util.LauncherBindableItemsContainer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +/** + * Launcher model Callbacks for rendering taskbar. + */ +public class TaskbarModelCallbacks implements + BgDataModel.Callbacks, LauncherBindableItemsContainer { + + private final SparseArray mHotseatItems = new SparseArray<>(); + private List mPredictedItems = Collections.emptyList(); + + private final TaskbarActivityContext mContext; + private final TaskbarView mContainer; + + private boolean mBindInProgress = false; + + public TaskbarModelCallbacks( + TaskbarActivityContext context, TaskbarView container) { + mContext = context; + mContainer = container; + } + + @Override + public void startBinding() { + mBindInProgress = true; + mHotseatItems.clear(); + mPredictedItems = Collections.emptyList(); + } + + @Override + public void finishBindingItems(IntSet pagesBoundFirst) { + mBindInProgress = false; + commitItemsToUI(); + } + + @Override + public void bindAppsAdded(IntArray newScreens, ArrayList addNotAnimated, + ArrayList addAnimated) { + boolean add1 = handleItemsAdded(addNotAnimated); + boolean add2 = handleItemsAdded(addAnimated); + if (add1 || add2) { + commitItemsToUI(); + } + } + + @Override + public void bindItems(List shortcuts, boolean forceAnimateIcons) { + if (handleItemsAdded(shortcuts)) { + commitItemsToUI(); + } + } + + private boolean handleItemsAdded(List items) { + boolean modified = false; + for (ItemInfo item : items) { + if (item.container == Favorites.CONTAINER_HOTSEAT) { + mHotseatItems.put(item.screenId, item); + modified = true; + } + } + return modified; + } + + + @Override + public void bindWorkspaceItemsChanged(List updated) { + updateWorkspaceItems(updated, mContext); + } + + @Override + public void bindRestoreItemsChange(HashSet updates) { + updateRestoreItems(updates, mContext); + } + + @Override + public void mapOverItems(ItemOperator op) { + final int itemCount = mContainer.getChildCount(); + for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) { + View item = mContainer.getChildAt(itemIdx); + if (op.evaluate((ItemInfo) item.getTag(), item)) { + return; + } + } + } + + @Override + public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { + if (handleItemsRemoved(matcher)) { + commitItemsToUI(); + } + } + + private boolean handleItemsRemoved(ItemInfoMatcher matcher) { + boolean modified = false; + for (int i = mHotseatItems.size() - 1; i >= 0; i--) { + if (matcher.matchesInfo(mHotseatItems.valueAt(i))) { + modified = true; + mHotseatItems.removeAt(i); + } + } + return modified; + } + + @Override + public void bindItemsModified(List items) { + boolean removed = handleItemsRemoved(ItemInfoMatcher.ofItems(items)); + boolean added = handleItemsAdded(items); + if (removed || added) { + commitItemsToUI(); + } + } + + @Override + public void bindExtraContainerItems(FixedContainerItems item) { + if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) { + mPredictedItems = item.items; + commitItemsToUI(); + } + } + + private void commitItemsToUI() { + if (mBindInProgress) { + return; + } + + ItemInfo[] hotseatItemInfos = + new ItemInfo[mContext.getDeviceProfile().numShownHotseatIcons]; + int predictionSize = mPredictedItems.size(); + int predictionNextIndex = 0; + + for (int i = 0; i < hotseatItemInfos.length; i++) { + hotseatItemInfos[i] = mHotseatItems.get(i); + if (hotseatItemInfos[i] == null && predictionNextIndex < predictionSize) { + hotseatItemInfos[i] = mPredictedItems.get(predictionNextIndex); + hotseatItemInfos[i].screenId = i; + predictionNextIndex++; + } + } + mContainer.updateHotseatItems(hotseatItemInfos); + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index 50c26b30b2..94c0ebeaf6 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -24,6 +24,7 @@ import android.graphics.Rect; import android.view.View; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.model.data.ItemInfo; @@ -50,6 +51,8 @@ public class TaskbarViewController { private final AnimatedFloat mTaskbarIconTranslationYForStash = new AnimatedFloat( this::updateTranslationY); + private final TaskbarModelCallbacks mModelCallbacks; + // Initialized in init. private TaskbarControllers mControllers; @@ -63,6 +66,7 @@ public class TaskbarViewController { mTaskbarView = taskbarView; mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, 4); mTaskbarIconAlpha.setUpdateVisibility(true); + mModelCallbacks = new TaskbarModelCallbacks(activity, mTaskbarView); } public void init(TaskbarControllers controllers) { @@ -71,6 +75,11 @@ public class TaskbarViewController { mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize; mTaskbarIconScaleForStash.updateValue(1f); + LauncherAppState.getInstance(mActivity).getModel().addCallbacksAndLoad(mModelCallbacks); + } + + public void onDestroy() { + LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks); } public boolean areIconsVisible() { diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index ec9893c84f..2009cd75d9 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -17,7 +17,6 @@ package com.android.launcher3.uioverrides; import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED; -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_DEEP_SHORTCUT; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; @@ -55,7 +54,6 @@ import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.StatsLogManager.StatsLogger; import com.android.launcher3.model.BgDataModel.FixedContainerItems; import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory; import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory; @@ -84,7 +82,6 @@ import com.android.quickstep.views.TaskView; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.List; import java.util.Objects; import java.util.stream.Stream; @@ -230,15 +227,6 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { } } - @Override - public void bindWorkspaceItemsChanged(List updated) { - super.bindWorkspaceItemsChanged(updated); - if (getTaskbarUIController() != null && updated.stream() - .filter(w -> w.container == CONTAINER_HOTSEAT).findFirst().isPresent()) { - getTaskbarUIController().onHotseatUpdated(); - } - } - @Override public void onDestroy() { super.onDestroy(); diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 00278e479b..f4447b109a 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -2147,7 +2147,7 @@ public class CellLayout extends ViewGroup { mShakeAnimators.clear(); } - private void commitTempPlacement() { + private void commitTempPlacement(View dragView) { mTmpOccupied.copyTo(mOccupied); int screenId = Launcher.cast(mActivity).getWorkspace().getIdForScreen(this); @@ -2165,7 +2165,7 @@ public class CellLayout extends ViewGroup { ItemInfo info = (ItemInfo) child.getTag(); // We do a null check here because the item info can be null in the case of the // AllApps button in the hotseat. - if (info != null) { + if (info != null && child != dragView) { final boolean requiresDbUpdate = (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY || info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan); @@ -2327,7 +2327,7 @@ public class CellLayout extends ViewGroup { animateItemsToSolution(swapSolution, dragView, commit); if (commit) { - commitTempPlacement(); + commitTempPlacement(null); completeAndClearReorderPreviewAnimations(); setItemPlacementDirty(false); } else { @@ -2421,7 +2421,8 @@ public class CellLayout extends ViewGroup { if (!DESTRUCTIVE_REORDER && (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) { - commitTempPlacement(); + // Since the temp solution didn't update dragView, don't commit it either + commitTempPlacement(dragView); completeAndClearReorderPreviewAnimations(); setItemPlacementDirty(false); } else { @@ -2877,7 +2878,7 @@ public class CellLayout extends ViewGroup { directionVector, null, false, configuration).isSolution) { if (commitConfig) { copySolutionToTempState(configuration, null); - commitTempPlacement(); + commitTempPlacement(null); // undo marking cells occupied since there is actually nothing being placed yet. mOccupied.markCells(0, mCountY - 1, mCountX, 1, false); } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 89b44a3b7f..78a8a9747a 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -48,7 +48,6 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_RECONFIGURED; import static com.android.launcher3.model.ItemInstallQueue.FLAG_ACTIVITY_PAUSED; import static com.android.launcher3.model.ItemInstallQueue.FLAG_DRAG_AND_DROP; -import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING; import static com.android.launcher3.popup.SystemShortcut.APP_INFO; import static com.android.launcher3.popup.SystemShortcut.INSTALL; import static com.android.launcher3.popup.SystemShortcut.WIDGETS; @@ -596,7 +595,7 @@ public class Launcher extends StatefulActivity implements Launche } onDeviceProfileInitiated(); - mModelWriter = mModel.getWriter(getDeviceProfile().isVerticalBarLayout(), true); + mModelWriter = mModel.getWriter(getDeviceProfile().isVerticalBarLayout(), true, this); } public RotationHelper getRotationHelper() { @@ -2598,9 +2597,6 @@ public class Launcher extends StatefulActivity implements Launche mPendingActivityResult = null; } - ItemInstallQueue.INSTANCE.get(this) - .resumeModelPush(FLAG_LOADER_RUNNING); - int currentPage = pagesBoundFirst != null && !pagesBoundFirst.isEmpty() ? pagesBoundFirst.getArray().get(0) : PagedView.INVALID_PAGE; // When undoing the removal of the last item on a page, return to that page. @@ -2677,7 +2673,7 @@ public class Launcher extends StatefulActivity implements Launche @Override public void bindWorkspaceItemsChanged(List updated) { if (!updated.isEmpty()) { - mWorkspace.updateShortcuts(updated); + mWorkspace.updateWorkspaceItems(updated, this); PopupContainerWithArrow.dismissInvalidPopup(this); } } @@ -2689,7 +2685,7 @@ public class Launcher extends StatefulActivity implements Launche */ @Override public void bindRestoreItemsChange(HashSet updates) { - mWorkspace.updateRestoreItems(updates); + mWorkspace.updateRestoreItems(updates, this); } /** diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index eef3980f96..6966abf7e2 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -144,9 +144,10 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList)); } - public ModelWriter getWriter(boolean hasVerticalHotseat, boolean verifyChanges) { + public ModelWriter getWriter(boolean hasVerticalHotseat, boolean verifyChanges, + @Nullable Callbacks owner) { return new ModelWriter(mApp.getContext(), this, mBgDataModel, - hasVerticalHotseat, verifyChanges); + hasVerticalHotseat, verifyChanges, owner); } @Override @@ -329,7 +330,7 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi public boolean addCallbacksAndLoad(Callbacks callbacks) { synchronized (mLock) { addCallbacks(callbacks); - return startLoader(); + return startLoader(new Callbacks[] { callbacks }); } } @@ -349,26 +350,32 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi * @return true if the page could be bound synchronously. */ public boolean startLoader() { + return startLoader(new Callbacks[0]); + } + + private boolean startLoader(Callbacks[] newCallbacks) { // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems ItemInstallQueue.INSTANCE.get(mApp.getContext()) .pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING); synchronized (mLock) { - // Don't bother to start the thread if we know it's not going to do anything - final Callbacks[] callbacksList = getCallbacks(); + // If there is already one running, tell it to stop. + boolean wasRunning = stopLoader(); + boolean bindDirectly = mModelLoaded && !mIsLoaderTaskRunning; + boolean bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.length == 0; + final Callbacks[] callbacksList = bindAllCallbacks ? getCallbacks() : newCallbacks; + if (callbacksList.length > 0) { // Clear any pending bind-runnables from the synchronized load process. for (Callbacks cb : callbacksList) { MAIN_EXECUTOR.execute(cb::clearPendingBinds); } - // If there is already one running, tell it to stop. - stopLoader(); LoaderResults loaderResults = new LoaderResults( mApp, mBgDataModel, mBgAllAppsList, callbacksList); - if (mModelLoaded && !mIsLoaderTaskRunning) { + if (bindDirectly) { // Divide the set of loaded items into those that we are binding synchronously, // and everything else that is to be bound normally (asynchronously). - loaderResults.bindWorkspace(); + loaderResults.bindWorkspace(bindAllCallbacks); // For now, continue posting the binding of AllApps as there are other // issues that arise from that. loaderResults.bindAllApps(); @@ -387,7 +394,7 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi * If there is already a loader task running, tell it to stop. * @return true if an existing loader was stopped. */ - public boolean stopLoader() { + private boolean stopLoader() { synchronized (mLock) { LoaderTask oldTask = mLoaderTask; mLoaderTask = null; diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 56893949bf..a585be63a0 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -83,7 +83,6 @@ import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.folder.PreviewBackground; import com.android.launcher3.graphics.DragPreviewProvider; -import com.android.launcher3.graphics.PreloadIconDrawable; import com.android.launcher3.icons.BitmapRenderer; import com.android.launcher3.icons.FastBitmapDrawable; import com.android.launcher3.logger.LauncherAtom; @@ -105,6 +104,7 @@ import com.android.launcher3.util.Executors; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.ItemInfoMatcher; +import com.android.launcher3.util.LauncherBindableItemsContainer; import com.android.launcher3.util.OverlayEdgeEffect; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.RunnableList; @@ -123,7 +123,6 @@ import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverla import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -136,7 +135,7 @@ import java.util.stream.Collectors; public class Workspace extends PagedView implements DropTarget, DragSource, View.OnTouchListener, DragController.DragListener, Insettable, StateHandler, - WorkspaceLayoutManager { + WorkspaceLayoutManager, LauncherBindableItemsContainer { /** The value that {@link #mTransitionProgress} must be greater than for * {@link #transitionStateShouldAllowDrop()} to return true. */ @@ -3009,9 +3008,9 @@ public class Workspace extends PagedView * @param user The user of the app to match. */ public View getFirstMatchForAppClose(int preferredItemId, String packageName, UserHandle user) { - final Workspace.ItemOperator preferredItem = (ItemInfo info, View view) -> + final ItemOperator preferredItem = (ItemInfo info, View view) -> info != null && info.id == preferredItemId; - final Workspace.ItemOperator preferredItemInFolder = (info, view) -> { + final ItemOperator preferredItemInFolder = (info, view) -> { if (info instanceof FolderInfo) { FolderInfo folderInfo = (FolderInfo) info; for (WorkspaceItemInfo shortcutInfo : folderInfo.contents) { @@ -3022,14 +3021,14 @@ public class Workspace extends PagedView } return false; }; - final Workspace.ItemOperator packageAndUserAndApp = (ItemInfo info, View view) -> + final ItemOperator packageAndUserAndApp = (ItemInfo info, View view) -> info != null && info.itemType == ITEM_TYPE_APPLICATION && info.user.equals(user) && info.getTargetComponent() != null && TextUtils.equals(info.getTargetComponent().getPackageName(), packageName); - final Workspace.ItemOperator packageAndUserAndAppInFolder = (info, view) -> { + final ItemOperator packageAndUserAndAppInFolder = (info, view) -> { if (info instanceof FolderInfo) { FolderInfo folderInfo = (FolderInfo) info; for (WorkspaceItemInfo shortcutInfo : folderInfo.contents) { @@ -3148,22 +3147,7 @@ public class Workspace extends PagedView stripEmptyScreens(); } - public interface ItemOperator { - /** - * Process the next itemInfo, possibly with side-effect on the next item. - * - * @param info info for the shortcut - * @param view view for the shortcut - * @return true if done, false to continue the map - */ - boolean evaluate(ItemInfo info, View view); - } - - /** - * Map the operator over the shortcuts and widgets, return the first-non-null value. - * - * @param op the operator to map over the shortcuts - */ + @Override public void mapOverItems(ItemOperator op) { for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) { if (mapOverCellLayout(layout, op) != null) { @@ -3189,31 +3173,6 @@ public class Workspace extends PagedView return null; } - void updateShortcuts(List shortcuts) { - final HashSet updates = new HashSet<>(shortcuts); - ItemOperator op = (info, v) -> { - if (v instanceof BubbleTextView && updates.contains(info)) { - WorkspaceItemInfo si = (WorkspaceItemInfo) info; - BubbleTextView shortcut = (BubbleTextView) v; - Drawable oldIcon = shortcut.getIcon(); - boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable) - && ((PreloadIconDrawable) oldIcon).hasNotCompleted(); - shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState); - } else if (info instanceof FolderInfo && v instanceof FolderIcon) { - ((FolderIcon) v).updatePreviewItems(updates::contains); - } - - // Iterate all items - return false; - }; - - mapOverItems(op); - Folder openFolder = Folder.getOpen(mLauncher); - if (openFolder != null) { - openFolder.iterateOverItems(op); - } - } - public void updateNotificationDots(Predicate updatedDots) { final PackageUserKey packageUserKey = new PackageUserKey(null, null); Predicate matcher = info -> !packageUserKey.updateFromItemInfo(info) @@ -3253,28 +3212,6 @@ public class Workspace extends PagedView removeItemsByMatcher(matcher); } - public void updateRestoreItems(final HashSet updates) { - ItemOperator op = (info, v) -> { - if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView - && updates.contains(info)) { - ((BubbleTextView) v).applyLoadingState(false /* promiseStateChanged */); - } else if (v instanceof PendingAppWidgetHostView - && info instanceof LauncherAppWidgetInfo - && updates.contains(info)) { - ((PendingAppWidgetHostView) v).applyState(); - } else if (v instanceof FolderIcon && info instanceof FolderInfo) { - ((FolderIcon) v).updatePreviewItems(updates::contains); - } - // process all the shortcuts - return false; - }; - mapOverItems(op); - Folder folder = Folder.getOpen(mLauncher); - if (folder != null) { - folder.iterateOverItems(op); - } - } - public void widgetsRestored(final ArrayList changedInfo) { if (!changedInfo.isEmpty()) { DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo, diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 22bb56c472..7187188a13 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -75,7 +75,6 @@ import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; import com.android.launcher3.Utilities; -import com.android.launcher3.Workspace.ItemOperator; import com.android.launcher3.accessibility.AccessibleDragListenerAdapter; import com.android.launcher3.accessibility.FolderAccessibilityHelper; import com.android.launcher3.anim.KeyboardInsetAnimationCallback; @@ -94,6 +93,7 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.pageindicators.PageIndicatorDots; import com.android.launcher3.util.Executors; +import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; import com.android.launcher3.util.Thunk; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; @@ -1196,8 +1196,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } void replaceFolderWithFinalItem() { - mLauncherDelegate.replaceFolderWithFinalItem(this); - mDestroyed = true; + mDestroyed = mLauncherDelegate.replaceFolderWithFinalItem(this); } public boolean isDestroyed() { diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index d076be6c45..f005b61823 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -679,6 +679,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel @Override public void onAdd(WorkspaceItemInfo item, int rank) { + updatePreviewItems(false); boolean wasDotted = mDotInfo.hasDot(); mDotInfo.addDotInfo(mActivity.getDotInfoForItem(item)); boolean isDotted = mDotInfo.hasDot(); @@ -690,6 +691,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel @Override public void onRemove(List items) { + updatePreviewItems(false); boolean wasDotted = mDotInfo.hasDot(); items.stream().map(mActivity::getDotInfoForItem).forEach(mDotInfo::subtractDotInfo); boolean isDotted = mDotInfo.hasDot(); diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index 3d2884a24c..65991e48b2 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -41,12 +41,12 @@ import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; import com.android.launcher3.Utilities; -import com.android.launcher3.Workspace.ItemOperator; import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.pageindicators.PageIndicatorDots; import com.android.launcher3.touch.ItemClickHandler; +import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.ViewCache; import com.android.launcher3.views.ActivityContext; diff --git a/src/com/android/launcher3/folder/LauncherDelegate.java b/src/com/android/launcher3/folder/LauncherDelegate.java index f7d8e8c0d1..e599e8cb0b 100644 --- a/src/com/android/launcher3/folder/LauncherDelegate.java +++ b/src/com/android/launcher3/folder/LauncherDelegate.java @@ -93,7 +93,7 @@ public class LauncherDelegate { } } - void replaceFolderWithFinalItem(Folder folder) { + boolean replaceFolderWithFinalItem(Folder folder) { // Add the last remaining child to the workspace in place of the folder Runnable onCompleteRunnable = new Runnable() { @Override @@ -147,6 +147,7 @@ public class LauncherDelegate { } else { onCompleteRunnable.run(); } + return true; } @@ -191,7 +192,7 @@ public class LauncherDelegate { ModelWriter getModelWriter() { if (mWriter == null) { mWriter = LauncherAppState.getInstance((Context) mContext).getModel() - .getWriter(false, false); + .getWriter(false, false, null); } return mWriter; } @@ -205,7 +206,9 @@ public class LauncherDelegate { } @Override - void replaceFolderWithFinalItem(Folder folder) { } + boolean replaceFolderWithFinalItem(Folder folder) { + return false; + } @Override boolean interceptOutsideTouch(MotionEvent ev, BaseDragLayer dl, Folder folder) { diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java index 30755e398b..c202d8d137 100644 --- a/src/com/android/launcher3/model/BaseLoaderResults.java +++ b/src/com/android/launcher3/model/BaseLoaderResults.java @@ -16,6 +16,7 @@ package com.android.launcher3.model; +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.sortWorkspaceItemsSpatially; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; @@ -73,7 +74,7 @@ public abstract class BaseLoaderResults { /** * Binds all loaded data to actual views on the main thread. */ - public void bindWorkspace() { + public void bindWorkspace(boolean incrementBindId) { // Save a copy of all the bg-thread collections ArrayList workspaceItems = new ArrayList<>(); ArrayList appWidgets = new ArrayList<>(); @@ -85,7 +86,9 @@ public abstract class BaseLoaderResults { appWidgets.addAll(mBgDataModel.appWidgets); orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens()); mBgDataModel.extraItems.forEach(extraItems::add); - mBgDataModel.lastBindId++; + if (incrementBindId) { + mBgDataModel.lastBindId++; + } mMyBindingId = mBgDataModel.lastBindId; } @@ -217,7 +220,11 @@ public abstract class BaseLoaderResults { bindAppWidgets(otherAppWidgets, pendingExecutor); executeCallbacksTask(c -> c.finishBindingItems(currentScreenIndices), pendingExecutor); pendingExecutor.execute( - () -> MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT)); + () -> { + MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); + ItemInstallQueue.INSTANCE.get(mApp.getContext()) + .resumeModelPush(FLAG_LOADER_RUNNING); + }); executeCallbacksTask( c -> { diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java index ad553d5e46..a3a471775c 100644 --- a/src/com/android/launcher3/model/BaseModelUpdateTask.java +++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java @@ -90,7 +90,7 @@ public abstract class BaseModelUpdateTask implements ModelUpdateTask { public ModelWriter getModelWriter() { // Updates from model task, do not deal with icon position in hotseat. Also no need to // verify changes as the ModelTasks always push the changes to callbacks - return mModel.getWriter(false /* hasVerticalHotseat */, false /* verifyChanges */); + return mModel.getWriter(false /* hasVerticalHotseat */, false /* verifyChanges */, null); } public void bindUpdatedWorkspaceItems(List allUpdates) { diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index ba825ca300..dd4c3c37a5 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -476,6 +476,11 @@ public class BgDataModel { default void bindAppsAdded(IntArray newScreens, ArrayList addNotAnimated, ArrayList addAnimated) { } + /** + * Called when some persistent property of an item is modified + */ + default void bindItemsModified(List items) { } + /** * Binds updated incremental download progress */ diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index d5b54522a8..31ca6e7c74 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -210,7 +210,7 @@ public class LoaderTask implements Runnable { } verifyNotStopped(); - mResults.bindWorkspace(); + mResults.bindWorkspace(true /* incrementBindId */); logASplit(logger, "bindWorkspace"); mModelDelegate.workspaceLoadComplete(); diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java index 2b93118192..55edfd433a 100644 --- a/src/com/android/launcher3/model/ModelWriter.java +++ b/src/com/android/launcher3/model/ModelWriter.java @@ -23,12 +23,13 @@ import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.net.Uri; -import android.os.Handler; -import android.os.Looper; import android.util.Log; +import androidx.annotation.Nullable; + import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; +import com.android.launcher3.LauncherModel.CallbackTask; import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; @@ -36,19 +37,22 @@ import com.android.launcher3.LauncherSettings.Settings; import com.android.launcher3.Utilities; 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.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.util.ContentWriter; +import com.android.launcher3.util.Executors; import com.android.launcher3.util.ItemInfoMatcher; +import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.widget.LauncherAppWidgetHost; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; -import java.util.concurrent.Executor; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -63,7 +67,10 @@ public class ModelWriter { private final Context mContext; private final LauncherModel mModel; private final BgDataModel mBgDataModel; - private final Handler mUiHandler; + private final LooperExecutor mUiExecutor; + + @Nullable + private final Callbacks mOwner; private final boolean mHasVerticalHotseat; private final boolean mVerifyChanges; @@ -73,13 +80,15 @@ public class ModelWriter { private boolean mPreparingToUndo; public ModelWriter(Context context, LauncherModel model, BgDataModel dataModel, - boolean hasVerticalHotseat, boolean verifyChanges) { + boolean hasVerticalHotseat, boolean verifyChanges, + @Nullable Callbacks owner) { mContext = context; mModel = model; mBgDataModel = dataModel; mHasVerticalHotseat = hasVerticalHotseat; mVerifyChanges = verifyChanges; - mUiHandler = new Handler(Looper.getMainLooper()); + mOwner = owner; + mUiExecutor = Executors.MAIN_EXECUTOR; } private void updateItemInfoProps( @@ -162,6 +171,8 @@ public class ModelWriter { public void moveItemInDatabase(final ItemInfo item, int container, int screenId, int cellX, int cellY) { updateItemInfoProps(item, container, screenId, cellX, cellY); + notifyItemModified(item); + enqueueDeleteRunnable(new UpdateItemRunnable(item, () -> new ContentWriter(mContext) .put(Favorites.CONTAINER, item.container) @@ -178,6 +189,7 @@ public class ModelWriter { public void moveItemsInDatabase(final ArrayList items, int container, int screen) { ArrayList contentValues = new ArrayList<>(); int count = items.size(); + notifyOtherCallbacks(c -> c.bindItemsModified(items)); for (int i = 0; i < count; i++) { ItemInfo item = items.get(i); @@ -203,8 +215,9 @@ public class ModelWriter { updateItemInfoProps(item, container, screenId, cellX, cellY); item.spanX = spanX; item.spanY = spanY; + notifyItemModified(item); - ((Executor) MODEL_EXECUTOR).execute(new UpdateItemRunnable(item, () -> + MODEL_EXECUTOR.execute(new UpdateItemRunnable(item, () -> new ContentWriter(mContext) .put(Favorites.CONTAINER, item.container) .put(Favorites.CELLX, item.cellX) @@ -219,13 +232,18 @@ public class ModelWriter { * Update an item to the database in a specified container. */ public void updateItemInDatabase(ItemInfo item) { - ((Executor) MODEL_EXECUTOR).execute(new UpdateItemRunnable(item, () -> { + notifyItemModified(item); + MODEL_EXECUTOR.execute(new UpdateItemRunnable(item, () -> { ContentWriter writer = new ContentWriter(mContext); item.onAddToDatabase(writer); return writer; })); } + private void notifyItemModified(ItemInfo item) { + notifyOtherCallbacks(c -> c.bindItemsModified(Collections.singletonList(item))); + } + /** * Add an item to the database in a specified container. Sets the container, screen, cellX and * cellY fields of the item. Also assigns an ID to the item. @@ -236,10 +254,11 @@ public class ModelWriter { final ContentResolver cr = mContext.getContentResolver(); item.id = Settings.call(cr, Settings.METHOD_NEW_ITEM_ID).getInt(Settings.EXTRA_VALUE); + notifyOtherCallbacks(c -> c.bindItems(Collections.singletonList(item), false)); ModelVerifier verifier = new ModelVerifier(); final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); - ((Executor) MODEL_EXECUTOR).execute(() -> { + MODEL_EXECUTOR.execute(() -> { // Write the item on background thread, as some properties might have been updated in // the background. final ContentWriter writer = new ContentWriter(mContext); @@ -281,6 +300,7 @@ public class ModelWriter { (item) -> item.getTargetComponent() == null ? "" : item.getTargetComponent().getPackageName()).collect( Collectors.joining(",")), new Exception()); + notifyDelete(items); enqueueDeleteRunnable(() -> { for (ItemInfo item : items) { final Uri uri = Favorites.getContentUri(item.id); @@ -297,6 +317,7 @@ public class ModelWriter { */ public void deleteFolderAndContentsFromDatabase(final FolderInfo info) { ModelVerifier verifier = new ModelVerifier(); + notifyDelete(Collections.singleton(info)); enqueueDeleteRunnable(() -> { ContentResolver cr = mContext.getContentResolver(); @@ -315,6 +336,7 @@ public class ModelWriter { * Deletes the widget info and the widget id. */ public void deleteWidgetInfo(final LauncherAppWidgetInfo info, LauncherAppWidgetHost host) { + notifyDelete(Collections.singleton(info)); if (host != null && !info.isCustomWidget() && info.isWidgetIdAllocated()) { // Deleting an app widget ID is a void call but writes to disk before returning // to the caller... @@ -323,6 +345,10 @@ public class ModelWriter { deleteItemFromDatabase(info); } + private void notifyDelete(Collection items) { + notifyOtherCallbacks(c -> c.bindWorkspaceComponentsRemoved(ItemInfoMatcher.ofItems(items))); + } + /** * Delete operations tracked using {@link #enqueueDeleteRunnable} will only be called * if {@link #commitDelete} is called. Note that one of {@link #commitDelete()} or @@ -348,14 +374,14 @@ public class ModelWriter { if (mPreparingToUndo) { mDeleteRunnables.add(r); } else { - ((Executor) MODEL_EXECUTOR).execute(r); + MODEL_EXECUTOR.execute(r); } } public void commitDelete() { mPreparingToUndo = false; for (Runnable runnable : mDeleteRunnables) { - ((Executor) MODEL_EXECUTOR).execute(runnable); + MODEL_EXECUTOR.execute(runnable); } mDeleteRunnables.clear(); } @@ -371,6 +397,20 @@ public class ModelWriter { mModel.forceReload(); } + private void notifyOtherCallbacks(CallbackTask task) { + if (mOwner == null) { + // If the call is happening from a model, it will take care of updating the callbacks + return; + } + mUiExecutor.execute(() -> { + for (Callbacks c : mModel.getCallbacks()) { + if (c != mOwner) { + task.execute(c); + } + } + }); + } + private class UpdateItemRunnable extends UpdateItemBaseRunnable { private final ItemInfo mItem; private final Supplier mWriter; @@ -491,7 +531,7 @@ public class ModelWriter { int executeId = mBgDataModel.lastBindId; - mUiHandler.post(() -> { + mUiExecutor.post(() -> { int currentId = mBgDataModel.lastBindId; if (currentId > executeId) { // Model was already bound after job was executed. diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java index 354609d463..e8ba28f9b2 100644 --- a/src/com/android/launcher3/util/ItemInfoMatcher.java +++ b/src/com/android/launcher3/util/ItemInfoMatcher.java @@ -23,6 +23,7 @@ import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.shortcuts.ShortcutKey; +import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -89,4 +90,13 @@ public interface ItemInfoMatcher { static ItemInfoMatcher ofItemIds(IntSet ids) { return (info, cn) -> ids.contains(info.id); } + + /** + * Returns a matcher for items with provided items + */ + static ItemInfoMatcher ofItems(Collection items) { + IntSet ids = new IntSet(); + items.forEach(item -> ids.add(item.id)); + return ofItemIds(ids); + } } diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java new file mode 100644 index 0000000000..a4cb30a374 --- /dev/null +++ b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2021 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.util; + +import android.graphics.drawable.Drawable; +import android.view.View; + +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.folder.Folder; +import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.graphics.PreloadIconDrawable; +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; +import com.android.launcher3.views.ActivityContext; +import com.android.launcher3.widget.PendingAppWidgetHostView; + +import java.util.HashSet; +import java.util.List; + +/** + * Interface representing a container which can bind Launcher items with some utility methods + */ +public interface LauncherBindableItemsContainer { + + /** + * Called to update workspace items as a result of + * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindWorkspaceItemsChanged(List)} + */ + default void updateWorkspaceItems(List shortcuts, ActivityContext context) { + final HashSet updates = new HashSet<>(shortcuts); + ItemOperator op = (info, v) -> { + if (v instanceof BubbleTextView && updates.contains(info)) { + WorkspaceItemInfo si = (WorkspaceItemInfo) info; + BubbleTextView shortcut = (BubbleTextView) v; + Drawable oldIcon = shortcut.getIcon(); + boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable) + && ((PreloadIconDrawable) oldIcon).hasNotCompleted(); + shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState); + } else if (info instanceof FolderInfo && v instanceof FolderIcon) { + ((FolderIcon) v).updatePreviewItems(updates::contains); + } + + // Iterate all items + return false; + }; + + mapOverItems(op); + Folder openFolder = Folder.getOpen(context); + if (openFolder != null) { + openFolder.iterateOverItems(op); + } + } + + /** + * Called to update restored items as a result of + * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindRestoreItemsChange(HashSet)}} + */ + default void updateRestoreItems(final HashSet updates, ActivityContext context) { + ItemOperator op = (info, v) -> { + if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView + && updates.contains(info)) { + ((BubbleTextView) v).applyLoadingState(false /* promiseStateChanged */); + } else if (v instanceof PendingAppWidgetHostView + && info instanceof LauncherAppWidgetInfo + && updates.contains(info)) { + ((PendingAppWidgetHostView) v).applyState(); + } else if (v instanceof FolderIcon && info instanceof FolderInfo) { + ((FolderIcon) v).updatePreviewItems(updates::contains); + } + // process all the shortcuts + return false; + }; + + mapOverItems(op); + Folder folder = Folder.getOpen(context); + if (folder != null) { + folder.iterateOverItems(op); + } + } + + /** + * Map the operator over the shortcuts and widgets. + * + * @param op the operator to map over the shortcuts + */ + void mapOverItems(ItemOperator op); + + interface ItemOperator { + /** + * Process the next itemInfo, possibly with side-effect on the next item. + * + * @param info info for the shortcut + * @param view view for the shortcut + * @return true if done, false to continue the map + */ + boolean evaluate(ItemInfo info, View view); + } +} diff --git a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java index 33066e403e..032a7b4418 100644 --- a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java +++ b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java @@ -25,8 +25,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; -import com.android.launcher3.Workspace; import com.android.launcher3.ui.AbstractLauncherUiTest; +import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; import org.junit.After; import org.junit.Test; @@ -75,7 +75,7 @@ public class PromiseIconUiTest extends AbstractLauncherUiTest { @Test public void testPromiseIcon_addedFromEligibleSession() throws Throwable { final String appLabel = "Test Promise App " + UUID.randomUUID().toString(); - final Workspace.ItemOperator findPromiseApp = (info, view) -> + final ItemOperator findPromiseApp = (info, view) -> info != null && TextUtils.equals(info.title, appLabel); // Create and add test session @@ -97,7 +97,7 @@ public class PromiseIconUiTest extends AbstractLauncherUiTest { @Test public void testPromiseIcon_notAddedFromIneligibleSession() throws Throwable { final String appLabel = "Test Promise App " + UUID.randomUUID().toString(); - final Workspace.ItemOperator findPromiseApp = (info, view) -> + final ItemOperator findPromiseApp = (info, view) -> info != null && TextUtils.equals(info.title, appLabel); // Create and add test session without icon or label diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java index 4978c0190d..5ea5d6588a 100644 --- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java @@ -28,13 +28,13 @@ import android.view.View; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; -import com.android.launcher3.Workspace; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.tapl.Widgets; import com.android.launcher3.testcomponent.WidgetConfigActivity; import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.ui.TestViewHelpers; +import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; import com.android.launcher3.util.Wait; import com.android.launcher3.util.Wait.Condition; import com.android.launcher3.util.rule.ShellCommandRule; @@ -121,7 +121,7 @@ public class AddConfigWidgetTest extends AbstractLauncherUiTest { /** * Condition for searching widget id */ - private class WidgetSearchCondition implements Condition, Workspace.ItemOperator { + private class WidgetSearchCondition implements Condition, ItemOperator { @Override public boolean isTrue() throws Throwable { diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java index 745dc220ba..641e53aa4d 100644 --- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java +++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java @@ -30,7 +30,6 @@ import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; import com.android.launcher3.LauncherSettings.Favorites; -import com.android.launcher3.Workspace.ItemOperator; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -40,6 +39,7 @@ import com.android.launcher3.testcomponent.AppWidgetNoConfig; import com.android.launcher3.testcomponent.AppWidgetWithConfig; import com.android.launcher3.testcomponent.RequestPinItemActivity; import com.android.launcher3.ui.AbstractLauncherUiTest; +import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; import com.android.launcher3.util.Wait; import com.android.launcher3.util.Wait.Condition; import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord; diff --git a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java index 6a6ec3e41d..2093682373 100644 --- a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java +++ b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java @@ -18,7 +18,7 @@ package com.android.launcher3.util.rule; import android.app.Activity; import com.android.launcher3.Launcher; -import com.android.launcher3.Workspace.ItemOperator; +import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; import org.junit.runner.Description; import org.junit.runners.model.Statement;