• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3;
18 
19 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
20 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
21 import static com.android.launcher3.LauncherState.ALL_APPS;
22 import static com.android.launcher3.LauncherState.EDIT_MODE;
23 import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE;
24 import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED;
25 import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_INACCESSIBLE;
26 import static com.android.launcher3.LauncherState.HINT_STATE;
27 import static com.android.launcher3.LauncherState.NORMAL;
28 import static com.android.launcher3.LauncherState.SPRING_LOADED;
29 import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
30 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
31 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
32 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
33 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPELEFT;
34 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPERIGHT;
35 
36 import android.animation.Animator;
37 import android.animation.AnimatorListenerAdapter;
38 import android.animation.LayoutTransition;
39 import android.animation.ValueAnimator;
40 import android.animation.ValueAnimator.AnimatorUpdateListener;
41 import android.annotation.SuppressLint;
42 import android.app.WallpaperManager;
43 import android.appwidget.AppWidgetHostView;
44 import android.appwidget.AppWidgetProviderInfo;
45 import android.content.Context;
46 import android.content.res.Resources;
47 import android.graphics.Bitmap;
48 import android.graphics.Point;
49 import android.graphics.PointF;
50 import android.graphics.Rect;
51 import android.graphics.drawable.Drawable;
52 import android.os.Handler;
53 import android.os.Message;
54 import android.os.Parcelable;
55 import android.util.AttributeSet;
56 import android.util.Log;
57 import android.util.SparseArray;
58 import android.view.Gravity;
59 import android.view.LayoutInflater;
60 import android.view.MotionEvent;
61 import android.view.View;
62 import android.view.ViewGroup;
63 import android.view.accessibility.AccessibilityNodeInfo;
64 import android.widget.FrameLayout;
65 import android.widget.Toast;
66 
67 import androidx.annotation.Nullable;
68 import androidx.annotation.VisibleForTesting;
69 
70 import com.android.app.animation.Interpolators;
71 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
72 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
73 import com.android.launcher3.anim.PendingAnimation;
74 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
75 import com.android.launcher3.celllayout.CellPosMapper;
76 import com.android.launcher3.celllayout.CellPosMapper.CellPos;
77 import com.android.launcher3.config.FeatureFlags;
78 import com.android.launcher3.dot.FolderDotInfo;
79 import com.android.launcher3.dragndrop.DragController;
80 import com.android.launcher3.dragndrop.DragLayer;
81 import com.android.launcher3.dragndrop.DragOptions;
82 import com.android.launcher3.dragndrop.DragView;
83 import com.android.launcher3.dragndrop.DraggableView;
84 import com.android.launcher3.dragndrop.SpringLoadedDragController;
85 import com.android.launcher3.folder.Folder;
86 import com.android.launcher3.folder.FolderIcon;
87 import com.android.launcher3.folder.PreviewBackground;
88 import com.android.launcher3.graphics.DragPreviewProvider;
89 import com.android.launcher3.icons.BitmapRenderer;
90 import com.android.launcher3.icons.FastBitmapDrawable;
91 import com.android.launcher3.logger.LauncherAtom;
92 import com.android.launcher3.logging.InstanceId;
93 import com.android.launcher3.logging.StatsLogManager;
94 import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
95 import com.android.launcher3.model.data.FolderInfo;
96 import com.android.launcher3.model.data.ItemInfo;
97 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
98 import com.android.launcher3.model.data.WorkspaceItemFactory;
99 import com.android.launcher3.model.data.WorkspaceItemInfo;
100 import com.android.launcher3.pageindicators.PageIndicator;
101 import com.android.launcher3.statemanager.StateManager;
102 import com.android.launcher3.statemanager.StateManager.StateHandler;
103 import com.android.launcher3.states.StateAnimationConfig;
104 import com.android.launcher3.touch.WorkspaceTouchListener;
105 import com.android.launcher3.util.EdgeEffectCompat;
106 import com.android.launcher3.util.Executors;
107 import com.android.launcher3.util.IntArray;
108 import com.android.launcher3.util.IntSet;
109 import com.android.launcher3.util.IntSparseArrayMap;
110 import com.android.launcher3.util.LauncherBindableItemsContainer;
111 import com.android.launcher3.util.OverlayEdgeEffect;
112 import com.android.launcher3.util.PackageUserKey;
113 import com.android.launcher3.util.RunnableList;
114 import com.android.launcher3.util.Thunk;
115 import com.android.launcher3.util.WallpaperOffsetInterpolator;
116 import com.android.launcher3.widget.LauncherAppWidgetHostView;
117 import com.android.launcher3.widget.LauncherWidgetHolder;
118 import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
119 import com.android.launcher3.widget.NavigableAppWidgetHostView;
120 import com.android.launcher3.widget.PendingAddShortcutInfo;
121 import com.android.launcher3.widget.PendingAddWidgetInfo;
122 import com.android.launcher3.widget.PendingAppWidgetHostView;
123 import com.android.launcher3.widget.WidgetManagerHelper;
124 import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
125 import com.android.launcher3.widget.util.WidgetSizes;
126 import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
127 import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayCallbacks;
128 
129 import java.util.ArrayList;
130 import java.util.Iterator;
131 import java.util.List;
132 import java.util.function.Consumer;
133 import java.util.function.Predicate;
134 import java.util.stream.Collectors;
135 
136 /**
137  * The workspace is a wide area with a wallpaper and a finite number of pages.
138  * Each page contains a number of icons, folders or widgets the user can
139  * interact with. A workspace is meant to be used with a fixed width only.
140  *
141  * @param <T> Class that extends View and PageIndicator
142  */
143 public class Workspace<T extends View & PageIndicator> extends PagedView<T>
144         implements DropTarget, DragSource, View.OnTouchListener,
145         DragController.DragListener, Insettable, StateHandler<LauncherState>,
146         WorkspaceLayoutManager, LauncherBindableItemsContainer, LauncherOverlayCallbacks {
147 
148     /**
149      * The value that {@link #mTransitionProgress} must be greater than for
150      * {@link #transitionStateShouldAllowDrop()} to return true.
151      */
152     private static final float ALLOW_DROP_TRANSITION_PROGRESS = 0.25f;
153 
154     /**
155      * The value that {@link #mTransitionProgress} must be greater than for
156      * {@link #isFinishedSwitchingState()} ()} to return true.
157      */
158     private static final float FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS = 0.5f;
159 
160     private static final float SIGNIFICANT_MOVE_SCREEN_WIDTH_PERCENTAGE = 0.15f;
161 
162     private static final boolean ENFORCE_DRAG_EVENT_ORDER = false;
163 
164     private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
165 
166     public static final int DEFAULT_PAGE = 0;
167 
168     private final int mAllAppsIconSize;
169 
170     private LayoutTransition mLayoutTransition;
171     @Thunk
172     final WallpaperManager mWallpaperManager;
173 
174     protected ShortcutAndWidgetContainer mDragSourceInternal;
175 
176     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
177     @Thunk
178     public final IntSparseArrayMap<CellLayout> mWorkspaceScreens = new IntSparseArrayMap<>();
179 
180     @Thunk
181     final IntArray mScreenOrder = new IntArray();
182 
183     @Thunk
184     boolean mDeferRemoveExtraEmptyScreen = false;
185 
186     /**
187      * CellInfo for the cell that is currently being dragged
188      */
189     protected CellLayout.CellInfo mDragInfo;
190 
191     /**
192      * Target drop area calculated during last acceptDrop call.
193      */
194     @Thunk
195     int[] mTargetCell = new int[2];
196     private int mDragOverX = -1;
197     private int mDragOverY = -1;
198 
199     /**
200      * The CellLayout that is currently being dragged over
201      */
202     @Thunk
203     CellLayout mDragTargetLayout = null;
204     /**
205      * The CellLayout that we will show as highlighted
206      */
207     private CellLayout mDragOverlappingLayout = null;
208 
209     /**
210      * The CellLayout which will be dropped to
211      */
212     private CellLayout mDropToLayout = null;
213 
214     @Thunk
215     final Launcher mLauncher;
216     @Thunk
217     DragController mDragController;
218 
219     protected final int[] mTempXY = new int[2];
220     private final float[] mTempFXY = new float[2];
221     private final Rect mTempRect = new Rect();
222     @Thunk
223     float[] mDragViewVisualCenter = new float[2];
224 
225     private SpringLoadedDragController mSpringLoadedDragController;
226 
227     private boolean mIsSwitchingState = false;
228 
229     boolean mChildrenLayersEnabled = true;
230 
231     private boolean mStripScreensOnPageStopMoving = false;
232 
233     private boolean mWorkspaceFadeInAdjacentScreens;
234 
235     final WallpaperOffsetInterpolator mWallpaperOffset;
236     private boolean mUnlockWallpaperFromDefaultPageOnLayout;
237 
238     public static final int REORDER_TIMEOUT = 650;
239     protected final Alarm mReorderAlarm = new Alarm();
240     private PreviewBackground mFolderCreateBg;
241     private FolderIcon mDragOverFolderIcon = null;
242     private boolean mCreateUserFolderOnDrop = false;
243     private boolean mAddToExistingFolderOnDrop = false;
244 
245     // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
246     private float mXDown;
247     private float mYDown;
248     private View mFirstPagePinnedItem;
249     private boolean mIsEventOverFirstPagePinnedItem;
250 
251     final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
252     final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
253     final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
254 
255     // Relating to the animation of items being dropped externally
256     public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0;
257     public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1;
258     public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2;
259     public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3;
260     public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4;
261 
262     // Related to dragging, folder creation and reordering
263     private static final int DRAG_MODE_NONE = 0;
264     private static final int DRAG_MODE_CREATE_FOLDER = 1;
265     private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
266     private static final int DRAG_MODE_REORDER = 3;
267     protected int mDragMode = DRAG_MODE_NONE;
268     @Thunk
269     int mLastReorderX = -1;
270     @Thunk
271     int mLastReorderY = -1;
272 
273     private SparseArray<Parcelable> mSavedStates;
274     private final IntArray mRestoredPages = new IntArray();
275 
276     private float mCurrentScale;
277     private float mTransitionProgress;
278 
279     // State related to Launcher Overlay
280     private OverlayEdgeEffect mOverlayEdgeEffect;
281     private boolean mOverlayShown = false;
282     private float mOverlayProgress; // 1 -> overlay completely visible, 0 -> home visible
283     private final List<LauncherOverlayCallbacks> mOverlayCallbacks = new ArrayList<>();
284 
285     private boolean mForceDrawAdjacentPages = false;
286 
287     // Handles workspace state transitions
288     private final WorkspaceStateTransitionAnimation mStateTransitionAnimation;
289 
290     private final StatsLogManager mStatsLogManager;
291 
292     /**
293      * Used to inflate the Workspace from XML.
294      *
295      * @param context The application's context.
296      * @param attrs   The attributes set containing the Workspace's customization values.
297      */
Workspace(Context context, AttributeSet attrs)298     public Workspace(Context context, AttributeSet attrs) {
299         this(context, attrs, 0);
300     }
301 
302     /**
303      * Used to inflate the Workspace from XML.
304      *
305      * @param context  The application's context.
306      * @param attrs    The attributes set containing the Workspace's customization values.
307      * @param defStyle Unused.
308      */
Workspace(Context context, AttributeSet attrs, int defStyle)309     public Workspace(Context context, AttributeSet attrs, int defStyle) {
310         super(context, attrs, defStyle);
311 
312         mLauncher = Launcher.getLauncher(context);
313         mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this);
314         mWallpaperManager = WallpaperManager.getInstance(context);
315         mAllAppsIconSize = mLauncher.getDeviceProfile().allAppsIconSizePx;
316         mWallpaperOffset = new WallpaperOffsetInterpolator(this);
317 
318         setHapticFeedbackEnabled(false);
319         initWorkspace();
320 
321         // Disable multitouch across the workspace/all apps/customize tray
322         setMotionEventSplittingEnabled(true);
323         setOnTouchListener(new WorkspaceTouchListener(mLauncher, this));
324         mStatsLogManager = StatsLogManager.newInstance(context);
325     }
326 
327     @Override
setInsets(Rect insets)328     public void setInsets(Rect insets) {
329         DeviceProfile grid = mLauncher.getDeviceProfile();
330 
331         mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
332 
333         Rect padding = grid.workspacePadding;
334         setPadding(padding.left, padding.top, padding.right, padding.bottom);
335         mInsets.set(insets);
336 
337         if (mWorkspaceFadeInAdjacentScreens) {
338             // In landscape mode the page spacing is set to the default.
339             setPageSpacing(grid.edgeMarginPx);
340         } else {
341             // In portrait, we want the pages spaced such that there is no
342             // overhang of the previous / next page into the current page viewport.
343             // We assume symmetrical padding in portrait mode.
344             int maxInsets = Math.max(insets.left, insets.right);
345             int maxPadding = Math.max(grid.edgeMarginPx, padding.left + 1);
346             setPageSpacing(Math.max(maxInsets, maxPadding));
347         }
348 
349         updateCellLayoutPadding();
350         updateWorkspaceWidgetsSizes();
351         setPageIndicatorInset();
352     }
353 
setPageIndicatorInset()354     private void setPageIndicatorInset() {
355         DeviceProfile grid = mLauncher.getDeviceProfile();
356 
357         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPageIndicator.getLayoutParams();
358 
359         // Set insets for page indicator
360         Rect padding = grid.workspacePadding;
361         if (grid.isVerticalBarLayout()) {
362             lp.leftMargin = padding.left + grid.workspaceCellPaddingXPx;
363             lp.rightMargin = padding.right + grid.workspaceCellPaddingXPx;
364             lp.bottomMargin = padding.bottom;
365         } else {
366             lp.leftMargin = lp.rightMargin = 0;
367             lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
368             lp.bottomMargin = grid.hotseatBarSizePx;
369         }
370         mPageIndicator.setLayoutParams(lp);
371     }
372 
updateCellLayoutPadding()373     private void updateCellLayoutPadding() {
374         Rect padding = mLauncher.getDeviceProfile().cellLayoutPaddingPx;
375         mWorkspaceScreens.forEach(
376                 s -> s.setPadding(padding.left, padding.top, padding.right, padding.bottom));
377     }
378 
updateWorkspaceWidgetsSizes()379     private void updateWorkspaceWidgetsSizes() {
380         int numberOfScreens = mScreenOrder.size();
381         for (int i = 0; i < numberOfScreens; i++) {
382             ShortcutAndWidgetContainer shortcutAndWidgetContainer =
383                     mWorkspaceScreens.get(mScreenOrder.get(i)).getShortcutsAndWidgets();
384             int shortcutsAndWidgetCount = shortcutAndWidgetContainer.getChildCount();
385             for (int j = 0; j < shortcutsAndWidgetCount; j++) {
386                 View view = shortcutAndWidgetContainer.getChildAt(j);
387                 if (view instanceof LauncherAppWidgetHostView
388                         && view.getTag() instanceof LauncherAppWidgetInfo) {
389                     LauncherAppWidgetInfo launcherAppWidgetInfo =
390                             (LauncherAppWidgetInfo) view.getTag();
391                     WidgetSizes.updateWidgetSizeRanges((LauncherAppWidgetHostView) view,
392                             mLauncher, launcherAppWidgetInfo.spanX, launcherAppWidgetInfo.spanY);
393                 }
394             }
395         }
396     }
397 
398     /**
399      * Estimates the size of an item using spans: hSpan, vSpan.
400      *
401      * @return MAX_VALUE for each dimension if unsuccessful.
402      */
estimateItemSize(ItemInfo itemInfo)403     public int[] estimateItemSize(ItemInfo itemInfo) {
404         int[] size = new int[2];
405         if (getChildCount() > 0) {
406             // Use the first page to estimate the child position
407             CellLayout cl = (CellLayout) getChildAt(0);
408             boolean isWidget = itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
409 
410             Rect r = estimateItemPosition(cl, 0, 0, itemInfo.spanX, itemInfo.spanY);
411 
412             float scale = 1;
413             if (isWidget) {
414                 DeviceProfile profile = mLauncher.getDeviceProfile();
415                 final PointF appWidgetScale = profile.getAppWidgetScale(null);
416                 scale = Utilities.shrinkRect(r, appWidgetScale.x, appWidgetScale.y);
417             }
418             size[0] = r.width();
419             size[1] = r.height();
420 
421             if (isWidget) {
422                 size[0] /= scale;
423                 size[1] /= scale;
424             }
425             return size;
426         } else {
427             size[0] = Integer.MAX_VALUE;
428             size[1] = Integer.MAX_VALUE;
429             return size;
430         }
431     }
432 
getWallpaperOffsetForCenterPage()433     public float getWallpaperOffsetForCenterPage() {
434         return getWallpaperOffsetForPage(getPageNearestToCenterOfScreen());
435     }
436 
getWallpaperOffsetForPage(int page)437     private float getWallpaperOffsetForPage(int page) {
438         int pageScroll = getScrollForPage(page);
439         return mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
440     }
441 
442     /**
443      * Returns the number of pages used for the wallpaper parallax.
444      */
getNumPagesForWallpaperParallax()445     public int getNumPagesForWallpaperParallax() {
446         return mWallpaperOffset.getNumPagesForWallpaperParallax();
447     }
448 
estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan)449     public Rect estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan) {
450         Rect r = new Rect();
451         cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
452         return r;
453     }
454 
455     @Override
onDragStart(DragObject dragObject, DragOptions options)456     public void onDragStart(DragObject dragObject, DragOptions options) {
457         if (ENFORCE_DRAG_EVENT_ORDER) {
458             enforceDragParity("onDragStart", 0, 0);
459         }
460 
461         if (mDragInfo != null && mDragInfo.cell != null) {
462             CellLayout layout = (CellLayout) (mDragInfo.cell instanceof LauncherAppWidgetHostView
463                     ? dragObject.dragView.getContentViewParent().getParent()
464                     : mDragInfo.cell.getParent().getParent());
465             layout.markCellsAsUnoccupiedForView(mDragInfo.cell);
466         }
467 
468         updateChildrenLayersEnabled();
469 
470         // Do not add a new page if it is a accessible drag which was not started by the workspace.
471         // We do not support accessibility drag from other sources and instead provide a direct
472         // action for move/add to homescreen.
473         // When a accessible drag is started by the folder, we only allow rearranging withing the
474         // folder.
475         boolean addNewPage = !(options.isAccessibleDrag && dragObject.dragSource != this);
476         if (addNewPage) {
477             mDeferRemoveExtraEmptyScreen = false;
478             addExtraEmptyScreenOnDrag(dragObject);
479 
480             if (dragObject.dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
481                     && dragObject.dragSource != this) {
482                 // When dragging a widget from different source, move to a page which has
483                 // enough space to place this widget (after rearranging/resizing). We special case
484                 // widgets as they cannot be placed inside a folder.
485                 // Start at the current page and search right (on LTR) until finding a page with
486                 // enough space. Since an empty screen is the furthest right, a page must be found.
487                 int currentPage = getDestinationPage();
488                 for (int pageIndex = currentPage; pageIndex < getPageCount(); pageIndex++) {
489                     CellLayout page = (CellLayout) getPageAt(pageIndex);
490                     if (page.hasReorderSolution(dragObject.dragInfo)) {
491                         setCurrentPage(pageIndex);
492                         break;
493                     }
494                 }
495             }
496         }
497 
498         if (!mLauncher.isInState(EDIT_MODE)) {
499             mLauncher.getStateManager().goToState(SPRING_LOADED);
500         }
501         mStatsLogManager.logger().withItemInfo(dragObject.dragInfo)
502                 .withInstanceId(dragObject.logInstanceId)
503                 .log(LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED);
504     }
505 
isTwoPanelEnabled()506     private boolean isTwoPanelEnabled() {
507         return !FOLDABLE_SINGLE_PAGE.get() && mLauncher.mDeviceProfile.isTwoPanels;
508     }
509 
510     @Override
getPanelCount()511     public int getPanelCount() {
512         return isTwoPanelEnabled() ? 2 : super.getPanelCount();
513     }
514 
deferRemoveExtraEmptyScreen()515     public void deferRemoveExtraEmptyScreen() {
516         mDeferRemoveExtraEmptyScreen = true;
517     }
518 
519     @Override
onDragEnd()520     public void onDragEnd() {
521         if (ENFORCE_DRAG_EVENT_ORDER) {
522             enforceDragParity("onDragEnd", 0, 0);
523         }
524 
525         updateChildrenLayersEnabled();
526         StateManager<LauncherState> stateManager = mLauncher.getStateManager();
527         stateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
528             @Override
529             public void onStateTransitionComplete(LauncherState finalState) {
530                 if (finalState == NORMAL) {
531                     if (!mDeferRemoveExtraEmptyScreen) {
532                         removeExtraEmptyScreen(true /* stripEmptyScreens */);
533                     }
534                     stateManager.removeStateListener(this);
535                 }
536             }
537         });
538 
539         mDragInfo = null;
540         mDragSourceInternal = null;
541     }
542 
543     /**
544      * Initializes various states for this workspace.
545      */
initWorkspace()546     protected void initWorkspace() {
547         mCurrentPage = DEFAULT_PAGE;
548         setClipToPadding(false);
549 
550         setupLayoutTransition();
551 
552         // Set the wallpaper dimensions when Launcher starts up
553         setWallpaperDimension();
554     }
555 
setupLayoutTransition()556     private void setupLayoutTransition() {
557         // We want to show layout transitions when pages are deleted, to close the gap.
558         mLayoutTransition = new LayoutTransition();
559 
560         mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
561         mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
562         // Change the interpolators such that the fade animation plays before the move animation.
563         // This prevents empty adjacent pages to overlay during animation
564         mLayoutTransition.setInterpolator(LayoutTransition.DISAPPEARING,
565                 Interpolators.clampToProgress(Interpolators.ACCELERATE_DECELERATE, 0, 0.5f));
566         mLayoutTransition.setInterpolator(LayoutTransition.CHANGE_DISAPPEARING,
567                 Interpolators.clampToProgress(Interpolators.ACCELERATE_DECELERATE, 0.5f, 1));
568 
569         mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
570         mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
571         setLayoutTransition(mLayoutTransition);
572     }
573 
enableLayoutTransitions()574     void enableLayoutTransitions() {
575         setLayoutTransition(mLayoutTransition);
576     }
577 
disableLayoutTransitions()578     void disableLayoutTransitions() {
579         setLayoutTransition(null);
580     }
581 
582     @Override
onViewAdded(View child)583     public void onViewAdded(View child) {
584         if (!(child instanceof CellLayout)) {
585             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
586         }
587         CellLayout cl = ((CellLayout) child);
588         cl.setOnInterceptTouchListener(this);
589         cl.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
590         super.onViewAdded(child);
591     }
592 
593     /**
594      * Initializes and binds the first page
595      */
bindAndInitFirstWorkspaceScreen()596     public void bindAndInitFirstWorkspaceScreen() {
597         if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
598             return;
599         }
600 
601         // Add the first page
602         CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
603         // Always add a first page pinned widget on the first screen.
604         if (mFirstPagePinnedItem == null) {
605             // In transposed layout, we add the first page pinned widget in the Grid.
606             // As workspace does not touch the edges, we do not need a full
607             // width first page pinned widget.
608             mFirstPagePinnedItem = LayoutInflater.from(getContext())
609                     .inflate(R.layout.search_container_workspace, firstPage, false);
610         }
611 
612         int cellHSpan = mLauncher.getDeviceProfile().inv.numSearchContainerColumns;
613         CellLayoutLayoutParams lp = new CellLayoutLayoutParams(0, 0, cellHSpan, 1);
614         lp.canReorder = false;
615         if (!firstPage.addViewToCellLayout(
616                 mFirstPagePinnedItem, 0, R.id.search_container_workspace, lp, true)) {
617             Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
618             mFirstPagePinnedItem = null;
619         }
620     }
621 
removeAllWorkspaceScreens()622     public void removeAllWorkspaceScreens() {
623         // Disable all layout transitions before removing all pages to ensure that we don't get the
624         // transition animations competing with us changing the scroll when we add pages
625         disableLayoutTransitions();
626 
627         // Recycle the first page pinned widget
628         if (mFirstPagePinnedItem != null) {
629             ((ViewGroup) mFirstPagePinnedItem.getParent()).removeView(mFirstPagePinnedItem);
630         }
631 
632         // Remove the pages and clear the screen models
633         removeFolderListeners();
634         removeAllViews();
635         mScreenOrder.clear();
636         mWorkspaceScreens.clear();
637 
638         // Remove any deferred refresh callbacks
639         mLauncher.mHandler.removeCallbacksAndMessages(DeferredWidgetRefresh.class);
640 
641         // Ensure that the first page is always present
642         bindAndInitFirstWorkspaceScreen();
643 
644         // Re-enable the layout transitions
645         enableLayoutTransitions();
646     }
647 
insertNewWorkspaceScreenBeforeEmptyScreen(int screenId)648     public void insertNewWorkspaceScreenBeforeEmptyScreen(int screenId) {
649         // Find the index to insert this view into.  If the empty screen exists, then
650         // insert it before that.
651         int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
652         if (insertIndex < 0) {
653             insertIndex = mScreenOrder.size();
654         }
655         insertNewWorkspaceScreen(screenId, insertIndex);
656     }
657 
insertNewWorkspaceScreen(int screenId)658     public void insertNewWorkspaceScreen(int screenId) {
659         insertNewWorkspaceScreen(screenId, getChildCount());
660     }
661 
insertNewWorkspaceScreen(int screenId, int insertIndex)662     public CellLayout insertNewWorkspaceScreen(int screenId, int insertIndex) {
663         if (mWorkspaceScreens.containsKey(screenId)) {
664             throw new RuntimeException("Screen id " + screenId + " already exists!");
665         }
666 
667         // Inflate the cell layout, but do not add it automatically so that we can get the newly
668         // created CellLayout.
669         DeviceProfile dp = mLauncher.getDeviceProfile();
670         CellLayout newScreen;
671         if (FOLDABLE_SINGLE_PAGE.get() && dp.isTwoPanels) {
672             newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
673                     R.layout.workspace_screen_foldable, this, false /* attachToRoot */);
674         } else {
675             newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
676                     R.layout.workspace_screen, this, false /* attachToRoot */);
677         }
678 
679         mWorkspaceScreens.put(screenId, newScreen);
680         mScreenOrder.add(insertIndex, screenId);
681         addView(newScreen, insertIndex);
682         mStateTransitionAnimation.applyChildState(
683                 mLauncher.getStateManager().getState(), newScreen, insertIndex);
684 
685         updatePageScrollValues();
686         updateCellLayoutPadding();
687         return newScreen;
688     }
689 
addExtraEmptyScreenOnDrag(DragObject dragObject)690     private void addExtraEmptyScreenOnDrag(DragObject dragObject) {
691         boolean lastChildOnScreen = false;
692         boolean childOnFinalScreen = false;
693 
694         if (mDragSourceInternal != null) {
695             int dragSourceChildCount = mDragSourceInternal.getChildCount();
696 
697             // If the icon was dragged from Hotseat, there is no page pair
698             if (isTwoPanelEnabled() && !(mDragSourceInternal.getParent() instanceof Hotseat)) {
699                 int pagePairScreenId = getScreenPair(getCellPosMapper().mapModelToPresenter(
700                         dragObject.dragInfo).screenId);
701                 CellLayout pagePair = mWorkspaceScreens.get(pagePairScreenId);
702                 dragSourceChildCount += pagePair.getShortcutsAndWidgets().getChildCount();
703             }
704 
705             // When the drag view content is a LauncherAppWidgetHostView, we should increment the
706             // drag source child count by 1 because the widget in drag has been detached from its
707             // original parent, ShortcutAndWidgetContainer, and reattached to the DragView.
708             if (dragObject.dragView.getContentView() instanceof LauncherAppWidgetHostView) {
709                 dragSourceChildCount++;
710             }
711 
712             if (dragSourceChildCount == 1) {
713                 lastChildOnScreen = true;
714             }
715             CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
716             if (!FOLDABLE_SINGLE_PAGE.get() && getLeftmostVisiblePageForIndex(indexOfChild(cl))
717                     == getLeftmostVisiblePageForIndex(getPageCount() - 1)) {
718                 childOnFinalScreen = true;
719             }
720         }
721 
722         // If this is the last item on the final screen
723         if (lastChildOnScreen && childOnFinalScreen) {
724             return;
725         }
726 
727         forEachExtraEmptyPageId(extraEmptyPageId -> {
728             if (!mWorkspaceScreens.containsKey(extraEmptyPageId)) {
729                 insertNewWorkspaceScreen(extraEmptyPageId);
730             }
731         });
732     }
733 
734 
735     /**
736      * Returns if the given screenId is already in the Workspace
737      */
containsScreenId(int screenId)738     public boolean containsScreenId(int screenId) {
739         return this.mWorkspaceScreens.containsKey(screenId);
740     }
741 
742     /**
743      * Inserts extra empty pages to the end of the existing workspaces.
744      * Usually we add one extra empty screen, but when two panel home is enabled we add
745      * two extra screens.
746      **/
addExtraEmptyScreens()747     public void addExtraEmptyScreens() {
748         forEachExtraEmptyPageId(extraEmptyPageId -> {
749             if (!mWorkspaceScreens.containsKey(extraEmptyPageId)) {
750                 insertNewWorkspaceScreen(extraEmptyPageId);
751             }
752         });
753     }
754 
755     /**
756      * Calls the consumer with all the necessary extra empty page IDs.
757      * On a normal one panel Workspace that means only EXTRA_EMPTY_SCREEN_ID,
758      * but in a two panel scenario this also includes EXTRA_EMPTY_SCREEN_SECOND_ID.
759      */
forEachExtraEmptyPageId(Consumer<Integer> callback)760     private void forEachExtraEmptyPageId(Consumer<Integer> callback) {
761         callback.accept(EXTRA_EMPTY_SCREEN_ID);
762         if (isTwoPanelEnabled()) {
763             callback.accept(EXTRA_EMPTY_SCREEN_SECOND_ID);
764         }
765     }
766 
767     /**
768      * If two panel home is enabled we convert the last two screens that are visible at the same
769      * time. In other cases we only convert the last page.
770      */
convertFinalScreenToEmptyScreenIfNecessary()771     private void convertFinalScreenToEmptyScreenIfNecessary() {
772         if (mLauncher.isWorkspaceLoading()) {
773             // Invalid and dangerous operation if workspace is loading
774             return;
775         }
776 
777         int panelCount = getPanelCount();
778         if (hasExtraEmptyScreens() || mScreenOrder.size() < panelCount) {
779             return;
780         }
781 
782         SparseArray<CellLayout> finalScreens = new SparseArray<>();
783 
784         int pageCount = mScreenOrder.size();
785         // First we add the last page(s) to the finalScreens collection. The number of final pages
786         // depends on the panel count.
787         for (int pageIndex = pageCount - panelCount; pageIndex < pageCount; pageIndex++) {
788             int screenId = mScreenOrder.get(pageIndex);
789             CellLayout screen = mWorkspaceScreens.get(screenId);
790             if (screen == null || screen.getShortcutsAndWidgets().getChildCount() != 0
791                     || screen.isDropPending()) {
792                 // Final screen doesn't exist or it isn't empty or there's a pending drop
793                 return;
794             }
795             finalScreens.append(screenId, screen);
796         }
797 
798         // Then we remove the final screens from the collections (but not from the view hierarchy)
799         // and we store them as extra empty screens.
800         for (int i = 0; i < finalScreens.size(); i++) {
801             int screenId = finalScreens.keyAt(i);
802             CellLayout screen = finalScreens.get(screenId);
803 
804             mWorkspaceScreens.remove(screenId);
805             mScreenOrder.removeValue(screenId);
806 
807             int newScreenId = mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)
808                     ? EXTRA_EMPTY_SCREEN_SECOND_ID : EXTRA_EMPTY_SCREEN_ID;
809             mWorkspaceScreens.put(newScreenId, screen);
810             mScreenOrder.add(newScreenId);
811         }
812     }
813 
removeExtraEmptyScreen(boolean stripEmptyScreens)814     public void removeExtraEmptyScreen(boolean stripEmptyScreens) {
815         removeExtraEmptyScreenDelayed(0, stripEmptyScreens, null);
816     }
817 
818     /**
819      * The purpose of this method is to remove empty pages from Workspace.
820      * Empty page(s) from the end of mWorkspaceScreens will always be removed. The pages with
821      * ID = Workspace.EXTRA_EMPTY_SCREEN_IDS will be removed if there are other non-empty pages.
822      * If there are no more non-empty pages left, extra empty page(s) will either stay or get added.
823      * <p>
824      * If stripEmptyScreens is true, all empty pages (not just the ones on the end) will be removed
825      * from the Workspace, and if there are no more pages left then extra empty page(s) will be
826      * added.
827      * <p>
828      * The number of extra empty pages is equal to what getPanelCount() returns.
829      * <p>
830      * After the method returns the possible pages are:
831      * stripEmptyScreens = true : [non-empty pages, extra empty page(s) alone]
832      * stripEmptyScreens = false : [non-empty pages, empty pages (not in the end),
833      * extra empty page(s) alone]
834      */
removeExtraEmptyScreenDelayed( int delay, boolean stripEmptyScreens, Runnable onComplete)835     public void removeExtraEmptyScreenDelayed(
836             int delay, boolean stripEmptyScreens, Runnable onComplete) {
837         if (mLauncher.isWorkspaceLoading()) {
838             // Don't strip empty screens if the workspace is still loading
839             return;
840         }
841 
842         if (delay > 0) {
843             postDelayed(
844                     () -> removeExtraEmptyScreenDelayed(0, stripEmptyScreens, onComplete), delay);
845             return;
846         }
847 
848         // First we convert the last page to an extra page if the last page is empty
849         // and we don't already have an extra page.
850         convertFinalScreenToEmptyScreenIfNecessary();
851         // Then we remove the extra page(s) if they are not the only pages left in Workspace.
852         if (hasExtraEmptyScreens()) {
853             forEachExtraEmptyPageId(extraEmptyPageId -> {
854                 removeView(mWorkspaceScreens.get(extraEmptyPageId));
855                 mWorkspaceScreens.remove(extraEmptyPageId);
856                 mScreenOrder.removeValue(extraEmptyPageId);
857             });
858 
859             setCurrentPage(getNextPage());
860 
861             // Update the page indicator to reflect the removed page.
862             showPageIndicatorAtCurrentScroll();
863         }
864 
865         if (stripEmptyScreens) {
866             // This will remove all empty pages from the Workspace. If there are no more pages left,
867             // it will add extra page(s) so that users can put items on at least one page.
868             stripEmptyScreens();
869         }
870 
871         if (onComplete != null) {
872             onComplete.run();
873         }
874     }
875 
hasExtraEmptyScreens()876     public boolean hasExtraEmptyScreens() {
877         return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)
878                 && getChildCount() > getPanelCount()
879                 && (!isTwoPanelEnabled()
880                 || mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_SECOND_ID));
881     }
882 
883     /**
884      * Commits the extra empty pages then returns the screen ids of those new screens.
885      * Usually there's only one extra empty screen, but when two panel home is enabled we commit
886      * two extra screens.
887      * <p>
888      * Returns an empty IntSet in case we cannot commit any new screens.
889      */
commitExtraEmptyScreens()890     public IntSet commitExtraEmptyScreens() {
891         if (mLauncher.isWorkspaceLoading()) {
892             // Invalid and dangerous operation if workspace is loading
893             return new IntSet();
894         }
895 
896         IntSet extraEmptyPageIds = new IntSet();
897         forEachExtraEmptyPageId(extraEmptyPageId ->
898                 extraEmptyPageIds.add(commitExtraEmptyScreen(extraEmptyPageId)));
899 
900         return extraEmptyPageIds;
901     }
902 
commitExtraEmptyScreen(int emptyScreenId)903     private int commitExtraEmptyScreen(int emptyScreenId) {
904         CellLayout cl = mWorkspaceScreens.get(emptyScreenId);
905         mWorkspaceScreens.remove(emptyScreenId);
906         mScreenOrder.removeValue(emptyScreenId);
907 
908         int newScreenId = LauncherAppState.getInstance(getContext())
909                 .getModel().getModelDbController().getNewScreenId();
910         // Launcher database isn't aware of empty pages that are already bound, so we need to
911         // skip those IDs manually.
912         while (mWorkspaceScreens.containsKey(newScreenId)) {
913             newScreenId++;
914         }
915 
916         mWorkspaceScreens.put(newScreenId, cl);
917         mScreenOrder.add(newScreenId);
918 
919         return newScreenId;
920     }
921 
922     @Override
getHotseat()923     public Hotseat getHotseat() {
924         return mLauncher.getHotseat();
925     }
926 
927     @Override
onAddDropTarget(DropTarget target)928     public void onAddDropTarget(DropTarget target) {
929         mDragController.addDropTarget(target);
930     }
931 
932     @Override
getScreenWithId(int screenId)933     public CellLayout getScreenWithId(int screenId) {
934         return mWorkspaceScreens.get(screenId);
935     }
936 
getIdForScreen(CellLayout layout)937     public int getIdForScreen(CellLayout layout) {
938         int index = mWorkspaceScreens.indexOfValue(layout);
939         if (index != -1) {
940             return mWorkspaceScreens.keyAt(index);
941         }
942         return -1;
943     }
944 
getPageIndexForScreenId(int screenId)945     public int getPageIndexForScreenId(int screenId) {
946         return indexOfChild(mWorkspaceScreens.get(screenId));
947     }
948 
getCurrentPageScreenIds()949     public IntSet getCurrentPageScreenIds() {
950         return IntSet.wrap(getScreenIdForPageIndex(getCurrentPage()));
951     }
952 
getScreenIdForPageIndex(int index)953     public int getScreenIdForPageIndex(int index) {
954         if (0 <= index && index < mScreenOrder.size()) {
955             return mScreenOrder.get(index);
956         }
957         return -1;
958     }
959 
getScreenOrder()960     public IntArray getScreenOrder() {
961         return mScreenOrder;
962     }
963 
964     /**
965      * Returns the screen ID of a page that is shown together with the given page screen ID when the
966      * two panel UI is enabled.
967      */
getScreenPair(int screenId)968     public int getScreenPair(int screenId) {
969         if (screenId == EXTRA_EMPTY_SCREEN_ID) {
970             return EXTRA_EMPTY_SCREEN_SECOND_ID;
971         } else if (screenId == EXTRA_EMPTY_SCREEN_SECOND_ID) {
972             return EXTRA_EMPTY_SCREEN_ID;
973         } else if (screenId % 2 == 0) {
974             return screenId + 1;
975         } else {
976             return screenId - 1;
977         }
978     }
979 
980     /**
981      * Returns {@link CellLayout} that is shown together with the given {@link CellLayout} when the
982      * two panel UI is enabled.
983      */
984     @Nullable
getScreenPair(CellLayout cellLayout)985     public CellLayout getScreenPair(CellLayout cellLayout) {
986         if (!isTwoPanelEnabled()) {
987             return null;
988         }
989         int screenId = getIdForScreen(cellLayout);
990         if (screenId == -1) {
991             return null;
992         }
993         return getScreenWithId(getScreenPair(screenId));
994     }
995 
stripEmptyScreens()996     public void stripEmptyScreens() {
997         if (mLauncher.isWorkspaceLoading()) {
998             // Don't strip empty screens if the workspace is still loading.
999             // This is dangerous and can result in data loss.
1000             return;
1001         }
1002 
1003         if (isPageInTransition()) {
1004             mStripScreensOnPageStopMoving = true;
1005             return;
1006         }
1007 
1008         int currentPage = getNextPage();
1009         IntArray removeScreens = new IntArray();
1010         int total = mWorkspaceScreens.size();
1011         for (int i = 0; i < total; i++) {
1012             int id = mWorkspaceScreens.keyAt(i);
1013             CellLayout cl = mWorkspaceScreens.valueAt(i);
1014             // FIRST_SCREEN_ID can never be removed.
1015             if ((!FeatureFlags.QSB_ON_FIRST_SCREEN || id > FIRST_SCREEN_ID)
1016                     && cl.getShortcutsAndWidgets().getChildCount() == 0) {
1017                 removeScreens.add(id);
1018             }
1019         }
1020 
1021         // When two panel home is enabled we only remove an empty page if both visible pages are
1022         // empty.
1023         if (isTwoPanelEnabled()) {
1024             // We go through all the pages that were marked as removable and check their page pair
1025             Iterator<Integer> removeScreensIterator = removeScreens.iterator();
1026             while (removeScreensIterator.hasNext()) {
1027                 int pageToRemove = removeScreensIterator.next();
1028                 int pagePair = getScreenPair(pageToRemove);
1029                 if (!removeScreens.contains(pagePair)) {
1030                     // The page pair isn't empty so we want to remove the current page from the
1031                     // removable pages' collection
1032                     removeScreensIterator.remove();
1033                 }
1034             }
1035         }
1036 
1037         // We enforce at least one page (two pages on two panel home) to add new items to.
1038         // In the case that we remove the last such screen(s), we convert the last screen(s)
1039         // to the empty screen(s)
1040         int minScreens = getPanelCount();
1041 
1042         int pageShift = 0;
1043         for (int i = 0; i < removeScreens.size(); i++) {
1044             int id = removeScreens.get(i);
1045             CellLayout cl = mWorkspaceScreens.get(id);
1046             mWorkspaceScreens.remove(id);
1047             mScreenOrder.removeValue(id);
1048 
1049             if (getChildCount() > minScreens) {
1050                 // If this isn't the last page, just remove it
1051                 if (indexOfChild(cl) < currentPage) {
1052                     pageShift++;
1053                 }
1054                 removeView(cl);
1055             } else {
1056                 // The last page(s) should be converted into extra empty page(s)
1057                 int extraScreenId = isTwoPanelEnabled() && id % 2 == 1
1058                         // This is the right panel in a two panel scenario
1059                         ? EXTRA_EMPTY_SCREEN_SECOND_ID
1060                         // This is either the last screen in a one panel scenario, or the left panel
1061                         // in a two panel scenario when there are only two empty pages left
1062                         : EXTRA_EMPTY_SCREEN_ID;
1063                 mWorkspaceScreens.put(extraScreenId, cl);
1064                 mScreenOrder.add(extraScreenId);
1065             }
1066         }
1067 
1068         if (pageShift >= 0) {
1069             setCurrentPage(currentPage - pageShift);
1070         }
1071     }
1072 
1073     /**
1074      * Needed here because launcher has a fullscreen exclusion rect and doesn't pilfer the pointers.
1075      */
1076     @Override
onInterceptTouchEvent(MotionEvent ev)1077     public boolean onInterceptTouchEvent(MotionEvent ev) {
1078         if (isTrackpadMultiFingerSwipe(ev)) {
1079             return false;
1080         }
1081         return super.onInterceptTouchEvent(ev);
1082     }
1083 
1084     /**
1085      * Needed here because launcher has a fullscreen exclusion rect and doesn't pilfer the pointers.
1086      */
1087     @SuppressLint("ClickableViewAccessibility")
1088     @Override
onTouchEvent(MotionEvent ev)1089     public boolean onTouchEvent(MotionEvent ev) {
1090         if (isTrackpadMultiFingerSwipe(ev)) {
1091             return false;
1092         }
1093         return super.onTouchEvent(ev);
1094     }
1095 
1096     /**
1097      * Called directly from a CellLayout (not by the framework), after we've been added as a
1098      * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
1099      * that it should intercept touch events, which is not something that is normally supported.
1100      */
1101     @SuppressLint("ClickableViewAccessibility")
1102     @Override
onTouch(View v, MotionEvent event)1103     public boolean onTouch(View v, MotionEvent event) {
1104         return shouldConsumeTouch(v);
1105     }
1106 
shouldConsumeTouch(View v)1107     private boolean shouldConsumeTouch(View v) {
1108         return !workspaceIconsCanBeDragged()
1109                 || (!workspaceInModalState() && !isVisible(v));
1110     }
1111 
isSwitchingState()1112     public boolean isSwitchingState() {
1113         return mIsSwitchingState;
1114     }
1115 
1116     /**
1117      * This differs from isSwitchingState in that we take into account how far the transition
1118      * has completed.
1119      */
isFinishedSwitchingState()1120     public boolean isFinishedSwitchingState() {
1121         return !mIsSwitchingState
1122                 || (mTransitionProgress > FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS);
1123     }
1124 
1125     @Override
dispatchUnhandledMove(View focused, int direction)1126     public boolean dispatchUnhandledMove(View focused, int direction) {
1127         if (workspaceInModalState() || !isFinishedSwitchingState()) {
1128             // when the home screens are shrunken, shouldn't allow side-scrolling
1129             return false;
1130         }
1131         return super.dispatchUnhandledMove(focused, direction);
1132     }
1133 
1134     @Override
updateIsBeingDraggedOnTouchDown(MotionEvent ev)1135     protected void updateIsBeingDraggedOnTouchDown(MotionEvent ev) {
1136         super.updateIsBeingDraggedOnTouchDown(ev);
1137 
1138         mXDown = ev.getX();
1139         mYDown = ev.getY();
1140         if (mFirstPagePinnedItem != null) {
1141             final float[] tempFXY = new float[2];
1142             tempFXY[0] = mXDown;
1143             tempFXY[1] = mYDown;
1144             Utilities.mapCoordInSelfToDescendant(mFirstPagePinnedItem, this, tempFXY);
1145             mIsEventOverFirstPagePinnedItem = mFirstPagePinnedItem.getLeft() <= tempFXY[0]
1146                     && mFirstPagePinnedItem.getRight() >= tempFXY[0]
1147                     && mFirstPagePinnedItem.getTop() <= tempFXY[1]
1148                     && mFirstPagePinnedItem.getBottom() >= tempFXY[1];
1149         } else {
1150             mIsEventOverFirstPagePinnedItem = false;
1151         }
1152     }
1153 
1154     @Override
determineScrollingStart(MotionEvent ev)1155     protected void determineScrollingStart(MotionEvent ev) {
1156         if (!isFinishedSwitchingState() || mIsEventOverFirstPagePinnedItem) return;
1157 
1158         float deltaX = ev.getX() - mXDown;
1159         float absDeltaX = Math.abs(deltaX);
1160         float absDeltaY = Math.abs(ev.getY() - mYDown);
1161 
1162         if (Float.compare(absDeltaX, 0f) == 0) return;
1163 
1164         float slope = absDeltaY / absDeltaX;
1165         float theta = (float) Math.atan(slope);
1166 
1167         if (absDeltaX > mTouchSlop || absDeltaY > mTouchSlop) {
1168             cancelCurrentPageLongPress();
1169         }
1170 
1171         if (theta > MAX_SWIPE_ANGLE) {
1172             // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
1173             return;
1174         } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
1175             // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
1176             // increase the touch slop to make it harder to begin scrolling the workspace. This
1177             // results in vertically scrolling widgets to more easily. The higher the angle, the
1178             // more we increase touch slop.
1179             theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
1180             float extraRatio = (float)
1181                     Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
1182             super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
1183         } else {
1184             // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
1185             super.determineScrollingStart(ev);
1186         }
1187     }
1188 
onPageBeginTransition()1189     protected void onPageBeginTransition() {
1190         super.onPageBeginTransition();
1191         updateChildrenLayersEnabled();
1192     }
1193 
onPageEndTransition()1194     protected void onPageEndTransition() {
1195         super.onPageEndTransition();
1196         updateChildrenLayersEnabled();
1197 
1198         if (mDragController.isDragging()) {
1199             if (workspaceInModalState()) {
1200                 // If we are in springloaded mode, then force an event to check if the current touch
1201                 // is under a new page (to scroll to)
1202                 mDragController.forceTouchMove();
1203             }
1204         }
1205 
1206         if (mStripScreensOnPageStopMoving) {
1207             stripEmptyScreens();
1208             mStripScreensOnPageStopMoving = false;
1209         }
1210 
1211         // Inform the Launcher activity that the page transition ended so that it can react to the
1212         // newly visible page if it wants to.
1213         mLauncher.onPageEndTransition();
1214     }
1215 
setLauncherOverlay(LauncherOverlay overlay)1216     public void setLauncherOverlay(LauncherOverlay overlay) {
1217         final EdgeEffectCompat newEffect;
1218         if (overlay == null) {
1219             newEffect = new EdgeEffectCompat(getContext());
1220             mOverlayEdgeEffect = null;
1221         } else {
1222             newEffect = mOverlayEdgeEffect = new OverlayEdgeEffect(getContext(), overlay);
1223             overlay.setOverlayCallbacks(this);
1224         }
1225 
1226         if (mIsRtl) {
1227             mEdgeGlowRight = newEffect;
1228         } else {
1229             mEdgeGlowLeft = newEffect;
1230         }
1231         onOverlayScrollChanged(0);
1232     }
1233 
hasOverlay()1234     public boolean hasOverlay() {
1235         return mOverlayEdgeEffect != null;
1236     }
1237 
1238     @Override
snapToDestination()1239     protected void snapToDestination() {
1240         if (mOverlayEdgeEffect != null && !mOverlayEdgeEffect.isFinished()) {
1241             snapToPageImmediately(0);
1242         } else {
1243             super.snapToDestination();
1244         }
1245     }
1246 
1247     @Override
onScrollChanged(int l, int t, int oldl, int oldt)1248     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
1249         super.onScrollChanged(l, t, oldl, oldt);
1250 
1251         // Update the page indicator progress.
1252         // Unlike from other states, we show the page indicator when transitioning from HINT_STATE.
1253         boolean isSwitchingState = mIsSwitchingState
1254                 && mLauncher.getStateManager().getCurrentStableState() != HINT_STATE;
1255         boolean isTransitioning = isSwitchingState
1256                 || (getLayoutTransition() != null && getLayoutTransition().isRunning());
1257         if (!isTransitioning) {
1258             showPageIndicatorAtCurrentScroll();
1259         }
1260 
1261         updatePageAlphaValues();
1262         updatePageScrollValues();
1263         enableHwLayersOnVisiblePages();
1264     }
1265 
showPageIndicatorAtCurrentScroll()1266     public void showPageIndicatorAtCurrentScroll() {
1267         if (mPageIndicator != null) {
1268             mPageIndicator.setScroll(getScrollX(), computeMaxScroll());
1269         }
1270     }
1271 
1272     @Override
shouldFlingForVelocity(int velocityX)1273     protected boolean shouldFlingForVelocity(int velocityX) {
1274         // When the overlay is moving, the fling or settle transition is controlled by the overlay.
1275         return Float.compare(Math.abs(mOverlayProgress), 0) == 0
1276                 && super.shouldFlingForVelocity(velocityX);
1277     }
1278 
1279     /**
1280      * The overlay scroll is being controlled locally, just update our overlay effect
1281      */
1282     @Override
onOverlayScrollChanged(float scroll)1283     public void onOverlayScrollChanged(float scroll) {
1284         mOverlayProgress = Utilities.boundToRange(scroll, 0, 1);
1285         if (Float.compare(mOverlayProgress, 1f) == 0) {
1286             if (!mOverlayShown) {
1287                 mOverlayShown = true;
1288                 mLauncher.onOverlayVisibilityChanged(true);
1289             }
1290         } else if (Float.compare(mOverlayProgress, 0f) == 0) {
1291             if (mOverlayShown) {
1292                 mOverlayShown = false;
1293                 mLauncher.onOverlayVisibilityChanged(false);
1294             }
1295         }
1296         int count = mOverlayCallbacks.size();
1297         for (int i = 0; i < count; i++) {
1298             mOverlayCallbacks.get(i).onOverlayScrollChanged(mOverlayProgress);
1299         }
1300     }
1301 
1302     /**
1303      * Adds a callback for receiving overlay progress
1304      */
addOverlayCallback(LauncherOverlayCallbacks callback)1305     public void addOverlayCallback(LauncherOverlayCallbacks callback) {
1306         mOverlayCallbacks.add(callback);
1307         callback.onOverlayScrollChanged(mOverlayProgress);
1308     }
1309 
1310     /**
1311      * Removes a previously added overlay progress callback
1312      */
removeOverlayCallback(LauncherOverlayCallbacks callback)1313     public void removeOverlayCallback(LauncherOverlayCallbacks callback) {
1314         mOverlayCallbacks.remove(callback);
1315     }
1316 
1317     @Override
notifyPageSwitchListener(int prevPage)1318     protected void notifyPageSwitchListener(int prevPage) {
1319         super.notifyPageSwitchListener(prevPage);
1320         if (prevPage != mCurrentPage) {
1321             StatsLogManager.EventEnum event = (prevPage < mCurrentPage)
1322                     ? LAUNCHER_SWIPERIGHT : LAUNCHER_SWIPELEFT;
1323             mLauncher.getStatsLogManager().logger()
1324                     .withSrcState(LAUNCHER_STATE_HOME)
1325                     .withDstState(LAUNCHER_STATE_HOME)
1326                     .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
1327                             .setWorkspace(
1328                                     LauncherAtom.WorkspaceContainer.newBuilder()
1329                                             .setPageIndex(prevPage)).build())
1330                     .log(event);
1331         }
1332     }
1333 
setWallpaperDimension()1334     protected void setWallpaperDimension() {
1335         Executors.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1336             @Override
1337             public void run() {
1338                 final Point size = LauncherAppState.getIDP(getContext()).defaultWallpaperSize;
1339                 if (size.x != mWallpaperManager.getDesiredMinimumWidth()
1340                         || size.y != mWallpaperManager.getDesiredMinimumHeight()) {
1341                     mWallpaperManager.suggestDesiredDimensions(size.x, size.y);
1342                 }
1343             }
1344         });
1345     }
1346 
lockWallpaperToDefaultPage()1347     public void lockWallpaperToDefaultPage() {
1348         mWallpaperOffset.setLockToDefaultPage(true);
1349     }
1350 
unlockWallpaperFromDefaultPageOnNextLayout()1351     public void unlockWallpaperFromDefaultPageOnNextLayout() {
1352         if (mWallpaperOffset.isLockedToDefaultPage()) {
1353             mUnlockWallpaperFromDefaultPageOnLayout = true;
1354             requestLayout();
1355         }
1356     }
1357 
1358     @Override
computeScroll()1359     public void computeScroll() {
1360         super.computeScroll();
1361         mWallpaperOffset.syncWithScroll();
1362     }
1363 
1364     @Override
announceForAccessibility(CharSequence text)1365     public void announceForAccessibility(CharSequence text) {
1366         // Don't announce if apps is on top of us.
1367         if (!mLauncher.isInState(ALL_APPS)) {
1368             super.announceForAccessibility(text);
1369         }
1370     }
1371 
updatePageAlphaValues()1372     private void updatePageAlphaValues() {
1373         // We need to check the isDragging case because updatePageAlphaValues is called between
1374         // goToState(SPRING_LOADED) and onStartStateTransition.
1375         if (!workspaceInModalState() && !mIsSwitchingState && !mDragController.isDragging()) {
1376             int screenCenter = getScrollX() + getMeasuredWidth() / 2;
1377             for (int i = 0; i < getChildCount(); i++) {
1378                 CellLayout child = (CellLayout) getChildAt(i);
1379                 if (child != null) {
1380                     float scrollProgress = getScrollProgress(screenCenter, child, i);
1381                     float alpha = 1 - Math.abs(scrollProgress);
1382                     if (mWorkspaceFadeInAdjacentScreens) {
1383                         child.getShortcutsAndWidgets().setAlpha(alpha);
1384                     } else {
1385                         // Pages that are off-screen aren't important for accessibility.
1386                         child.getShortcutsAndWidgets().setImportantForAccessibility(
1387                                 alpha > 0 ? IMPORTANT_FOR_ACCESSIBILITY_AUTO
1388                                         : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
1389                     }
1390                 }
1391             }
1392         }
1393     }
1394 
updatePageScrollValues()1395     private void updatePageScrollValues() {
1396         int screenCenter = getScrollX() + getMeasuredWidth() / 2;
1397         for (int i = 0; i < getChildCount(); i++) {
1398             CellLayout child = (CellLayout) getChildAt(i);
1399             if (child != null) {
1400                 float scrollProgress = getScrollProgress(screenCenter, child, i);
1401                 child.setScrollProgress(scrollProgress);
1402             }
1403         }
1404     }
1405 
onAttachedToWindow()1406     protected void onAttachedToWindow() {
1407         super.onAttachedToWindow();
1408         mWallpaperOffset.setWindowToken(getWindowToken());
1409         computeScroll();
1410     }
1411 
onDetachedFromWindow()1412     protected void onDetachedFromWindow() {
1413         super.onDetachedFromWindow();
1414         mWallpaperOffset.setWindowToken(null);
1415     }
1416 
1417     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)1418     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1419         if (mUnlockWallpaperFromDefaultPageOnLayout) {
1420             mWallpaperOffset.setLockToDefaultPage(false);
1421             mUnlockWallpaperFromDefaultPageOnLayout = false;
1422         }
1423         if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
1424             mWallpaperOffset.syncWithScroll();
1425             mWallpaperOffset.jumpToFinal();
1426         }
1427         super.onLayout(changed, left, top, right, bottom);
1428         updatePageAlphaValues();
1429     }
1430 
1431     @Override
getDescendantFocusability()1432     public int getDescendantFocusability() {
1433         if (workspaceInModalState()) {
1434             return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
1435         }
1436         return super.getDescendantFocusability();
1437     }
1438 
workspaceInModalState()1439     private boolean workspaceInModalState() {
1440         return !mLauncher.isInState(NORMAL);
1441     }
1442 
workspaceInScrollableState()1443     private boolean workspaceInScrollableState() {
1444         return mLauncher.isInState(SPRING_LOADED) || mLauncher.isInState(EDIT_MODE)
1445                 || !workspaceInModalState();
1446     }
1447 
1448     /**
1449      * Returns whether a drag should be allowed to be started from the current workspace state.
1450      */
workspaceIconsCanBeDragged()1451     public boolean workspaceIconsCanBeDragged() {
1452         return mLauncher.getStateManager().getState().hasFlag(FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED);
1453     }
1454 
updateChildrenLayersEnabled()1455     private void updateChildrenLayersEnabled() {
1456         boolean enableChildrenLayers = mIsSwitchingState || isPageInTransition();
1457 
1458         if (enableChildrenLayers != mChildrenLayersEnabled) {
1459             mChildrenLayersEnabled = enableChildrenLayers;
1460             if (mChildrenLayersEnabled) {
1461                 enableHwLayersOnVisiblePages();
1462             } else {
1463                 for (int i = 0; i < getPageCount(); i++) {
1464                     final CellLayout cl = (CellLayout) getChildAt(i);
1465                     cl.enableHardwareLayer(false);
1466                 }
1467             }
1468         }
1469     }
1470 
enableHwLayersOnVisiblePages()1471     private void enableHwLayersOnVisiblePages() {
1472         if (mChildrenLayersEnabled) {
1473             final int screenCount = getChildCount();
1474 
1475             final int[] visibleScreens = getVisibleChildrenRange();
1476             int leftScreen = visibleScreens[0];
1477             int rightScreen = visibleScreens[1];
1478             if (mForceDrawAdjacentPages) {
1479                 // In overview mode, make sure that the two side pages are visible.
1480                 leftScreen = Utilities.boundToRange(getCurrentPage() - 1, 0, rightScreen);
1481                 rightScreen = Utilities.boundToRange(getCurrentPage() + 1,
1482                         leftScreen, getPageCount() - 1);
1483             }
1484 
1485             if (leftScreen == rightScreen) {
1486                 // make sure we're caching at least two pages always
1487                 if (rightScreen < screenCount - 1) {
1488                     rightScreen++;
1489                 } else if (leftScreen > 0) {
1490                     leftScreen--;
1491                 }
1492             }
1493 
1494             for (int i = 0; i < screenCount; i++) {
1495                 final CellLayout layout = (CellLayout) getPageAt(i);
1496                 // enable layers between left and right screen inclusive.
1497                 boolean enableLayer = leftScreen <= i && i <= rightScreen;
1498                 layout.enableHardwareLayer(enableLayer);
1499             }
1500         }
1501     }
1502 
onWallpaperTap(MotionEvent ev)1503     public void onWallpaperTap(MotionEvent ev) {
1504         final int[] position = mTempXY;
1505         getLocationOnScreen(position);
1506 
1507         int pointerIndex = ev.getActionIndex();
1508         position[0] += (int) ev.getX(pointerIndex);
1509         position[1] += (int) ev.getY(pointerIndex);
1510 
1511         mWallpaperManager.sendWallpaperCommand(getWindowToken(),
1512                 ev.getAction() == MotionEvent.ACTION_UP
1513                         ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
1514                 position[0], position[1], 0, null);
1515     }
1516 
onStartStateTransition()1517     private void onStartStateTransition() {
1518         mIsSwitchingState = true;
1519         mTransitionProgress = 0;
1520 
1521         updateChildrenLayersEnabled();
1522     }
1523 
onEndStateTransition()1524     private void onEndStateTransition() {
1525         mIsSwitchingState = false;
1526         mForceDrawAdjacentPages = false;
1527         mTransitionProgress = 1;
1528 
1529         updateChildrenLayersEnabled();
1530         updateAccessibilityFlags();
1531     }
1532 
1533     /**
1534      * Sets the current workspace {@link LauncherState} and updates the UI without any animations
1535      */
1536     @Override
setState(LauncherState toState)1537     public void setState(LauncherState toState) {
1538         onStartStateTransition();
1539         mLauncher.getStateManager().getState().onLeavingState(mLauncher, toState);
1540         mStateTransitionAnimation.setState(toState);
1541         onEndStateTransition();
1542     }
1543 
1544     /**
1545      * Sets the current workspace {@link LauncherState}, then animates the UI
1546      */
1547     @Override
setStateWithAnimation( LauncherState toState, StateAnimationConfig config, PendingAnimation animation)1548     public void setStateWithAnimation(
1549             LauncherState toState, StateAnimationConfig config, PendingAnimation animation) {
1550         StateTransitionListener listener = new StateTransitionListener();
1551         mLauncher.getStateManager().getState().onLeavingState(mLauncher, toState);
1552         mStateTransitionAnimation.setStateWithAnimation(toState, config, animation);
1553 
1554         // Invalidate the pages now, so that we have the visible pages before the
1555         // animation is started
1556         if (toState.hasFlag(FLAG_MULTI_PAGE)) {
1557             mForceDrawAdjacentPages = true;
1558         }
1559         invalidate(); // This will call dispatchDraw(), which calls getVisiblePages().
1560 
1561         ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1);
1562         stepAnimator.addUpdateListener(listener);
1563         stepAnimator.addListener(listener);
1564         animation.add(stepAnimator);
1565     }
1566 
getStateTransitionAnimation()1567     public WorkspaceStateTransitionAnimation getStateTransitionAnimation() {
1568         return mStateTransitionAnimation;
1569     }
1570 
updateAccessibilityFlags()1571     public void updateAccessibilityFlags() {
1572         // TODO: Update the accessibility flags appropriately when dragging.
1573         int accessibilityFlag =
1574                 mLauncher.getStateManager().getState().hasFlag(FLAG_WORKSPACE_INACCESSIBLE)
1575                         ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
1576                         : IMPORTANT_FOR_ACCESSIBILITY_AUTO;
1577         if (!mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
1578             int total = getPageCount();
1579             for (int i = 0; i < total; i++) {
1580                 updateAccessibilityFlags(accessibilityFlag, (CellLayout) getPageAt(i));
1581             }
1582             setImportantForAccessibility(accessibilityFlag);
1583         }
1584     }
1585 
1586     @Override
createAccessibilityNodeInfo()1587     public AccessibilityNodeInfo createAccessibilityNodeInfo() {
1588         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
1589             // TAPL tests verify that workspace is not present in Overview and AllApps states.
1590             // TAPL can work only if UIDevice is set up as setCompressedLayoutHeirarchy(false).
1591             // Hiding workspace from the tests when it's
1592             // IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS.
1593             return AccessibilityNodeInfo.obtain();
1594         }
1595         return super.createAccessibilityNodeInfo();
1596     }
1597 
updateAccessibilityFlags(int accessibilityFlag, CellLayout page)1598     private void updateAccessibilityFlags(int accessibilityFlag, CellLayout page) {
1599         page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
1600         page.getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
1601         page.setContentDescription(null);
1602         page.setAccessibilityDelegate(null);
1603     }
1604 
startDrag(CellLayout.CellInfo cellInfo, DragOptions options)1605     public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) {
1606         View child = cellInfo.cell;
1607 
1608         mDragInfo = cellInfo;
1609         child.setVisibility(INVISIBLE);
1610 
1611         if (options.isAccessibleDrag) {
1612             mDragController.addDragListener(
1613                     new AccessibleDragListenerAdapter(this, WorkspaceAccessibilityHelper::new) {
1614                         @Override
1615                         protected void enableAccessibleDrag(boolean enable) {
1616                             super.enableAccessibleDrag(enable);
1617                             setEnableForLayout(mLauncher.getHotseat(), enable);
1618                         }
1619                     });
1620         }
1621 
1622         beginDragShared(child, this, options);
1623     }
1624 
beginDragShared(View child, DragSource source, DragOptions options)1625     public void beginDragShared(View child, DragSource source, DragOptions options) {
1626         Object dragObject = child.getTag();
1627         if (!(dragObject instanceof ItemInfo)) {
1628             String msg = "Drag started with a view that has no tag set. This "
1629                     + "will cause a crash (issue 11627249) down the line. "
1630                     + "View: " + child + "  tag: " + child.getTag();
1631             throw new IllegalStateException(msg);
1632         }
1633         beginDragShared(child, null, source, (ItemInfo) dragObject,
1634                 new DragPreviewProvider(child), options);
1635     }
1636 
1637     /**
1638      * Core functionality for beginning a drag operation for an item that will be dropped within
1639      * the workspace
1640      */
beginDragShared(View child, DraggableView draggableView, DragSource source, ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions dragOptions)1641     public DragView beginDragShared(View child, DraggableView draggableView, DragSource source,
1642             ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions dragOptions) {
1643 
1644         float iconScale = 1f;
1645         if (child instanceof BubbleTextView) {
1646             Drawable icon = ((BubbleTextView) child).getIcon();
1647             if (icon instanceof FastBitmapDrawable) {
1648                 iconScale = ((FastBitmapDrawable) icon).getAnimatedScale();
1649             }
1650         }
1651 
1652         // Clear the pressed state if necessary
1653         child.clearFocus();
1654         child.setPressed(false);
1655         if (child instanceof BubbleTextView) {
1656             BubbleTextView icon = (BubbleTextView) child;
1657             icon.clearPressedBackground();
1658         }
1659 
1660         if (draggableView == null && child instanceof DraggableView) {
1661             draggableView = (DraggableView) child;
1662         }
1663 
1664         final View contentView = previewProvider.getContentView();
1665         final float scale;
1666         // The draggable drawable follows the touch point around on the screen
1667         final Drawable drawable;
1668         if (contentView == null) {
1669             drawable = previewProvider.createDrawable();
1670             scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
1671         } else {
1672             drawable = null;
1673             scale = previewProvider.getScaleAndPosition(contentView, mTempXY);
1674         }
1675 
1676         int dragLayerX = mTempXY[0];
1677         int dragLayerY = mTempXY[1];
1678 
1679         Rect dragRect = new Rect();
1680 
1681         if (draggableView != null) {
1682             draggableView.getSourceVisualDragBounds(dragRect);
1683             dragLayerY += dragRect.top;
1684         }
1685 
1686 
1687         if (child.getParent() instanceof ShortcutAndWidgetContainer) {
1688             mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
1689         }
1690 
1691         if (child instanceof BubbleTextView) {
1692             BubbleTextView btv = (BubbleTextView) child;
1693             if (!dragOptions.isAccessibleDrag) {
1694                 dragOptions.preDragCondition = btv.startLongPressAction();
1695             }
1696             if (btv.isDisplaySearchResult()) {
1697                 dragOptions.preDragEndScale = (float) mAllAppsIconSize / btv.getIconSize();
1698             }
1699         }
1700 
1701         if (dragOptions.preDragCondition != null) {
1702             int xDragOffSet = dragOptions.preDragCondition.getDragOffset().x;
1703             int yDragOffSet = dragOptions.preDragCondition.getDragOffset().y;
1704             if (xDragOffSet != 0 || yDragOffSet != 0) {
1705                 dragLayerX += xDragOffSet;
1706                 dragLayerY += yDragOffSet;
1707             }
1708         }
1709 
1710         final DragView dv;
1711         if (contentView instanceof View) {
1712             if (contentView instanceof LauncherAppWidgetHostView) {
1713                 mDragController.addDragListener(new AppWidgetHostViewDragListener(mLauncher));
1714             }
1715             dv = mDragController.startDrag(
1716                     contentView,
1717                     draggableView,
1718                     dragLayerX,
1719                     dragLayerY,
1720                     source,
1721                     dragObject,
1722                     dragRect,
1723                     scale * iconScale,
1724                     scale,
1725                     dragOptions);
1726         } else {
1727             dv = mDragController.startDrag(
1728                     drawable,
1729                     draggableView,
1730                     dragLayerX,
1731                     dragLayerY,
1732                     source,
1733                     dragObject,
1734                     dragRect,
1735                     scale * iconScale,
1736                     scale,
1737                     dragOptions);
1738         }
1739         return dv;
1740     }
1741 
transitionStateShouldAllowDrop()1742     private boolean transitionStateShouldAllowDrop() {
1743         return (!isSwitchingState() || mTransitionProgress > ALLOW_DROP_TRANSITION_PROGRESS) &&
1744                 workspaceIconsCanBeDragged();
1745     }
1746 
1747     /**
1748      * {@inheritDoc}
1749      */
1750     @Override
acceptDrop(DragObject d)1751     public boolean acceptDrop(DragObject d) {
1752         // If it's an external drop (e.g. from All Apps), check if it should be accepted
1753         CellLayout dropTargetLayout = mDropToLayout;
1754         if (d.dragSource != this) {
1755             // Don't accept the drop if we're not over a valid drop target at time of drop
1756             if (dropTargetLayout == null) {
1757                 return false;
1758             }
1759             if (!transitionStateShouldAllowDrop()) return false;
1760 
1761             mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
1762 
1763             // We want the point to be mapped to the dragTarget.
1764             mapPointFromDropLayout(dropTargetLayout, mDragViewVisualCenter);
1765 
1766             int spanX;
1767             int spanY;
1768             if (mDragInfo != null) {
1769                 final CellLayout.CellInfo dragCellInfo = mDragInfo;
1770                 spanX = dragCellInfo.spanX;
1771                 spanY = dragCellInfo.spanY;
1772             } else {
1773                 spanX = d.dragInfo.spanX;
1774                 spanY = d.dragInfo.spanY;
1775             }
1776 
1777             int minSpanX = spanX;
1778             int minSpanY = spanY;
1779             if (d.dragInfo instanceof PendingAddWidgetInfo) {
1780                 minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
1781                 minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
1782             }
1783 
1784             mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
1785                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
1786                     mTargetCell);
1787             float distance = dropTargetLayout.getDistanceFromWorkspaceCellVisualCenter(
1788                     mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
1789             if (mCreateUserFolderOnDrop && willCreateUserFolder(d.dragInfo,
1790                     dropTargetLayout, mTargetCell, distance, true)) {
1791                 return true;
1792             }
1793 
1794             if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder(d.dragInfo,
1795                     dropTargetLayout, mTargetCell, distance)) {
1796                 return true;
1797             }
1798 
1799             int[] resultSpan = new int[2];
1800             mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
1801                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
1802                     null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
1803             boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
1804 
1805             // Don't accept the drop if there's no room for the item
1806             if (!foundCell) {
1807                 onNoCellFound(dropTargetLayout, d.dragInfo, d.logInstanceId);
1808                 return false;
1809             }
1810         }
1811 
1812         int screenId = getIdForScreen(dropTargetLayout);
1813         if (Workspace.EXTRA_EMPTY_SCREEN_IDS.contains(screenId)) {
1814             commitExtraEmptyScreens();
1815         }
1816 
1817         return true;
1818     }
1819 
willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float distance, boolean considerTimeout)1820     boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell,
1821                                  float distance, boolean considerTimeout) {
1822         if (distance > target.getFolderCreationRadius(targetCell)) return false;
1823         View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
1824         return willCreateUserFolder(info, dropOverView, considerTimeout);
1825     }
1826 
willCreateUserFolder(ItemInfo info, View dropOverView, boolean considerTimeout)1827     boolean willCreateUserFolder(ItemInfo info, View dropOverView, boolean considerTimeout) {
1828         if (dropOverView != null) {
1829             CellLayoutLayoutParams lp = (CellLayoutLayoutParams) dropOverView.getLayoutParams();
1830             if (lp.useTmpCoords && (lp.getTmpCellX() != lp.getCellX()
1831                     || lp.getTmpCellY() != lp.getCellY())) {
1832                 return false;
1833             }
1834         }
1835 
1836         boolean hasntMoved = false;
1837         if (mDragInfo != null) {
1838             hasntMoved = dropOverView == mDragInfo.cell;
1839         }
1840 
1841         if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
1842             return false;
1843         }
1844 
1845         boolean aboveShortcut = (dropOverView.getTag() instanceof WorkspaceItemInfo
1846                 && ((WorkspaceItemInfo) dropOverView.getTag()).container
1847                 != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION);
1848         boolean willBecomeShortcut =
1849                 (info.itemType == ITEM_TYPE_APPLICATION ||
1850                         info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT);
1851 
1852         return (aboveShortcut && willBecomeShortcut);
1853     }
1854 
willAddToExistingUserFolder(ItemInfo dragInfo, CellLayout target, int[] targetCell, float distance)1855     boolean willAddToExistingUserFolder(ItemInfo dragInfo, CellLayout target, int[] targetCell,
1856                                         float distance) {
1857         if (distance > target.getFolderCreationRadius(targetCell)) return false;
1858         View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
1859         return willAddToExistingUserFolder(dragInfo, dropOverView);
1860 
1861     }
1862 
willAddToExistingUserFolder(ItemInfo dragInfo, View dropOverView)1863     boolean willAddToExistingUserFolder(ItemInfo dragInfo, View dropOverView) {
1864         if (dropOverView != null) {
1865             CellLayoutLayoutParams lp = (CellLayoutLayoutParams) dropOverView.getLayoutParams();
1866             if (lp.useTmpCoords && (lp.getTmpCellX() != lp.getCellX()
1867                     || lp.getTmpCellY() != lp.getCellY())) {
1868                 return false;
1869             }
1870         }
1871 
1872         if (dropOverView instanceof FolderIcon) {
1873             FolderIcon fi = (FolderIcon) dropOverView;
1874             if (fi.acceptDrop(dragInfo)) {
1875                 return true;
1876             }
1877         }
1878         return false;
1879     }
1880 
createUserFolderIfNecessary(View newView, int container, CellLayout target, int[] targetCell, float distance, boolean external, DragObject d)1881     boolean createUserFolderIfNecessary(View newView, int container, CellLayout target,
1882             int[] targetCell, float distance, boolean external, DragObject d) {
1883         if (distance > target.getFolderCreationRadius(targetCell)) return false;
1884         View v = target.getChildAt(targetCell[0], targetCell[1]);
1885 
1886         boolean hasntMoved = false;
1887         if (mDragInfo != null) {
1888             CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
1889             hasntMoved = (mDragInfo.cellX == targetCell[0] &&
1890                     mDragInfo.cellY == targetCell[1]) && (cellParent == target);
1891         }
1892 
1893         if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
1894         mCreateUserFolderOnDrop = false;
1895         final int screenId = getIdForScreen(target);
1896 
1897         boolean aboveShortcut = (v.getTag() instanceof WorkspaceItemInfo);
1898         boolean willBecomeShortcut = (newView.getTag() instanceof WorkspaceItemInfo);
1899 
1900         if (aboveShortcut && willBecomeShortcut) {
1901             WorkspaceItemInfo sourceInfo = (WorkspaceItemInfo) newView.getTag();
1902             WorkspaceItemInfo destInfo = (WorkspaceItemInfo) v.getTag();
1903             // if the drag started here, we need to remove it from the workspace
1904             if (!external) {
1905                 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
1906             }
1907 
1908             Rect folderLocation = new Rect();
1909             float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
1910             target.removeView(v);
1911             mStatsLogManager.logger().withItemInfo(destInfo).withInstanceId(d.logInstanceId)
1912                     .log(LauncherEvent.LAUNCHER_ITEM_DROP_FOLDER_CREATED);
1913             FolderIcon fi = mLauncher.addFolder(target, container, screenId, targetCell[0],
1914                     targetCell[1]);
1915             destInfo.cellX = -1;
1916             destInfo.cellY = -1;
1917             sourceInfo.cellX = -1;
1918             sourceInfo.cellY = -1;
1919 
1920             // If the dragView is null, we can't animate
1921             boolean animate = d != null;
1922             if (animate) {
1923                 // In order to keep everything continuous, we hand off the currently rendered
1924                 // folder background to the newly created icon. This preserves animation state.
1925                 fi.setFolderBackground(mFolderCreateBg);
1926                 mFolderCreateBg = new PreviewBackground();
1927                 fi.performCreateAnimation(destInfo, v, sourceInfo, d, folderLocation, scale);
1928             } else {
1929                 fi.prepareCreateAnimation(v);
1930                 fi.addItem(destInfo);
1931                 fi.addItem(sourceInfo);
1932             }
1933             return true;
1934         }
1935         return false;
1936     }
1937 
addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell, float distance, DragObject d, boolean external)1938     boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
1939             float distance, DragObject d, boolean external) {
1940         if (distance > target.getFolderCreationRadius(targetCell)) return false;
1941 
1942         View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
1943         if (!mAddToExistingFolderOnDrop) return false;
1944         mAddToExistingFolderOnDrop = false;
1945 
1946         if (dropOverView instanceof FolderIcon) {
1947             FolderIcon fi = (FolderIcon) dropOverView;
1948             if (fi.acceptDrop(d.dragInfo)) {
1949                 mStatsLogManager.logger().withItemInfo(fi.mInfo).withInstanceId(d.logInstanceId)
1950                         .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED_ON_FOLDER_ICON);
1951                 fi.onDrop(d, false /* itemReturnedOnFailedDrop */);
1952                 // if the drag started here, we need to remove it from the workspace
1953                 if (!external) {
1954                     getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
1955                 }
1956                 return true;
1957             }
1958         }
1959         return false;
1960     }
1961 
1962     @Override
prepareAccessibilityDrop()1963     public void prepareAccessibilityDrop() {}
1964 
1965     @Override
onDrop(final DragObject d, DragOptions options)1966     public void onDrop(final DragObject d, DragOptions options) {
1967         mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
1968         CellLayout dropTargetLayout = mDropToLayout;
1969 
1970         // We want the point to be mapped to the dragTarget.
1971         if (dropTargetLayout != null) {
1972             mapPointFromDropLayout(dropTargetLayout, mDragViewVisualCenter);
1973         }
1974 
1975         boolean droppedOnOriginalCell = false;
1976 
1977         boolean snappedToNewPage = false;
1978         boolean resizeOnDrop = false;
1979         Runnable onCompleteRunnable = null;
1980         if (d.dragSource != this || mDragInfo == null) {
1981             final int[] touchXY = new int[]{(int) mDragViewVisualCenter[0],
1982                     (int) mDragViewVisualCenter[1]};
1983             onDropExternal(touchXY, dropTargetLayout, d);
1984         } else {
1985             final View cell = mDragInfo.cell;
1986             boolean droppedOnOriginalCellDuringTransition = false;
1987 
1988             if (dropTargetLayout != null && !d.cancelled) {
1989                 // Move internally
1990                 boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
1991                 boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
1992                 int container = hasMovedIntoHotseat ?
1993                         LauncherSettings.Favorites.CONTAINER_HOTSEAT :
1994                         LauncherSettings.Favorites.CONTAINER_DESKTOP;
1995                 int screenId = (mTargetCell[0] < 0) ?
1996                         mDragInfo.screenId : getIdForScreen(dropTargetLayout);
1997                 int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
1998                 int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
1999                 // First we find the cell nearest to point at which the item is
2000                 // dropped, without any consideration to whether there is an item there.
2001 
2002                 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
2003                         mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
2004                 float distance = dropTargetLayout.getDistanceFromWorkspaceCellVisualCenter(
2005                         mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
2006 
2007                 // If the item being dropped is a shortcut and the nearest drop
2008                 // cell also contains a shortcut, then create a folder with the two shortcuts.
2009                 if (createUserFolderIfNecessary(cell, container, dropTargetLayout, mTargetCell,
2010                         distance, false, d)
2011                         || addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
2012                         distance, d, false)) {
2013                     if (!mLauncher.isInState(EDIT_MODE)) {
2014                         mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
2015                     }
2016                     return;
2017                 }
2018 
2019                 // Aside from the special case where we're dropping a shortcut onto a shortcut,
2020                 // we need to find the nearest cell location that is vacant
2021                 ItemInfo item = d.dragInfo;
2022                 int minSpanX = item.spanX;
2023                 int minSpanY = item.spanY;
2024                 if (item.minSpanX > 0 && item.minSpanY > 0) {
2025                     minSpanX = item.minSpanX;
2026                     minSpanY = item.minSpanY;
2027                 }
2028 
2029                 CellPos originalPresenterPos = getCellPosMapper().mapModelToPresenter(item);
2030                 droppedOnOriginalCell = originalPresenterPos.screenId == screenId
2031                         && item.container == container
2032                         && originalPresenterPos.cellX == mTargetCell[0]
2033                         && originalPresenterPos.cellY == mTargetCell[1];
2034                 droppedOnOriginalCellDuringTransition = droppedOnOriginalCell && mIsSwitchingState;
2035 
2036                 // When quickly moving an item, a user may accidentally rearrange their
2037                 // workspace. So instead we move the icon back safely to its original position.
2038                 boolean returnToOriginalCellToPreventShuffling = !isFinishedSwitchingState()
2039                         && !droppedOnOriginalCellDuringTransition && !dropTargetLayout
2040                         .isRegionVacant(mTargetCell[0], mTargetCell[1], spanX, spanY);
2041                 int[] resultSpan = new int[2];
2042                 if (returnToOriginalCellToPreventShuffling) {
2043                     mTargetCell[0] = mTargetCell[1] = -1;
2044                 } else {
2045                     mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
2046                             (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2047                             cell, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
2048                 }
2049 
2050                 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2051 
2052                 // if the widget resizes on drop
2053                 if (foundCell && (cell instanceof AppWidgetHostView) &&
2054                         (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
2055                     resizeOnDrop = true;
2056                     item.spanX = resultSpan[0];
2057                     item.spanY = resultSpan[1];
2058                     AppWidgetHostView awhv = (AppWidgetHostView) cell;
2059                     WidgetSizes.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
2060                             resultSpan[1]);
2061                 }
2062 
2063                 if (foundCell) {
2064                     int targetScreenIndex = getPageIndexForScreenId(screenId);
2065                     int snapScreen = getLeftmostVisiblePageForIndex(targetScreenIndex);
2066                     // On large screen devices two pages can be shown at the same time, and snap
2067                     // isn't needed if the source and target screens appear at the same time
2068                     if (snapScreen != mCurrentPage && !hasMovedIntoHotseat) {
2069                         snapToPage(snapScreen);
2070                         snappedToNewPage = true;
2071                     }
2072                     final ItemInfo info = (ItemInfo) cell.getTag();
2073                     if (hasMovedLayouts) {
2074                         // Reparent the view
2075                         CellLayout parentCell = getParentCellLayoutForView(cell);
2076                         if (parentCell != null) {
2077                             parentCell.removeView(cell);
2078                         } else if (mDragInfo.cell instanceof LauncherAppWidgetHostView) {
2079                             d.dragView.detachContentView(/* reattachToPreviousParent= */ false);
2080                         } else if (FeatureFlags.IS_STUDIO_BUILD) {
2081                             throw new NullPointerException("mDragInfo.cell has null parent");
2082                         }
2083                         addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
2084                                 info.spanX, info.spanY);
2085                     }
2086 
2087                     // update the item's position after drop
2088                     CellLayoutLayoutParams lp = (CellLayoutLayoutParams) cell.getLayoutParams();
2089                     lp.setTmpCellX(mTargetCell[0]);
2090                     lp.setCellX(mTargetCell[0]);
2091                     lp.setTmpCellY(mTargetCell[1]);
2092                     lp.setCellY(mTargetCell[1]);
2093                     lp.cellHSpan = item.spanX;
2094                     lp.cellVSpan = item.spanY;
2095                     lp.isLockedToGrid = true;
2096 
2097                     if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
2098                             cell instanceof LauncherAppWidgetHostView) {
2099 
2100                         // We post this call so that the widget has a chance to be placed
2101                         // in its final location
2102                         onCompleteRunnable = getWidgetResizeFrameRunnable(options,
2103                                 (LauncherAppWidgetHostView) cell, dropTargetLayout);
2104                     }
2105                     mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId,
2106                             lp.getCellX(), lp.getCellY(), item.spanX, item.spanY);
2107                 } else {
2108                     if (!returnToOriginalCellToPreventShuffling) {
2109                         onNoCellFound(dropTargetLayout, d.dragInfo, d.logInstanceId);
2110                     }
2111                     if (mDragInfo.cell instanceof LauncherAppWidgetHostView) {
2112                         d.dragView.detachContentView(/* reattachToPreviousParent= */ true);
2113                     }
2114 
2115                     // If we can't find a drop location, we return the item to its original position
2116                     CellLayoutLayoutParams lp = (CellLayoutLayoutParams) cell.getLayoutParams();
2117                     mTargetCell[0] = lp.getCellX();
2118                     mTargetCell[1] = lp.getCellY();
2119                     CellLayout layout = (CellLayout) cell.getParent().getParent();
2120                     layout.markCellsAsOccupiedForView(cell);
2121                 }
2122             } else {
2123                 // When drag is cancelled, reattach content view back to its original parent.
2124                 if (cell instanceof LauncherAppWidgetHostView) {
2125                     d.dragView.detachContentView(/* reattachToPreviousParent= */ true);
2126 
2127                     final CellLayout cellLayout = getParentCellLayoutForView(cell);
2128                     boolean pageIsVisible = isVisible(cellLayout);
2129 
2130                     if (pageIsVisible) {
2131                         onCompleteRunnable = getWidgetResizeFrameRunnable(options,
2132                                 (LauncherAppWidgetHostView) cell, cellLayout);
2133                     }
2134                 }
2135             }
2136 
2137             final CellLayout parent = (CellLayout) cell.getParent().getParent();
2138             if (d.dragView.hasDrawn()) {
2139                 if (droppedOnOriginalCellDuringTransition) {
2140                     // Animate the item to its original position, while simultaneously exiting
2141                     // spring-loaded mode so the page meets the icon where it was picked up.
2142                     final RunnableList callbackList = new RunnableList();
2143                     final Runnable onCompleteCallback = onCompleteRunnable;
2144                     LauncherState currentState = mLauncher.getStateManager().getState();
2145                     mLauncher.getDragController().animateDragViewToOriginalPosition(
2146                             /* onComplete= */ callbackList::executeAllAndDestroy, cell,
2147                             currentState.getTransitionDuration(mLauncher, true /* isToState */));
2148                     if (!mLauncher.isInState(EDIT_MODE)) {
2149                         mLauncher.getStateManager().goToState(NORMAL, /* delay= */ 0,
2150                                 onCompleteCallback == null
2151                                         ? null
2152                                         : forSuccessCallback(
2153                                                 () -> callbackList.add(onCompleteCallback)));
2154                     } else if (onCompleteCallback != null) {
2155                         forSuccessCallback(() -> callbackList.add(onCompleteCallback));
2156                     }
2157                     mLauncher.getDropTargetBar().onDragEnd();
2158                     parent.onDropChild(cell);
2159                     return;
2160                 }
2161                 final ItemInfo info = (ItemInfo) cell.getTag();
2162                 boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
2163                         || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
2164                 if (isWidget && dropTargetLayout != null) {
2165                     // animate widget to a valid place
2166                     int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
2167                             ANIMATE_INTO_POSITION_AND_DISAPPEAR;
2168                     animateWidgetDrop(info, parent, d.dragView, null, animationType, cell, false);
2169                 } else {
2170                     int duration = snappedToNewPage ? ADJACENT_SCREEN_DROP_DURATION : -1;
2171                     mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
2172                             this);
2173                 }
2174             } else {
2175                 d.deferDragViewCleanupPostAnimation = false;
2176                 cell.setVisibility(VISIBLE);
2177             }
2178             parent.onDropChild(cell);
2179 
2180             if (!mLauncher.isInState(EDIT_MODE)) {
2181                 mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY,
2182                         onCompleteRunnable == null ? null : forSuccessCallback(onCompleteRunnable));
2183             } else if (onCompleteRunnable != null) {
2184                 forSuccessCallback(onCompleteRunnable);
2185             }
2186             mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId)
2187                     .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
2188         }
2189 
2190         if (d.stateAnnouncer != null && !droppedOnOriginalCell) {
2191             d.stateAnnouncer.completeAction(R.string.item_moved);
2192         }
2193     }
2194 
2195     @Nullable
getWidgetResizeFrameRunnable(DragOptions options, LauncherAppWidgetHostView hostView, CellLayout cellLayout)2196     private Runnable getWidgetResizeFrameRunnable(DragOptions options,
2197             LauncherAppWidgetHostView hostView, CellLayout cellLayout) {
2198         AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo();
2199         if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE
2200                 && !options.isAccessibleDrag) {
2201             return () -> {
2202                 if (!isPageInTransition()) {
2203                     AppWidgetResizeFrame.showForWidget(hostView, cellLayout);
2204                 }
2205             };
2206         }
2207         return null;
2208     }
2209 
2210     public void onNoCellFound(
2211             View dropTargetLayout, ItemInfo itemInfo, @Nullable InstanceId logInstanceId) {
2212         int strId = mLauncher.isHotseatLayout(dropTargetLayout)
2213                 ? R.string.hotseat_out_of_space : R.string.out_of_space;
2214         Toast.makeText(mLauncher, mLauncher.getString(strId), Toast.LENGTH_SHORT).show();
2215         StatsLogManager.StatsLogger logger = mStatsLogManager.logger().withItemInfo(itemInfo);
2216         if (logInstanceId != null) {
2217             logger = logger.withInstanceId(logInstanceId);
2218         }
2219         logger.log(LauncherEvent.LAUNCHER_ITEM_DROP_FAILED_INSUFFICIENT_SPACE);
2220     }
2221 
2222     /**
2223      * Computes and returns the area relative to dragLayer which is used to display a page.
2224      * In case we have multiple pages displayed at the same time, we return the union of the areas.
2225      */
2226     public Rect getPageAreaRelativeToDragLayer() {
2227         Rect area = new Rect();
2228         int nextPage = getNextPage();
2229         int panelCount = getPanelCount();
2230         for (int page = nextPage; page < nextPage + panelCount; page++) {
2231             CellLayout child = (CellLayout) getChildAt(page);
2232             if (child == null) {
2233                 break;
2234             }
2235 
2236             ShortcutAndWidgetContainer boundingLayout = child.getShortcutsAndWidgets();
2237             Rect tmpRect = new Rect();
2238             mLauncher.getDragLayer().getDescendantRectRelativeToSelf(boundingLayout, tmpRect);
2239             area.union(tmpRect);
2240         }
2241 
2242         return area;
2243     }
2244 
2245     @Override
2246     public void onDragEnter(DragObject d) {
2247         if (ENFORCE_DRAG_EVENT_ORDER) {
2248             enforceDragParity("onDragEnter", 1, 1);
2249         }
2250 
2251         mCreateUserFolderOnDrop = false;
2252         mAddToExistingFolderOnDrop = false;
2253 
2254         mDropToLayout = null;
2255         mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
2256         setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1]);
2257     }
2258 
2259     @Override
2260     public void onDragExit(DragObject d) {
2261         if (ENFORCE_DRAG_EVENT_ORDER) {
2262             enforceDragParity("onDragExit", -1, 0);
2263         }
2264 
2265         // Here we store the final page that will be dropped to, if the workspace in fact
2266         // receives the drop
2267         mDropToLayout = mDragTargetLayout;
2268         if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
2269             mCreateUserFolderOnDrop = true;
2270         } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
2271             mAddToExistingFolderOnDrop = true;
2272         }
2273 
2274         // Reset the previous drag target
2275         setCurrentDropLayout(null);
2276         setCurrentDragOverlappingLayout(null);
2277 
2278         mSpringLoadedDragController.cancel();
2279     }
2280 
2281     private void enforceDragParity(String event, int update, int expectedValue) {
2282         enforceDragParity(this, event, update, expectedValue);
2283         for (int i = 0; i < getChildCount(); i++) {
2284             enforceDragParity(getChildAt(i), event, update, expectedValue);
2285         }
2286     }
2287 
2288     private void enforceDragParity(View v, String event, int update, int expectedValue) {
2289         Object tag = v.getTag(R.id.drag_event_parity);
2290         int value = tag == null ? 0 : (Integer) tag;
2291         value += update;
2292         v.setTag(R.id.drag_event_parity, value);
2293 
2294         if (value != expectedValue) {
2295             Log.e(TAG, event + ": Drag contract violated: " + value);
2296         }
2297     }
2298 
2299     void setCurrentDropLayout(CellLayout layout) {
2300         if (mDragTargetLayout != null) {
2301             mDragTargetLayout.revertTempState();
2302             mDragTargetLayout.onDragExit();
2303         }
2304         mDragTargetLayout = layout;
2305         if (mDragTargetLayout != null) {
2306             mDragTargetLayout.onDragEnter();
2307         }
2308         cleanupReorder(true);
2309         cleanupFolderCreation();
2310         setCurrentDropOverCell(-1, -1);
2311     }
2312 
2313     void setCurrentDragOverlappingLayout(CellLayout layout) {
2314         if (mDragOverlappingLayout != null) {
2315             mDragOverlappingLayout.setIsDragOverlapping(false);
2316         }
2317         mDragOverlappingLayout = layout;
2318         if (mDragOverlappingLayout != null) {
2319             mDragOverlappingLayout.setIsDragOverlapping(true);
2320         }
2321     }
2322 
2323     public CellLayout getCurrentDragOverlappingLayout() {
2324         return mDragOverlappingLayout;
2325     }
2326 
2327     void setCurrentDropOverCell(int x, int y) {
2328         if (x != mDragOverX || y != mDragOverY) {
2329             mDragOverX = x;
2330             mDragOverY = y;
2331             setDragMode(DRAG_MODE_NONE);
2332         }
2333     }
2334 
2335     void setDragMode(int dragMode) {
2336         if (dragMode != mDragMode) {
2337             if (dragMode == DRAG_MODE_NONE) {
2338                 cleanupAddToFolder();
2339                 // We don't want to cancel the re-order alarm every time the target cell changes
2340                 // as this feels to slow / unresponsive.
2341                 cleanupReorder(false);
2342                 cleanupFolderCreation();
2343             } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
2344                 cleanupReorder(true);
2345                 cleanupFolderCreation();
2346             } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
2347                 cleanupAddToFolder();
2348                 cleanupReorder(true);
2349             } else if (dragMode == DRAG_MODE_REORDER) {
2350                 cleanupAddToFolder();
2351                 cleanupFolderCreation();
2352             }
2353             mDragMode = dragMode;
2354         }
2355     }
2356 
2357     protected void cleanupFolderCreation() {
2358         if (mFolderCreateBg != null) {
2359             mFolderCreateBg.animateToRest();
2360         }
2361     }
2362 
2363     private void cleanupAddToFolder() {
2364         if (mDragOverFolderIcon != null) {
2365             mDragOverFolderIcon.onDragExit();
2366             mDragOverFolderIcon = null;
2367         }
2368     }
2369 
2370     protected void cleanupReorder(boolean cancelAlarm) {
2371         // Any pending reorders are canceled
2372         if (cancelAlarm) {
2373             mReorderAlarm.cancelAlarm();
2374         }
2375         mLastReorderX = -1;
2376         mLastReorderY = -1;
2377     }
2378 
2379     /*
2380      *
2381      * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
2382      * coordinate space. The argument xy is modified with the return result.
2383      */
2384     private void mapPointFromSelfToChild(View v, float[] xy) {
2385         xy[0] = xy[0] - v.getLeft();
2386         xy[1] = xy[1] - v.getTop();
2387     }
2388 
2389     /**
2390      * Updates the point in {@param xy} to point to the co-ordinate space of {@param layout}
2391      *
2392      * @param layout either hotseat of a page in workspace
2393      * @param xy     the point location in workspace co-ordinate space
2394      */
2395     private void mapPointFromDropLayout(CellLayout layout, float[] xy) {
2396         if (mLauncher.isHotseatLayout(layout)) {
2397             mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, xy, true);
2398             mLauncher.getDragLayer().mapCoordInSelfToDescendant(layout, xy);
2399         } else {
2400             mapPointFromSelfToChild(layout, xy);
2401         }
2402     }
2403 
2404     private boolean isDragWidget(DragObject d) {
2405         return (d.dragInfo instanceof LauncherAppWidgetInfo ||
2406                 d.dragInfo instanceof PendingAddWidgetInfo);
2407     }
2408 
2409     public void onDragOver(DragObject d) {
2410         // Skip drag over events while we are dragging over side pages
2411         if (!transitionStateShouldAllowDrop()) return;
2412 
2413         ItemInfo item = d.dragInfo;
2414         if (item == null) {
2415             if (FeatureFlags.IS_STUDIO_BUILD) {
2416                 throw new NullPointerException("DragObject has null info");
2417             }
2418             return;
2419         }
2420 
2421         // Ensure that we have proper spans for the item that we are dropping
2422         if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
2423         mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
2424 
2425         final View child = (mDragInfo == null) ? null : mDragInfo.cell;
2426         if (setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1])) {
2427             if (mDragTargetLayout == null || mLauncher.isHotseatLayout(mDragTargetLayout)) {
2428                 mSpringLoadedDragController.cancel();
2429             } else {
2430                 mSpringLoadedDragController.setAlarm(mDragTargetLayout);
2431             }
2432         }
2433 
2434         // Handle the drag over
2435         if (mDragTargetLayout != null) {
2436             // We want the point to be mapped to the dragTarget.
2437             mapPointFromDropLayout(mDragTargetLayout, mDragViewVisualCenter);
2438 
2439             int minSpanX = item.spanX;
2440             int minSpanY = item.spanY;
2441             if (item.minSpanX > 0 && item.minSpanY > 0) {
2442                 minSpanX = item.minSpanX;
2443                 minSpanY = item.minSpanY;
2444             }
2445 
2446             mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2447                     (int) mDragViewVisualCenter[1], item.spanX, item.spanY,
2448                     mDragTargetLayout, mTargetCell);
2449             int reorderX = mTargetCell[0];
2450             int reorderY = mTargetCell[1];
2451 
2452             setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
2453 
2454             float targetCellDistance = mDragTargetLayout.getDistanceFromWorkspaceCellVisualCenter(
2455                     mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
2456 
2457             manageFolderFeedback(targetCellDistance, d);
2458 
2459             boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
2460                             mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
2461                     item.spanY, child, mTargetCell);
2462 
2463             manageReorderOnDragOver(d, targetCellDistance, nearestDropOccupied, minSpanX, minSpanY,
2464                     reorderX, reorderY);
2465 
2466             if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
2467                     !nearestDropOccupied) {
2468                 if (mDragTargetLayout != null) {
2469                     mDragTargetLayout.revertTempState();
2470                 }
2471             }
2472         }
2473     }
2474 
2475     protected void manageReorderOnDragOver(DragObject d, float targetCellDistance,
2476             boolean nearestDropOccupied, int minSpanX, int minSpanY, int reorderX, int reorderY) {
2477 
2478         ItemInfo item = d.dragInfo;
2479         final View child = (mDragInfo == null) ? null : mDragInfo.cell;
2480         if (!nearestDropOccupied) {
2481             int[] span = new int[2];
2482             mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
2483                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY,
2484                     child, mTargetCell, span, CellLayout.MODE_SHOW_REORDER_HINT);
2485             mDragTargetLayout.visualizeDropLocation(mTargetCell[0], mTargetCell[1], span[0],
2486                     span[1], d);
2487             nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
2488                             mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
2489                     item.spanY, child, mTargetCell);
2490         } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
2491                 && (mLastReorderX != reorderX || mLastReorderY != reorderY)
2492                 && targetCellDistance < mDragTargetLayout.getReorderRadius(mTargetCell, item.spanX,
2493                 item.spanY)) {
2494             mReorderAlarm.cancelAlarm();
2495             mLastReorderX = reorderX;
2496             mLastReorderY = reorderY;
2497             mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
2498                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY,
2499                     child, mTargetCell, new int[2], CellLayout.MODE_SHOW_REORDER_HINT);
2500             // Otherwise, if we aren't adding to or creating a folder and there's no pending
2501             // reorder, then we schedule a reorder
2502             ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
2503                     minSpanX, minSpanY, item.spanX, item.spanY, d, child);
2504             mReorderAlarm.setOnAlarmListener(listener);
2505             mReorderAlarm.setAlarm(REORDER_TIMEOUT);
2506         }
2507     }
2508 
2509     /**
2510      * Updates {@link #mDragTargetLayout} and {@link #mDragOverlappingLayout}
2511      * based on the DragObject's position.
2512      *
2513      * The layout will be:
2514      * - The Hotseat if the drag object is over it
2515      * - A side page if we are in spring-loaded mode and the drag object is over it
2516      * - The current page otherwise
2517      *
2518      * @return whether the layout is different from the current {@link #mDragTargetLayout}.
2519      */
2520     private boolean setDropLayoutForDragObject(DragObject d, float centerX, float centerY) {
2521         CellLayout layout = null;
2522         if (shouldUseHotseatAsDropLayout(d)) {
2523             layout = mLauncher.getHotseat();
2524         } else if (!isDragObjectOverSmartSpace(d)) {
2525             // If the object is over qsb/smartspace, we don't want to highlight anything.
2526 
2527             // Check neighbour pages
2528             layout = checkDragObjectIsOverNeighbourPages(d, centerX);
2529 
2530             if (layout == null) {
2531                 // Check visible pages
2532                 IntSet visiblePageIndices = getVisiblePageIndices();
2533                 for (int visiblePageIndex : visiblePageIndices) {
2534                     layout = verifyInsidePage(visiblePageIndex, d.x, d.y);
2535                     if (layout != null) break;
2536                 }
2537             }
2538         }
2539 
2540         // Update the current drop layout if the target changed
2541         if (layout != mDragTargetLayout) {
2542             setCurrentDropLayout(layout);
2543             setCurrentDragOverlappingLayout(layout);
2544             return true;
2545         }
2546         return false;
2547     }
2548 
2549     private boolean shouldUseHotseatAsDropLayout(DragObject dragObject) {
2550         if (mLauncher.getHotseat() == null
2551                 || mLauncher.getHotseat().getShortcutsAndWidgets() == null
2552                 || isDragWidget(dragObject)) {
2553             return false;
2554         }
2555         View hotseatShortcuts = mLauncher.getHotseat().getShortcutsAndWidgets();
2556         getViewBoundsRelativeToWorkspace(hotseatShortcuts, mTempRect);
2557         return mTempRect.contains(dragObject.x, dragObject.y);
2558     }
2559 
2560     private boolean isDragObjectOverSmartSpace(DragObject dragObject) {
2561         if (mFirstPagePinnedItem == null) {
2562             return false;
2563         }
2564         getViewBoundsRelativeToWorkspace(mFirstPagePinnedItem, mTempRect);
2565         return mTempRect.contains(dragObject.x, dragObject.y);
2566     }
2567 
2568     private CellLayout checkDragObjectIsOverNeighbourPages(DragObject d, float centerX) {
2569         if (isPageInTransition()) {
2570             return null;
2571         }
2572 
2573         // Check the workspace pages whether the object is over any of them
2574 
2575         // Note, centerX represents the center of the object that is being dragged, visually.
2576         // d.x represents the location of the finger within the dragged item.
2577         float touchX;
2578         float touchY = d.y;
2579 
2580         // Go through the pages and check if the dragged item is inside one of them. This block
2581         // is responsible for determining whether we need to snap to a different screen.
2582         int nextPage = getNextPage();
2583         IntSet pageIndexesToVerify = IntSet.wrap(nextPage - 1,
2584                 nextPage + (isTwoPanelEnabled() ? 2 : 1));
2585 
2586         for (int pageIndex : pageIndexesToVerify) {
2587             // When deciding whether to perform a page switch, we need to consider the most
2588             // extreme X coordinate between the finger location and the center of the object
2589             // being dragged. This is either the max or the min of the two depending on whether
2590             // dragging to the left / right, respectively.
2591             touchX = (((pageIndex < nextPage) && !mIsRtl) || (pageIndex > nextPage && mIsRtl))
2592                     ? Math.min(d.x, centerX) : Math.max(d.x, centerX);
2593             CellLayout layout = verifyInsidePage(pageIndex, touchX, touchY);
2594             if (layout != null) {
2595                 return layout;
2596             }
2597         }
2598         return null;
2599     }
2600 
2601     /**
2602      * Gets the given view's bounds relative to Workspace
2603      */
2604     private void getViewBoundsRelativeToWorkspace(View view, Rect outRect) {
2605         mLauncher.getDragLayer()
2606                 .getDescendantRectRelativeToSelf(view, mTempRect);
2607         // map draglayer relative bounds to workspace
2608         mLauncher.getDragLayer().mapRectInSelfToDescendant(this, mTempRect);
2609         outRect.set(mTempRect);
2610     }
2611 
2612     /**
2613      * Returns the child CellLayout if the point is inside the page coordinates, null otherwise.
2614      */
2615     private CellLayout verifyInsidePage(int pageNo, float x, float y) {
2616         if (pageNo >= 0 && pageNo < getPageCount()) {
2617             CellLayout cl = (CellLayout) getChildAt(pageNo);
2618             if (x >= cl.getLeft() && x <= cl.getRight()
2619                     && y >= cl.getTop() && y <= cl.getBottom()) {
2620                 // This point is inside the cell layout
2621                 return cl;
2622             }
2623         }
2624         return null;
2625     }
2626 
2627     private void manageFolderFeedback(float distance, DragObject dragObject) {
2628         if (distance > mDragTargetLayout.getFolderCreationRadius(mTargetCell)) {
2629             if ((mDragMode == DRAG_MODE_ADD_TO_FOLDER
2630                     || mDragMode == DRAG_MODE_CREATE_FOLDER)) {
2631                 setDragMode(DRAG_MODE_NONE);
2632             }
2633             return;
2634         }
2635 
2636         final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]);
2637         ItemInfo info = dragObject.dragInfo;
2638         boolean userFolderPending = willCreateUserFolder(info, dragOverView, false);
2639         if (mDragMode == DRAG_MODE_NONE && userFolderPending) {
2640 
2641             mFolderCreateBg = new PreviewBackground();
2642             mFolderCreateBg.setup(mLauncher, mLauncher, null,
2643                     dragOverView.getMeasuredWidth(), dragOverView.getPaddingTop());
2644 
2645             // The full preview background should appear behind the icon
2646             mFolderCreateBg.isClipping = false;
2647 
2648             mFolderCreateBg.animateToAccept(mDragTargetLayout, mTargetCell[0], mTargetCell[1]);
2649             mDragTargetLayout.clearDragOutlines();
2650             setDragMode(DRAG_MODE_CREATE_FOLDER);
2651 
2652             if (dragObject.stateAnnouncer != null) {
2653                 dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
2654                         .getDescriptionForDropOver(dragOverView, getContext()));
2655             }
2656             return;
2657         }
2658 
2659         boolean willAddToFolder = willAddToExistingUserFolder(info, dragOverView);
2660         if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
2661             mDragOverFolderIcon = ((FolderIcon) dragOverView);
2662             mDragOverFolderIcon.onDragEnter(info);
2663             if (mDragTargetLayout != null) {
2664                 mDragTargetLayout.clearDragOutlines();
2665             }
2666             setDragMode(DRAG_MODE_ADD_TO_FOLDER);
2667 
2668             if (dragObject.stateAnnouncer != null) {
2669                 dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
2670                         .getDescriptionForDropOver(dragOverView, getContext()));
2671             }
2672             return;
2673         }
2674 
2675         if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
2676             setDragMode(DRAG_MODE_NONE);
2677         }
2678         if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
2679             setDragMode(DRAG_MODE_NONE);
2680         }
2681     }
2682 
2683     class ReorderAlarmListener implements OnAlarmListener {
2684         final float[] dragViewCenter;
2685         final int minSpanX, minSpanY, spanX, spanY;
2686         final DragObject dragObject;
2687         final View child;
2688 
2689         public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
2690                                     int spanY, DragObject dragObject, View child) {
2691             this.dragViewCenter = dragViewCenter;
2692             this.minSpanX = minSpanX;
2693             this.minSpanY = minSpanY;
2694             this.spanX = spanX;
2695             this.spanY = spanY;
2696             this.child = child;
2697             this.dragObject = dragObject;
2698         }
2699 
2700         public void onAlarm(Alarm alarm) {
2701             int[] resultSpan = new int[2];
2702             mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2703                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout,
2704                     mTargetCell);
2705 
2706             mTargetCell = mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
2707                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2708                     child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
2709 
2710             if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
2711                 mDragTargetLayout.revertTempState();
2712             } else {
2713                 setDragMode(DRAG_MODE_REORDER);
2714             }
2715 
2716             mDragTargetLayout.visualizeDropLocation(mTargetCell[0], mTargetCell[1],
2717                     resultSpan[0], resultSpan[1], dragObject);
2718         }
2719     }
2720 
2721     @Override
2722     public void getHitRectRelativeToDragLayer(Rect outRect) {
2723         // We want the workspace to have the whole area of the display (it will find the correct
2724         // cell layout to drop to in the existing drag/drop logic.
2725         mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect);
2726     }
2727 
2728     /**
2729      * Drop an item that didn't originate on one of the workspace screens.
2730      * It may have come from Launcher (e.g. from all apps or customize), or it may have
2731      * come from another app altogether.
2732      * <p>
2733      * NOTE: This can also be called when we are outside of a drag event, when we want
2734      * to add an item to one of the workspace screens.
2735      */
2736     private void onDropExternal(final int[] touchXY, final CellLayout cellLayout, DragObject d) {
2737         if (d.dragInfo instanceof PendingAddShortcutInfo) {
2738             WorkspaceItemInfo si = ((PendingAddShortcutInfo) d.dragInfo)
2739                     .getActivityInfo(mLauncher).createWorkspaceItemInfo();
2740             if (si != null) {
2741                 d.dragInfo = si;
2742             }
2743         }
2744 
2745         ItemInfo info = d.dragInfo;
2746         int spanX = info.spanX;
2747         int spanY = info.spanY;
2748         if (mDragInfo != null) {
2749             spanX = mDragInfo.spanX;
2750             spanY = mDragInfo.spanY;
2751         }
2752 
2753         final int container = mLauncher.isHotseatLayout(cellLayout)
2754                 ? LauncherSettings.Favorites.CONTAINER_HOTSEAT
2755                 : LauncherSettings.Favorites.CONTAINER_DESKTOP;
2756         final int screenId = getIdForScreen(cellLayout);
2757         if (!mLauncher.isHotseatLayout(cellLayout)
2758                 && screenId != getScreenIdForPageIndex(mCurrentPage)
2759                 && !mLauncher.isInState(SPRING_LOADED)
2760                 && !mLauncher.isInState(EDIT_MODE)) {
2761             snapToPage(getPageIndexForScreenId(screenId));
2762         }
2763 
2764         if (info instanceof PendingAddItemInfo) {
2765             final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) info;
2766 
2767             boolean findNearestVacantCell = true;
2768             if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
2769                 mTargetCell = findNearestArea(touchXY[0], touchXY[1], spanX, spanY,
2770                         cellLayout, mTargetCell);
2771                 float distance = cellLayout.getDistanceFromWorkspaceCellVisualCenter(
2772                         mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
2773                 if (willCreateUserFolder(d.dragInfo, cellLayout, mTargetCell, distance, true)
2774                         || willAddToExistingUserFolder(
2775                         d.dragInfo, cellLayout, mTargetCell, distance)) {
2776                     findNearestVacantCell = false;
2777                 }
2778             }
2779 
2780             final ItemInfo item = d.dragInfo;
2781             boolean updateWidgetSize = false;
2782             if (findNearestVacantCell) {
2783                 int minSpanX = item.spanX;
2784                 int minSpanY = item.spanY;
2785                 if (item.minSpanX > 0 && item.minSpanY > 0) {
2786                     minSpanX = item.minSpanX;
2787                     minSpanY = item.minSpanY;
2788                 }
2789                 int[] resultSpan = new int[2];
2790                 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
2791                         (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
2792                         null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
2793 
2794                 if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) {
2795                     updateWidgetSize = true;
2796                 }
2797                 item.spanX = resultSpan[0];
2798                 item.spanY = resultSpan[1];
2799             }
2800 
2801             Runnable onAnimationCompleteRunnable = new Runnable() {
2802                 @Override
2803                 public void run() {
2804                     // Normally removeExtraEmptyScreen is called in Workspace#onDrop, but when
2805                     // adding an item that may not be dropped right away (due to a config activity)
2806                     // we defer the removal until the activity returns.
2807                     deferRemoveExtraEmptyScreen();
2808 
2809                     // When dragging and dropping from customization tray, we deal with creating
2810                     // widgets/shortcuts/folders in a slightly different way
2811                     mLauncher.addPendingItem(pendingInfo, container, screenId, mTargetCell,
2812                             item.spanX, item.spanY);
2813                     mStatsLogManager.logger().withItemInfo(d.dragInfo)
2814                             .withInstanceId(d.logInstanceId)
2815                             .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
2816                 }
2817             };
2818             boolean isWidget = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
2819                     || pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
2820 
2821             AppWidgetHostView finalView = isWidget ?
2822                     ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
2823 
2824             if (finalView != null && updateWidgetSize) {
2825                 WidgetSizes.updateWidgetSizeRanges(finalView, mLauncher, item.spanX, item.spanY);
2826             }
2827 
2828             int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
2829             if (isWidget && ((PendingAddWidgetInfo) pendingInfo).info != null &&
2830                     ((PendingAddWidgetInfo) pendingInfo).getHandler().needsConfigure()) {
2831                 animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
2832             }
2833             animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
2834                     animationStyle, finalView, true);
2835         } else {
2836             // This is for other drag/drop cases, like dragging from All Apps
2837             mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
2838             View view;
2839 
2840             switch (info.itemType) {
2841                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
2842                 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
2843                 case LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION:
2844                     if (info instanceof WorkspaceItemFactory) {
2845                         // Came from all apps -- make a copy
2846                         info = ((WorkspaceItemFactory) info).makeWorkspaceItem(mLauncher);
2847                         d.dragInfo = info;
2848                     }
2849                     if (info instanceof WorkspaceItemInfo
2850                             && info.container == LauncherSettings.Favorites.CONTAINER_PREDICTION) {
2851                         // Came from all apps prediction row -- make a copy
2852                         info = new WorkspaceItemInfo((WorkspaceItemInfo) info);
2853                         d.dragInfo = info;
2854                     }
2855                     view = mLauncher.createShortcut(cellLayout, (WorkspaceItemInfo) info);
2856                     break;
2857                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
2858                     view = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, mLauncher, cellLayout,
2859                             (FolderInfo) info);
2860                     break;
2861                 default:
2862                     throw new IllegalStateException("Unknown item type: " + info.itemType);
2863             }
2864 
2865             // First we find the cell nearest to point at which the item is
2866             // dropped, without any consideration to whether there is an item there.
2867             if (touchXY != null) {
2868                 mTargetCell = findNearestArea(touchXY[0], touchXY[1], spanX, spanY,
2869                         cellLayout, mTargetCell);
2870                 float distance = cellLayout.getDistanceFromWorkspaceCellVisualCenter(
2871                         mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
2872                 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
2873                         true, d)) {
2874                     return;
2875                 }
2876                 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
2877                         true)) {
2878                     return;
2879                 }
2880             }
2881 
2882             if (touchXY != null) {
2883                 // when dragging and dropping, just find the closest free spot
2884                 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
2885                         (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
2886                         null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
2887             } else {
2888                 cellLayout.findCellForSpan(mTargetCell, 1, 1);
2889             }
2890             // Add the item to DB before adding to screen ensures that the container and other
2891             // values of the info is properly updated.
2892             mLauncher.getModelWriter().addOrMoveItemInDatabase(info, container, screenId,
2893                     mTargetCell[0], mTargetCell[1]);
2894 
2895             addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1],
2896                     info.spanX, info.spanY);
2897             cellLayout.onDropChild(view);
2898             cellLayout.getShortcutsAndWidgets().measureChild(view);
2899 
2900             if (d.dragView != null) {
2901                 // We wrap the animation call in the temporary set and reset of the current
2902                 // cellLayout to its final transform -- this means we animate the drag view to
2903                 // the correct final location.
2904                 setFinalTransitionTransform();
2905                 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, this);
2906                 resetTransitionTransform();
2907             }
2908             mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId)
2909                     .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
2910         }
2911 
2912     }
2913 
2914     private Drawable createWidgetDrawable(ItemInfo widgetInfo, View layout) {
2915         int[] unScaledSize = estimateItemSize(widgetInfo);
2916         int visibility = layout.getVisibility();
2917         layout.setVisibility(VISIBLE);
2918 
2919         int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
2920         int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
2921         layout.measure(width, height);
2922         layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
2923         Bitmap b = BitmapRenderer.createHardwareBitmap(
2924                 unScaledSize[0], unScaledSize[1], layout::draw);
2925         layout.setVisibility(visibility);
2926         return new FastBitmapDrawable(b);
2927     }
2928 
2929     private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
2930             DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, boolean scale,
2931             final View finalView) {
2932         // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
2933         // location and size on the home screen.
2934         int spanX = info.spanX;
2935         int spanY = info.spanY;
2936 
2937         Rect r = estimateItemPosition(layout, targetCell[0], targetCell[1], spanX, spanY);
2938         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
2939             DeviceProfile profile = mLauncher.getDeviceProfile();
2940             if (finalView instanceof NavigableAppWidgetHostView) {
2941                 Rect widgetPadding = profile.widgetPadding;
2942                 r.left -= widgetPadding.left;
2943                 r.right += widgetPadding.right;
2944                 r.top -= widgetPadding.top;
2945                 r.bottom += widgetPadding.bottom;
2946             }
2947             PointF appWidgetScale = profile.getAppWidgetScale(null);
2948             Utilities.shrinkRect(r, appWidgetScale.x, appWidgetScale.y);
2949         }
2950 
2951         mTempFXY[0] = r.left;
2952         mTempFXY[1] = r.top;
2953         setFinalTransitionTransform();
2954         float cellLayoutScale =
2955                 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, mTempFXY, true);
2956         resetTransitionTransform();
2957         Utilities.roundArray(mTempFXY, loc);
2958 
2959         if (scale) {
2960             float dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
2961             float dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
2962 
2963             // The animation will scale the dragView about its center, so we need to center about
2964             // the final location.
2965             loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2
2966                     - Math.ceil(layout.getUnusedHorizontalSpace() / 2f);
2967             loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
2968             scaleXY[0] = dragViewScaleX * cellLayoutScale;
2969             scaleXY[1] = dragViewScaleY * cellLayoutScale;
2970         } else {
2971             // Since we are not cross-fading the dragView, align the drag view to the
2972             // final cell position.
2973             float dragScale = dragView.getInitialScale() * cellLayoutScale;
2974             loc[0] += (dragScale - 1) * dragView.getWidth() / 2;
2975             loc[1] += (dragScale - 1) * dragView.getHeight() / 2;
2976             scaleXY[0] = scaleXY[1] = dragScale;
2977 
2978             // If a dragRegion was provided, offset the final position accordingly.
2979             Rect dragRegion = dragView.getDragRegion();
2980             if (dragRegion != null) {
2981                 loc[0] += cellLayoutScale * dragRegion.left;
2982                 loc[1] += cellLayoutScale * dragRegion.top;
2983             }
2984         }
2985     }
2986 
2987     public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, final DragView dragView,
2988             final Runnable onCompleteRunnable, int animationType, final View finalView,
2989             boolean external) {
2990         int[] finalPos = new int[2];
2991         float scaleXY[] = new float[2];
2992         boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
2993         getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
2994                 scalePreview, finalView);
2995 
2996         Resources res = mLauncher.getResources();
2997         final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
2998 
2999         boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET ||
3000                 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
3001         if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external)
3002                 && finalView != null
3003                 && dragView.getContentView() != finalView) {
3004             Drawable crossFadeDrawable = createWidgetDrawable(info, finalView);
3005             dragView.crossFadeContent(crossFadeDrawable, (int) (duration * 0.8f));
3006         } else if (isWidget && external) {
3007             scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]);
3008         }
3009 
3010         DragLayer dragLayer = mLauncher.getDragLayer();
3011         if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
3012             mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
3013                     DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
3014         } else {
3015             int endStyle;
3016             if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
3017                 endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
3018             } else {
3019                 endStyle = DragLayer.ANIMATION_END_DISAPPEAR;
3020             }
3021 
3022             Runnable onComplete = new Runnable() {
3023                 @Override
3024                 public void run() {
3025                     if (finalView != null) {
3026                         finalView.setVisibility(VISIBLE);
3027                     }
3028                     if (onCompleteRunnable != null) {
3029                         onCompleteRunnable.run();
3030                     }
3031                 }
3032             };
3033             dragLayer.animateViewIntoPosition(dragView, finalPos[0],
3034                     finalPos[1], 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
3035                     duration, this);
3036         }
3037     }
3038 
3039     public void setFinalTransitionTransform() {
3040         if (isSwitchingState()) {
3041             mCurrentScale = getScaleX();
3042             setScaleX(mStateTransitionAnimation.getFinalScale());
3043             setScaleY(mStateTransitionAnimation.getFinalScale());
3044         }
3045     }
3046 
3047     public void resetTransitionTransform() {
3048         if (isSwitchingState()) {
3049             setScaleX(mCurrentScale);
3050             setScaleY(mCurrentScale);
3051         }
3052     }
3053 
3054     /**
3055      * Return the current CellInfo describing our current drag; this method exists
3056      * so that Launcher can sync this object with the correct info when the activity is created/
3057      * destroyed
3058      */
3059     public CellLayout.CellInfo getDragInfo() {
3060         return mDragInfo;
3061     }
3062 
3063     /**
3064      * Calculate the nearest cell where the given object would be dropped.
3065      * <p>
3066      * pixelX and pixelY should be in the coordinate system of layout
3067      */
3068     @Thunk
3069     int[] findNearestArea(int pixelX, int pixelY,
3070                           int spanX, int spanY, CellLayout layout, int[] recycle) {
3071         return layout.findNearestAreaIgnoreOccupied(
3072                 pixelX, pixelY, spanX, spanY, recycle);
3073     }
3074 
3075     void setup(DragController dragController) {
3076         mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
3077         mDragController = dragController;
3078 
3079         // hardware layers on children are enabled on startup, but should be disabled until
3080         // needed
3081         updateChildrenLayersEnabled();
3082     }
3083 
3084     /**
3085      * Called at the end of a drag which originated on the workspace.
3086      */
3087     public void onDropCompleted(final View target, final DragObject d,
3088                                 final boolean success) {
3089         if (success) {
3090             if (target != this && mDragInfo != null) {
3091                 removeWorkspaceItem(mDragInfo.cell);
3092             }
3093         } else if (mDragInfo != null) {
3094             // When drag is cancelled, reattach content view back to its original parent.
3095             if (mDragInfo.cell instanceof LauncherAppWidgetHostView && d.dragView != null) {
3096                 d.dragView.detachContentView(/* reattachToPreviousParent= */ true);
3097             }
3098             final CellLayout cellLayout = mLauncher.getCellLayout(
3099                     mDragInfo.container, mDragInfo.screenId);
3100             if (cellLayout != null) {
3101                 cellLayout.onDropChild(mDragInfo.cell);
3102             } else if (FeatureFlags.IS_STUDIO_BUILD) {
3103                 throw new RuntimeException("Invalid state: cellLayout == null in "
3104                         + "Workspace#onDropCompleted. Please file a bug. ");
3105             }
3106         }
3107         View cell = getHomescreenIconByItemId(d.originalDragInfo.id);
3108         if (d.cancelled && cell != null) {
3109             cell.setVisibility(VISIBLE);
3110         }
3111         mDragInfo = null;
3112     }
3113 
3114     /**
3115      * For opposite operation. See {@link #addInScreen}.
3116      */
3117     public void removeWorkspaceItem(View v) {
3118         CellLayout parentCell = getParentCellLayoutForView(v);
3119         if (parentCell != null) {
3120             parentCell.removeView(v);
3121         } else if (FeatureFlags.IS_STUDIO_BUILD) {
3122             // When an app is uninstalled using the drop target, we wait until resume to remove
3123             // the icon. We also remove all the corresponding items from the workspace at
3124             // {@link Launcher#bindComponentsRemoved}. That call can come before or after
3125             // {@link Launcher#mOnResumeCallbacks} depending on how busy the worker thread is.
3126             Log.e(TAG, "mDragInfo.cell has null parent");
3127         }
3128         if (v instanceof DropTarget) {
3129             mDragController.removeDropTarget((DropTarget) v);
3130         }
3131     }
3132 
3133     /**
3134      * Removed widget from workspace by appWidgetId
3135      *
3136      * @param appWidgetId
3137      */
3138     public void removeWidget(int appWidgetId) {
3139         mapOverItems((info, view) -> {
3140             if (info instanceof LauncherAppWidgetInfo) {
3141                 LauncherAppWidgetInfo appWidgetInfo = (LauncherAppWidgetInfo) info;
3142                 if (appWidgetInfo.appWidgetId == appWidgetId) {
3143                     mLauncher.removeItem(view, appWidgetInfo, true,
3144                             "widget is removed in response to widget remove broadcast");
3145                     return true;
3146                 }
3147             }
3148             return false;
3149         });
3150     }
3151 
3152     /**
3153      * Removes all folder listeners
3154      */
3155     public void removeFolderListeners() {
3156         mapOverItems(new ItemOperator() {
3157             @Override
3158             public boolean evaluate(ItemInfo info, View view) {
3159                 if (view instanceof FolderIcon) {
3160                     ((FolderIcon) view).removeListeners();
3161                 }
3162                 return false;
3163             }
3164         });
3165     }
3166 
3167     public boolean isDropEnabled() {
3168         return true;
3169     }
3170 
3171     @Override
3172     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
3173         // We don't dispatch restoreInstanceState to our children using this code path.
3174         // Some pages will be restored immediately as their items are bound immediately, and
3175         // others we will need to wait until after their items are bound.
3176         mSavedStates = container;
3177     }
3178 
3179     public void restoreInstanceStateForChild(int child) {
3180         if (mSavedStates != null) {
3181             mRestoredPages.add(child);
3182             CellLayout cl = (CellLayout) getChildAt(child);
3183             if (cl != null) {
3184                 cl.restoreInstanceState(mSavedStates);
3185             }
3186         }
3187     }
3188 
3189     public void restoreInstanceStateForRemainingPages() {
3190         int count = getChildCount();
3191         for (int i = 0; i < count; i++) {
3192             if (!mRestoredPages.contains(i)) {
3193                 restoreInstanceStateForChild(i);
3194             }
3195         }
3196         mRestoredPages.clear();
3197         mSavedStates = null;
3198     }
3199 
3200     @Override
3201     public boolean scrollLeft() {
3202         boolean result = false;
3203         if (!mIsSwitchingState && workspaceInScrollableState()) {
3204             result = super.scrollLeft();
3205         }
3206         Folder openFolder = Folder.getOpen(mLauncher);
3207         if (openFolder != null) {
3208             openFolder.completeDragExit();
3209         }
3210         return result;
3211     }
3212 
3213     @Override
3214     public boolean scrollRight() {
3215         boolean result = false;
3216         if (!mIsSwitchingState && workspaceInScrollableState()) {
3217             result = super.scrollRight();
3218         }
3219         Folder openFolder = Folder.getOpen(mLauncher);
3220         if (openFolder != null) {
3221             openFolder.completeDragExit();
3222         }
3223         return result;
3224     }
3225 
3226     /**
3227      * Returns a specific CellLayout
3228      */
3229     CellLayout getParentCellLayoutForView(View v) {
3230         for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) {
3231             if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
3232                 return layout;
3233             }
3234         }
3235         return null;
3236     }
3237 
3238     /**
3239      * Returns a list of all the CellLayouts on the Homescreen.
3240      */
3241     private CellLayout[] getWorkspaceAndHotseatCellLayouts() {
3242         int screenCount = getChildCount();
3243         final CellLayout[] layouts;
3244         if (mLauncher.getHotseat() != null) {
3245             layouts = new CellLayout[screenCount + 1];
3246             layouts[screenCount] = mLauncher.getHotseat();
3247         } else {
3248             layouts = new CellLayout[screenCount];
3249         }
3250         for (int screen = 0; screen < screenCount; screen++) {
3251             layouts[screen] = (CellLayout) getChildAt(screen);
3252         }
3253         return layouts;
3254     }
3255 
3256     public View getHomescreenIconByItemId(final int id) {
3257         return getFirstMatch((info, v) -> info != null && info.id == id);
3258     }
3259 
3260     public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) {
3261         return (LauncherAppWidgetHostView) getFirstMatch((info, v) ->
3262                 (info instanceof LauncherAppWidgetInfo) &&
3263                         ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId);
3264     }
3265 
3266     public View getFirstMatch(final ItemOperator operator) {
3267         final View[] value = new View[1];
3268         mapOverItems(new ItemOperator() {
3269             @Override
3270             public boolean evaluate(ItemInfo info, View v) {
3271                 if (operator.evaluate(info, v)) {
3272                     value[0] = v;
3273                     return true;
3274                 }
3275                 return false;
3276             }
3277         });
3278         return value[0];
3279     }
3280 
3281     void clearDropTargets() {
3282         mapOverItems(new ItemOperator() {
3283             @Override
3284             public boolean evaluate(ItemInfo info, View v) {
3285                 if (v instanceof DropTarget) {
3286                     mDragController.removeDropTarget((DropTarget) v);
3287                 }
3288                 // not done, process all the shortcuts
3289                 return false;
3290             }
3291         });
3292     }
3293 
3294     /**
3295      * Removes items that match the {@param matcher}. When applications are removed
3296      * as a part of an update, this is called to ensure that other widgets and application
3297      * shortcuts are not removed.
3298      */
3299     public void removeItemsByMatcher(final Predicate<ItemInfo> matcher) {
3300         for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) {
3301             ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets();
3302             // Iterate in reverse order as we are removing items
3303             for (int i = container.getChildCount() - 1; i >= 0; i--) {
3304                 View child = container.getChildAt(i);
3305                 ItemInfo info = (ItemInfo) child.getTag();
3306 
3307                 if (matcher.test(info)) {
3308                     layout.removeViewInLayout(child);
3309                     if (child instanceof DropTarget) {
3310                         mDragController.removeDropTarget((DropTarget) child);
3311                     }
3312                 } else if (child instanceof FolderIcon) {
3313                     FolderInfo folderInfo = (FolderInfo) info;
3314                     List<WorkspaceItemInfo> matches = folderInfo.contents.stream()
3315                             .filter(matcher)
3316                             .collect(Collectors.toList());
3317                     if (!matches.isEmpty()) {
3318                         folderInfo.removeAll(matches, false);
3319                         if (((FolderIcon) child).getFolder().isOpen()) {
3320                             ((FolderIcon) child).getFolder().close(false /* animate */);
3321                         }
3322                     }
3323                 }
3324             }
3325         }
3326 
3327         // Strip all the empty screens
3328         stripEmptyScreens();
3329     }
3330 
3331     @Override
3332     public void mapOverItems(ItemOperator op) {
3333         for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) {
3334             if (mapOverCellLayout(layout, op) != null) {
3335                 return;
3336             }
3337         }
3338     }
3339 
3340     /**
3341      * Perform {param operator} over all the items in a given {param layout}.
3342      *
3343      * @return The first item that satisfies the operator or null.
3344      */
3345     public View mapOverCellLayout(CellLayout layout, ItemOperator operator) {
3346         // TODO(b/128460496) Potential race condition where layout is not yet loaded
3347         if (layout == null) {
3348             return null;
3349         }
3350         ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets();
3351         // map over all the shortcuts on the workspace
3352         final int itemCount = container.getChildCount();
3353         for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
3354             View item = container.getChildAt(itemIdx);
3355             if (operator.evaluate((ItemInfo) item.getTag(), item)) {
3356                 return item;
3357             }
3358         }
3359         return null;
3360     }
3361 
3362     public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
3363         final PackageUserKey packageUserKey = new PackageUserKey(null, null);
3364         Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
3365                 || updatedDots.test(packageUserKey);
3366 
3367         ItemOperator op = (info, v) -> {
3368             if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) {
3369                 if (matcher.test(info)) {
3370                     ((BubbleTextView) v).applyDotState(info, true /* animate */);
3371                 }
3372             } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
3373                 FolderInfo fi = (FolderInfo) info;
3374                 if (fi.contents.stream().anyMatch(matcher)) {
3375                     FolderDotInfo folderDotInfo = new FolderDotInfo();
3376                     for (WorkspaceItemInfo si : fi.contents) {
3377                         folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si));
3378                     }
3379                     ((FolderIcon) v).setDotInfo(folderDotInfo);
3380                 }
3381             }
3382 
3383             // process all the shortcuts
3384             return false;
3385         };
3386 
3387         mapOverItems(op);
3388         Folder folder = Folder.getOpen(mLauncher);
3389         if (folder != null) {
3390             folder.iterateOverItems(op);
3391         }
3392     }
3393 
3394     /**
3395      * Remove workspace icons & widget information related to items in matcher.
3396      *
3397      * @param matcher the matcher generated by the caller.
3398      */
3399     public void persistRemoveItemsByMatcher(Predicate<ItemInfo> matcher,
3400                                             @Nullable final String reason) {
3401         mLauncher.getModelWriter().deleteItemsFromDatabase(matcher, reason);
3402         removeItemsByMatcher(matcher);
3403     }
3404 
3405     public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) {
3406         if (!changedInfo.isEmpty()) {
3407             DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
3408                     mLauncher.getAppWidgetHolder());
3409 
3410             LauncherAppWidgetInfo item = changedInfo.get(0);
3411             final AppWidgetProviderInfo widgetInfo;
3412             WidgetManagerHelper widgetHelper = new WidgetManagerHelper(getContext());
3413             if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
3414                 widgetInfo = widgetHelper.findProvider(item.providerName, item.user);
3415             } else {
3416                 widgetInfo = widgetHelper.getLauncherAppWidgetInfo(item.appWidgetId);
3417             }
3418 
3419             if (widgetInfo != null) {
3420                 // Re-inflate the widgets which have changed status
3421                 widgetRefresh.run();
3422             } else {
3423                 // widgetRefresh will automatically run when the packages are updated.
3424                 // For now just update the progress bars
3425                 mapOverItems(new ItemOperator() {
3426                     @Override
3427                     public boolean evaluate(ItemInfo info, View view) {
3428                         if (view instanceof PendingAppWidgetHostView
3429                                 && changedInfo.contains(info)) {
3430                             ((LauncherAppWidgetInfo) info).installProgress = 100;
3431                             ((PendingAppWidgetHostView) view).applyState();
3432                         }
3433                         // process all the shortcuts
3434                         return false;
3435                     }
3436                 });
3437             }
3438         }
3439     }
3440 
3441     public boolean isOverlayShown() {
3442         return mOverlayShown;
3443     }
3444 
3445     /**
3446      * Calls {@link #snapToPage(int)} on the {@link #DEFAULT_PAGE}, then requests focus on it.
3447      */
3448     public void moveToDefaultScreen() {
3449         int page = DEFAULT_PAGE;
3450         if (!workspaceInModalState() && getNextPage() != page) {
3451             snapToPage(page);
3452         }
3453         View child = getChildAt(page);
3454         if (child != null) {
3455             child.requestFocus();
3456         }
3457     }
3458 
3459     /**
3460      * Set the given view's pivot point to match the workspace's, so that it scales together. Since
3461      * both this view and workspace can move, transform the point manually instead of using
3462      * dragLayer.getDescendantCoordRelativeToSelf and related methods.
3463      */
3464     public void setPivotToScaleWithSelf(View sibling) {
3465         sibling.setPivotY(getPivotY() + getTop()
3466                 - sibling.getTop() - sibling.getTranslationY());
3467         sibling.setPivotX(getPivotX() + getLeft()
3468                 - sibling.getLeft() - sibling.getTranslationX());
3469     }
3470 
3471     @Override
3472     public int getExpectedHeight() {
3473         return getMeasuredHeight() <= 0 || !mIsLayoutValid
3474                 ? mLauncher.getDeviceProfile().heightPx : getMeasuredHeight();
3475     }
3476 
3477     @Override
3478     public int getExpectedWidth() {
3479         return getMeasuredWidth() <= 0 || !mIsLayoutValid
3480                 ? mLauncher.getDeviceProfile().widthPx : getMeasuredWidth();
3481     }
3482 
3483     @Override
3484     protected boolean canAnnouncePageDescription() {
3485         // Disable announcements while overscrolling potentially to overlay screen because if we end
3486         // up on the overlay screen, it will take care of announcing itself.
3487         return Float.compare(mOverlayProgress, 0f) == 0;
3488     }
3489 
3490     @Override
3491     protected String getCurrentPageDescription() {
3492         int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
3493         return getPageDescription(page);
3494     }
3495 
3496     /**
3497      * @param page page index.
3498      * @return Description of the page at the given page index.
3499      */
3500     public String getPageDescription(int page) {
3501         int nScreens = getChildCount();
3502         int extraScreenId = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
3503         if (extraScreenId >= 0 && nScreens > 1) {
3504             if (page == extraScreenId) {
3505                 return getContext().getString(R.string.workspace_new_page);
3506             }
3507             nScreens--;
3508         }
3509         if (nScreens == 0) {
3510             // When the workspace is not loaded, we do not know how many screen will be bound.
3511             return getContext().getString(R.string.home_screen);
3512         }
3513         int panelCount = getPanelCount();
3514         int currentPage = (page / panelCount) + 1;
3515         int totalPages = nScreens / panelCount + nScreens % panelCount;
3516         return getContext().getString(R.string.workspace_scroll_format, currentPage, totalPages);
3517     }
3518 
3519     @Override
3520     protected boolean isSignificantMove(float absoluteDelta, int pageOrientedSize) {
3521         DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
3522         if (!deviceProfile.isTablet) {
3523             return super.isSignificantMove(absoluteDelta, pageOrientedSize);
3524         }
3525 
3526         return absoluteDelta
3527                 > deviceProfile.availableWidthPx * SIGNIFICANT_MOVE_SCREEN_WIDTH_PERCENTAGE;
3528     }
3529 
3530     @Override
3531     public CellPosMapper getCellPosMapper() {
3532         return mLauncher.getCellPosMapper();
3533     }
3534 
3535     /**
3536      * Used as a workaround to ensure that the AppWidgetService receives the
3537      * PACKAGE_ADDED broadcast before updating widgets.
3538      */
3539     private class DeferredWidgetRefresh implements Runnable, ProviderChangedListener {
3540         private final ArrayList<LauncherAppWidgetInfo> mInfos;
3541         private final LauncherWidgetHolder mWidgetHolder;
3542         private final Handler mHandler;
3543 
3544         private boolean mRefreshPending;
3545 
3546         DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos,
3547                               LauncherWidgetHolder holder) {
3548             mInfos = infos;
3549             mWidgetHolder = holder;
3550             mHandler = mLauncher.mHandler;
3551             mRefreshPending = true;
3552 
3553             mWidgetHolder.addProviderChangeListener(this);
3554             // Force refresh after 10 seconds, if we don't get the provider changed event.
3555             // This could happen when the provider is no longer available in the app.
3556             Message msg = Message.obtain(mHandler, this);
3557             msg.obj = DeferredWidgetRefresh.class;
3558             mHandler.sendMessageDelayed(msg, 10000);
3559         }
3560 
3561         @Override
3562         public void run() {
3563             mWidgetHolder.removeProviderChangeListener(this);
3564             mHandler.removeCallbacks(this);
3565 
3566             if (!mRefreshPending) {
3567                 return;
3568             }
3569 
3570             mRefreshPending = false;
3571 
3572             ArrayList<PendingAppWidgetHostView> views = new ArrayList<>(mInfos.size());
3573             mapOverItems((info, view) -> {
3574                 if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
3575                     views.add((PendingAppWidgetHostView) view);
3576                 }
3577                 // process all children
3578                 return false;
3579             });
3580             for (PendingAppWidgetHostView view : views) {
3581                 view.reInflate();
3582             }
3583         }
3584 
3585         @Override
3586         public void notifyWidgetProvidersChanged() {
3587             run();
3588         }
3589     }
3590 
3591     private class StateTransitionListener extends AnimatorListenerAdapter
3592             implements AnimatorUpdateListener {
3593 
3594         @Override
3595         public void onAnimationUpdate(ValueAnimator anim) {
3596             mTransitionProgress = anim.getAnimatedFraction();
3597         }
3598 
3599         @Override
3600         public void onAnimationStart(Animator animation) {
3601             onStartStateTransition();
3602         }
3603 
3604         @Override
3605         public void onAnimationEnd(Animator animation) {
3606             onEndStateTransition();
3607         }
3608     }
3609 }
3610