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