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