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