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