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