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