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