mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-28 15:56:49 +00:00
Binding Taskbar directly from Launcher model am: 711c596c86
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/15082875 Change-Id: I3c0247dc339993ecb9d6c740a064231a06857c22
This commit is contained in:
@@ -262,10 +262,6 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
} else {
|
||||
removeOutlineDrawings();
|
||||
}
|
||||
|
||||
if (mLauncher.getTaskbarUIController() != null) {
|
||||
mLauncher.getTaskbarUIController().onHotseatUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
private void removeOutlineDrawings() {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -85,5 +85,6 @@ public class TaskbarControllers {
|
||||
rotationButtonController.onDestroy();
|
||||
taskbarDragLayerController.onDestroy();
|
||||
taskbarKeyguardController.onDestroy();
|
||||
taskbarViewController.onDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ItemInfo[]> 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<ItemInfo[]> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<ItemInfo> mHotseatItems = new SparseArray<>();
|
||||
private List<ItemInfo> 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<ItemInfo> addNotAnimated,
|
||||
ArrayList<ItemInfo> addAnimated) {
|
||||
boolean add1 = handleItemsAdded(addNotAnimated);
|
||||
boolean add2 = handleItemsAdded(addAnimated);
|
||||
if (add1 || add2) {
|
||||
commitItemsToUI();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) {
|
||||
if (handleItemsAdded(shortcuts)) {
|
||||
commitItemsToUI();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleItemsAdded(List<ItemInfo> 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<WorkspaceItemInfo> updated) {
|
||||
updateWorkspaceItems(updated, mContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindRestoreItemsChange(HashSet<ItemInfo> 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<ItemInfo> 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);
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<WorkspaceItemInfo> 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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<LauncherState> 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<LauncherState> 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<LauncherState> implements Launche
|
||||
@Override
|
||||
public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
|
||||
if (!updated.isEmpty()) {
|
||||
mWorkspace.updateShortcuts(updated);
|
||||
mWorkspace.updateWorkspaceItems(updated, this);
|
||||
PopupContainerWithArrow.dismissInvalidPopup(this);
|
||||
}
|
||||
}
|
||||
@@ -2689,7 +2685,7 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
|
||||
*/
|
||||
@Override
|
||||
public void bindRestoreItemsChange(HashSet<ItemInfo> updates) {
|
||||
mWorkspace.updateRestoreItems(updates);
|
||||
mWorkspace.updateRestoreItems(updates, this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<WorkspacePageIndicator>
|
||||
implements DropTarget, DragSource, View.OnTouchListener,
|
||||
DragController.DragListener, Insettable, StateHandler<LauncherState>,
|
||||
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<WorkspacePageIndicator>
|
||||
* @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<WorkspacePageIndicator>
|
||||
}
|
||||
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<WorkspacePageIndicator>
|
||||
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<WorkspacePageIndicator>
|
||||
return null;
|
||||
}
|
||||
|
||||
void updateShortcuts(List<WorkspaceItemInfo> shortcuts) {
|
||||
final HashSet<WorkspaceItemInfo> 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<PackageUserKey> updatedDots) {
|
||||
final PackageUserKey packageUserKey = new PackageUserKey(null, null);
|
||||
Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
|
||||
@@ -3253,28 +3212,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
|
||||
removeItemsByMatcher(matcher);
|
||||
}
|
||||
|
||||
public void updateRestoreItems(final HashSet<ItemInfo> 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<LauncherAppWidgetInfo> changedInfo) {
|
||||
if (!changedInfo.isEmpty()) {
|
||||
DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<WorkspaceItemInfo> items) {
|
||||
updatePreviewItems(false);
|
||||
boolean wasDotted = mDotInfo.hasDot();
|
||||
items.stream().map(mActivity::getDotInfoForItem).forEach(mDotInfo::subtractDotInfo);
|
||||
boolean isDotted = mDotInfo.hasDot();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<ItemInfo> workspaceItems = new ArrayList<>();
|
||||
ArrayList<LauncherAppWidgetInfo> 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 -> {
|
||||
|
||||
@@ -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<WorkspaceItemInfo> allUpdates) {
|
||||
|
||||
@@ -476,6 +476,11 @@ public class BgDataModel {
|
||||
default void bindAppsAdded(IntArray newScreens,
|
||||
ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated) { }
|
||||
|
||||
/**
|
||||
* Called when some persistent property of an item is modified
|
||||
*/
|
||||
default void bindItemsModified(List<ItemInfo> items) { }
|
||||
|
||||
/**
|
||||
* Binds updated incremental download progress
|
||||
*/
|
||||
|
||||
@@ -210,7 +210,7 @@ public class LoaderTask implements Runnable {
|
||||
}
|
||||
|
||||
verifyNotStopped();
|
||||
mResults.bindWorkspace();
|
||||
mResults.bindWorkspace(true /* incrementBindId */);
|
||||
logASplit(logger, "bindWorkspace");
|
||||
|
||||
mModelDelegate.workspaceLoadComplete();
|
||||
|
||||
@@ -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<ItemInfo> items, int container, int screen) {
|
||||
ArrayList<ContentValues> 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<? extends ItemInfo> 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<ContentWriter> 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.
|
||||
|
||||
@@ -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<? extends ItemInfo> items) {
|
||||
IntSet ids = new IntSet();
|
||||
items.forEach(item -> ids.add(item.id));
|
||||
return ofItemIds(ids);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<WorkspaceItemInfo> shortcuts, ActivityContext context) {
|
||||
final HashSet<WorkspaceItemInfo> 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<ItemInfo> 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user