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