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