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