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