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