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