• 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 android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.LayoutTransition;
23 import android.animation.ObjectAnimator;
24 import android.animation.PropertyValuesHolder;
25 import android.animation.ValueAnimator;
26 import android.animation.ValueAnimator.AnimatorUpdateListener;
27 import android.annotation.SuppressLint;
28 import android.app.WallpaperManager;
29 import android.appwidget.AppWidgetHostView;
30 import android.appwidget.AppWidgetProviderInfo;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.res.Resources;
34 import android.graphics.Bitmap;
35 import android.graphics.Canvas;
36 import android.graphics.Matrix;
37 import android.graphics.Point;
38 import android.graphics.PointF;
39 import android.graphics.Rect;
40 import android.graphics.drawable.Drawable;
41 import android.os.Handler;
42 import android.os.IBinder;
43 import android.os.Parcelable;
44 import android.util.AttributeSet;
45 import android.util.Log;
46 import android.util.Property;
47 import android.util.SparseArray;
48 import android.view.MotionEvent;
49 import android.view.View;
50 import android.view.ViewDebug;
51 import android.view.ViewGroup;
52 import android.view.accessibility.AccessibilityManager;
53 import android.view.animation.DecelerateInterpolator;
54 import android.view.animation.Interpolator;
55 import android.widget.TextView;
56 
57 import com.android.launcher3.Launcher.CustomContentCallbacks;
58 import com.android.launcher3.Launcher.LauncherOverlay;
59 import com.android.launcher3.UninstallDropTarget.DropTargetSource;
60 import com.android.launcher3.accessibility.AccessibileDragListenerAdapter;
61 import com.android.launcher3.accessibility.OverviewAccessibilityDelegate;
62 import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
63 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
64 import com.android.launcher3.compat.AppWidgetManagerCompat;
65 import com.android.launcher3.compat.UserHandleCompat;
66 import com.android.launcher3.config.FeatureFlags;
67 import com.android.launcher3.config.ProviderConfig;
68 import com.android.launcher3.dragndrop.DragController;
69 import com.android.launcher3.dragndrop.DragLayer;
70 import com.android.launcher3.dragndrop.DragOptions;
71 import com.android.launcher3.dragndrop.DragScroller;
72 import com.android.launcher3.dragndrop.DragView;
73 import com.android.launcher3.dragndrop.SpringLoadedDragController;
74 import com.android.launcher3.folder.Folder;
75 import com.android.launcher3.folder.FolderIcon;
76 import com.android.launcher3.graphics.DragPreviewProvider;
77 import com.android.launcher3.userevent.nano.LauncherLogProto;
78 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
79 import com.android.launcher3.util.ItemInfoMatcher;
80 import com.android.launcher3.util.LongArrayMap;
81 import com.android.launcher3.util.MultiStateAlphaController;
82 import com.android.launcher3.util.Thunk;
83 import com.android.launcher3.util.VerticalFlingDetector;
84 import com.android.launcher3.util.WallpaperOffsetInterpolator;
85 import com.android.launcher3.widget.PendingAddShortcutInfo;
86 import com.android.launcher3.widget.PendingAddWidgetInfo;
87 
88 import java.util.ArrayList;
89 import java.util.HashMap;
90 import java.util.HashSet;
91 
92 /**
93  * The workspace is a wide area with a wallpaper and a finite number of pages.
94  * Each page contains a number of icons, folders or widgets the user can
95  * interact with. A workspace is meant to be used with a fixed width only.
96  */
97 public class Workspace extends PagedView
98         implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
99         DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener,
100         Insettable, DropTargetSource {
101     private static final String TAG = "Launcher.Workspace";
102 
103     private static boolean ENFORCE_DRAG_EVENT_ORDER = false;
104 
105     private static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
106     private static final int FADE_EMPTY_SCREEN_DURATION = 150;
107 
108     private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
109 
110     private static final boolean MAP_NO_RECURSE = false;
111     private static final boolean MAP_RECURSE = true;
112 
113     // The screen id used for the empty screen always present to the right.
114     public static final long EXTRA_EMPTY_SCREEN_ID = -201;
115     // The is the first screen. It is always present, even if its empty.
116     public static final long FIRST_SCREEN_ID = 0;
117 
118     private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
119 
120     private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200;
121     private long mTouchDownTime = -1;
122     private long mCustomContentShowTime = -1;
123 
124     private LayoutTransition mLayoutTransition;
125     @Thunk final WallpaperManager mWallpaperManager;
126 
127     private ShortcutAndWidgetContainer mDragSourceInternal;
128 
129     @Thunk LongArrayMap<CellLayout> mWorkspaceScreens = new LongArrayMap<>();
130     @Thunk ArrayList<Long> mScreenOrder = new ArrayList<Long>();
131 
132     @Thunk Runnable mRemoveEmptyScreenRunnable;
133     @Thunk boolean mDeferRemoveExtraEmptyScreen = false;
134 
135     /**
136      * CellInfo for the cell that is currently being dragged
137      */
138     private CellLayout.CellInfo mDragInfo;
139 
140     /**
141      * Target drop area calculated during last acceptDrop call.
142      */
143     @Thunk int[] mTargetCell = new int[2];
144     private int mDragOverX = -1;
145     private int mDragOverY = -1;
146 
147     CustomContentCallbacks mCustomContentCallbacks;
148     boolean mCustomContentShowing;
149     private float mLastCustomContentScrollProgress = -1f;
150     private String mCustomContentDescription = "";
151 
152     /**
153      * The CellLayout that is currently being dragged over
154      */
155     @Thunk CellLayout mDragTargetLayout = null;
156     /**
157      * The CellLayout that we will show as highlighted
158      */
159     private CellLayout mDragOverlappingLayout = null;
160 
161     /**
162      * The CellLayout which will be dropped to
163      */
164     private CellLayout mDropToLayout = null;
165 
166     @Thunk Launcher mLauncher;
167     @Thunk IconCache mIconCache;
168     @Thunk DragController mDragController;
169 
170     // These are temporary variables to prevent having to allocate a new object just to
171     // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
172     private static final Rect sTempRect = new Rect();
173     private final int[] mTempXY = new int[2];
174     @Thunk float[] mDragViewVisualCenter = new float[2];
175     private float[] mTempCellLayoutCenterCoordinates = new float[2];
176     private int[] mTempVisiblePagesRange = new int[2];
177     private Matrix mTempMatrix = new Matrix();
178 
179     private SpringLoadedDragController mSpringLoadedDragController;
180     private float mOverviewModeShrinkFactor;
181 
182     // State variable that indicates whether the pages are small (ie when you're
183     // in all apps or customize mode)
184 
185     public enum State {
186         NORMAL          (false, false),
187         NORMAL_HIDDEN   (false, false),
188         SPRING_LOADED   (false, true),
189         OVERVIEW        (true, true),
190         OVERVIEW_HIDDEN (true, false);
191 
192         public final boolean shouldUpdateWidget;
193         public final boolean hasMultipleVisiblePages;
194 
State(boolean shouldUpdateWidget, boolean hasMultipleVisiblePages)195         State(boolean shouldUpdateWidget, boolean hasMultipleVisiblePages) {
196             this.shouldUpdateWidget = shouldUpdateWidget;
197             this.hasMultipleVisiblePages = hasMultipleVisiblePages;
198         }
199     }
200 
201     // Direction used for moving the workspace and hotseat UI
202     public enum Direction {
203         X  (TRANSLATION_X),
204         Y  (TRANSLATION_Y);
205 
206         private final Property<View, Float> viewProperty;
207 
Direction(Property<View, Float> viewProperty)208         Direction(Property<View, Float> viewProperty) {
209             this.viewProperty = viewProperty;
210         }
211     }
212 
213     private static final int HOTSEAT_STATE_ALPHA_INDEX = 2;
214 
215     /**
216      * These values correspond to {@link Direction#X} & {@link Direction#Y}
217      */
218     private float[] mPageAlpha = new float[] {1, 1};
219     /**
220      * Hotseat alpha can be changed when moving horizontally, vertically, changing states.
221      * The values correspond to {@link Direction#X}, {@link Direction#Y} &
222      * {@link #HOTSEAT_STATE_ALPHA_INDEX} respectively.
223      */
224     private float[] mHotseatAlpha = new float[] {1, 1, 1};
225 
226     public static final int QSB_ALPHA_INDEX_STATE_CHANGE = 0;
227     public static final int QSB_ALPHA_INDEX_Y_TRANSLATION = 1;
228     public static final int QSB_ALPHA_INDEX_PAGE_SCROLL = 2;
229     public static final int QSB_ALPHA_INDEX_OVERLAY_SCROLL = 3;
230 
231 
232     MultiStateAlphaController mQsbAlphaController;
233 
234     @ViewDebug.ExportedProperty(category = "launcher")
235     private State mState = State.NORMAL;
236     private boolean mIsSwitchingState = false;
237 
238     boolean mAnimatingViewIntoPlace = false;
239     boolean mChildrenLayersEnabled = true;
240 
241     private boolean mStripScreensOnPageStopMoving = false;
242 
243     /** Is the user is dragging an item near the edge of a page? */
244     private boolean mInScrollArea = false;
245 
246     private DragPreviewProvider mOutlineProvider = null;
247     public static final int DRAG_BITMAP_PADDING = DragPreviewProvider.DRAG_BITMAP_PADDING;
248     private boolean mWorkspaceFadeInAdjacentScreens;
249 
250     final WallpaperOffsetInterpolator mWallpaperOffset;
251     private boolean mUnlockWallpaperFromDefaultPageOnLayout;
252 
253     @Thunk Runnable mDelayedResizeRunnable;
254     private Runnable mDelayedSnapToPageRunnable;
255 
256     // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
257     private static final int FOLDER_CREATION_TIMEOUT = 0;
258     public static final int REORDER_TIMEOUT = 350;
259     private final Alarm mFolderCreationAlarm = new Alarm();
260     private final Alarm mReorderAlarm = new Alarm();
261     private FolderIcon.PreviewBackground mFolderCreateBg;
262     private FolderIcon mDragOverFolderIcon = null;
263     private boolean mCreateUserFolderOnDrop = false;
264     private boolean mAddToExistingFolderOnDrop = false;
265     private float mMaxDistanceForFolderCreation;
266 
267     private final Canvas mCanvas = new Canvas();
268 
269     // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
270     private float mXDown;
271     private float mYDown;
272     final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
273     final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
274     final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
275 
276     // Relating to the animation of items being dropped externally
277     public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0;
278     public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1;
279     public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2;
280     public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3;
281     public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4;
282 
283     // Related to dragging, folder creation and reordering
284     private static final int DRAG_MODE_NONE = 0;
285     private static final int DRAG_MODE_CREATE_FOLDER = 1;
286     private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
287     private static final int DRAG_MODE_REORDER = 3;
288     private int mDragMode = DRAG_MODE_NONE;
289     @Thunk int mLastReorderX = -1;
290     @Thunk int mLastReorderY = -1;
291 
292     private SparseArray<Parcelable> mSavedStates;
293     private final ArrayList<Integer> mRestoredPages = new ArrayList<Integer>();
294 
295     private float mCurrentScale;
296     private float mTransitionProgress;
297 
298     @Thunk Runnable mDeferredAction;
299     private boolean mDeferDropAfterUninstall;
300     private boolean mUninstallSuccessful;
301 
302     // State related to Launcher Overlay
303     LauncherOverlay mLauncherOverlay;
304     boolean mScrollInteractionBegan;
305     boolean mStartedSendingScrollEvents;
306     float mLastOverlaySroll = 0;
307     // Total over scrollX in the overlay direction.
308     private int mUnboundedScrollX;
309     private boolean mForceDrawAdjacentPages = false;
310     // Total over scrollX in the overlay direction.
311     private float mOverlayTranslation;
312     private int mFirstPageScrollX;
313     private boolean mIgnoreQsbScroll;
314 
315     // Handles workspace state transitions
316     private WorkspaceStateTransitionAnimation mStateTransitionAnimation;
317 
318     private AccessibilityDelegate mPagesAccessibilityDelegate;
319     private OnStateChangeListener mOnStateChangeListener;
320 
321     /**
322      * Used to inflate the Workspace from XML.
323      *
324      * @param context The application's context.
325      * @param attrs The attributes set containing the Workspace's customization values.
326      */
Workspace(Context context, AttributeSet attrs)327     public Workspace(Context context, AttributeSet attrs) {
328         this(context, attrs, 0);
329     }
330 
331     /**
332      * Used to inflate the Workspace from XML.
333      *
334      * @param context The application's context.
335      * @param attrs The attributes set containing the Workspace's customization values.
336      * @param defStyle Unused.
337      */
Workspace(Context context, AttributeSet attrs, int defStyle)338     public Workspace(Context context, AttributeSet attrs, int defStyle) {
339         super(context, attrs, defStyle);
340 
341         mLauncher = Launcher.getLauncher(context);
342         mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this);
343         final Resources res = getResources();
344         DeviceProfile grid = mLauncher.getDeviceProfile();
345         mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
346         mWallpaperManager = WallpaperManager.getInstance(context);
347 
348         mWallpaperOffset = new WallpaperOffsetInterpolator(this);
349         mOverviewModeShrinkFactor =
350                 res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f;
351 
352         setOnHierarchyChangeListener(this);
353         setHapticFeedbackEnabled(false);
354 
355         initWorkspace();
356 
357         // Disable multitouch across the workspace/all apps/customize tray
358         setMotionEventSplittingEnabled(true);
359     }
360 
361     @Override
setInsets(Rect insets)362     public void setInsets(Rect insets) {
363         mInsets.set(insets);
364 
365         CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
366         if (customScreen != null) {
367             View customContent = customScreen.getShortcutsAndWidgets().getChildAt(0);
368             if (customContent instanceof Insettable) {
369                 ((Insettable) customContent).setInsets(mInsets);
370             }
371         }
372     }
373 
setOnStateChangeListener(OnStateChangeListener listener)374     public void setOnStateChangeListener(OnStateChangeListener listener) {
375         mOnStateChangeListener = listener;
376     }
377 
378     // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
379     // dimension if unsuccessful
estimateItemSize(ItemInfo itemInfo, boolean springLoaded)380     public int[] estimateItemSize(ItemInfo itemInfo, boolean springLoaded) {
381         float shrinkFactor = mLauncher.getDeviceProfile().workspaceSpringLoadShrinkFactor;
382         int[] size = new int[2];
383         if (getChildCount() > 0) {
384             // Use the first non-custom page to estimate the child position
385             CellLayout cl = (CellLayout) getChildAt(numCustomPages());
386             Rect r = estimateItemPosition(cl, 0, 0, itemInfo.spanX, itemInfo.spanY);
387             size[0] = r.width();
388             size[1] = r.height();
389             if (springLoaded) {
390                 size[0] *= shrinkFactor;
391                 size[1] *= shrinkFactor;
392             }
393             return size;
394         } else {
395             size[0] = Integer.MAX_VALUE;
396             size[1] = Integer.MAX_VALUE;
397             return size;
398         }
399     }
400 
estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan)401     public Rect estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan) {
402         Rect r = new Rect();
403         cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
404         return r;
405     }
406 
407     @Override
onDragStart(DropTarget.DragObject dragObject, DragOptions options)408     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
409         if (ENFORCE_DRAG_EVENT_ORDER) {
410             enfoceDragParity("onDragStart", 0, 0);
411         }
412 
413         if (mOutlineProvider != null) {
414             // The outline is used to visualize where the item will land if dropped
415             mOutlineProvider.generateDragOutline(mCanvas);
416         }
417 
418         updateChildrenLayersEnabled(false);
419         mLauncher.onDragStarted();
420         mLauncher.lockScreenOrientation();
421         mLauncher.onInteractionBegin();
422         // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging
423         InstallShortcutReceiver.enableInstallQueue();
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 
432         if (addNewPage) {
433             mDeferRemoveExtraEmptyScreen = false;
434             addExtraEmptyScreenOnDrag();
435 
436             if (dragObject.dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
437                     && dragObject.dragSource != this) {
438                 // When dragging a widget from different source, move to a page which has
439                 // enough space to place this widget (after rearranging/resizing). We special case
440                 // widgets as they cannot be placed inside a folder.
441                 // Start at the current page and search right (on LTR) until finding a page with
442                 // enough space. Since an empty screen is the furthest right, a page must be found.
443                 int currentPage = getPageNearestToCenterOfScreen();
444                 for (int pageIndex = currentPage; pageIndex < getPageCount(); pageIndex++) {
445                     CellLayout page = (CellLayout) getPageAt(pageIndex);
446                     if (page.hasReorderSolution(dragObject.dragInfo)) {
447                         setCurrentPage(pageIndex);
448                         break;
449                     }
450                 }
451             }
452         }
453 
454         if (!FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
455             // Always enter the spring loaded mode
456             mLauncher.enterSpringLoadedDragMode();
457         }
458     }
459 
deferRemoveExtraEmptyScreen()460     public void deferRemoveExtraEmptyScreen() {
461         mDeferRemoveExtraEmptyScreen = true;
462     }
463 
464     @Override
onDragEnd()465     public void onDragEnd() {
466         if (ENFORCE_DRAG_EVENT_ORDER) {
467             enfoceDragParity("onDragEnd", 0, 0);
468         }
469 
470         if (!mDeferRemoveExtraEmptyScreen) {
471             removeExtraEmptyScreen(true, mDragSourceInternal != null);
472         }
473 
474         updateChildrenLayersEnabled(false);
475         mLauncher.unlockScreenOrientation(false);
476 
477         // Re-enable any Un/InstallShortcutReceiver and now process any queued items
478         InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
479 
480         mDragSourceInternal = null;
481         mLauncher.onInteractionEnd();
482     }
483 
484     /**
485      * Initializes various states for this workspace.
486      */
initWorkspace()487     protected void initWorkspace() {
488         mCurrentPage = getDefaultPage();
489         LauncherAppState app = LauncherAppState.getInstance();
490         DeviceProfile grid = mLauncher.getDeviceProfile();
491         mIconCache = app.getIconCache();
492         setWillNotDraw(false);
493         setClipChildren(false);
494         setClipToPadding(false);
495         setChildrenDrawnWithCacheEnabled(true);
496 
497         setMinScale(mOverviewModeShrinkFactor);
498         setupLayoutTransition();
499 
500         mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
501 
502         // Set the wallpaper dimensions when Launcher starts up
503         setWallpaperDimension();
504 
505         setEdgeGlowColor(getResources().getColor(R.color.workspace_edge_effect_color));
506     }
507 
508     @Override
initParentViews(View parent)509     public void initParentViews(View parent) {
510         super.initParentViews(parent);
511         mPageIndicator.setAccessibilityDelegate(new OverviewAccessibilityDelegate());
512         mQsbAlphaController = new MultiStateAlphaController(mLauncher.getQsbContainer(), 4);
513     }
514 
getDefaultPage()515     private int getDefaultPage() {
516         return numCustomPages();
517     }
518 
setupLayoutTransition()519     private void setupLayoutTransition() {
520         // We want to show layout transitions when pages are deleted, to close the gap.
521         mLayoutTransition = new LayoutTransition();
522         mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
523         mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
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
onChildViewAdded(View parent, View child)537     public void onChildViewAdded(View parent, 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.setClickable(true);
544         cl.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
545         super.onChildViewAdded(parent, child);
546     }
547 
shouldDrawChild(View child)548     protected boolean shouldDrawChild(View child) {
549         final CellLayout cl = (CellLayout) child;
550         return super.shouldDrawChild(child) &&
551             (mIsSwitchingState ||
552              cl.getShortcutsAndWidgets().getAlpha() > 0 ||
553              cl.getBackgroundAlpha() > 0);
554     }
555 
556     /**
557      * @return The open folder on the current screen, or null if there is none
558      */
getOpenFolder()559     public Folder getOpenFolder() {
560         DragLayer dragLayer = mLauncher.getDragLayer();
561         // Iterate in reverse order. Folder is added later to the dragLayer,
562         // and will be one of the last views.
563         for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
564             View child = dragLayer.getChildAt(i);
565             if (child instanceof Folder) {
566                 Folder folder = (Folder) child;
567                 if (folder.getInfo().opened)
568                     return folder;
569             }
570         }
571         return null;
572     }
573 
isTouchActive()574     boolean isTouchActive() {
575         return mTouchState != TOUCH_STATE_REST;
576     }
577 
getEmbeddedQsbId()578     private int getEmbeddedQsbId() {
579         return mLauncher.getDeviceProfile().isVerticalBarLayout()
580                 ? R.id.qsb_container : R.id.workspace_blocked_row;
581     }
582 
583     /**
584      * Initializes and binds the first page
585      * @param qsb an exisitng qsb to recycle or null.
586      */
bindAndInitFirstWorkspaceScreen(View qsb)587     public void bindAndInitFirstWorkspaceScreen(View qsb) {
588         if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
589             return;
590         }
591         // Add the first page
592         CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, 0);
593         if (FeatureFlags.PULLDOWN_SEARCH) {
594             firstPage.setOnTouchListener(new VerticalFlingDetector(mLauncher) {
595                 // detect fling when touch started from empty space
596                 @Override
597                 public boolean onTouch(View v, MotionEvent ev) {
598                     if (workspaceInModalState()) return false;
599                     if (shouldConsumeTouch(v)) return true;
600                     if (super.onTouch(v, ev)) {
601                         mLauncher.startSearch("", false, null, false);
602                         return true;
603                     }
604                     return false;
605                 }
606             });
607             firstPage.setOnInterceptTouchListener(new VerticalFlingDetector(mLauncher) {
608                 // detect fling when touch started from on top of the icons
609                 @Override
610                 public boolean onTouch(View v, MotionEvent ev) {
611                     if (shouldConsumeTouch(v)) return true;
612                     if (super.onTouch(v, ev)) {
613                         mLauncher.startSearch("", false, null, false);
614                         return true;
615                     }
616                     return false;
617                 }
618             });
619         }
620         // Always add a QSB on the first screen.
621         if (qsb == null) {
622             // In transposed layout, we add the QSB in the Grid. As workspace does not touch the
623             // edges, we do not need a full width QSB.
624             qsb = mLauncher.getLayoutInflater().inflate(
625                     mLauncher.getDeviceProfile().isVerticalBarLayout()
626                             ? R.layout.qsb_container : R.layout.qsb_blocker_view,
627                     firstPage, false);
628         }
629 
630         CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, firstPage.getCountX(), 1);
631         lp.canReorder = false;
632         if (!firstPage.addViewToCellLayout(qsb, 0, getEmbeddedQsbId(), lp, true)) {
633             Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
634         }
635     }
636 
637     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)638     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
639         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
640 
641         // Update the QSB to match the cell height. This is treating the QSB essentially as a child
642         // of workspace despite that it's not a true child.
643         // Note that it relies on the strict ordering of measuring the workspace before the QSB
644         // at the dragLayer level.
645         if (getChildCount() > 0) {
646             CellLayout firstPage = (CellLayout) getChildAt(0);
647             int cellHeight = firstPage.getCellHeight();
648 
649             View qsbContainer = mLauncher.getQsbContainer();
650             ViewGroup.LayoutParams lp = qsbContainer.getLayoutParams();
651             if (cellHeight > 0 && lp.height != cellHeight) {
652                 lp.height = cellHeight;
653                 qsbContainer.setLayoutParams(lp);
654             }
655         }
656     }
657 
removeAllWorkspaceScreens()658     public void removeAllWorkspaceScreens() {
659         // Disable all layout transitions before removing all pages to ensure that we don't get the
660         // transition animations competing with us changing the scroll when we add pages or the
661         // custom content screen
662         disableLayoutTransitions();
663 
664         // Since we increment the current page when we call addCustomContentPage via bindScreens
665         // (and other places), we need to adjust the current page back when we clear the pages
666         if (hasCustomContent()) {
667             removeCustomContentPage();
668         }
669 
670         // Recycle the QSB widget
671         View qsb = findViewById(getEmbeddedQsbId());
672         if (qsb != null) {
673             ((ViewGroup) qsb.getParent()).removeView(qsb);
674         }
675 
676         // Remove the pages and clear the screen models
677         removeAllViews();
678         mScreenOrder.clear();
679         mWorkspaceScreens.clear();
680 
681         // Ensure that the first page is always present
682         bindAndInitFirstWorkspaceScreen(qsb);
683 
684         // Re-enable the layout transitions
685         enableLayoutTransitions();
686     }
687 
insertNewWorkspaceScreenBeforeEmptyScreen(long screenId)688     public void insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
689         // Find the index to insert this view into.  If the empty screen exists, then
690         // insert it before that.
691         int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
692         if (insertIndex < 0) {
693             insertIndex = mScreenOrder.size();
694         }
695         insertNewWorkspaceScreen(screenId, insertIndex);
696     }
697 
insertNewWorkspaceScreen(long screenId)698     public void insertNewWorkspaceScreen(long screenId) {
699         insertNewWorkspaceScreen(screenId, getChildCount());
700     }
701 
insertNewWorkspaceScreen(long screenId, int insertIndex)702     public CellLayout insertNewWorkspaceScreen(long screenId, int insertIndex) {
703         if (mWorkspaceScreens.containsKey(screenId)) {
704             throw new RuntimeException("Screen id " + screenId + " already exists!");
705         }
706 
707         // Inflate the cell layout, but do not add it automatically so that we can get the newly
708         // created CellLayout.
709         CellLayout newScreen = (CellLayout) mLauncher.getLayoutInflater().inflate(
710                         R.layout.workspace_screen, this, false /* attachToRoot */);
711         newScreen.setOnLongClickListener(mLongClickListener);
712         newScreen.setOnClickListener(mLauncher);
713         newScreen.setSoundEffectsEnabled(false);
714         mWorkspaceScreens.put(screenId, newScreen);
715         mScreenOrder.add(insertIndex, screenId);
716         addView(newScreen, insertIndex);
717 
718         if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
719             newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
720         }
721 
722         return newScreen;
723     }
724 
createCustomContentContainer()725     public void createCustomContentContainer() {
726         CellLayout customScreen = (CellLayout)
727                 mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, this, false);
728         customScreen.disableDragTarget();
729         customScreen.disableJailContent();
730 
731         mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen);
732         mScreenOrder.add(0, CUSTOM_CONTENT_SCREEN_ID);
733 
734         // We want no padding on the custom content
735         customScreen.setPadding(0, 0, 0, 0);
736 
737         addFullScreenPage(customScreen);
738 
739         // Update the custom content hint
740         if (mRestorePage != INVALID_RESTORE_PAGE) {
741             mRestorePage = mRestorePage + 1;
742         } else {
743             setCurrentPage(getCurrentPage() + 1);
744         }
745     }
746 
removeCustomContentPage()747     public void removeCustomContentPage() {
748         CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
749         if (customScreen == null) {
750             throw new RuntimeException("Expected custom content screen to exist");
751         }
752 
753         mWorkspaceScreens.remove(CUSTOM_CONTENT_SCREEN_ID);
754         mScreenOrder.remove(CUSTOM_CONTENT_SCREEN_ID);
755         removeView(customScreen);
756 
757         if (mCustomContentCallbacks != null) {
758             mCustomContentCallbacks.onScrollProgressChanged(0);
759             mCustomContentCallbacks.onHide();
760         }
761 
762         mCustomContentCallbacks = null;
763 
764         // Update the custom content hint
765         if (mRestorePage != INVALID_RESTORE_PAGE) {
766             mRestorePage = mRestorePage - 1;
767         } else {
768             setCurrentPage(getCurrentPage() - 1);
769         }
770     }
771 
addToCustomContentPage(View customContent, CustomContentCallbacks callbacks, String description)772     public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks,
773             String description) {
774         if (getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID) < 0) {
775             throw new RuntimeException("Expected custom content screen to exist");
776         }
777 
778         // Add the custom content to the full screen custom page
779         CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
780         int spanX = customScreen.getCountX();
781         int spanY = customScreen.getCountY();
782         CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, spanX, spanY);
783         lp.canReorder  = false;
784         lp.isFullscreen = true;
785         if (customContent instanceof Insettable) {
786             ((Insettable)customContent).setInsets(mInsets);
787         }
788 
789         // Verify that the child is removed from any existing parent.
790         if (customContent.getParent() instanceof ViewGroup) {
791             ViewGroup parent = (ViewGroup) customContent.getParent();
792             parent.removeView(customContent);
793         }
794         customScreen.removeAllViews();
795         customContent.setFocusable(true);
796         customContent.setOnKeyListener(new FullscreenKeyEventListener());
797         customContent.setOnFocusChangeListener(mLauncher.mFocusHandler
798                 .getHideIndicatorOnFocusListener());
799         customScreen.addViewToCellLayout(customContent, 0, 0, lp, true);
800         mCustomContentDescription = description;
801 
802         mCustomContentCallbacks = callbacks;
803     }
804 
addExtraEmptyScreenOnDrag()805     public void addExtraEmptyScreenOnDrag() {
806         boolean lastChildOnScreen = false;
807         boolean childOnFinalScreen = false;
808 
809         // Cancel any pending removal of empty screen
810         mRemoveEmptyScreenRunnable = null;
811 
812         if (mDragSourceInternal != null) {
813             if (mDragSourceInternal.getChildCount() == 1) {
814                 lastChildOnScreen = true;
815             }
816             CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
817             if (indexOfChild(cl) == getChildCount() - 1) {
818                 childOnFinalScreen = true;
819             }
820         }
821 
822         // If this is the last item on the final screen
823         if (lastChildOnScreen && childOnFinalScreen) {
824             return;
825         }
826         if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
827             insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
828         }
829     }
830 
addExtraEmptyScreen()831     public boolean addExtraEmptyScreen() {
832         if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
833             insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
834             return true;
835         }
836         return false;
837     }
838 
convertFinalScreenToEmptyScreenIfNecessary()839     private void convertFinalScreenToEmptyScreenIfNecessary() {
840         if (mLauncher.isWorkspaceLoading()) {
841             // Invalid and dangerous operation if workspace is loading
842             return;
843         }
844 
845         if (hasExtraEmptyScreen() || mScreenOrder.size() == 0) return;
846         long finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1);
847 
848         if (finalScreenId == CUSTOM_CONTENT_SCREEN_ID) return;
849         CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId);
850 
851         // If the final screen is empty, convert it to the extra empty screen
852         if (finalScreen.getShortcutsAndWidgets().getChildCount() == 0 &&
853                 !finalScreen.isDropPending()) {
854             mWorkspaceScreens.remove(finalScreenId);
855             mScreenOrder.remove(finalScreenId);
856 
857             // if this is the last non-custom content screen, convert it to the empty screen
858             mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen);
859             mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
860 
861             // Update the model if we have changed any screens
862             mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
863         }
864     }
865 
removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens)866     public void removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens) {
867         removeExtraEmptyScreenDelayed(animate, null, 0, stripEmptyScreens);
868     }
869 
removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete, final int delay, final boolean stripEmptyScreens)870     public void removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete,
871             final int delay, final boolean stripEmptyScreens) {
872         if (mLauncher.isWorkspaceLoading()) {
873             // Don't strip empty screens if the workspace is still loading
874             return;
875         }
876 
877         if (delay > 0) {
878             postDelayed(new Runnable() {
879                 @Override
880                 public void run() {
881                     removeExtraEmptyScreenDelayed(animate, onComplete, 0, stripEmptyScreens);
882                 }
883             }, delay);
884             return;
885         }
886 
887         convertFinalScreenToEmptyScreenIfNecessary();
888         if (hasExtraEmptyScreen()) {
889             int emptyIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
890             if (getNextPage() == emptyIndex) {
891                 snapToPage(getNextPage() - 1, SNAP_OFF_EMPTY_SCREEN_DURATION);
892                 fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION,
893                         onComplete, stripEmptyScreens);
894             } else {
895                 snapToPage(getNextPage(), 0);
896                 fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION,
897                         onComplete, stripEmptyScreens);
898             }
899             return;
900         } else if (stripEmptyScreens) {
901             // If we're not going to strip the empty screens after removing
902             // the extra empty screen, do it right away.
903             stripEmptyScreens();
904         }
905 
906         if (onComplete != null) {
907             onComplete.run();
908         }
909     }
910 
fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete, final boolean stripEmptyScreens)911     private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete,
912             final boolean stripEmptyScreens) {
913         // XXX: Do we need to update LM workspace screens below?
914         PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
915         PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", 0f);
916 
917         final CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
918 
919         mRemoveEmptyScreenRunnable = new Runnable() {
920             @Override
921             public void run() {
922                 if (hasExtraEmptyScreen()) {
923                     mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
924                     mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
925                     removeView(cl);
926                     if (stripEmptyScreens) {
927                         stripEmptyScreens();
928                     }
929                     // Update the page indicator to reflect the removed page.
930                     showPageIndicatorAtCurrentScroll();
931                 }
932             }
933         };
934 
935         ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(cl, alpha, bgAlpha);
936         oa.setDuration(duration);
937         oa.setStartDelay(delay);
938         oa.addListener(new AnimatorListenerAdapter() {
939             @Override
940             public void onAnimationEnd(Animator animation) {
941                 if (mRemoveEmptyScreenRunnable != null) {
942                     mRemoveEmptyScreenRunnable.run();
943                 }
944                 if (onComplete != null) {
945                     onComplete.run();
946                 }
947             }
948         });
949         oa.start();
950     }
951 
hasExtraEmptyScreen()952     public boolean hasExtraEmptyScreen() {
953         int nScreens = getChildCount();
954         nScreens = nScreens - numCustomPages();
955         return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && nScreens > 1;
956     }
957 
commitExtraEmptyScreen()958     public long commitExtraEmptyScreen() {
959         if (mLauncher.isWorkspaceLoading()) {
960             // Invalid and dangerous operation if workspace is loading
961             return -1;
962         }
963 
964         int index = getPageIndexForScreenId(EXTRA_EMPTY_SCREEN_ID);
965         CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
966         mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
967         mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
968 
969         long newId = LauncherSettings.Settings.call(getContext().getContentResolver(),
970                 LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
971                 .getLong(LauncherSettings.Settings.EXTRA_VALUE);
972         mWorkspaceScreens.put(newId, cl);
973         mScreenOrder.add(newId);
974 
975         // Update the model for the new screen
976         mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
977 
978         return newId;
979     }
980 
getScreenWithId(long screenId)981     public CellLayout getScreenWithId(long screenId) {
982         return mWorkspaceScreens.get(screenId);
983     }
984 
getIdForScreen(CellLayout layout)985     public long getIdForScreen(CellLayout layout) {
986         int index = mWorkspaceScreens.indexOfValue(layout);
987         if (index != -1) {
988             return mWorkspaceScreens.keyAt(index);
989         }
990         return -1;
991     }
992 
getPageIndexForScreenId(long screenId)993     public int getPageIndexForScreenId(long screenId) {
994         return indexOfChild(mWorkspaceScreens.get(screenId));
995     }
996 
getScreenIdForPageIndex(int index)997     public long getScreenIdForPageIndex(int index) {
998         if (0 <= index && index < mScreenOrder.size()) {
999             return mScreenOrder.get(index);
1000         }
1001         return -1;
1002     }
1003 
getScreenOrder()1004     public ArrayList<Long> getScreenOrder() {
1005         return mScreenOrder;
1006     }
1007 
stripEmptyScreens()1008     public void stripEmptyScreens() {
1009         if (mLauncher.isWorkspaceLoading()) {
1010             // Don't strip empty screens if the workspace is still loading.
1011             // This is dangerous and can result in data loss.
1012             return;
1013         }
1014 
1015         if (isPageMoving()) {
1016             mStripScreensOnPageStopMoving = true;
1017             return;
1018         }
1019 
1020         int currentPage = getNextPage();
1021         ArrayList<Long> removeScreens = new ArrayList<Long>();
1022         int total = mWorkspaceScreens.size();
1023         for (int i = 0; i < total; i++) {
1024             long id = mWorkspaceScreens.keyAt(i);
1025             CellLayout cl = mWorkspaceScreens.valueAt(i);
1026             // FIRST_SCREEN_ID can never be removed.
1027             if ((!FeatureFlags.QSB_ON_FIRST_SCREEN || id > FIRST_SCREEN_ID)
1028                     && cl.getShortcutsAndWidgets().getChildCount() == 0) {
1029                 removeScreens.add(id);
1030             }
1031         }
1032 
1033         boolean isInAccessibleDrag = mLauncher.getAccessibilityDelegate().isInAccessibleDrag();
1034 
1035         // We enforce at least one page to add new items to. In the case that we remove the last
1036         // such screen, we convert the last screen to the empty screen
1037         int minScreens = 1 + numCustomPages();
1038 
1039         int pageShift = 0;
1040         for (Long id: removeScreens) {
1041             CellLayout cl = mWorkspaceScreens.get(id);
1042             mWorkspaceScreens.remove(id);
1043             mScreenOrder.remove(id);
1044 
1045             if (getChildCount() > minScreens) {
1046                 if (indexOfChild(cl) < currentPage) {
1047                     pageShift++;
1048                 }
1049 
1050                 if (isInAccessibleDrag) {
1051                     cl.enableAccessibleDrag(false, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
1052                 }
1053 
1054                 removeView(cl);
1055             } else {
1056                 // if this is the last non-custom content screen, convert it to the empty screen
1057                 mRemoveEmptyScreenRunnable = null;
1058                 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
1059                 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
1060             }
1061         }
1062 
1063         if (!removeScreens.isEmpty()) {
1064             // Update the model if we have changed any screens
1065             mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
1066         }
1067 
1068         if (pageShift >= 0) {
1069             setCurrentPage(currentPage - pageShift);
1070         }
1071     }
1072 
1073     // See implementation for parameter definition.
addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY)1074     void addInScreen(View child, long container, long screenId,
1075             int x, int y, int spanX, int spanY) {
1076         addInScreen(child, container, screenId, x, y, spanX, spanY, false, false);
1077     }
1078 
1079     // At bind time, we use the rank (screenId) to compute x and y for hotseat items.
1080     // See implementation for parameter definition.
addInScreenFromBind(View child, long container, long screenId, int x, int y, int spanX, int spanY)1081     public void addInScreenFromBind(View child, long container, long screenId, int x, int y,
1082             int spanX, int spanY) {
1083         addInScreen(child, container, screenId, x, y, spanX, spanY, false, true);
1084     }
1085 
1086     // See implementation for parameter definition.
addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY, boolean insert)1087     void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
1088             boolean insert) {
1089         addInScreen(child, container, screenId, x, y, spanX, spanY, insert, false);
1090     }
1091 
1092     /**
1093      * Adds the specified child in the specified screen. The position and dimension of
1094      * the child are defined by x, y, spanX and spanY.
1095      *
1096      * @param child The child to add in one of the workspace's screens.
1097      * @param screenId The screen in which to add the child.
1098      * @param x The X position of the child in the screen's grid.
1099      * @param y The Y position of the child in the screen's grid.
1100      * @param spanX The number of cells spanned horizontally by the child.
1101      * @param spanY The number of cells spanned vertically by the child.
1102      * @param insert When true, the child is inserted at the beginning of the children list.
1103      * @param computeXYFromRank When true, we use the rank (stored in screenId) to compute
1104      *                          the x and y position in which to place hotseat items. Otherwise
1105      *                          we use the x and y position to compute the rank.
1106      */
addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY, boolean insert, boolean computeXYFromRank)1107     void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
1108             boolean insert, boolean computeXYFromRank) {
1109         if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1110             if (getScreenWithId(screenId) == null) {
1111                 Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
1112                 // DEBUGGING - Print out the stack trace to see where we are adding from
1113                 new Throwable().printStackTrace();
1114                 return;
1115             }
1116         }
1117         if (screenId == EXTRA_EMPTY_SCREEN_ID) {
1118             // This should never happen
1119             throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
1120         }
1121 
1122         final CellLayout layout;
1123         if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1124             layout = mLauncher.getHotseat().getLayout();
1125             child.setOnKeyListener(new HotseatIconKeyEventListener());
1126 
1127             // Hide folder title in the hotseat
1128             if (child instanceof FolderIcon) {
1129                 ((FolderIcon) child).setTextVisible(false);
1130             }
1131 
1132             if (computeXYFromRank) {
1133                 x = mLauncher.getHotseat().getCellXFromOrder((int) screenId);
1134                 y = mLauncher.getHotseat().getCellYFromOrder((int) screenId);
1135             } else {
1136                 screenId = mLauncher.getHotseat().getOrderInHotseat(x, y);
1137             }
1138         } else {
1139             // Show folder title if not in the hotseat
1140             if (child instanceof FolderIcon) {
1141                 ((FolderIcon) child).setTextVisible(true);
1142             }
1143             layout = getScreenWithId(screenId);
1144             child.setOnKeyListener(new IconKeyEventListener());
1145         }
1146 
1147         ViewGroup.LayoutParams genericLp = child.getLayoutParams();
1148         CellLayout.LayoutParams lp;
1149         if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
1150             lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
1151         } else {
1152             lp = (CellLayout.LayoutParams) genericLp;
1153             lp.cellX = x;
1154             lp.cellY = y;
1155             lp.cellHSpan = spanX;
1156             lp.cellVSpan = spanY;
1157         }
1158 
1159         if (spanX < 0 && spanY < 0) {
1160             lp.isLockedToGrid = false;
1161         }
1162 
1163         // Get the canonical child id to uniquely represent this view in this screen
1164         ItemInfo info = (ItemInfo) child.getTag();
1165         int childId = mLauncher.getViewIdForItem(info);
1166 
1167         boolean markCellsAsOccupied = !(child instanceof Folder);
1168         if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
1169             // TODO: This branch occurs when the workspace is adding views
1170             // outside of the defined grid
1171             // maybe we should be deleting these items from the LauncherModel?
1172             Log.e(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
1173         }
1174 
1175         if (!(child instanceof Folder)) {
1176             child.setHapticFeedbackEnabled(false);
1177             child.setOnLongClickListener(mLongClickListener);
1178         }
1179         if (child instanceof DropTarget) {
1180             mDragController.addDropTarget((DropTarget) child);
1181         }
1182     }
1183 
1184     /**
1185      * Called directly from a CellLayout (not by the framework), after we've been added as a
1186      * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
1187      * that it should intercept touch events, which is not something that is normally supported.
1188      */
1189     @SuppressLint("ClickableViewAccessibility")
1190     @Override
onTouch(View v, MotionEvent event)1191     public boolean onTouch(View v, MotionEvent event) {
1192         return shouldConsumeTouch(v);
1193     }
1194 
shouldConsumeTouch(View v)1195     private boolean shouldConsumeTouch(View v) {
1196         return (workspaceInModalState() || !isFinishedSwitchingState())
1197                 || (!workspaceInModalState() && indexOfChild(v) != mCurrentPage);
1198     }
1199 
isSwitchingState()1200     public boolean isSwitchingState() {
1201         return mIsSwitchingState;
1202     }
1203 
1204     /** This differs from isSwitchingState in that we take into account how far the transition
1205      *  has completed. */
isFinishedSwitchingState()1206     public boolean isFinishedSwitchingState() {
1207         return !mIsSwitchingState || (mTransitionProgress > 0.5f);
1208     }
1209 
onWindowVisibilityChanged(int visibility)1210     protected void onWindowVisibilityChanged (int visibility) {
1211         mLauncher.onWindowVisibilityChanged(visibility);
1212     }
1213 
1214     @Override
dispatchUnhandledMove(View focused, int direction)1215     public boolean dispatchUnhandledMove(View focused, int direction) {
1216         if (workspaceInModalState() || !isFinishedSwitchingState()) {
1217             // when the home screens are shrunken, shouldn't allow side-scrolling
1218             return false;
1219         }
1220         return super.dispatchUnhandledMove(focused, direction);
1221     }
1222 
1223     @Override
onInterceptTouchEvent(MotionEvent ev)1224     public boolean onInterceptTouchEvent(MotionEvent ev) {
1225         switch (ev.getAction() & MotionEvent.ACTION_MASK) {
1226         case MotionEvent.ACTION_DOWN:
1227             mXDown = ev.getX();
1228             mYDown = ev.getY();
1229             mTouchDownTime = System.currentTimeMillis();
1230             break;
1231         case MotionEvent.ACTION_POINTER_UP:
1232         case MotionEvent.ACTION_UP:
1233             if (mTouchState == TOUCH_STATE_REST) {
1234                 final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
1235                 if (currentPage != null) {
1236                     onWallpaperTap(ev);
1237                 }
1238             }
1239         }
1240         return super.onInterceptTouchEvent(ev);
1241     }
1242 
1243     @Override
onGenericMotionEvent(MotionEvent event)1244     public boolean onGenericMotionEvent(MotionEvent event) {
1245         // Ignore pointer scroll events if the custom content doesn't allow scrolling.
1246         if ((getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID)
1247                 && (mCustomContentCallbacks != null)
1248                 && !mCustomContentCallbacks.isScrollingAllowed()) {
1249             return false;
1250         }
1251         return super.onGenericMotionEvent(event);
1252     }
1253 
reinflateWidgetsIfNecessary()1254     protected void reinflateWidgetsIfNecessary() {
1255         final int clCount = getChildCount();
1256         for (int i = 0; i < clCount; i++) {
1257             CellLayout cl = (CellLayout) getChildAt(i);
1258             ShortcutAndWidgetContainer swc = cl.getShortcutsAndWidgets();
1259             final int itemCount = swc.getChildCount();
1260             for (int j = 0; j < itemCount; j++) {
1261                 View v = swc.getChildAt(j);
1262 
1263                 if (v instanceof LauncherAppWidgetHostView
1264                         && v.getTag() instanceof LauncherAppWidgetInfo) {
1265                     LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
1266                     LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) v;
1267                     if (lahv.isReinflateRequired()) {
1268                         // Remove and rebind the current widget (which was inflated in the wrong
1269                         // orientation), but don't delete it from the database
1270                         mLauncher.removeItem(lahv, info, false  /* deleteFromDb */);
1271                         mLauncher.bindAppWidget(info);
1272                     }
1273                 }
1274             }
1275         }
1276     }
1277 
1278     @Override
determineScrollingStart(MotionEvent ev)1279     protected void determineScrollingStart(MotionEvent ev) {
1280         if (!isFinishedSwitchingState()) return;
1281 
1282         float deltaX = ev.getX() - mXDown;
1283         float absDeltaX = Math.abs(deltaX);
1284         float absDeltaY = Math.abs(ev.getY() - mYDown);
1285 
1286         if (Float.compare(absDeltaX, 0f) == 0) return;
1287 
1288         float slope = absDeltaY / absDeltaX;
1289         float theta = (float) Math.atan(slope);
1290 
1291         if (absDeltaX > mTouchSlop || absDeltaY > mTouchSlop) {
1292             cancelCurrentPageLongPress();
1293         }
1294 
1295         boolean passRightSwipesToCustomContent =
1296                 (mTouchDownTime - mCustomContentShowTime) > CUSTOM_CONTENT_GESTURE_DELAY;
1297 
1298         boolean swipeInIgnoreDirection = mIsRtl ? deltaX < 0 : deltaX > 0;
1299         boolean onCustomContentScreen =
1300                 getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID;
1301         if (swipeInIgnoreDirection && onCustomContentScreen && passRightSwipesToCustomContent) {
1302             // Pass swipes to the right to the custom content page.
1303             return;
1304         }
1305 
1306         if (onCustomContentScreen && (mCustomContentCallbacks != null)
1307                 && !mCustomContentCallbacks.isScrollingAllowed()) {
1308             // Don't allow workspace scrolling if the current custom content screen doesn't allow
1309             // scrolling.
1310             return;
1311         }
1312 
1313         if (theta > MAX_SWIPE_ANGLE) {
1314             // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
1315             return;
1316         } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
1317             // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
1318             // increase the touch slop to make it harder to begin scrolling the workspace. This
1319             // results in vertically scrolling widgets to more easily. The higher the angle, the
1320             // more we increase touch slop.
1321             theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
1322             float extraRatio = (float)
1323                     Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
1324             super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
1325         } else {
1326             // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
1327             super.determineScrollingStart(ev);
1328         }
1329     }
1330 
onPageBeginMoving()1331     protected void onPageBeginMoving() {
1332         super.onPageBeginMoving();
1333 
1334         if (isHardwareAccelerated()) {
1335             updateChildrenLayersEnabled(false);
1336         } else {
1337             if (mNextPage != INVALID_PAGE) {
1338                 // we're snapping to a particular screen
1339                 enableChildrenCache(mCurrentPage, mNextPage);
1340             } else {
1341                 // this is when user is actively dragging a particular screen, they might
1342                 // swipe it either left or right (but we won't advance by more than one screen)
1343                 enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
1344             }
1345         }
1346     }
1347 
onPageEndMoving()1348     protected void onPageEndMoving() {
1349         super.onPageEndMoving();
1350 
1351         if (isHardwareAccelerated()) {
1352             updateChildrenLayersEnabled(false);
1353         } else {
1354             clearChildrenCache();
1355         }
1356 
1357         if (mDragController.isDragging()) {
1358             if (workspaceInModalState()) {
1359                 // If we are in springloaded mode, then force an event to check if the current touch
1360                 // is under a new page (to scroll to)
1361                 mDragController.forceTouchMove();
1362             }
1363         }
1364 
1365         if (mDelayedResizeRunnable != null && !mIsSwitchingState) {
1366             mDelayedResizeRunnable.run();
1367             mDelayedResizeRunnable = null;
1368         }
1369 
1370         if (mDelayedSnapToPageRunnable != null) {
1371             mDelayedSnapToPageRunnable.run();
1372             mDelayedSnapToPageRunnable = null;
1373         }
1374         if (mStripScreensOnPageStopMoving) {
1375             stripEmptyScreens();
1376             mStripScreensOnPageStopMoving = false;
1377         }
1378     }
1379 
onScrollInteractionBegin()1380     protected void onScrollInteractionBegin() {
1381         super.onScrollInteractionEnd();
1382         mScrollInteractionBegan = true;
1383     }
1384 
onScrollInteractionEnd()1385     protected void onScrollInteractionEnd() {
1386         super.onScrollInteractionEnd();
1387         mScrollInteractionBegan = false;
1388         if (mStartedSendingScrollEvents) {
1389             mStartedSendingScrollEvents = false;
1390             mLauncherOverlay.onScrollInteractionEnd();
1391         }
1392     }
1393 
setLauncherOverlay(LauncherOverlay overlay)1394     public void setLauncherOverlay(LauncherOverlay overlay) {
1395         mLauncherOverlay = overlay;
1396         // A new overlay has been set. Reset event tracking
1397         mStartedSendingScrollEvents = false;
1398         onOverlayScrollChanged(0);
1399     }
1400 
1401     @Override
getUnboundedScrollX()1402     protected int getUnboundedScrollX() {
1403         if (isScrollingOverlay()) {
1404             return mUnboundedScrollX;
1405         }
1406 
1407         return super.getUnboundedScrollX();
1408     }
1409 
isScrollingOverlay()1410     private boolean isScrollingOverlay() {
1411         return mLauncherOverlay != null &&
1412                 ((mIsRtl && mUnboundedScrollX > mMaxScrollX) || (!mIsRtl && mUnboundedScrollX < 0));
1413     }
1414 
1415     @Override
snapToDestination()1416     protected void snapToDestination() {
1417         // If we're overscrolling the overlay, we make sure to immediately reset the PagedView
1418         // to it's baseline position instead of letting the overscroll settle. The overlay handles
1419         // it's own settling, and every gesture to the overlay should be self-contained and start
1420         // from 0, so we zero it out here.
1421         if (isScrollingOverlay()) {
1422             int finalScroll = mIsRtl ? mMaxScrollX : 0;
1423 
1424             // We reset mWasInOverscroll so that PagedView doesn't zero out the overscroll
1425             // interaction when we call scrollTo.
1426             mWasInOverscroll = false;
1427             scrollTo(finalScroll, getScrollY());
1428         } else {
1429             super.snapToDestination();
1430         }
1431     }
1432 
1433     @Override
scrollTo(int x, int y)1434     public void scrollTo(int x, int y) {
1435         mUnboundedScrollX = x;
1436         super.scrollTo(x, y);
1437     }
1438 
onWorkspaceOverallScrollChanged()1439     private void onWorkspaceOverallScrollChanged() {
1440         if (!mIgnoreQsbScroll) {
1441             mLauncher.getQsbContainer().setTranslationX(
1442                     mOverlayTranslation + mFirstPageScrollX - getScrollX());
1443         }
1444     }
1445 
1446     @Override
onScrollChanged(int l, int t, int oldl, int oldt)1447     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
1448         super.onScrollChanged(l, t, oldl, oldt);
1449         onWorkspaceOverallScrollChanged();
1450 
1451         // Update the page indicator progress.
1452         boolean isTransitioning = mIsSwitchingState
1453                 || (getLayoutTransition() != null && getLayoutTransition().isRunning());
1454         if (!isTransitioning) {
1455             showPageIndicatorAtCurrentScroll();
1456         }
1457     }
1458 
showPageIndicatorAtCurrentScroll()1459     private void showPageIndicatorAtCurrentScroll() {
1460         if (mPageIndicator != null) {
1461             mPageIndicator.setScroll(getScrollX(), computeMaxScrollX());
1462         }
1463     }
1464 
1465     @Override
overScroll(float amount)1466     protected void overScroll(float amount) {
1467         boolean shouldOverScroll = (amount <= 0 && (!hasCustomContent() || mIsRtl)) ||
1468                 (amount >= 0 && (!hasCustomContent() || !mIsRtl));
1469 
1470         boolean shouldScrollOverlay = mLauncherOverlay != null &&
1471                 ((amount <= 0 && !mIsRtl) || (amount >= 0 && mIsRtl));
1472 
1473         boolean shouldZeroOverlay = mLauncherOverlay != null && mLastOverlaySroll != 0 &&
1474                 ((amount >= 0 && !mIsRtl) || (amount <= 0 && mIsRtl));
1475 
1476         if (shouldScrollOverlay) {
1477             if (!mStartedSendingScrollEvents && mScrollInteractionBegan) {
1478                 mStartedSendingScrollEvents = true;
1479                 mLauncherOverlay.onScrollInteractionBegin();
1480             }
1481 
1482             mLastOverlaySroll = Math.abs(amount / getViewportWidth());
1483             mLauncherOverlay.onScrollChange(mLastOverlaySroll, mIsRtl);
1484         } else if (shouldOverScroll) {
1485             dampedOverScroll(amount);
1486         }
1487 
1488         if (shouldZeroOverlay) {
1489             mLauncherOverlay.onScrollChange(0, mIsRtl);
1490         }
1491     }
1492 
1493     private final Interpolator mAlphaInterpolator = new DecelerateInterpolator(3f);
1494 
1495     /**
1496      * The overlay scroll is being controlled locally, just update our overlay effect
1497      */
onOverlayScrollChanged(float scroll)1498     public void onOverlayScrollChanged(float scroll) {
1499         float offset = 0f;
1500         float slip = 0f;
1501 
1502         scroll = Math.max(scroll - offset, 0);
1503         scroll = Math.min(1, scroll / (1 - offset));
1504 
1505         float alpha = 1 - mAlphaInterpolator.getInterpolation(scroll);
1506         float transX = mLauncher.getDragLayer().getMeasuredWidth() * scroll;
1507         transX *= 1 - slip;
1508 
1509         if (mIsRtl) {
1510             transX = -transX;
1511         }
1512         mOverlayTranslation = transX;
1513 
1514         // TODO(adamcohen): figure out a final effect here. We may need to recommend
1515         // different effects based on device performance. On at least one relatively high-end
1516         // device I've tried, translating the launcher causes things to get quite laggy.
1517         setWorkspaceTranslationAndAlpha(Direction.X, transX, alpha);
1518         setHotseatTranslationAndAlpha(Direction.X, transX, alpha);
1519         onWorkspaceOverallScrollChanged();
1520 
1521         mQsbAlphaController.setAlphaAtIndex(alpha, QSB_ALPHA_INDEX_OVERLAY_SCROLL);
1522     }
1523 
1524     /**
1525      * Moves the workspace UI in the Y direction.
1526      * @param translation the amount of shift.
1527      * @param alpha the alpha for the workspace page
1528      */
setWorkspaceYTranslationAndAlpha(float translation, float alpha)1529     public void setWorkspaceYTranslationAndAlpha(float translation, float alpha) {
1530         setWorkspaceTranslationAndAlpha(Direction.Y, translation, alpha);
1531 
1532         mLauncher.getQsbContainer().setTranslationY(translation);
1533         mQsbAlphaController.setAlphaAtIndex(alpha, QSB_ALPHA_INDEX_Y_TRANSLATION);
1534     }
1535 
1536     /**
1537      * Moves the workspace UI in the provided direction.
1538      * @param direction the direction to move the workspace
1539      * @param translation the amount of shift.
1540      * @param alpha the alpha for the workspace page
1541      */
setWorkspaceTranslationAndAlpha(Direction direction, float translation, float alpha)1542     private void setWorkspaceTranslationAndAlpha(Direction direction, float translation, float alpha) {
1543         Property<View, Float> property = direction.viewProperty;
1544         mPageAlpha[direction.ordinal()] = alpha;
1545         float finalAlpha = mPageAlpha[0] * mPageAlpha[1];
1546 
1547         View currentChild = getChildAt(getCurrentPage());
1548         if (currentChild != null) {
1549             property.set(currentChild, translation);
1550             currentChild.setAlpha(finalAlpha);
1551         }
1552 
1553         // When the animation finishes, reset all pages, just in case we missed a page.
1554         if (Float.compare(translation, 0) == 0) {
1555             for (int i = getChildCount() - 1; i >= 0; i--) {
1556                 View child = getChildAt(i);
1557                 property.set(child, translation);
1558                 child.setAlpha(finalAlpha);
1559             }
1560         }
1561     }
1562 
1563     /**
1564      * Moves the Hotseat UI in the provided direction.
1565      * @param direction the direction to move the workspace
1566      * @param translation the amount of shift.
1567      * @param alpha the alpha for the hotseat page
1568      */
setHotseatTranslationAndAlpha(Direction direction, float translation, float alpha)1569     public void setHotseatTranslationAndAlpha(Direction direction, float translation, float alpha) {
1570         Property<View, Float> property = direction.viewProperty;
1571         // Skip the page indicator movement in the vertical bar layout
1572         if (direction != Direction.Y || !mLauncher.getDeviceProfile().isVerticalBarLayout()) {
1573             property.set(mPageIndicator, translation);
1574         }
1575         property.set(mLauncher.getHotseat(), translation);
1576         setHotseatAlphaAtIndex(alpha, direction.ordinal());
1577     }
1578 
setHotseatAlphaAtIndex(float alpha, int index)1579     private void setHotseatAlphaAtIndex(float alpha, int index) {
1580         mHotseatAlpha[index] = alpha;
1581         final float hotseatAlpha = mHotseatAlpha[0] * mHotseatAlpha[1] * mHotseatAlpha[2];
1582         final float pageIndicatorAlpha = mHotseatAlpha[0] * mHotseatAlpha[2];
1583 
1584         mLauncher.getHotseat().setAlpha(hotseatAlpha);
1585         mPageIndicator.setAlpha(pageIndicatorAlpha);
1586     }
1587 
createHotseatAlphaAnimator(float finalValue)1588     public ValueAnimator createHotseatAlphaAnimator(float finalValue) {
1589         if (Float.compare(finalValue, mHotseatAlpha[HOTSEAT_STATE_ALPHA_INDEX]) == 0) {
1590             // Return a dummy animator to avoid null checks.
1591             return ValueAnimator.ofFloat(0, 0);
1592         } else {
1593             ValueAnimator animator = ValueAnimator
1594                     .ofFloat(mHotseatAlpha[HOTSEAT_STATE_ALPHA_INDEX], finalValue);
1595             animator.addUpdateListener(new AnimatorUpdateListener() {
1596                 @Override
1597                 public void onAnimationUpdate(ValueAnimator valueAnimator) {
1598                     float value = (Float) valueAnimator.getAnimatedValue();
1599                     setHotseatAlphaAtIndex(value, HOTSEAT_STATE_ALPHA_INDEX);
1600                 }
1601             });
1602 
1603             AccessibilityManager am = (AccessibilityManager)
1604                     mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
1605             final boolean accessibilityEnabled = am.isEnabled();
1606             animator.addUpdateListener(
1607                     new AlphaUpdateListener(mLauncher.getHotseat(), accessibilityEnabled));
1608             animator.addUpdateListener(
1609                     new AlphaUpdateListener(mPageIndicator, accessibilityEnabled));
1610             return animator;
1611         }
1612     }
1613 
1614     @Override
getPageShiftMatrix()1615     protected Matrix getPageShiftMatrix() {
1616         if (Float.compare(mOverlayTranslation, 0) != 0) {
1617             // The pages are translated by mOverlayTranslation. incorporate that in the
1618             // visible page calculation by shifting everything back by that same amount.
1619             mTempMatrix.set(getMatrix());
1620             mTempMatrix.postTranslate(-mOverlayTranslation, 0);
1621             return mTempMatrix;
1622         }
1623         return super.getPageShiftMatrix();
1624     }
1625 
1626     @Override
getEdgeVerticalPostion(int[] pos)1627     protected void getEdgeVerticalPostion(int[] pos) {
1628         View child = getChildAt(getPageCount() - 1);
1629         pos[0] = child.getTop();
1630         pos[1] = child.getBottom();
1631     }
1632 
1633     @Override
notifyPageSwitchListener()1634     protected void notifyPageSwitchListener() {
1635         super.notifyPageSwitchListener();
1636 
1637         if (hasCustomContent() && getNextPage() == 0 && !mCustomContentShowing) {
1638             mCustomContentShowing = true;
1639             if (mCustomContentCallbacks != null) {
1640                 mCustomContentCallbacks.onShow(false);
1641                 mCustomContentShowTime = System.currentTimeMillis();
1642             }
1643         } else if (hasCustomContent() && getNextPage() != 0 && mCustomContentShowing) {
1644             mCustomContentShowing = false;
1645             if (mCustomContentCallbacks != null) {
1646                 mCustomContentCallbacks.onHide();
1647             }
1648         }
1649     }
1650 
getCustomContentCallbacks()1651     protected CustomContentCallbacks getCustomContentCallbacks() {
1652         return mCustomContentCallbacks;
1653     }
1654 
setWallpaperDimension()1655     protected void setWallpaperDimension() {
1656         Utilities.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1657             @Override
1658             public void run() {
1659                 final Point size = LauncherAppState.getInstance()
1660                         .getInvariantDeviceProfile().defaultWallpaperSize;
1661                 if (size.x != mWallpaperManager.getDesiredMinimumWidth()
1662                         || size.y != mWallpaperManager.getDesiredMinimumHeight()) {
1663                     mWallpaperManager.suggestDesiredDimensions(size.x, size.y);
1664                 }
1665             }
1666         });
1667     }
1668 
lockWallpaperToDefaultPage()1669     public void lockWallpaperToDefaultPage() {
1670         mWallpaperOffset.setLockToDefaultPage(true);
1671     }
1672 
unlockWallpaperFromDefaultPageOnNextLayout()1673     public void unlockWallpaperFromDefaultPageOnNextLayout() {
1674         if (mWallpaperOffset.isLockedToDefaultPage()) {
1675             mUnlockWallpaperFromDefaultPageOnLayout = true;
1676             requestLayout();
1677         }
1678     }
1679 
snapToPage(int whichPage, Runnable r)1680     protected void snapToPage(int whichPage, Runnable r) {
1681         snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION, r);
1682     }
1683 
snapToPage(int whichPage, int duration, Runnable r)1684     protected void snapToPage(int whichPage, int duration, Runnable r) {
1685         if (mDelayedSnapToPageRunnable != null) {
1686             mDelayedSnapToPageRunnable.run();
1687         }
1688         mDelayedSnapToPageRunnable = r;
1689         snapToPage(whichPage, duration);
1690     }
1691 
snapToScreenId(long screenId)1692     public void snapToScreenId(long screenId) {
1693         snapToScreenId(screenId, null);
1694     }
1695 
snapToScreenId(long screenId, Runnable r)1696     protected void snapToScreenId(long screenId, Runnable r) {
1697         snapToPage(getPageIndexForScreenId(screenId), r);
1698     }
1699 
1700     @Override
computeScroll()1701     public void computeScroll() {
1702         super.computeScroll();
1703         mWallpaperOffset.syncWithScroll();
1704     }
1705 
computeScrollWithoutInvalidation()1706     public void computeScrollWithoutInvalidation() {
1707         computeScrollHelper(false);
1708     }
1709 
1710     @Override
determineScrollingStart(MotionEvent ev, float touchSlopScale)1711     protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
1712         if (!isSwitchingState()) {
1713             super.determineScrollingStart(ev, touchSlopScale);
1714         }
1715     }
1716 
1717     @Override
announceForAccessibility(CharSequence text)1718     public void announceForAccessibility(CharSequence text) {
1719         // Don't announce if apps is on top of us.
1720         if (!mLauncher.isAppsViewVisible()) {
1721             super.announceForAccessibility(text);
1722         }
1723     }
1724 
showOutlinesTemporarily()1725     public void showOutlinesTemporarily() {
1726         if (!mIsPageMoving && !isTouchActive()) {
1727             snapToPage(mCurrentPage);
1728         }
1729     }
1730 
updatePageAlphaValues(int screenCenter)1731     private void updatePageAlphaValues(int screenCenter) {
1732         if (mWorkspaceFadeInAdjacentScreens &&
1733                 !workspaceInModalState() &&
1734                 !mIsSwitchingState) {
1735             for (int i = numCustomPages(); i < getChildCount(); i++) {
1736                 CellLayout child = (CellLayout) getChildAt(i);
1737                 if (child != null) {
1738                     float scrollProgress = getScrollProgress(screenCenter, child, i);
1739                     float alpha = 1 - Math.abs(scrollProgress);
1740                     child.getShortcutsAndWidgets().setAlpha(alpha);
1741 
1742                     if (isQsbContainerPage(i)) {
1743                         mQsbAlphaController.setAlphaAtIndex(alpha, QSB_ALPHA_INDEX_PAGE_SCROLL);
1744                     }
1745                 }
1746             }
1747         }
1748     }
1749 
hasCustomContent()1750     public boolean hasCustomContent() {
1751         return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID);
1752     }
1753 
numCustomPages()1754     public int numCustomPages() {
1755         return hasCustomContent() ? 1 : 0;
1756     }
1757 
isOnOrMovingToCustomContent()1758     public boolean isOnOrMovingToCustomContent() {
1759         return hasCustomContent() && getNextPage() == 0 && mRestorePage == INVALID_RESTORE_PAGE;
1760     }
1761 
updateStateForCustomContent(int screenCenter)1762     private void updateStateForCustomContent(int screenCenter) {
1763         float translationX = 0;
1764         float progress = 0;
1765         if (hasCustomContent()) {
1766             int index = mScreenOrder.indexOf(CUSTOM_CONTENT_SCREEN_ID);
1767 
1768             int scrollDelta = getScrollX() - getScrollForPage(index) -
1769                     getLayoutTransitionOffsetForPage(index);
1770             float scrollRange = getScrollForPage(index + 1) - getScrollForPage(index);
1771             translationX = scrollRange - scrollDelta;
1772             progress = (scrollRange - scrollDelta) / scrollRange;
1773 
1774             if (mIsRtl) {
1775                 translationX = Math.min(0, translationX);
1776             } else {
1777                 translationX = Math.max(0, translationX);
1778             }
1779             progress = Math.max(0, progress);
1780         }
1781 
1782         if (Float.compare(progress, mLastCustomContentScrollProgress) == 0) return;
1783 
1784         CellLayout cc = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1785         if (progress > 0 && cc.getVisibility() != VISIBLE && !workspaceInModalState()) {
1786             cc.setVisibility(VISIBLE);
1787         }
1788 
1789         mLastCustomContentScrollProgress = progress;
1790 
1791         // We should only update the drag layer background alpha if we are not in all apps or the
1792         // widgets tray
1793         if (mState == State.NORMAL) {
1794             mLauncher.getDragLayer().setBackgroundAlpha(progress == 1 ? 0 : progress * 0.8f);
1795         }
1796 
1797         if (mLauncher.getHotseat() != null) {
1798             mLauncher.getHotseat().setTranslationX(translationX);
1799         }
1800 
1801         if (mPageIndicator != null) {
1802             mPageIndicator.setTranslationX(translationX);
1803         }
1804 
1805         if (mCustomContentCallbacks != null) {
1806             mCustomContentCallbacks.onScrollProgressChanged(progress);
1807         }
1808     }
1809 
1810     @Override
screenScrolled(int screenCenter)1811     protected void screenScrolled(int screenCenter) {
1812         updatePageAlphaValues(screenCenter);
1813         updateStateForCustomContent(screenCenter);
1814         enableHwLayersOnVisiblePages();
1815     }
1816 
onAttachedToWindow()1817     protected void onAttachedToWindow() {
1818         super.onAttachedToWindow();
1819         IBinder windowToken = getWindowToken();
1820         mWallpaperOffset.setWindowToken(windowToken);
1821         computeScroll();
1822         mDragController.setWindowToken(windowToken);
1823     }
1824 
onDetachedFromWindow()1825     protected void onDetachedFromWindow() {
1826         super.onDetachedFromWindow();
1827         mWallpaperOffset.setWindowToken(null);
1828     }
1829 
onResume()1830     protected void onResume() {
1831         // Update wallpaper dimensions if they were changed since last onResume
1832         // (we also always set the wallpaper dimensions in the constructor)
1833         if (LauncherAppState.getInstance().hasWallpaperChangedSinceLastCheck()) {
1834             setWallpaperDimension();
1835         }
1836         mWallpaperOffset.onResume();
1837     }
1838 
1839     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)1840     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1841         if (mUnlockWallpaperFromDefaultPageOnLayout) {
1842             mWallpaperOffset.setLockToDefaultPage(false);
1843             mUnlockWallpaperFromDefaultPageOnLayout = false;
1844         }
1845         if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
1846             mWallpaperOffset.syncWithScroll();
1847             mWallpaperOffset.jumpToFinal();
1848         }
1849         super.onLayout(changed, left, top, right, bottom);
1850         mFirstPageScrollX = getScrollForPage(0);
1851         onWorkspaceOverallScrollChanged();
1852 
1853         final LayoutTransition transition = getLayoutTransition();
1854         // If the transition is running defer updating max scroll, as some empty pages could
1855         // still be present, and a max scroll change could cause sudden jumps in scroll.
1856         if (transition != null && transition.isRunning()) {
1857             transition.addTransitionListener(new LayoutTransition.TransitionListener() {
1858 
1859                 @Override
1860                 public void startTransition(LayoutTransition transition, ViewGroup container,
1861                                             View view, int transitionType) {
1862                     mIgnoreQsbScroll = true;
1863                 }
1864 
1865                 @Override
1866                 public void endTransition(LayoutTransition transition, ViewGroup container,
1867                                           View view, int transitionType) {
1868                     // Wait until all transitions are complete.
1869                     if (!transition.isRunning()) {
1870                         mIgnoreQsbScroll = false;
1871                         transition.removeTransitionListener(this);
1872                         mFirstPageScrollX = getScrollForPage(0);
1873                         onWorkspaceOverallScrollChanged();
1874                     }
1875                 }
1876             });
1877         }
1878 
1879     }
1880 
1881     @Override
getDescendantFocusability()1882     public int getDescendantFocusability() {
1883         if (workspaceInModalState()) {
1884             return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
1885         }
1886         return super.getDescendantFocusability();
1887     }
1888 
workspaceInModalState()1889     public boolean workspaceInModalState() {
1890         return mState != State.NORMAL;
1891     }
1892 
enableChildrenCache(int fromPage, int toPage)1893     void enableChildrenCache(int fromPage, int toPage) {
1894         if (fromPage > toPage) {
1895             final int temp = fromPage;
1896             fromPage = toPage;
1897             toPage = temp;
1898         }
1899 
1900         final int screenCount = getChildCount();
1901 
1902         fromPage = Math.max(fromPage, 0);
1903         toPage = Math.min(toPage, screenCount - 1);
1904 
1905         for (int i = fromPage; i <= toPage; i++) {
1906             final CellLayout layout = (CellLayout) getChildAt(i);
1907             layout.setChildrenDrawnWithCacheEnabled(true);
1908             layout.setChildrenDrawingCacheEnabled(true);
1909         }
1910     }
1911 
clearChildrenCache()1912     void clearChildrenCache() {
1913         final int screenCount = getChildCount();
1914         for (int i = 0; i < screenCount; i++) {
1915             final CellLayout layout = (CellLayout) getChildAt(i);
1916             layout.setChildrenDrawnWithCacheEnabled(false);
1917             // In software mode, we don't want the items to continue to be drawn into bitmaps
1918             if (!isHardwareAccelerated()) {
1919                 layout.setChildrenDrawingCacheEnabled(false);
1920             }
1921         }
1922     }
1923 
updateChildrenLayersEnabled(boolean force)1924     @Thunk void updateChildrenLayersEnabled(boolean force) {
1925         boolean small = mState == State.OVERVIEW || mIsSwitchingState;
1926         boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving();
1927 
1928         if (enableChildrenLayers != mChildrenLayersEnabled) {
1929             mChildrenLayersEnabled = enableChildrenLayers;
1930             if (mChildrenLayersEnabled) {
1931                 enableHwLayersOnVisiblePages();
1932             } else {
1933                 for (int i = 0; i < getPageCount(); i++) {
1934                     final CellLayout cl = (CellLayout) getChildAt(i);
1935                     cl.enableHardwareLayer(false);
1936                 }
1937             }
1938         }
1939     }
1940 
enableHwLayersOnVisiblePages()1941     private void enableHwLayersOnVisiblePages() {
1942         if (mChildrenLayersEnabled) {
1943             final int screenCount = getChildCount();
1944             getVisiblePages(mTempVisiblePagesRange);
1945             int leftScreen = mTempVisiblePagesRange[0];
1946             int rightScreen = mTempVisiblePagesRange[1];
1947             if (leftScreen == rightScreen) {
1948                 // make sure we're caching at least two pages always
1949                 if (rightScreen < screenCount - 1) {
1950                     rightScreen++;
1951                 } else if (leftScreen > 0) {
1952                     leftScreen--;
1953                 }
1954             }
1955 
1956             final CellLayout customScreen = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1957             for (int i = 0; i < screenCount; i++) {
1958                 final CellLayout layout = (CellLayout) getPageAt(i);
1959 
1960                 // enable layers between left and right screen inclusive, except for the
1961                 // customScreen, which may animate its content during transitions.
1962                 boolean enableLayer = layout != customScreen &&
1963                         leftScreen <= i && i <= rightScreen && shouldDrawChild(layout);
1964                 layout.enableHardwareLayer(enableLayer);
1965             }
1966         }
1967     }
1968 
buildPageHardwareLayers()1969     public void buildPageHardwareLayers() {
1970         // force layers to be enabled just for the call to buildLayer
1971         updateChildrenLayersEnabled(true);
1972         if (getWindowToken() != null) {
1973             final int childCount = getChildCount();
1974             for (int i = 0; i < childCount; i++) {
1975                 CellLayout cl = (CellLayout) getChildAt(i);
1976                 cl.buildHardwareLayer();
1977             }
1978         }
1979         updateChildrenLayersEnabled(false);
1980     }
1981 
1982     @Override
getVisiblePages(int[] range)1983     protected void getVisiblePages(int[] range) {
1984         super.getVisiblePages(range);
1985         if (mForceDrawAdjacentPages) {
1986             // In overview mode, make sure that the two side pages are visible.
1987             range[0] = Utilities.boundToRange(getCurrentPage() - 1, numCustomPages(), range[1]);
1988             range[1] = Utilities.boundToRange(getCurrentPage() + 1, range[0], getPageCount() - 1);
1989         }
1990     }
1991 
onWallpaperTap(MotionEvent ev)1992     protected void onWallpaperTap(MotionEvent ev) {
1993         final int[] position = mTempXY;
1994         getLocationOnScreen(position);
1995 
1996         int pointerIndex = ev.getActionIndex();
1997         position[0] += (int) ev.getX(pointerIndex);
1998         position[1] += (int) ev.getY(pointerIndex);
1999 
2000         mWallpaperManager.sendWallpaperCommand(getWindowToken(),
2001                 ev.getAction() == MotionEvent.ACTION_UP
2002                         ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
2003                 position[0], position[1], 0, null);
2004     }
2005 
prepareDragWithProvider(DragPreviewProvider outlineProvider)2006     public void prepareDragWithProvider(DragPreviewProvider outlineProvider) {
2007         mOutlineProvider = outlineProvider;
2008     }
2009 
exitWidgetResizeMode()2010     public void exitWidgetResizeMode() {
2011         DragLayer dragLayer = mLauncher.getDragLayer();
2012         dragLayer.clearAllResizeFrames();
2013     }
2014 
2015     @Override
getFreeScrollPageRange(int[] range)2016     protected void getFreeScrollPageRange(int[] range) {
2017         getOverviewModePages(range);
2018     }
2019 
getOverviewModePages(int[] range)2020     private void getOverviewModePages(int[] range) {
2021         int start = numCustomPages();
2022         int end = getChildCount() - 1;
2023 
2024         range[0] = Math.max(0, Math.min(start, getChildCount() - 1));
2025         range[1] = Math.max(0, end);
2026     }
2027 
onStartReordering()2028     public void onStartReordering() {
2029         super.onStartReordering();
2030         // Reordering handles its own animations, disable the automatic ones.
2031         disableLayoutTransitions();
2032     }
2033 
onEndReordering()2034     public void onEndReordering() {
2035         super.onEndReordering();
2036 
2037         if (mLauncher.isWorkspaceLoading()) {
2038             // Invalid and dangerous operation if workspace is loading
2039             return;
2040         }
2041 
2042         mScreenOrder.clear();
2043         int count = getChildCount();
2044         for (int i = 0; i < count; i++) {
2045             CellLayout cl = ((CellLayout) getChildAt(i));
2046             mScreenOrder.add(getIdForScreen(cl));
2047         }
2048 
2049         mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
2050 
2051         // Re-enable auto layout transitions for page deletion.
2052         enableLayoutTransitions();
2053     }
2054 
isInOverviewMode()2055     public boolean isInOverviewMode() {
2056         return mState == State.OVERVIEW;
2057     }
2058 
snapToPageFromOverView(int whichPage)2059     public void snapToPageFromOverView(int whichPage) {
2060         mStateTransitionAnimation.snapToPageFromOverView(whichPage);
2061     }
2062 
getOverviewModeTranslationY()2063     int getOverviewModeTranslationY() {
2064         DeviceProfile grid = mLauncher.getDeviceProfile();
2065         int overviewButtonBarHeight = grid.getOverviewModeButtonBarHeight();
2066 
2067         int scaledHeight = (int) (mOverviewModeShrinkFactor * getNormalChildHeight());
2068         Rect workspacePadding = grid.getWorkspacePadding(sTempRect);
2069         int workspaceTop = mInsets.top + workspacePadding.top;
2070         int workspaceBottom = getViewportHeight() - mInsets.bottom - workspacePadding.bottom;
2071         int overviewTop = mInsets.top;
2072         int overviewBottom = getViewportHeight() - mInsets.bottom - overviewButtonBarHeight;
2073         int workspaceOffsetTopEdge = workspaceTop + ((workspaceBottom - workspaceTop) - scaledHeight) / 2;
2074         int overviewOffsetTopEdge = overviewTop + (overviewBottom - overviewTop - scaledHeight) / 2;
2075         return -workspaceOffsetTopEdge + overviewOffsetTopEdge;
2076     }
2077 
getSpringLoadedTranslationY()2078     float getSpringLoadedTranslationY() {
2079         DeviceProfile grid = mLauncher.getDeviceProfile();
2080         if (grid.isVerticalBarLayout() || getChildCount() == 0) {
2081             return 0;
2082         }
2083 
2084         float scaledHeight = grid.workspaceSpringLoadShrinkFactor * getNormalChildHeight();
2085         float shrunkTop = mInsets.top + grid.dropTargetBarSizePx;
2086         float shrunkBottom = getViewportHeight() - mInsets.bottom
2087                 - grid.getWorkspacePadding(sTempRect).bottom
2088                 - grid.workspaceSpringLoadedBottomSpace;
2089         float totalShrunkSpace = shrunkBottom - shrunkTop;
2090 
2091         float desiredCellTop = shrunkTop + (totalShrunkSpace - scaledHeight) / 2;
2092 
2093         float halfHeight = getHeight() / 2;
2094         float myCenter = getTop() + halfHeight;
2095         float cellTopFromCenter = halfHeight - getChildAt(0).getTop();
2096         float actualCellTop = myCenter - cellTopFromCenter * grid.workspaceSpringLoadShrinkFactor;
2097         return (desiredCellTop - actualCellTop) / grid.workspaceSpringLoadShrinkFactor;
2098     }
2099 
getOverviewModeShrinkFactor()2100     float getOverviewModeShrinkFactor() {
2101         return mOverviewModeShrinkFactor;
2102     }
2103 
2104     /**
2105      * Sets the current workspace {@link State}, returning an animation transitioning the workspace
2106      * to that new state.
2107      */
setStateWithAnimation(State toState, boolean animated, HashMap<View, Integer> layerViews)2108     public Animator setStateWithAnimation(State toState, boolean animated,
2109             HashMap<View, Integer> layerViews) {
2110         // Create the animation to the new state
2111         AnimatorSet workspaceAnim =  mStateTransitionAnimation.getAnimationToState(mState,
2112                 toState, animated, layerViews);
2113 
2114         boolean shouldNotifyWidgetChange = !mState.shouldUpdateWidget
2115                 && toState.shouldUpdateWidget;
2116         // Update the current state
2117         mState = toState;
2118         updateAccessibilityFlags();
2119 
2120         if (shouldNotifyWidgetChange) {
2121             mLauncher.notifyWidgetProvidersChanged();
2122         }
2123 
2124         if (mOnStateChangeListener != null) {
2125             mOnStateChangeListener.prepareStateChange(toState, animated ? workspaceAnim : null);
2126         }
2127 
2128         return workspaceAnim;
2129     }
2130 
getState()2131     public State getState() {
2132         return mState;
2133     }
2134 
updateAccessibilityFlags()2135     public void updateAccessibilityFlags() {
2136         // TODO: Update the accessibility flags appropriately when dragging.
2137         if (!mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
2138             if (Utilities.ATLEAST_LOLLIPOP) {
2139                 int total = getPageCount();
2140                 for (int i = numCustomPages(); i < total; i++) {
2141                     updateAccessibilityFlags((CellLayout) getPageAt(i), i);
2142                 }
2143                 setImportantForAccessibility((mState == State.NORMAL || mState == State.OVERVIEW)
2144                         ? IMPORTANT_FOR_ACCESSIBILITY_AUTO
2145                         : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
2146             } else {
2147                 int accessible = mState == State.NORMAL ?
2148                         IMPORTANT_FOR_ACCESSIBILITY_AUTO :
2149                         IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
2150                 setImportantForAccessibility(accessible);
2151             }
2152         }
2153     }
2154 
updateAccessibilityFlags(CellLayout page, int pageNo)2155     private void updateAccessibilityFlags(CellLayout page, int pageNo) {
2156         if (mState == State.OVERVIEW) {
2157             page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
2158             page.getShortcutsAndWidgets().setImportantForAccessibility(
2159                     IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
2160             page.setContentDescription(getPageDescription(pageNo));
2161 
2162             // No custom action for the first page.
2163             if (!FeatureFlags.QSB_ON_FIRST_SCREEN || pageNo > 0) {
2164                 if (mPagesAccessibilityDelegate == null) {
2165                     mPagesAccessibilityDelegate = new OverviewScreenAccessibilityDelegate(this);
2166                 }
2167                 page.setAccessibilityDelegate(mPagesAccessibilityDelegate);
2168             }
2169         } else {
2170             int accessible = mState == State.NORMAL ?
2171                     IMPORTANT_FOR_ACCESSIBILITY_AUTO :
2172                         IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
2173             page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
2174             page.getShortcutsAndWidgets().setImportantForAccessibility(accessible);
2175             page.setContentDescription(null);
2176             page.setAccessibilityDelegate(null);
2177         }
2178     }
2179 
2180     @Override
onLauncherTransitionPrepare(Launcher l, boolean animated, boolean multiplePagesVisible)2181     public void onLauncherTransitionPrepare(Launcher l, boolean animated,
2182             boolean multiplePagesVisible) {
2183         mIsSwitchingState = true;
2184         mTransitionProgress = 0;
2185 
2186         if (multiplePagesVisible) {
2187             mForceDrawAdjacentPages = true;
2188         }
2189         invalidate(); // This will call dispatchDraw(), which calls getVisiblePages().
2190 
2191         updateChildrenLayersEnabled(false);
2192         hideCustomContentIfNecessary();
2193     }
2194 
2195     @Override
onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace)2196     public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
2197         if (mPageIndicator != null) {
2198             boolean isNewStateSpringLoaded = mState == State.SPRING_LOADED;
2199             mPageIndicator.setShouldAutoHide(!isNewStateSpringLoaded);
2200             if (isNewStateSpringLoaded) {
2201                 // Show the page indicator at the same time as the rest of the transition.
2202                 showPageIndicatorAtCurrentScroll();
2203             }
2204         }
2205     }
2206 
2207     @Override
onLauncherTransitionStep(Launcher l, float t)2208     public void onLauncherTransitionStep(Launcher l, float t) {
2209         mTransitionProgress = t;
2210     }
2211 
2212     @Override
onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace)2213     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
2214         mIsSwitchingState = false;
2215         updateChildrenLayersEnabled(false);
2216         showCustomContentIfNecessary();
2217         mForceDrawAdjacentPages = false;
2218         if (mState == State.SPRING_LOADED) {
2219             showPageIndicatorAtCurrentScroll();
2220         }
2221     }
2222 
updateCustomContentVisibility()2223     void updateCustomContentVisibility() {
2224         int visibility = mState == Workspace.State.NORMAL ? VISIBLE : INVISIBLE;
2225         setCustomContentVisibility(visibility);
2226     }
2227 
setCustomContentVisibility(int visibility)2228     void setCustomContentVisibility(int visibility) {
2229         if (hasCustomContent()) {
2230             mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(visibility);
2231         }
2232     }
2233 
showCustomContentIfNecessary()2234     void showCustomContentIfNecessary() {
2235         boolean show  = mState == Workspace.State.NORMAL;
2236         if (show && hasCustomContent()) {
2237             mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(VISIBLE);
2238         }
2239     }
2240 
hideCustomContentIfNecessary()2241     void hideCustomContentIfNecessary() {
2242         boolean hide  = mState != Workspace.State.NORMAL;
2243         if (hide && hasCustomContent()) {
2244             disableLayoutTransitions();
2245             mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE);
2246             enableLayoutTransitions();
2247         }
2248     }
2249 
2250     /**
2251      * Returns the drawable for the given text view.
2252      */
getTextViewIcon(TextView tv)2253     public static Drawable getTextViewIcon(TextView tv) {
2254         final Drawable[] drawables = tv.getCompoundDrawables();
2255         for (int i = 0; i < drawables.length; i++) {
2256             if (drawables[i] != null) {
2257                 return drawables[i];
2258             }
2259         }
2260         return null;
2261     }
2262 
startDrag(CellLayout.CellInfo cellInfo, DragOptions options)2263     public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) {
2264         View child = cellInfo.cell;
2265 
2266         // Make sure the drag was started by a long press as opposed to a long click.
2267         if (!child.isInTouchMode()) {
2268             return;
2269         }
2270 
2271         mDragInfo = cellInfo;
2272         child.setVisibility(INVISIBLE);
2273         CellLayout layout = (CellLayout) child.getParent().getParent();
2274         layout.prepareChildForDrag(child);
2275 
2276         if (options.isAccessibleDrag) {
2277             mDragController.addDragListener(new AccessibileDragListenerAdapter(
2278                     this, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG) {
2279                 @Override
2280                 protected void enableAccessibleDrag(boolean enable) {
2281                     super.enableAccessibleDrag(enable);
2282                     setEnableForLayout(mLauncher.getHotseat().getLayout(),enable);
2283 
2284                     // We need to allow our individual children to become click handlers in this
2285                     // case, so temporarily unset the click handlers.
2286                     setOnClickListener(enable ? null : mLauncher);
2287                 }
2288             });
2289         }
2290 
2291         beginDragShared(child, this, options);
2292     }
2293 
beginDragShared(View child, DragSource source, DragOptions options)2294     public void beginDragShared(View child, DragSource source, DragOptions options) {
2295         Object dragObject = child.getTag();
2296         if (!(dragObject instanceof ItemInfo)) {
2297             String msg = "Drag started with a view that has no tag set. This "
2298                     + "will cause a crash (issue 11627249) down the line. "
2299                     + "View: " + child + "  tag: " + child.getTag();
2300             throw new IllegalStateException(msg);
2301         }
2302         beginDragShared(child, source, (ItemInfo) dragObject,
2303                 new DragPreviewProvider(child), options);
2304     }
2305 
2306 
beginDragShared(View child, DragSource source, ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions dragOptions)2307     public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject,
2308             DragPreviewProvider previewProvider, DragOptions dragOptions) {
2309         child.clearFocus();
2310         child.setPressed(false);
2311         mOutlineProvider = previewProvider;
2312 
2313         // The drag bitmap follows the touch point around on the screen
2314         final Bitmap b = previewProvider.createDragBitmap(mCanvas);
2315         int halfPadding = previewProvider.previewPadding / 2;
2316 
2317         float scale = previewProvider.getScaleAndPosition(b, mTempXY);
2318         int dragLayerX = mTempXY[0];
2319         int dragLayerY = mTempXY[1];
2320 
2321         DeviceProfile grid = mLauncher.getDeviceProfile();
2322         Point dragVisualizeOffset = null;
2323         Rect dragRect = null;
2324         if (child instanceof BubbleTextView) {
2325             int iconSize = grid.iconSizePx;
2326             int top = child.getPaddingTop();
2327             int left = (b.getWidth() - iconSize) / 2;
2328             int right = left + iconSize;
2329             int bottom = top + iconSize;
2330             dragLayerY += top;
2331             // Note: The drag region is used to calculate drag layer offsets, but the
2332             // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2333             dragVisualizeOffset = new Point(- halfPadding, halfPadding);
2334             dragRect = new Rect(left, top, right, bottom);
2335         } else if (child instanceof FolderIcon) {
2336             int previewSize = grid.folderIconSizePx;
2337             dragVisualizeOffset = new Point(- halfPadding, halfPadding - child.getPaddingTop());
2338             dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
2339         }
2340 
2341         // Clear the pressed state if necessary
2342         if (child instanceof BubbleTextView) {
2343             BubbleTextView icon = (BubbleTextView) child;
2344             icon.clearPressedBackground();
2345         }
2346 
2347         if (child.getParent() instanceof ShortcutAndWidgetContainer) {
2348             mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
2349         }
2350 
2351         DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
2352                 dragObject, dragVisualizeOffset, dragRect, scale, dragOptions);
2353         dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
2354         b.recycle();
2355         return dv;
2356     }
2357 
transitionStateShouldAllowDrop()2358     public boolean transitionStateShouldAllowDrop() {
2359         return ((!isSwitchingState() || mTransitionProgress > 0.5f) &&
2360                 (mState == State.NORMAL || mState == State.SPRING_LOADED));
2361     }
2362 
2363     /**
2364      * {@inheritDoc}
2365      */
acceptDrop(DragObject d)2366     public boolean acceptDrop(DragObject d) {
2367         // If it's an external drop (e.g. from All Apps), check if it should be accepted
2368         CellLayout dropTargetLayout = mDropToLayout;
2369         if (d.dragSource != this) {
2370             // Don't accept the drop if we're not over a screen at time of drop
2371             if (dropTargetLayout == null) {
2372                 return false;
2373             }
2374             if (!transitionStateShouldAllowDrop()) return false;
2375 
2376             mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
2377 
2378             // We want the point to be mapped to the dragTarget.
2379             if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2380                 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2381             } else {
2382                 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter);
2383             }
2384 
2385             int spanX = 1;
2386             int spanY = 1;
2387             if (mDragInfo != null) {
2388                 final CellLayout.CellInfo dragCellInfo = mDragInfo;
2389                 spanX = dragCellInfo.spanX;
2390                 spanY = dragCellInfo.spanY;
2391             } else {
2392                 spanX = d.dragInfo.spanX;
2393                 spanY = d.dragInfo.spanY;
2394             }
2395 
2396             int minSpanX = spanX;
2397             int minSpanY = spanY;
2398             if (d.dragInfo instanceof PendingAddWidgetInfo) {
2399                 minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
2400                 minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
2401             }
2402 
2403             mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2404                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
2405                     mTargetCell);
2406             float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2407                     mDragViewVisualCenter[1], mTargetCell);
2408             if (mCreateUserFolderOnDrop && willCreateUserFolder(d.dragInfo,
2409                     dropTargetLayout, mTargetCell, distance, true)) {
2410                 return true;
2411             }
2412 
2413             if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder(d.dragInfo,
2414                     dropTargetLayout, mTargetCell, distance)) {
2415                 return true;
2416             }
2417 
2418             int[] resultSpan = new int[2];
2419             mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
2420                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2421                     null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
2422             boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2423 
2424             // Don't accept the drop if there's no room for the item
2425             if (!foundCell) {
2426                 // Don't show the message if we are dropping on the AllApps button and the hotseat
2427                 // is full
2428                 boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2429                 if (mTargetCell != null && isHotseat && !FeatureFlags.NO_ALL_APPS_ICON) {
2430                     Hotseat hotseat = mLauncher.getHotseat();
2431                     if (mLauncher.getDeviceProfile().inv.isAllAppsButtonRank(
2432                             hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
2433                         return false;
2434                     }
2435                 }
2436 
2437                 mLauncher.showOutOfSpaceMessage(isHotseat);
2438                 return false;
2439             }
2440         }
2441 
2442         long screenId = getIdForScreen(dropTargetLayout);
2443         if (screenId == EXTRA_EMPTY_SCREEN_ID) {
2444             commitExtraEmptyScreen();
2445         }
2446 
2447         return true;
2448     }
2449 
willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float distance, boolean considerTimeout)2450     boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell,
2451             float distance, boolean considerTimeout) {
2452         if (distance > mMaxDistanceForFolderCreation) return false;
2453         View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2454         return willCreateUserFolder(info, dropOverView, considerTimeout);
2455     }
2456 
willCreateUserFolder(ItemInfo info, View dropOverView, boolean considerTimeout)2457     boolean willCreateUserFolder(ItemInfo info, View dropOverView, boolean considerTimeout) {
2458         if (dropOverView != null) {
2459             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2460             if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) {
2461                 return false;
2462             }
2463         }
2464 
2465         boolean hasntMoved = false;
2466         if (mDragInfo != null) {
2467             hasntMoved = dropOverView == mDragInfo.cell;
2468         }
2469 
2470         if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
2471             return false;
2472         }
2473 
2474         boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
2475         boolean willBecomeShortcut =
2476                 (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
2477                         info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
2478                         info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT);
2479 
2480         return (aboveShortcut && willBecomeShortcut);
2481     }
2482 
willAddToExistingUserFolder(ItemInfo dragInfo, CellLayout target, int[] targetCell, float distance)2483     boolean willAddToExistingUserFolder(ItemInfo dragInfo, CellLayout target, int[] targetCell,
2484             float distance) {
2485         if (distance > mMaxDistanceForFolderCreation) return false;
2486         View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2487         return willAddToExistingUserFolder(dragInfo, dropOverView);
2488 
2489     }
willAddToExistingUserFolder(ItemInfo dragInfo, View dropOverView)2490     boolean willAddToExistingUserFolder(ItemInfo dragInfo, View dropOverView) {
2491         if (dropOverView != null) {
2492             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2493             if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) {
2494                 return false;
2495             }
2496         }
2497 
2498         if (dropOverView instanceof FolderIcon) {
2499             FolderIcon fi = (FolderIcon) dropOverView;
2500             if (fi.acceptDrop(dragInfo)) {
2501                 return true;
2502             }
2503         }
2504         return false;
2505     }
2506 
createUserFolderIfNecessary(View newView, long container, CellLayout target, int[] targetCell, float distance, boolean external, DragView dragView, Runnable postAnimationRunnable)2507     boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
2508             int[] targetCell, float distance, boolean external, DragView dragView,
2509             Runnable postAnimationRunnable) {
2510         if (distance > mMaxDistanceForFolderCreation) return false;
2511         View v = target.getChildAt(targetCell[0], targetCell[1]);
2512 
2513         boolean hasntMoved = false;
2514         if (mDragInfo != null) {
2515             CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
2516             hasntMoved = (mDragInfo.cellX == targetCell[0] &&
2517                     mDragInfo.cellY == targetCell[1]) && (cellParent == target);
2518         }
2519 
2520         if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
2521         mCreateUserFolderOnDrop = false;
2522         final long screenId = getIdForScreen(target);
2523 
2524         boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
2525         boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
2526 
2527         if (aboveShortcut && willBecomeShortcut) {
2528             ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
2529             ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
2530             // if the drag started here, we need to remove it from the workspace
2531             if (!external) {
2532                 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2533             }
2534 
2535             Rect folderLocation = new Rect();
2536             float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
2537             target.removeView(v);
2538 
2539             FolderIcon fi =
2540                 mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
2541             destInfo.cellX = -1;
2542             destInfo.cellY = -1;
2543             sourceInfo.cellX = -1;
2544             sourceInfo.cellY = -1;
2545 
2546             // If the dragView is null, we can't animate
2547             boolean animate = dragView != null;
2548             if (animate) {
2549                 // In order to keep everything continuous, we hand off the currently rendered
2550                 // folder background to the newly created icon. This preserves animation state.
2551                 fi.setFolderBackground(mFolderCreateBg);
2552                 mFolderCreateBg = new FolderIcon.PreviewBackground();
2553                 fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
2554                         postAnimationRunnable);
2555             } else {
2556                 fi.prepareCreate(v);
2557                 fi.addItem(destInfo);
2558                 fi.addItem(sourceInfo);
2559             }
2560             return true;
2561         }
2562         return false;
2563     }
2564 
addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell, float distance, DragObject d, boolean external)2565     boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
2566             float distance, DragObject d, boolean external) {
2567         if (distance > mMaxDistanceForFolderCreation) return false;
2568 
2569         View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2570         if (!mAddToExistingFolderOnDrop) return false;
2571         mAddToExistingFolderOnDrop = false;
2572 
2573         if (dropOverView instanceof FolderIcon) {
2574             FolderIcon fi = (FolderIcon) dropOverView;
2575             if (fi.acceptDrop(d.dragInfo)) {
2576                 fi.onDrop(d);
2577 
2578                 // if the drag started here, we need to remove it from the workspace
2579                 if (!external) {
2580                     getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2581                 }
2582                 return true;
2583             }
2584         }
2585         return false;
2586     }
2587 
2588     @Override
prepareAccessibilityDrop()2589     public void prepareAccessibilityDrop() { }
2590 
onDrop(final DragObject d)2591     public void onDrop(final DragObject d) {
2592         mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
2593         CellLayout dropTargetLayout = mDropToLayout;
2594 
2595         // We want the point to be mapped to the dragTarget.
2596         if (dropTargetLayout != null) {
2597             if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2598                 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2599             } else {
2600                 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter);
2601             }
2602         }
2603 
2604         int snapScreen = -1;
2605         boolean resizeOnDrop = false;
2606         if (d.dragSource != this) {
2607             final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
2608                     (int) mDragViewVisualCenter[1] };
2609             onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
2610         } else if (mDragInfo != null) {
2611             final View cell = mDragInfo.cell;
2612 
2613             if (dropTargetLayout != null && !d.cancelled) {
2614                 // Move internally
2615                 boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
2616                 boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2617                 long container = hasMovedIntoHotseat ?
2618                         LauncherSettings.Favorites.CONTAINER_HOTSEAT :
2619                         LauncherSettings.Favorites.CONTAINER_DESKTOP;
2620                 long screenId = (mTargetCell[0] < 0) ?
2621                         mDragInfo.screenId : getIdForScreen(dropTargetLayout);
2622                 int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
2623                 int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
2624                 // First we find the cell nearest to point at which the item is
2625                 // dropped, without any consideration to whether there is an item there.
2626 
2627                 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
2628                         mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
2629                 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2630                         mDragViewVisualCenter[1], mTargetCell);
2631 
2632                 // If the item being dropped is a shortcut and the nearest drop
2633                 // cell also contains a shortcut, then create a folder with the two shortcuts.
2634                 if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
2635                         dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
2636                     return;
2637                 }
2638 
2639                 if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
2640                         distance, d, false)) {
2641                     return;
2642                 }
2643 
2644                 // Aside from the special case where we're dropping a shortcut onto a shortcut,
2645                 // we need to find the nearest cell location that is vacant
2646                 ItemInfo item = d.dragInfo;
2647                 int minSpanX = item.spanX;
2648                 int minSpanY = item.spanY;
2649                 if (item.minSpanX > 0 && item.minSpanY > 0) {
2650                     minSpanX = item.minSpanX;
2651                     minSpanY = item.minSpanY;
2652                 }
2653 
2654                 int[] resultSpan = new int[2];
2655                 mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
2656                         (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
2657                         mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
2658 
2659                 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2660 
2661                 // if the widget resizes on drop
2662                 if (foundCell && (cell instanceof AppWidgetHostView) &&
2663                         (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
2664                     resizeOnDrop = true;
2665                     item.spanX = resultSpan[0];
2666                     item.spanY = resultSpan[1];
2667                     AppWidgetHostView awhv = (AppWidgetHostView) cell;
2668                     AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
2669                             resultSpan[1]);
2670                 }
2671 
2672                 if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
2673                     snapScreen = getPageIndexForScreenId(screenId);
2674                     snapToPage(snapScreen);
2675                 }
2676 
2677                 if (foundCell) {
2678                     final ItemInfo info = (ItemInfo) cell.getTag();
2679                     if (hasMovedLayouts) {
2680                         // Reparent the view
2681                         CellLayout parentCell = getParentCellLayoutForView(cell);
2682                         if (parentCell != null) {
2683                             parentCell.removeView(cell);
2684                         } else if (ProviderConfig.IS_DOGFOOD_BUILD) {
2685                             throw new NullPointerException("mDragInfo.cell has null parent");
2686                         }
2687                         addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
2688                                 info.spanX, info.spanY);
2689                     }
2690 
2691                     // update the item's position after drop
2692                     CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2693                     lp.cellX = lp.tmpCellX = mTargetCell[0];
2694                     lp.cellY = lp.tmpCellY = mTargetCell[1];
2695                     lp.cellHSpan = item.spanX;
2696                     lp.cellVSpan = item.spanY;
2697                     lp.isLockedToGrid = true;
2698 
2699                     if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
2700                             cell instanceof LauncherAppWidgetHostView) {
2701                         final CellLayout cellLayout = dropTargetLayout;
2702                         // We post this call so that the widget has a chance to be placed
2703                         // in its final location
2704 
2705                         final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
2706                         AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo();
2707                         if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE
2708                                 && !d.accessibleDrag) {
2709                             mDelayedResizeRunnable = new Runnable() {
2710                                 public void run() {
2711                                     if (!isPageMoving() && !mIsSwitchingState) {
2712                                         DragLayer dragLayer = mLauncher.getDragLayer();
2713                                         dragLayer.addResizeFrame(info, hostView, cellLayout);
2714                                     }
2715                                 }
2716                             };
2717                         }
2718                     }
2719 
2720                     LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX,
2721                             lp.cellY, item.spanX, item.spanY);
2722                 } else {
2723                     // If we can't find a drop location, we return the item to its original position
2724                     CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2725                     mTargetCell[0] = lp.cellX;
2726                     mTargetCell[1] = lp.cellY;
2727                     CellLayout layout = (CellLayout) cell.getParent().getParent();
2728                     layout.markCellsAsOccupiedForView(cell);
2729                 }
2730             }
2731 
2732             final CellLayout parent = (CellLayout) cell.getParent().getParent();
2733             // Prepare it to be animated into its new position
2734             // This must be called after the view has been re-parented
2735             final Runnable onCompleteRunnable = new Runnable() {
2736                 @Override
2737                 public void run() {
2738                     mAnimatingViewIntoPlace = false;
2739                     updateChildrenLayersEnabled(false);
2740                 }
2741             };
2742             mAnimatingViewIntoPlace = true;
2743             if (d.dragView.hasDrawn()) {
2744                 final ItemInfo info = (ItemInfo) cell.getTag();
2745                 boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
2746                         || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
2747                 if (isWidget) {
2748                     int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
2749                             ANIMATE_INTO_POSITION_AND_DISAPPEAR;
2750                     animateWidgetDrop(info, parent, d.dragView,
2751                             onCompleteRunnable, animationType, cell, false);
2752                 } else {
2753                     int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
2754                     mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
2755                             onCompleteRunnable, this);
2756                 }
2757             } else {
2758                 d.deferDragViewCleanupPostAnimation = false;
2759                 cell.setVisibility(VISIBLE);
2760             }
2761             parent.onDropChild(cell);
2762         }
2763         if (d.stateAnnouncer != null) {
2764             d.stateAnnouncer.completeAction(R.string.item_moved);
2765         }
2766     }
2767 
2768     /**
2769      * Computes the area relative to dragLayer which is used to display a page.
2770      */
2771     public void getPageAreaRelativeToDragLayer(Rect outArea) {
2772         CellLayout child = (CellLayout) getChildAt(getNextPage());
2773         if (child == null) {
2774             return;
2775         }
2776         ShortcutAndWidgetContainer boundingLayout = child.getShortcutsAndWidgets();
2777 
2778         // Use the absolute left instead of the child left, as we want the visible area
2779         // irrespective of the visible child. Since the view can only scroll horizontally, the
2780         // top position is not affected.
2781         mTempXY[0] = getViewportOffsetX() + getPaddingLeft() + boundingLayout.getLeft();
2782         mTempXY[1] = child.getTop() + boundingLayout.getTop();
2783 
2784         float scale = mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY);
2785         outArea.set(mTempXY[0], mTempXY[1],
2786                 (int) (mTempXY[0] + scale * boundingLayout.getMeasuredWidth()),
2787                 (int) (mTempXY[1] + scale * boundingLayout.getMeasuredHeight()));
2788     }
2789 
2790     @Override
2791     public void onDragEnter(DragObject d) {
2792         if (ENFORCE_DRAG_EVENT_ORDER) {
2793             enfoceDragParity("onDragEnter", 1, 1);
2794         }
2795 
2796         mCreateUserFolderOnDrop = false;
2797         mAddToExistingFolderOnDrop = false;
2798 
2799         mDropToLayout = null;
2800         setDropLayoutForDragObject(d);
2801 
2802         if (!workspaceInModalState() && FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
2803             mLauncher.getDragLayer().showPageHints();
2804         }
2805     }
2806 
2807     @Override
2808     public void onDragExit(DragObject d) {
2809         if (ENFORCE_DRAG_EVENT_ORDER) {
2810             enfoceDragParity("onDragExit", -1, 0);
2811         }
2812 
2813         // Here we store the final page that will be dropped to, if the workspace in fact
2814         // receives the drop
2815         if (mInScrollArea) {
2816             if (isPageMoving()) {
2817                 // If the user drops while the page is scrolling, we should use that page as the
2818                 // destination instead of the page that is being hovered over.
2819                 mDropToLayout = (CellLayout) getPageAt(getNextPage());
2820             } else {
2821                 mDropToLayout = mDragOverlappingLayout;
2822             }
2823         } else {
2824             mDropToLayout = mDragTargetLayout;
2825         }
2826 
2827         if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
2828             mCreateUserFolderOnDrop = true;
2829         } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
2830             mAddToExistingFolderOnDrop = true;
2831         }
2832 
2833         // Reset the scroll area and previous drag target
2834         onResetScrollArea();
2835         setCurrentDropLayout(null);
2836         setCurrentDragOverlappingLayout(null);
2837 
2838         mSpringLoadedDragController.cancel();
2839 
2840         mLauncher.getDragLayer().hidePageHints();
2841     }
2842 
2843     private void enfoceDragParity(String event, int update, int expectedValue) {
2844         enfoceDragParity(this, event, update, expectedValue);
2845         for (int i = 0; i < getChildCount(); i++) {
2846             enfoceDragParity(getChildAt(i), event, update, expectedValue);
2847         }
2848     }
2849 
2850     private void enfoceDragParity(View v, String event, int update, int expectedValue) {
2851         Object tag = v.getTag(R.id.drag_event_parity);
2852         int value = tag == null ? 0 : (Integer) tag;
2853         value += update;
2854         v.setTag(R.id.drag_event_parity, value);
2855 
2856         if (value != expectedValue) {
2857             Log.e(TAG, event + ": Drag contract violated: " + value);
2858         }
2859     }
2860 
2861     void setCurrentDropLayout(CellLayout layout) {
2862         if (mDragTargetLayout != null) {
2863             mDragTargetLayout.revertTempState();
2864             mDragTargetLayout.onDragExit();
2865         }
2866         mDragTargetLayout = layout;
2867         if (mDragTargetLayout != null) {
2868             mDragTargetLayout.onDragEnter();
2869         }
2870         cleanupReorder(true);
2871         cleanupFolderCreation();
2872         setCurrentDropOverCell(-1, -1);
2873     }
2874 
2875     void setCurrentDragOverlappingLayout(CellLayout layout) {
2876         if (mDragOverlappingLayout != null) {
2877             mDragOverlappingLayout.setIsDragOverlapping(false);
2878         }
2879         mDragOverlappingLayout = layout;
2880         if (mDragOverlappingLayout != null) {
2881             mDragOverlappingLayout.setIsDragOverlapping(true);
2882         }
2883         // Invalidating the scrim will also force this CellLayout
2884         // to be invalidated so that it is highlighted if necessary.
2885         mLauncher.getDragLayer().invalidateScrim();
2886     }
2887 
2888     public CellLayout getCurrentDragOverlappingLayout() {
2889         return mDragOverlappingLayout;
2890     }
2891 
2892     void setCurrentDropOverCell(int x, int y) {
2893         if (x != mDragOverX || y != mDragOverY) {
2894             mDragOverX = x;
2895             mDragOverY = y;
2896             setDragMode(DRAG_MODE_NONE);
2897         }
2898     }
2899 
2900     void setDragMode(int dragMode) {
2901         if (dragMode != mDragMode) {
2902             if (dragMode == DRAG_MODE_NONE) {
2903                 cleanupAddToFolder();
2904                 // We don't want to cancel the re-order alarm every time the target cell changes
2905                 // as this feels to slow / unresponsive.
2906                 cleanupReorder(false);
2907                 cleanupFolderCreation();
2908             } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
2909                 cleanupReorder(true);
2910                 cleanupFolderCreation();
2911             } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
2912                 cleanupAddToFolder();
2913                 cleanupReorder(true);
2914             } else if (dragMode == DRAG_MODE_REORDER) {
2915                 cleanupAddToFolder();
2916                 cleanupFolderCreation();
2917             }
2918             mDragMode = dragMode;
2919         }
2920     }
2921 
2922     private void cleanupFolderCreation() {
2923         if (mFolderCreateBg != null) {
2924             mFolderCreateBg.animateToRest();
2925         }
2926         mFolderCreationAlarm.setOnAlarmListener(null);
2927         mFolderCreationAlarm.cancelAlarm();
2928     }
2929 
2930     private void cleanupAddToFolder() {
2931         if (mDragOverFolderIcon != null) {
2932             mDragOverFolderIcon.onDragExit();
2933             mDragOverFolderIcon = null;
2934         }
2935     }
2936 
2937     private void cleanupReorder(boolean cancelAlarm) {
2938         // Any pending reorders are canceled
2939         if (cancelAlarm) {
2940             mReorderAlarm.cancelAlarm();
2941         }
2942         mLastReorderX = -1;
2943         mLastReorderY = -1;
2944     }
2945 
2946    /*
2947     *
2948     * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
2949     * coordinate space. The argument xy is modified with the return result.
2950     */
2951    void mapPointFromSelfToChild(View v, float[] xy) {
2952        xy[0] = xy[0] - v.getLeft();
2953        xy[1] = xy[1] - v.getTop();
2954    }
2955 
2956    boolean isPointInSelfOverHotseat(int x, int y) {
2957        mTempXY[0] = x;
2958        mTempXY[1] = y;
2959        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
2960        View hotseat = mLauncher.getHotseat();
2961        return mTempXY[0] >= hotseat.getLeft() &&
2962                mTempXY[0] <= hotseat.getRight() &&
2963                mTempXY[1] >= hotseat.getTop() &&
2964                mTempXY[1] <= hotseat.getBottom();
2965    }
2966 
2967    void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
2968        mTempXY[0] = (int) xy[0];
2969        mTempXY[1] = (int) xy[1];
2970        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
2971        mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempXY);
2972 
2973        xy[0] = mTempXY[0];
2974        xy[1] = mTempXY[1];
2975    }
2976 
2977    /*
2978     *
2979     * Convert the 2D coordinate xy from this CellLayout's coordinate space to
2980     * the parent View's coordinate space. The argument xy is modified with the return result.
2981     *
2982     */
2983    void mapPointFromChildToSelf(View v, float[] xy) {
2984        xy[0] += v.getLeft();
2985        xy[1] += v.getTop();
2986    }
2987 
2988    static private float squaredDistance(float[] point1, float[] point2) {
2989         float distanceX = point1[0] - point2[0];
2990         float distanceY = point2[1] - point2[1];
2991         return distanceX * distanceX + distanceY * distanceY;
2992    }
2993 
2994     /*
2995      *
2996      * This method returns the CellLayout that is currently being dragged to. In order to drag
2997      * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
2998      * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
2999      *
3000      * Return null if no CellLayout is currently being dragged over
3001      *
3002      */
3003     private CellLayout findMatchingPageForDragOver(
3004             DragView dragView, float originX, float originY, boolean exact) {
3005         // We loop through all the screens (ie CellLayouts) and see which ones overlap
3006         // with the item being dragged and then choose the one that's closest to the touch point
3007         final int screenCount = getChildCount();
3008         CellLayout bestMatchingScreen = null;
3009         float smallestDistSoFar = Float.MAX_VALUE;
3010 
3011         for (int i = 0; i < screenCount; i++) {
3012             // The custom content screen is not a valid drag over option
3013             if (mScreenOrder.get(i) == CUSTOM_CONTENT_SCREEN_ID) {
3014                 continue;
3015             }
3016 
3017             CellLayout cl = (CellLayout) getChildAt(i);
3018 
3019             final float[] touchXy = {originX, originY};
3020             mapPointFromSelfToChild(cl, touchXy);
3021 
3022             if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
3023                     touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
3024                 return cl;
3025             }
3026 
3027             if (!exact) {
3028                 // Get the center of the cell layout in screen coordinates
3029                 final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
3030                 cellLayoutCenter[0] = cl.getWidth()/2;
3031                 cellLayoutCenter[1] = cl.getHeight()/2;
3032                 mapPointFromChildToSelf(cl, cellLayoutCenter);
3033 
3034                 touchXy[0] = originX;
3035                 touchXy[1] = originY;
3036 
3037                 // Calculate the distance between the center of the CellLayout
3038                 // and the touch point
3039                 float dist = squaredDistance(touchXy, cellLayoutCenter);
3040 
3041                 if (dist < smallestDistSoFar) {
3042                     smallestDistSoFar = dist;
3043                     bestMatchingScreen = cl;
3044                 }
3045             }
3046         }
3047         return bestMatchingScreen;
3048     }
3049 
3050     private boolean isDragWidget(DragObject d) {
3051         return (d.dragInfo instanceof LauncherAppWidgetInfo ||
3052                 d.dragInfo instanceof PendingAddWidgetInfo);
3053     }
3054 
3055     public void onDragOver(DragObject d) {
3056         // Skip drag over events while we are dragging over side pages
3057         if (mInScrollArea || !transitionStateShouldAllowDrop()) return;
3058 
3059         ItemInfo item = d.dragInfo;
3060         if (item == null) {
3061             if (ProviderConfig.IS_DOGFOOD_BUILD) {
3062                 throw new NullPointerException("DragObject has null info");
3063             }
3064             return;
3065         }
3066 
3067         // Ensure that we have proper spans for the item that we are dropping
3068         if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
3069         mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
3070 
3071         final View child = (mDragInfo == null) ? null : mDragInfo.cell;
3072         if (setDropLayoutForDragObject(d)) {
3073             boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
3074             if (isInSpringLoadedMode) {
3075                 if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
3076                     mSpringLoadedDragController.cancel();
3077                 } else {
3078                     mSpringLoadedDragController.setAlarm(mDragTargetLayout);
3079                 }
3080             }
3081         }
3082 
3083         // Handle the drag over
3084         if (mDragTargetLayout != null) {
3085             // We want the point to be mapped to the dragTarget.
3086             if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
3087                 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
3088             } else {
3089                 mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter);
3090             }
3091 
3092             int minSpanX = item.spanX;
3093             int minSpanY = item.spanY;
3094             if (item.minSpanX > 0 && item.minSpanY > 0) {
3095                 minSpanX = item.minSpanX;
3096                 minSpanY = item.minSpanY;
3097             }
3098 
3099             mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3100                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY,
3101                     mDragTargetLayout, mTargetCell);
3102             int reorderX = mTargetCell[0];
3103             int reorderY = mTargetCell[1];
3104 
3105             setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
3106 
3107             float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
3108                     mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
3109 
3110             manageFolderFeedback(mDragTargetLayout, mTargetCell, targetCellDistance, d);
3111 
3112             boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
3113                     mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
3114                     item.spanY, child, mTargetCell);
3115 
3116             if (!nearestDropOccupied) {
3117                 mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider,
3118                         mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, d);
3119             } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
3120                     && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
3121                     mLastReorderY != reorderY)) {
3122 
3123                 int[] resultSpan = new int[2];
3124                 mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
3125                         (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY,
3126                         child, mTargetCell, resultSpan, CellLayout.MODE_SHOW_REORDER_HINT);
3127 
3128                 // Otherwise, if we aren't adding to or creating a folder and there's no pending
3129                 // reorder, then we schedule a reorder
3130                 ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
3131                         minSpanX, minSpanY, item.spanX, item.spanY, d, child);
3132                 mReorderAlarm.setOnAlarmListener(listener);
3133                 mReorderAlarm.setAlarm(REORDER_TIMEOUT);
3134             }
3135 
3136             if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
3137                     !nearestDropOccupied) {
3138                 if (mDragTargetLayout != null) {
3139                     mDragTargetLayout.revertTempState();
3140                 }
3141             }
3142         }
3143     }
3144 
3145     /**
3146      * Updates {@link #mDragTargetLayout} and {@link #mDragOverlappingLayout}
3147      * based on the DragObject's position.
3148      *
3149      * The layout will be:
3150      * - The Hotseat if the drag object is over it
3151      * - A side page if we are in spring-loaded mode and the drag object is over it
3152      * - The current page otherwise
3153      *
3154      * @return whether the layout is different from the current {@link #mDragTargetLayout}.
3155      */
3156     private boolean setDropLayoutForDragObject(DragObject d) {
3157         CellLayout layout = null;
3158         // Test to see if we are over the hotseat first
3159         if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
3160             if (isPointInSelfOverHotseat(d.x, d.y)) {
3161                 layout = mLauncher.getHotseat().getLayout();
3162             }
3163         }
3164         if (layout == null) {
3165             // Identify whether we have dragged over a side page,
3166             // otherwise just use the current page
3167             layout = workspaceInModalState() ?
3168                     findMatchingPageForDragOver(d.dragView, d.x, d.y, false)
3169                     : getCurrentDropLayout();
3170         }
3171         if (layout != mDragTargetLayout) {
3172             setCurrentDropLayout(layout);
3173             setCurrentDragOverlappingLayout(layout);
3174             return true;
3175         }
3176         return false;
3177     }
3178 
3179     private void manageFolderFeedback(CellLayout targetLayout,
3180             int[] targetCell, float distance, DragObject dragObject) {
3181         if (distance > mMaxDistanceForFolderCreation) return;
3182 
3183         final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]);
3184         ItemInfo info = dragObject.dragInfo;
3185         boolean userFolderPending = willCreateUserFolder(info, dragOverView, false);
3186         if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
3187                 !mFolderCreationAlarm.alarmPending()) {
3188 
3189             FolderCreationAlarmListener listener = new
3190                     FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]);
3191 
3192             if (!dragObject.accessibleDrag) {
3193                 mFolderCreationAlarm.setOnAlarmListener(listener);
3194                 mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
3195             } else {
3196                 listener.onAlarm(mFolderCreationAlarm);
3197             }
3198 
3199             if (dragObject.stateAnnouncer != null) {
3200                 dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
3201                         .getDescriptionForDropOver(dragOverView, getContext()));
3202             }
3203             return;
3204         }
3205 
3206         boolean willAddToFolder = willAddToExistingUserFolder(info, dragOverView);
3207         if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
3208             mDragOverFolderIcon = ((FolderIcon) dragOverView);
3209             mDragOverFolderIcon.onDragEnter(info);
3210             if (targetLayout != null) {
3211                 targetLayout.clearDragOutlines();
3212             }
3213             setDragMode(DRAG_MODE_ADD_TO_FOLDER);
3214 
3215             if (dragObject.stateAnnouncer != null) {
3216                 dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
3217                         .getDescriptionForDropOver(dragOverView, getContext()));
3218             }
3219             return;
3220         }
3221 
3222         if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
3223             setDragMode(DRAG_MODE_NONE);
3224         }
3225         if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
3226             setDragMode(DRAG_MODE_NONE);
3227         }
3228     }
3229 
3230     class FolderCreationAlarmListener implements OnAlarmListener {
3231         CellLayout layout;
3232         int cellX;
3233         int cellY;
3234 
3235         FolderIcon.PreviewBackground bg = new FolderIcon.PreviewBackground();
3236 
3237         public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
3238             this.layout = layout;
3239             this.cellX = cellX;
3240             this.cellY = cellY;
3241 
3242             DeviceProfile grid = mLauncher.getDeviceProfile();
3243             BubbleTextView cell = (BubbleTextView) layout.getChildAt(cellX, cellY);
3244 
3245             bg.setup(getResources().getDisplayMetrics(), grid, null,
3246                     cell.getMeasuredWidth(), cell.getPaddingTop());
3247 
3248             // The full preview background should appear behind the icon
3249             bg.isClipping = false;
3250         }
3251 
3252         public void onAlarm(Alarm alarm) {
3253             mFolderCreateBg = bg;
3254             mFolderCreateBg.animateToAccept(layout, cellX, cellY);
3255             layout.clearDragOutlines();
3256             setDragMode(DRAG_MODE_CREATE_FOLDER);
3257         }
3258     }
3259 
3260     class ReorderAlarmListener implements OnAlarmListener {
3261         float[] dragViewCenter;
3262         int minSpanX, minSpanY, spanX, spanY;
3263         DragObject dragObject;
3264         View child;
3265 
3266         public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
3267                 int spanY, DragObject dragObject, View child) {
3268             this.dragViewCenter = dragViewCenter;
3269             this.minSpanX = minSpanX;
3270             this.minSpanY = minSpanY;
3271             this.spanX = spanX;
3272             this.spanY = spanY;
3273             this.child = child;
3274             this.dragObject = dragObject;
3275         }
3276 
3277         public void onAlarm(Alarm alarm) {
3278             int[] resultSpan = new int[2];
3279             mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3280                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout,
3281                     mTargetCell);
3282             mLastReorderX = mTargetCell[0];
3283             mLastReorderY = mTargetCell[1];
3284 
3285             mTargetCell = mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
3286                 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
3287                 child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
3288 
3289             if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
3290                 mDragTargetLayout.revertTempState();
3291             } else {
3292                 setDragMode(DRAG_MODE_REORDER);
3293             }
3294 
3295             boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
3296             mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider,
3297                 mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize, dragObject);
3298         }
3299     }
3300 
3301     @Override
3302     public void getHitRectRelativeToDragLayer(Rect outRect) {
3303         // We want the workspace to have the whole area of the display (it will find the correct
3304         // cell layout to drop to in the existing drag/drop logic.
3305         mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect);
3306     }
3307 
3308     /**
3309      * Drop an item that didn't originate on one of the workspace screens.
3310      * It may have come from Launcher (e.g. from all apps or customize), or it may have
3311      * come from another app altogether.
3312      *
3313      * NOTE: This can also be called when we are outside of a drag event, when we want
3314      * to add an item to one of the workspace screens.
3315      */
3316     private void onDropExternal(final int[] touchXY, final ItemInfo dragInfo,
3317             final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
3318         final Runnable exitSpringLoadedRunnable = new Runnable() {
3319             @Override
3320             public void run() {
3321                 mLauncher.exitSpringLoadedDragModeDelayed(true,
3322                         Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
3323             }
3324         };
3325 
3326         ItemInfo info = dragInfo;
3327         int spanX = info.spanX;
3328         int spanY = info.spanY;
3329         if (mDragInfo != null) {
3330             spanX = mDragInfo.spanX;
3331             spanY = mDragInfo.spanY;
3332         }
3333 
3334         final long container = mLauncher.isHotseatLayout(cellLayout) ?
3335                 LauncherSettings.Favorites.CONTAINER_HOTSEAT :
3336                     LauncherSettings.Favorites.CONTAINER_DESKTOP;
3337         final long screenId = getIdForScreen(cellLayout);
3338         if (!mLauncher.isHotseatLayout(cellLayout)
3339                 && screenId != getScreenIdForPageIndex(mCurrentPage)
3340                 && mState != State.SPRING_LOADED) {
3341             snapToScreenId(screenId, null);
3342         }
3343 
3344         if (info instanceof PendingAddItemInfo) {
3345             final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
3346 
3347             boolean findNearestVacantCell = true;
3348             if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
3349                 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3350                         cellLayout, mTargetCell);
3351                 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3352                         mDragViewVisualCenter[1], mTargetCell);
3353                 if (willCreateUserFolder(d.dragInfo, cellLayout, mTargetCell, distance, true)
3354                         || willAddToExistingUserFolder(
3355                                 d.dragInfo, cellLayout, mTargetCell, distance)) {
3356                     findNearestVacantCell = false;
3357                 }
3358             }
3359 
3360             final ItemInfo item = d.dragInfo;
3361             boolean updateWidgetSize = false;
3362             if (findNearestVacantCell) {
3363                 int minSpanX = item.spanX;
3364                 int minSpanY = item.spanY;
3365                 if (item.minSpanX > 0 && item.minSpanY > 0) {
3366                     minSpanX = item.minSpanX;
3367                     minSpanY = item.minSpanY;
3368                 }
3369                 int[] resultSpan = new int[2];
3370                 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
3371                         (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
3372                         null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
3373 
3374                 if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) {
3375                     updateWidgetSize = true;
3376                 }
3377                 item.spanX = resultSpan[0];
3378                 item.spanY = resultSpan[1];
3379             }
3380 
3381             Runnable onAnimationCompleteRunnable = new Runnable() {
3382                 @Override
3383                 public void run() {
3384                     // Normally removeExtraEmptyScreen is called in Workspace#onDragEnd, but when
3385                     // adding an item that may not be dropped right away (due to a config activity)
3386                     // we defer the removal until the activity returns.
3387                     deferRemoveExtraEmptyScreen();
3388 
3389                     // When dragging and dropping from customization tray, we deal with creating
3390                     // widgets/shortcuts/folders in a slightly different way
3391                     mLauncher.addPendingItem(pendingInfo, container, screenId, mTargetCell,
3392                             item.spanX, item.spanY);
3393                 }
3394             };
3395             boolean isWidget = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
3396                     || pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
3397 
3398             AppWidgetHostView finalView = isWidget ?
3399                     ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
3400 
3401             if (finalView != null && updateWidgetSize) {
3402                 AppWidgetResizeFrame.updateWidgetSizeRanges(finalView, mLauncher, item.spanX,
3403                         item.spanY);
3404             }
3405 
3406             int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3407             if (isWidget && ((PendingAddWidgetInfo) pendingInfo).info != null &&
3408                     ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
3409                 animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
3410             }
3411             animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
3412                     animationStyle, finalView, true);
3413         } else {
3414             // This is for other drag/drop cases, like dragging from All Apps
3415             View view = null;
3416 
3417             switch (info.itemType) {
3418             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3419             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3420             case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
3421                 if (info.container == NO_ID && info instanceof AppInfo) {
3422                     // Came from all apps -- make a copy
3423                     info = ((AppInfo) info).makeShortcut();
3424                     d.dragInfo = info;
3425                 }
3426                 view = mLauncher.createShortcut(cellLayout, (ShortcutInfo) info);
3427                 break;
3428             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3429                 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
3430                         (FolderInfo) info, mIconCache);
3431                 break;
3432             default:
3433                 throw new IllegalStateException("Unknown item type: " + info.itemType);
3434             }
3435 
3436             // First we find the cell nearest to point at which the item is
3437             // dropped, without any consideration to whether there is an item there.
3438             if (touchXY != null) {
3439                 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3440                         cellLayout, mTargetCell);
3441                 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3442                         mDragViewVisualCenter[1], mTargetCell);
3443                 d.postAnimationRunnable = exitSpringLoadedRunnable;
3444                 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
3445                         true, d.dragView, d.postAnimationRunnable)) {
3446                     return;
3447                 }
3448                 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
3449                         true)) {
3450                     return;
3451                 }
3452             }
3453 
3454             if (touchXY != null) {
3455                 // when dragging and dropping, just find the closest free spot
3456                 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
3457                         (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
3458                         null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
3459             } else {
3460                 cellLayout.findCellForSpan(mTargetCell, 1, 1);
3461             }
3462             // Add the item to DB before adding to screen ensures that the container and other
3463             // values of the info is properly updated.
3464             LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
3465                     mTargetCell[0], mTargetCell[1]);
3466 
3467             addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX,
3468                     info.spanY, insertAtFirst);
3469             cellLayout.onDropChild(view);
3470             cellLayout.getShortcutsAndWidgets().measureChild(view);
3471 
3472             if (d.dragView != null) {
3473                 // We wrap the animation call in the temporary set and reset of the current
3474                 // cellLayout to its final transform -- this means we animate the drag view to
3475                 // the correct final location.
3476                 setFinalTransitionTransform(cellLayout);
3477                 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
3478                         exitSpringLoadedRunnable, this);
3479                 resetTransitionTransform(cellLayout);
3480             }
3481         }
3482     }
3483 
3484     public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
3485         int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo, false);
3486         int visibility = layout.getVisibility();
3487         layout.setVisibility(VISIBLE);
3488 
3489         int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
3490         int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
3491         Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
3492                 Bitmap.Config.ARGB_8888);
3493         mCanvas.setBitmap(b);
3494 
3495         layout.measure(width, height);
3496         layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
3497         layout.draw(mCanvas);
3498         mCanvas.setBitmap(null);
3499         layout.setVisibility(visibility);
3500         return b;
3501     }
3502 
3503     private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
3504             DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, boolean scale) {
3505         // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
3506         // location and size on the home screen.
3507         int spanX = info.spanX;
3508         int spanY = info.spanY;
3509 
3510         Rect r = estimateItemPosition(layout, targetCell[0], targetCell[1], spanX, spanY);
3511         loc[0] = r.left;
3512         loc[1] = r.top;
3513 
3514         setFinalTransitionTransform(layout);
3515         float cellLayoutScale =
3516                 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
3517         resetTransitionTransform(layout);
3518 
3519         float dragViewScaleX;
3520         float dragViewScaleY;
3521         if (scale) {
3522             dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
3523             dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
3524         } else {
3525             dragViewScaleX = 1f;
3526             dragViewScaleY = 1f;
3527         }
3528 
3529         // The animation will scale the dragView about its center, so we need to center about
3530         // the final location.
3531         loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2
3532                 - Math.ceil(layout.getUnusedHorizontalSpace() / 2f);
3533         loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
3534 
3535         scaleXY[0] = dragViewScaleX * cellLayoutScale;
3536         scaleXY[1] = dragViewScaleY * cellLayoutScale;
3537     }
3538 
3539     public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, final DragView dragView,
3540             final Runnable onCompleteRunnable, int animationType, final View finalView,
3541             boolean external) {
3542         Rect from = new Rect();
3543         mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
3544 
3545         int[] finalPos = new int[2];
3546         float scaleXY[] = new float[2];
3547         boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
3548         getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
3549                 scalePreview);
3550 
3551         Resources res = mLauncher.getResources();
3552         final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
3553 
3554         boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET ||
3555                 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
3556         if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
3557             Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
3558             dragView.setCrossFadeBitmap(crossFadeBitmap);
3559             dragView.crossFade((int) (duration * 0.8f));
3560         } else if (isWidget && external) {
3561             scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0],  scaleXY[1]);
3562         }
3563 
3564         DragLayer dragLayer = mLauncher.getDragLayer();
3565         if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
3566             mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
3567                     DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
3568         } else {
3569             int endStyle;
3570             if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
3571                 endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
3572             } else {
3573                 endStyle = DragLayer.ANIMATION_END_DISAPPEAR;
3574             }
3575 
3576             Runnable onComplete = new Runnable() {
3577                 @Override
3578                 public void run() {
3579                     if (finalView != null) {
3580                         finalView.setVisibility(VISIBLE);
3581                     }
3582                     if (onCompleteRunnable != null) {
3583                         onCompleteRunnable.run();
3584                     }
3585                 }
3586             };
3587             dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
3588                     finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
3589                     duration, this);
3590         }
3591     }
3592 
3593     public void setFinalTransitionTransform(CellLayout layout) {
3594         if (isSwitchingState()) {
3595             mCurrentScale = getScaleX();
3596             setScaleX(mStateTransitionAnimation.getFinalScale());
3597             setScaleY(mStateTransitionAnimation.getFinalScale());
3598         }
3599     }
3600     public void resetTransitionTransform(CellLayout layout) {
3601         if (isSwitchingState()) {
3602             setScaleX(mCurrentScale);
3603             setScaleY(mCurrentScale);
3604         }
3605     }
3606 
3607     public WorkspaceStateTransitionAnimation getStateTransitionAnimation() {
3608         return mStateTransitionAnimation;
3609     }
3610 
3611     /**
3612      * Return the current {@link CellLayout}, correctly picking the destination
3613      * screen while a scroll is in progress.
3614      */
3615     public CellLayout getCurrentDropLayout() {
3616         return (CellLayout) getChildAt(getNextPage());
3617     }
3618 
3619     /**
3620      * Return the current CellInfo describing our current drag; this method exists
3621      * so that Launcher can sync this object with the correct info when the activity is created/
3622      * destroyed
3623      *
3624      */
3625     public CellLayout.CellInfo getDragInfo() {
3626         return mDragInfo;
3627     }
3628 
3629     public int getCurrentPageOffsetFromCustomContent() {
3630         return getNextPage() - numCustomPages();
3631     }
3632 
3633     /**
3634      * Calculate the nearest cell where the given object would be dropped.
3635      *
3636      * pixelX and pixelY should be in the coordinate system of layout
3637      */
3638     @Thunk int[] findNearestArea(int pixelX, int pixelY,
3639             int spanX, int spanY, CellLayout layout, int[] recycle) {
3640         return layout.findNearestArea(
3641                 pixelX, pixelY, spanX, spanY, recycle);
3642     }
3643 
3644     void setup(DragController dragController) {
3645         mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
3646         mDragController = dragController;
3647 
3648         // hardware layers on children are enabled on startup, but should be disabled until
3649         // needed
3650         updateChildrenLayersEnabled(false);
3651     }
3652 
3653     /**
3654      * Called at the end of a drag which originated on the workspace.
3655      */
3656     public void onDropCompleted(final View target, final DragObject d,
3657             final boolean isFlingToDelete, final boolean success) {
3658         if (mDeferDropAfterUninstall) {
3659             mDeferredAction = new Runnable() {
3660                 public void run() {
3661                     onDropCompleted(target, d, isFlingToDelete, success);
3662                     mDeferredAction = null;
3663                 }
3664             };
3665             return;
3666         }
3667 
3668         boolean beingCalledAfterUninstall = mDeferredAction != null;
3669 
3670         if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) {
3671             if (target != this && mDragInfo != null) {
3672                 removeWorkspaceItem(mDragInfo.cell);
3673             }
3674         } else if (mDragInfo != null) {
3675             final CellLayout cellLayout = mLauncher.getCellLayout(
3676                     mDragInfo.container, mDragInfo.screenId);
3677             if (cellLayout != null) {
3678                 cellLayout.onDropChild(mDragInfo.cell);
3679             } else if (ProviderConfig.IS_DOGFOOD_BUILD) {
3680                 throw new RuntimeException("Invalid state: cellLayout == null in "
3681                         + "Workspace#onDropCompleted. Please file a bug. ");
3682             };
3683         }
3684         if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful))
3685                 && mDragInfo.cell != null) {
3686             mDragInfo.cell.setVisibility(VISIBLE);
3687         }
3688         mOutlineProvider = null;
3689         mDragInfo = null;
3690 
3691         if (!isFlingToDelete) {
3692             // Fling to delete already exits spring loaded mode after the animation finishes.
3693             mLauncher.exitSpringLoadedDragModeDelayed(success,
3694                     Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, mDelayedResizeRunnable);
3695             mDelayedResizeRunnable = null;
3696         }
3697     }
3698 
3699     /**
3700      * For opposite operation. See {@link #addInScreen}.
3701      */
3702     public void removeWorkspaceItem(View v) {
3703         CellLayout parentCell = getParentCellLayoutForView(v);
3704         if (parentCell != null) {
3705             parentCell.removeView(v);
3706         } else if (ProviderConfig.IS_DOGFOOD_BUILD) {
3707             // When an app is uninstalled using the drop target, we wait until resume to remove
3708             // the icon. We also remove all the corresponding items from the workspace at
3709             // {@link Launcher#bindComponentsRemoved}. That call can come before or after
3710             // {@link Launcher#mOnResumeCallbacks} depending on how busy the worker thread is.
3711             Log.e(TAG, "mDragInfo.cell has null parent");
3712         }
3713         if (v instanceof DropTarget) {
3714             mDragController.removeDropTarget((DropTarget) v);
3715         }
3716     }
3717 
3718     /**
3719      * Removes all folder listeners
3720      */
3721     public void removeFolderListeners() {
3722         mapOverItems(false, new ItemOperator() {
3723             @Override
3724             public boolean evaluate(ItemInfo info, View view) {
3725                 if (view instanceof FolderIcon) {
3726                     ((FolderIcon) view).removeListeners();
3727                 }
3728                 return false;
3729             }
3730         });
3731     }
3732 
3733     @Override
3734     public void deferCompleteDropAfterUninstallActivity() {
3735         mDeferDropAfterUninstall = true;
3736     }
3737 
3738     /// maybe move this into a smaller part
3739     @Override
3740     public void onDragObjectRemoved(boolean success) {
3741         mDeferDropAfterUninstall = false;
3742         mUninstallSuccessful = success;
3743         if (mDeferredAction != null) {
3744             mDeferredAction.run();
3745         }
3746     }
3747 
3748     @Override
3749     public float getIntrinsicIconScaleFactor() {
3750         return 1f;
3751     }
3752 
3753     @Override
3754     public boolean supportsFlingToDelete() {
3755         return true;
3756     }
3757 
3758     @Override
3759     public boolean supportsAppInfoDropTarget() {
3760         return !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND;
3761     }
3762 
3763     @Override
3764     public boolean supportsDeleteDropTarget() {
3765         return true;
3766     }
3767 
3768     @Override
3769     public void onFlingToDelete(DragObject d, PointF vec) {
3770         // Do nothing
3771     }
3772 
3773     @Override
3774     public void onFlingToDeleteCompleted() {
3775         // Do nothing
3776     }
3777 
3778     public boolean isDropEnabled() {
3779         return true;
3780     }
3781 
3782     @Override
3783     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
3784         // We don't dispatch restoreInstanceState to our children using this code path.
3785         // Some pages will be restored immediately as their items are bound immediately, and
3786         // others we will need to wait until after their items are bound.
3787         mSavedStates = container;
3788     }
3789 
3790     public void restoreInstanceStateForChild(int child) {
3791         if (mSavedStates != null) {
3792             mRestoredPages.add(child);
3793             CellLayout cl = (CellLayout) getChildAt(child);
3794             if (cl != null) {
3795                 cl.restoreInstanceState(mSavedStates);
3796             }
3797         }
3798     }
3799 
3800     public void restoreInstanceStateForRemainingPages() {
3801         int count = getChildCount();
3802         for (int i = 0; i < count; i++) {
3803             if (!mRestoredPages.contains(i)) {
3804                 restoreInstanceStateForChild(i);
3805             }
3806         }
3807         mRestoredPages.clear();
3808         mSavedStates = null;
3809     }
3810 
3811     @Override
3812     public void scrollLeft() {
3813         if (!workspaceInModalState() && !mIsSwitchingState) {
3814             super.scrollLeft();
3815         }
3816         Folder openFolder = getOpenFolder();
3817         if (openFolder != null) {
3818             openFolder.completeDragExit();
3819         }
3820     }
3821 
3822     @Override
3823     public void scrollRight() {
3824         if (!workspaceInModalState() && !mIsSwitchingState) {
3825             super.scrollRight();
3826         }
3827         Folder openFolder = getOpenFolder();
3828         if (openFolder != null) {
3829             openFolder.completeDragExit();
3830         }
3831     }
3832 
3833     @Override
3834     public boolean onEnterScrollArea(int x, int y, int direction) {
3835         // Ignore the scroll area if we are dragging over the hot seat
3836         boolean isPortrait = !mLauncher.getDeviceProfile().isLandscape;
3837         if (mLauncher.getHotseat() != null && isPortrait) {
3838             Rect r = new Rect();
3839             mLauncher.getHotseat().getHitRect(r);
3840             if (r.contains(x, y)) {
3841                 return false;
3842             }
3843         }
3844 
3845         boolean result = false;
3846         if (!workspaceInModalState() && !mIsSwitchingState && getOpenFolder() == null) {
3847             mInScrollArea = true;
3848 
3849             final int page = getNextPage() +
3850                        (direction == DragController.SCROLL_LEFT ? -1 : 1);
3851             // We always want to exit the current layout to ensure parity of enter / exit
3852             setCurrentDropLayout(null);
3853 
3854             if (0 <= page && page < getChildCount()) {
3855                 // Ensure that we are not dragging over to the custom content screen
3856                 if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) {
3857                     return false;
3858                 }
3859 
3860                 CellLayout layout = (CellLayout) getChildAt(page);
3861                 setCurrentDragOverlappingLayout(layout);
3862 
3863                 // Workspace is responsible for drawing the edge glow on adjacent pages,
3864                 // so we need to redraw the workspace when this may have changed.
3865                 invalidate();
3866                 result = true;
3867             }
3868         }
3869         return result;
3870     }
3871 
3872     @Override
3873     public boolean onExitScrollArea() {
3874         boolean result = false;
3875         if (mInScrollArea) {
3876             invalidate();
3877             CellLayout layout = getCurrentDropLayout();
3878             setCurrentDropLayout(layout);
3879             setCurrentDragOverlappingLayout(layout);
3880 
3881             result = true;
3882             mInScrollArea = false;
3883         }
3884         return result;
3885     }
3886 
3887     private void onResetScrollArea() {
3888         setCurrentDragOverlappingLayout(null);
3889         mInScrollArea = false;
3890     }
3891 
3892     /**
3893      * Returns a specific CellLayout
3894      */
3895     CellLayout getParentCellLayoutForView(View v) {
3896         ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
3897         for (CellLayout layout : layouts) {
3898             if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
3899                 return layout;
3900             }
3901         }
3902         return null;
3903     }
3904 
3905     /**
3906      * Returns a list of all the CellLayouts in the workspace.
3907      */
3908     ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
3909         ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
3910         int screenCount = getChildCount();
3911         for (int screen = 0; screen < screenCount; screen++) {
3912             layouts.add(((CellLayout) getChildAt(screen)));
3913         }
3914         if (mLauncher.getHotseat() != null) {
3915             layouts.add(mLauncher.getHotseat().getLayout());
3916         }
3917         return layouts;
3918     }
3919 
3920     /**
3921      * We should only use this to search for specific children.  Do not use this method to modify
3922      * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
3923      * the hotseat and workspace pages
3924      */
3925     ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
3926         ArrayList<ShortcutAndWidgetContainer> childrenLayouts = new ArrayList<>();
3927         int screenCount = getChildCount();
3928         for (int screen = 0; screen < screenCount; screen++) {
3929             childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
3930         }
3931         if (mLauncher.getHotseat() != null) {
3932             childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
3933         }
3934         return childrenLayouts;
3935     }
3936 
3937     public View getHomescreenIconByItemId(final long id) {
3938         return getFirstMatch(new ItemOperator() {
3939 
3940             @Override
3941             public boolean evaluate(ItemInfo info, View v) {
3942                 return info != null && info.id == id;
3943             }
3944         });
3945     }
3946 
3947     public View getViewForTag(final Object tag) {
3948         return getFirstMatch(new ItemOperator() {
3949 
3950             @Override
3951             public boolean evaluate(ItemInfo info, View v) {
3952                 return info == tag;
3953             }
3954         });
3955     }
3956 
3957     public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) {
3958         return (LauncherAppWidgetHostView) getFirstMatch(new ItemOperator() {
3959 
3960             @Override
3961             public boolean evaluate(ItemInfo info, View v) {
3962                 return (info instanceof LauncherAppWidgetInfo) &&
3963                         ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId;
3964             }
3965         });
3966     }
3967 
3968     public View getFirstMatch(final ItemOperator operator) {
3969         final View[] value = new View[1];
3970         mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
3971             @Override
3972             public boolean evaluate(ItemInfo info, View v) {
3973                 if (operator.evaluate(info, v)) {
3974                     value[0] = v;
3975                     return true;
3976                 }
3977                 return false;
3978             }
3979         });
3980         return value[0];
3981     }
3982 
3983     void clearDropTargets() {
3984         mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
3985             @Override
3986             public boolean evaluate(ItemInfo info, View v) {
3987                 if (v instanceof DropTarget) {
3988                     mDragController.removeDropTarget((DropTarget) v);
3989                 }
3990                 // not done, process all the shortcuts
3991                 return false;
3992             }
3993         });
3994     }
3995 
3996     /**
3997      * Removes items that match the {@param matcher}. When applications are removed
3998      * as a part of an update, this is called to ensure that other widgets and application
3999      * shortcuts are not removed.
4000      */
4001     public void removeItemsByMatcher(final ItemInfoMatcher matcher) {
4002         ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4003         for (final CellLayout layoutParent: cellLayouts) {
4004             final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4005 
4006             final HashMap<ItemInfo, View> children = new HashMap<>();
4007             for (int j = 0; j < layout.getChildCount(); j++) {
4008                 final View view = layout.getChildAt(j);
4009                 children.put((ItemInfo) view.getTag(), view);
4010             }
4011 
4012             final ArrayList<View> childrenToRemove = new ArrayList<>();
4013             final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove = new HashMap<>();
4014             LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4015                 @Override
4016                 public boolean filterItem(ItemInfo parent, ItemInfo info,
4017                         ComponentName cn) {
4018                     if (parent instanceof FolderInfo) {
4019                         if (matcher.matches(info, cn)) {
4020                             FolderInfo folder = (FolderInfo) parent;
4021                             ArrayList<ShortcutInfo> appsToRemove;
4022                             if (folderAppsToRemove.containsKey(folder)) {
4023                                 appsToRemove = folderAppsToRemove.get(folder);
4024                             } else {
4025                                 appsToRemove = new ArrayList<ShortcutInfo>();
4026                                 folderAppsToRemove.put(folder, appsToRemove);
4027                             }
4028                             appsToRemove.add((ShortcutInfo) info);
4029                             return true;
4030                         }
4031                     } else {
4032                         if (matcher.matches(info, cn)) {
4033                             childrenToRemove.add(children.get(info));
4034                             return true;
4035                         }
4036                     }
4037                     return false;
4038                 }
4039             };
4040             LauncherModel.filterItemInfos(children.keySet(), filter);
4041 
4042             // Remove all the apps from their folders
4043             for (FolderInfo folder : folderAppsToRemove.keySet()) {
4044                 ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
4045                 for (ShortcutInfo info : appsToRemove) {
4046                     folder.remove(info, false);
4047                 }
4048             }
4049 
4050             // Remove all the other children
4051             for (View child : childrenToRemove) {
4052                 // Note: We can not remove the view directly from CellLayoutChildren as this
4053                 // does not re-mark the spaces as unoccupied.
4054                 layoutParent.removeViewInLayout(child);
4055                 if (child instanceof DropTarget) {
4056                     mDragController.removeDropTarget((DropTarget) child);
4057                 }
4058             }
4059 
4060             if (childrenToRemove.size() > 0) {
4061                 layout.requestLayout();
4062                 layout.invalidate();
4063             }
4064         }
4065 
4066         // Strip all the empty screens
4067         stripEmptyScreens();
4068     }
4069 
4070     public interface ItemOperator {
4071         /**
4072          * Process the next itemInfo, possibly with side-effect on the next item.
4073          *
4074          * @param info info for the shortcut
4075          * @param view view for the shortcut
4076          * @return true if done, false to continue the map
4077          */
4078         public boolean evaluate(ItemInfo info, View view);
4079     }
4080 
4081     /**
4082      * Map the operator over the shortcuts and widgets, return the first-non-null value.
4083      *
4084      * @param recurse true: iterate over folder children. false: op get the folders themselves.
4085      * @param op the operator to map over the shortcuts
4086      */
4087     void mapOverItems(boolean recurse, ItemOperator op) {
4088         ArrayList<ShortcutAndWidgetContainer> containers = getAllShortcutAndWidgetContainers();
4089         final int containerCount = containers.size();
4090         for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) {
4091             ShortcutAndWidgetContainer container = containers.get(containerIdx);
4092             // map over all the shortcuts on the workspace
4093             final int itemCount = container.getChildCount();
4094             for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
4095                 View item = container.getChildAt(itemIdx);
4096                 ItemInfo info = (ItemInfo) item.getTag();
4097                 if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) {
4098                     FolderIcon folder = (FolderIcon) item;
4099                     ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder();
4100                     // map over all the children in the folder
4101                     final int childCount = folderChildren.size();
4102                     for (int childIdx = 0; childIdx < childCount; childIdx++) {
4103                         View child = folderChildren.get(childIdx);
4104                         info = (ItemInfo) child.getTag();
4105                         if (op.evaluate(info, child)) {
4106                             return;
4107                         }
4108                     }
4109                 } else {
4110                     if (op.evaluate(info, item)) {
4111                         return;
4112                     }
4113                 }
4114             }
4115         }
4116     }
4117 
4118     void updateShortcuts(ArrayList<ShortcutInfo> shortcuts) {
4119         int total  = shortcuts.size();
4120         final HashSet<ShortcutInfo> updates = new HashSet<ShortcutInfo>(total);
4121         final HashSet<Long> folderIds = new HashSet<>();
4122 
4123         for (int i = 0; i < total; i++) {
4124             ShortcutInfo s = shortcuts.get(i);
4125             updates.add(s);
4126             folderIds.add(s.container);
4127         }
4128 
4129         mapOverItems(MAP_RECURSE, new ItemOperator() {
4130             @Override
4131             public boolean evaluate(ItemInfo info, View v) {
4132                 if (info instanceof ShortcutInfo && v instanceof BubbleTextView &&
4133                         updates.contains(info)) {
4134                     ShortcutInfo si = (ShortcutInfo) info;
4135                     BubbleTextView shortcut = (BubbleTextView) v;
4136                     Drawable oldIcon = getTextViewIcon(shortcut);
4137                     boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
4138                             && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
4139                     shortcut.applyFromShortcutInfo(si, mIconCache,
4140                             si.isPromise() != oldPromiseState);
4141                 }
4142                 // process all the shortcuts
4143                 return false;
4144             }
4145         });
4146 
4147         // Update folder icons
4148         mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
4149             @Override
4150             public boolean evaluate(ItemInfo info, View v) {
4151                 if (info instanceof FolderInfo && folderIds.contains(info.id)) {
4152                     ((FolderInfo) info).itemsChanged(false);
4153                 }
4154                 // process all the shortcuts
4155                 return false;
4156             }
4157         });
4158     }
4159 
4160     public void removeAbandonedPromise(String packageName, UserHandleCompat user) {
4161         HashSet<String> packages = new HashSet<>(1);
4162         packages.add(packageName);
4163         LauncherModel.deletePackageFromDatabase(mLauncher, packageName, user);
4164         removeItemsByMatcher(ItemInfoMatcher.ofPackages(packages, user));
4165     }
4166 
4167     public void updateRestoreItems(final HashSet<ItemInfo> updates) {
4168         mapOverItems(MAP_RECURSE, new ItemOperator() {
4169             @Override
4170             public boolean evaluate(ItemInfo info, View v) {
4171                 if (info instanceof ShortcutInfo && v instanceof BubbleTextView
4172                         && updates.contains(info)) {
4173                     ((BubbleTextView) v).applyState(false);
4174                 } else if (v instanceof PendingAppWidgetHostView
4175                         && info instanceof LauncherAppWidgetInfo
4176                         && updates.contains(info)) {
4177                     ((PendingAppWidgetHostView) v).applyState();
4178                 }
4179                 // process all the shortcuts
4180                 return false;
4181             }
4182         });
4183     }
4184 
4185     public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) {
4186         if (!changedInfo.isEmpty()) {
4187             DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
4188                     mLauncher.getAppWidgetHost());
4189 
4190             LauncherAppWidgetInfo item = changedInfo.get(0);
4191             final AppWidgetProviderInfo widgetInfo;
4192             if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
4193                 widgetInfo = AppWidgetManagerCompat
4194                         .getInstance(mLauncher).findProvider(item.providerName, item.user);
4195             } else {
4196                 widgetInfo = AppWidgetManagerCompat.getInstance(mLauncher)
4197                         .getAppWidgetInfo(item.appWidgetId);
4198             }
4199 
4200             if (widgetInfo != null) {
4201                 // Re-inflate the widgets which have changed status
4202                 widgetRefresh.run();
4203             } else {
4204                 // widgetRefresh will automatically run when the packages are updated.
4205                 // For now just update the progress bars
4206                 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
4207                     @Override
4208                     public boolean evaluate(ItemInfo info, View view) {
4209                         if (view instanceof PendingAppWidgetHostView
4210                                 && changedInfo.contains(info)) {
4211                             ((LauncherAppWidgetInfo) info).installProgress = 100;
4212                             ((PendingAppWidgetHostView) view).applyState();
4213                         }
4214                         // process all the shortcuts
4215                         return false;
4216                     }
4217                 });
4218             }
4219         }
4220     }
4221 
4222     private void moveToScreen(int page, boolean animate) {
4223         if (!workspaceInModalState()) {
4224             if (animate) {
4225                 snapToPage(page);
4226             } else {
4227                 setCurrentPage(page);
4228             }
4229         }
4230         View child = getChildAt(page);
4231         if (child != null) {
4232             child.requestFocus();
4233         }
4234     }
4235 
4236     void moveToDefaultScreen(boolean animate) {
4237         moveToScreen(getDefaultPage(), animate);
4238     }
4239 
4240     void moveToCustomContentScreen(boolean animate) {
4241         if (hasCustomContent()) {
4242             int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
4243             if (animate) {
4244                 snapToPage(ccIndex);
4245             } else {
4246                 setCurrentPage(ccIndex);
4247             }
4248             View child = getChildAt(ccIndex);
4249             if (child != null) {
4250                 child.requestFocus();
4251             }
4252          }
4253         exitWidgetResizeMode();
4254     }
4255 
4256     @Override
4257     protected String getPageIndicatorDescription() {
4258         return getResources().getString(R.string.all_apps_button_label);
4259     }
4260 
4261     @Override
4262     protected String getCurrentPageDescription() {
4263         if (hasCustomContent() && getNextPage() == 0) {
4264             return mCustomContentDescription;
4265         }
4266         int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
4267         return getPageDescription(page);
4268     }
4269 
4270     private String getPageDescription(int page) {
4271         int delta = numCustomPages();
4272         int nScreens = getChildCount() - delta;
4273         int extraScreenId = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
4274         if (extraScreenId >= 0 && nScreens > 1) {
4275             if (page == extraScreenId) {
4276                 return getContext().getString(R.string.workspace_new_page);
4277             }
4278             nScreens--;
4279         }
4280         if (nScreens == 0) {
4281             // When the workspace is not loaded, we do not know how many screen will be bound.
4282             return getContext().getString(R.string.all_apps_home_button_label);
4283         }
4284         return getContext().getString(R.string.workspace_scroll_format,
4285                 page + 1 - delta, nScreens);
4286     }
4287 
4288     @Override
4289     public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
4290         target.gridX = info.cellX;
4291         target.gridY = info.cellY;
4292         target.pageIndex = getCurrentPage();
4293         targetParent.containerType = LauncherLogProto.WORKSPACE;
4294         if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
4295             target.rank = info.rank;
4296             targetParent.containerType = LauncherLogProto.HOTSEAT;
4297         } else if (info.container >= 0) {
4298             targetParent.containerType = LauncherLogProto.FOLDER;
4299         }
4300     }
4301 
4302     /**
4303      * Used as a workaround to ensure that the AppWidgetService receives the
4304      * PACKAGE_ADDED broadcast before updating widgets.
4305      */
4306     private class DeferredWidgetRefresh implements Runnable {
4307         private final ArrayList<LauncherAppWidgetInfo> mInfos;
4308         private final LauncherAppWidgetHost mHost;
4309         private final Handler mHandler;
4310 
4311         private boolean mRefreshPending;
4312 
4313         public DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos,
4314                 LauncherAppWidgetHost host) {
4315             mInfos = infos;
4316             mHost = host;
4317             mHandler = new Handler();
4318             mRefreshPending = true;
4319 
4320             mHost.addProviderChangeListener(this);
4321             // Force refresh after 10 seconds, if we don't get the provider changed event.
4322             // This could happen when the provider is no longer available in the app.
4323             mHandler.postDelayed(this, 10000);
4324         }
4325 
4326         @Override
4327         public void run() {
4328             mHost.removeProviderChangeListener(this);
4329             mHandler.removeCallbacks(this);
4330 
4331             if (!mRefreshPending) {
4332                 return;
4333             }
4334 
4335             mRefreshPending = false;
4336 
4337             mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
4338                 @Override
4339                 public boolean evaluate(ItemInfo info, View view) {
4340                     if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
4341                         PendingAppWidgetHostView hostView = (PendingAppWidgetHostView) view;
4342                         mLauncher.removeItem(view, info, false /* deleteFromDb */);
4343                         mLauncher.bindAppWidget((LauncherAppWidgetInfo) info);
4344                     }
4345                     // process all the shortcuts
4346                     return false;
4347                 }
4348             });
4349         }
4350     }
4351 
4352     public interface OnStateChangeListener {
4353 
4354         /**
4355          * Called when the workspace state is changing.
4356          * @param toState final state
4357          * @param targetAnim animation which will be played during the transition or null.
4358          */
4359         void prepareStateChange(State toState, AnimatorSet targetAnim);
4360     }
4361 
4362     public static final boolean isQsbContainerPage(int pageNo) {
4363         return pageNo == 0;
4364     }
4365 }
4366