Workspace.java revision 360e63fd3e77247002b86da2a77bd8dfe8c8a807
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.launcher2;
18
19import android.animation.Animator;
20import android.animation.AnimatorSet;
21import android.animation.ObjectAnimator;
22import android.animation.TimeInterpolator;
23import android.animation.ValueAnimator;
24import android.animation.ValueAnimator.AnimatorUpdateListener;
25import android.app.WallpaperManager;
26import android.appwidget.AppWidgetHostView;
27import android.appwidget.AppWidgetManager;
28import android.appwidget.AppWidgetProviderInfo;
29import android.content.ComponentName;
30import android.content.Context;
31import android.content.Intent;
32import android.content.res.Resources;
33import android.content.res.TypedArray;
34import android.graphics.Bitmap;
35import android.graphics.Camera;
36import android.graphics.Canvas;
37import android.graphics.Matrix;
38import android.graphics.Paint;
39import android.graphics.Point;
40import android.graphics.PointF;
41import android.graphics.PorterDuff;
42import android.graphics.PorterDuffColorFilter;
43import android.graphics.Rect;
44import android.graphics.Region.Op;
45import android.graphics.drawable.Drawable;
46import android.os.IBinder;
47import android.os.Parcelable;
48import android.util.AttributeSet;
49import android.util.DisplayMetrics;
50import android.util.Log;
51import android.view.Display;
52import android.view.MotionEvent;
53import android.view.View;
54import android.view.ViewGroup;
55import android.view.animation.DecelerateInterpolator;
56import android.widget.ImageView;
57import android.widget.TextView;
58
59import com.android.launcher.R;
60import com.android.launcher2.FolderIcon.FolderRingAnimator;
61import com.android.launcher2.LauncherSettings.Favorites;
62
63import java.util.ArrayList;
64import java.util.HashSet;
65
66/**
67 * The workspace is a wide area with a wallpaper and a finite number of pages.
68 * Each page contains a number of icons, folders or widgets the user can
69 * interact with. A workspace is meant to be used with a fixed width only.
70 */
71public class Workspace extends SmoothPagedView
72        implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
73        DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener {
74    private static final String TAG = "Launcher.Workspace";
75
76    // Y rotation to apply to the workspace screens
77    private static final float WORKSPACE_ROTATION = 12.5f;
78    private static final float WORKSPACE_OVERSCROLL_ROTATION = 24f;
79    private static float CAMERA_DISTANCE = 6500;
80
81    private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
82    private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
83    private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
84
85    private static final int BACKGROUND_FADE_OUT_DURATION = 350;
86    private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
87    private static final int FLING_THRESHOLD_VELOCITY = 500;
88
89    private float mMaxDistanceForFolderCreation;
90
91    // These animators are used to fade the children's outlines
92    private ObjectAnimator mChildrenOutlineFadeInAnimation;
93    private ObjectAnimator mChildrenOutlineFadeOutAnimation;
94    private float mChildrenOutlineAlpha = 0;
95
96    // These properties refer to the background protection gradient used for AllApps and Customize
97    private ValueAnimator mBackgroundFadeInAnimation;
98    private ValueAnimator mBackgroundFadeOutAnimation;
99    private Drawable mBackground;
100    boolean mDrawBackground = true;
101    private float mBackgroundAlpha = 0;
102    private float mOverScrollMaxBackgroundAlpha = 0.0f;
103    private int mOverScrollPageIndex = -1;
104
105    private float mWallpaperScrollRatio = 1.0f;
106
107    private final WallpaperManager mWallpaperManager;
108    private IBinder mWindowToken;
109    private static final float WALLPAPER_SCREENS_SPAN = 2f;
110
111    private int mDefaultPage;
112
113    /**
114     * CellInfo for the cell that is currently being dragged
115     */
116    private CellLayout.CellInfo mDragInfo;
117
118    /**
119     * Target drop area calculated during last acceptDrop call.
120     */
121    private int[] mTargetCell = new int[2];
122    private int mDragOverX = -1;
123    private int mDragOverY = -1;
124
125    /**
126     * The CellLayout that is currently being dragged over
127     */
128    private CellLayout mDragTargetLayout = null;
129    /**
130     * The CellLayout that we will show as glowing
131     */
132    private CellLayout mDragOverlappingLayout = null;
133
134    /**
135     * The CellLayout which will be dropped to
136     */
137    private CellLayout mDropToLayout = null;
138
139    private Launcher mLauncher;
140    private IconCache mIconCache;
141    private DragController mDragController;
142
143    // These are temporary variables to prevent having to allocate a new object just to
144    // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
145    private int[] mTempCell = new int[2];
146    private int[] mTempEstimate = new int[2];
147    private float[] mDragViewVisualCenter = new float[2];
148    private float[] mTempDragCoordinates = new float[2];
149    private float[] mTempCellLayoutCenterCoordinates = new float[2];
150    private float[] mTempDragBottomRightCoordinates = new float[2];
151    private Matrix mTempInverseMatrix = new Matrix();
152
153    private SpringLoadedDragController mSpringLoadedDragController;
154    private float mSpringLoadedShrinkFactor;
155
156    private static final int DEFAULT_CELL_COUNT_X = 4;
157    private static final int DEFAULT_CELL_COUNT_Y = 4;
158
159    // State variable that indicates whether the pages are small (ie when you're
160    // in all apps or customize mode)
161
162    enum State { NORMAL, SPRING_LOADED, SMALL };
163    private State mState = State.NORMAL;
164    private boolean mIsSwitchingState = false;
165
166    boolean mAnimatingViewIntoPlace = false;
167    boolean mIsDragOccuring = false;
168    boolean mChildrenLayersEnabled = true;
169
170    /** Is the user is dragging an item near the edge of a page? */
171    private boolean mInScrollArea = false;
172
173    private final HolographicOutlineHelper mOutlineHelper = new HolographicOutlineHelper();
174    private Bitmap mDragOutline = null;
175    private final Rect mTempRect = new Rect();
176    private final int[] mTempXY = new int[2];
177    private float mOverscrollFade = 0;
178    private boolean mOverscrollTransformsSet;
179    public static final int DRAG_BITMAP_PADDING = 2;
180
181    // Camera and Matrix used to determine the final position of a neighboring CellLayout
182    private final Matrix mMatrix = new Matrix();
183    private final Camera mCamera = new Camera();
184    private final float mTempFloat2[] = new float[2];
185
186    enum WallpaperVerticalOffset { TOP, MIDDLE, BOTTOM };
187    int mWallpaperWidth;
188    int mWallpaperHeight;
189    WallpaperOffsetInterpolator mWallpaperOffset;
190    boolean mUpdateWallpaperOffsetImmediately = false;
191    private Runnable mDelayedResizeRunnable;
192    private Runnable mDelayedSnapToPageRunnable;
193    private Point mDisplaySize = new Point();
194    private boolean mIsStaticWallpaper;
195    private int mWallpaperTravelWidth;
196
197    // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
198    private static final int FOLDER_CREATION_TIMEOUT = 0;
199    private static final int REORDER_TIMEOUT = 250;
200    private final Alarm mFolderCreationAlarm = new Alarm();
201    private final Alarm mReorderAlarm = new Alarm();
202    private FolderRingAnimator mDragFolderRingAnimator = null;
203    private FolderIcon mDragOverFolderIcon = null;
204    private boolean mCreateUserFolderOnDrop = false;
205    private boolean mAddToExistingFolderOnDrop = false;
206    private DropTarget.DragEnforcer mDragEnforcer;
207
208    // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
209    private float mXDown;
210    private float mYDown;
211    final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
212    final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
213    final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
214
215    // Relating to the animation of items being dropped externally
216    public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0;
217    public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1;
218    public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2;
219    public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3;
220    public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4;
221
222    // Related to dragging, folder creation and reordering
223    private static final int DRAG_MODE_NONE = 0;
224    private static final int DRAG_MODE_CREATE_FOLDER = 1;
225    private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
226    private static final int DRAG_MODE_REORDER = 3;
227    private int mDragMode = DRAG_MODE_NONE;
228    private int mLastReorderX = -1;
229    private int mLastReorderY = -1;
230
231    // These variables are used for storing the initial and final values during workspace animations
232    private int mSavedScrollX;
233    private float mSavedRotationY;
234    private float mSavedTranslationX;
235    private float mCurrentScaleX;
236    private float mCurrentScaleY;
237    private float mCurrentRotationY;
238    private float mCurrentTranslationX;
239    private float mCurrentTranslationY;
240    private float[] mOldTranslationXs;
241    private float[] mOldTranslationYs;
242    private float[] mOldScaleXs;
243    private float[] mOldScaleYs;
244    private float[] mOldBackgroundAlphas;
245    private float[] mOldBackgroundAlphaMultipliers;
246    private float[] mOldAlphas;
247    private float[] mOldRotationYs;
248    private float[] mNewTranslationXs;
249    private float[] mNewTranslationYs;
250    private float[] mNewScaleXs;
251    private float[] mNewScaleYs;
252    private float[] mNewBackgroundAlphas;
253    private float[] mNewBackgroundAlphaMultipliers;
254    private float[] mNewAlphas;
255    private float[] mNewRotationYs;
256    private float mTransitionProgress;
257
258    /**
259     * Used to inflate the Workspace from XML.
260     *
261     * @param context The application's context.
262     * @param attrs The attributes set containing the Workspace's customization values.
263     */
264    public Workspace(Context context, AttributeSet attrs) {
265        this(context, attrs, 0);
266    }
267
268    /**
269     * Used to inflate the Workspace from XML.
270     *
271     * @param context The application's context.
272     * @param attrs The attributes set containing the Workspace's customization values.
273     * @param defStyle Unused.
274     */
275    public Workspace(Context context, AttributeSet attrs, int defStyle) {
276        super(context, attrs, defStyle);
277        mContentIsRefreshable = false;
278
279        mDragEnforcer = new DropTarget.DragEnforcer(context);
280        // With workspace, data is available straight from the get-go
281        setDataIsReady();
282
283        final Resources res = getResources();
284        mFadeInAdjacentScreens = res.getBoolean(R.bool.config_workspaceFadeAdjacentScreens);
285        mWallpaperManager = WallpaperManager.getInstance(context);
286
287        int cellCountX = DEFAULT_CELL_COUNT_X;
288        int cellCountY = DEFAULT_CELL_COUNT_Y;
289
290        TypedArray a = context.obtainStyledAttributes(attrs,
291                R.styleable.Workspace, defStyle, 0);
292
293        if (LauncherApplication.isScreenLarge()) {
294            // Determine number of rows/columns dynamically
295            // TODO: This code currently fails on tablets with an aspect ratio < 1.3.
296            // Around that ratio we should make cells the same size in portrait and
297            // landscape
298            TypedArray actionBarSizeTypedArray =
299                context.obtainStyledAttributes(new int[] { android.R.attr.actionBarSize });
300            DisplayMetrics displayMetrics = res.getDisplayMetrics();
301            final float actionBarHeight = actionBarSizeTypedArray.getDimension(0, 0f);
302            final float systemBarHeight = res.getDimension(R.dimen.status_bar_height);
303            final float smallestScreenDim = res.getConfiguration().smallestScreenWidthDp *
304                    displayMetrics.density;
305
306            cellCountX = 1;
307            while (CellLayout.widthInPortrait(res, cellCountX + 1) <= smallestScreenDim) {
308                cellCountX++;
309            }
310
311            cellCountY = 1;
312            while (actionBarHeight + CellLayout.heightInLandscape(res, cellCountY + 1)
313                <= smallestScreenDim - systemBarHeight) {
314                cellCountY++;
315            }
316        }
317
318        mSpringLoadedShrinkFactor =
319            res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
320
321        // if the value is manually specified, use that instead
322        cellCountX = a.getInt(R.styleable.Workspace_cellCountX, cellCountX);
323        cellCountY = a.getInt(R.styleable.Workspace_cellCountY, cellCountY);
324        mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
325        a.recycle();
326
327        setOnHierarchyChangeListener(this);
328
329        LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY);
330        setHapticFeedbackEnabled(false);
331
332        mLauncher = (Launcher) context;
333        initWorkspace();
334
335        // Disable multitouch across the workspace/all apps/customize tray
336        setMotionEventSplittingEnabled(true);
337    }
338
339    // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
340    // dimension if unsuccessful
341    public int[] estimateItemSize(int hSpan, int vSpan,
342            ItemInfo itemInfo, boolean springLoaded) {
343        int[] size = new int[2];
344        if (getChildCount() > 0) {
345            CellLayout cl = (CellLayout) mLauncher.getWorkspace().getChildAt(0);
346            Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan);
347            size[0] = r.width();
348            size[1] = r.height();
349            if (springLoaded) {
350                size[0] *= mSpringLoadedShrinkFactor;
351                size[1] *= mSpringLoadedShrinkFactor;
352            }
353            return size;
354        } else {
355            size[0] = Integer.MAX_VALUE;
356            size[1] = Integer.MAX_VALUE;
357            return size;
358        }
359    }
360    public Rect estimateItemPosition(CellLayout cl, ItemInfo pendingInfo,
361            int hCell, int vCell, int hSpan, int vSpan) {
362        Rect r = new Rect();
363        cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
364        return r;
365    }
366
367    public void buildPageHardwareLayers() {
368        if (getWindowToken() != null) {
369            final int childCount = getChildCount();
370            for (int i = 0; i < childCount; i++) {
371                CellLayout cl = (CellLayout) getChildAt(i);
372                cl.getShortcutsAndWidgets().buildLayer();
373            }
374        }
375    }
376
377    public void onDragStart(DragSource source, Object info, int dragAction) {
378        mIsDragOccuring = true;
379        updateChildrenLayersEnabled();
380        mLauncher.lockScreenOrientationOnLargeUI();
381    }
382
383    public void onDragEnd() {
384        mIsDragOccuring = false;
385        updateChildrenLayersEnabled();
386        mLauncher.unlockScreenOrientationOnLargeUI();
387    }
388
389    /**
390     * Initializes various states for this workspace.
391     */
392    protected void initWorkspace() {
393        Context context = getContext();
394        mCurrentPage = mDefaultPage;
395        Launcher.setScreen(mCurrentPage);
396        LauncherApplication app = (LauncherApplication)context.getApplicationContext();
397        mIconCache = app.getIconCache();
398        setWillNotDraw(false);
399        setChildrenDrawnWithCacheEnabled(true);
400
401        final Resources res = getResources();
402        try {
403            mBackground = res.getDrawable(R.drawable.apps_customize_bg);
404        } catch (Resources.NotFoundException e) {
405            // In this case, we will skip drawing background protection
406        }
407
408        mWallpaperOffset = new WallpaperOffsetInterpolator();
409        Display display = mLauncher.getWindowManager().getDefaultDisplay();
410        display.getSize(mDisplaySize);
411        mWallpaperTravelWidth = (int) (mDisplaySize.x *
412                wallpaperTravelToScreenWidthRatio(mDisplaySize.x, mDisplaySize.y));
413
414        mMaxDistanceForFolderCreation = (0.6f * res.getDimensionPixelSize(R.dimen.app_icon_size));
415        mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
416    }
417
418    @Override
419    protected int getScrollMode() {
420        return SmoothPagedView.X_LARGE_MODE;
421    }
422
423    @Override
424    public void onChildViewAdded(View parent, View child) {
425        if (!(child instanceof CellLayout)) {
426            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
427        }
428        CellLayout cl = ((CellLayout) child);
429        cl.setOnInterceptTouchListener(this);
430        cl.setClickable(true);
431        cl.enableHardwareLayers();
432    }
433
434    @Override
435    public void onChildViewRemoved(View parent, View child) {
436    }
437
438    /**
439     * @return The open folder on the current screen, or null if there is none
440     */
441    Folder getOpenFolder() {
442        DragLayer dragLayer = mLauncher.getDragLayer();
443        int count = dragLayer.getChildCount();
444        for (int i = 0; i < count; i++) {
445            View child = dragLayer.getChildAt(i);
446            if (child instanceof Folder) {
447                Folder folder = (Folder) child;
448                if (folder.getInfo().opened)
449                    return folder;
450            }
451        }
452        return null;
453    }
454
455    boolean isTouchActive() {
456        return mTouchState != TOUCH_STATE_REST;
457    }
458
459    /**
460     * Adds the specified child in the specified screen. The position and dimension of
461     * the child are defined by x, y, spanX and spanY.
462     *
463     * @param child The child to add in one of the workspace's screens.
464     * @param screen The screen in which to add the child.
465     * @param x The X position of the child in the screen's grid.
466     * @param y The Y position of the child in the screen's grid.
467     * @param spanX The number of cells spanned horizontally by the child.
468     * @param spanY The number of cells spanned vertically by the child.
469     */
470    void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY) {
471        addInScreen(child, container, screen, x, y, spanX, spanY, false);
472    }
473
474    /**
475     * Adds the specified child in the specified screen. The position and dimension of
476     * the child are defined by x, y, spanX and spanY.
477     *
478     * @param child The child to add in one of the workspace's screens.
479     * @param screen The screen in which to add the child.
480     * @param x The X position of the child in the screen's grid.
481     * @param y The Y position of the child in the screen's grid.
482     * @param spanX The number of cells spanned horizontally by the child.
483     * @param spanY The number of cells spanned vertically by the child.
484     * @param insert When true, the child is inserted at the beginning of the children list.
485     */
486    void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY,
487            boolean insert) {
488        if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
489            if (screen < 0 || screen >= getChildCount()) {
490                Log.e(TAG, "The screen must be >= 0 and < " + getChildCount()
491                    + " (was " + screen + "); skipping child");
492                return;
493            }
494        }
495
496        final CellLayout layout;
497        if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
498            layout = mLauncher.getHotseat().getLayout();
499            child.setOnKeyListener(null);
500
501            // Hide folder title in the hotseat
502            if (child instanceof FolderIcon) {
503                ((FolderIcon) child).setTextVisible(false);
504            }
505
506            if (screen < 0) {
507                screen = mLauncher.getHotseat().getOrderInHotseat(x, y);
508            } else {
509                // Note: We do this to ensure that the hotseat is always laid out in the orientation
510                // of the hotseat in order regardless of which orientation they were added
511                x = mLauncher.getHotseat().getCellXFromOrder(screen);
512                y = mLauncher.getHotseat().getCellYFromOrder(screen);
513            }
514        } else {
515            // Show folder title if not in the hotseat
516            if (child instanceof FolderIcon) {
517                ((FolderIcon) child).setTextVisible(true);
518            }
519
520            layout = (CellLayout) getChildAt(screen);
521            child.setOnKeyListener(new IconKeyEventListener());
522        }
523
524        LayoutParams genericLp = child.getLayoutParams();
525        CellLayout.LayoutParams lp;
526        if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
527            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
528        } else {
529            lp = (CellLayout.LayoutParams) genericLp;
530            lp.cellX = x;
531            lp.cellY = y;
532            lp.cellHSpan = spanX;
533            lp.cellVSpan = spanY;
534        }
535
536        if (spanX < 0 && spanY < 0) {
537            lp.isLockedToGrid = false;
538        }
539
540        // Get the canonical child id to uniquely represent this view in this screen
541        int childId = LauncherModel.getCellLayoutChildId(container, screen, x, y, spanX, spanY);
542        boolean markCellsAsOccupied = !(child instanceof Folder);
543        if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
544            // TODO: This branch occurs when the workspace is adding views
545            // outside of the defined grid
546            // maybe we should be deleting these items from the LauncherModel?
547            Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
548        }
549
550        if (!(child instanceof Folder)) {
551            child.setHapticFeedbackEnabled(false);
552            child.setOnLongClickListener(mLongClickListener);
553        }
554        if (child instanceof DropTarget) {
555            mDragController.addDropTarget((DropTarget) child);
556        }
557    }
558
559    /**
560     * Check if the point (x, y) hits a given page.
561     */
562    private boolean hitsPage(int index, float x, float y) {
563        final View page = getChildAt(index);
564        if (page != null) {
565            float[] localXY = { x, y };
566            mapPointFromSelfToChild(page, localXY);
567            return (localXY[0] >= 0 && localXY[0] < page.getWidth()
568                    && localXY[1] >= 0 && localXY[1] < page.getHeight());
569        }
570        return false;
571    }
572
573    @Override
574    protected boolean hitsPreviousPage(float x, float y) {
575        // mNextPage is set to INVALID_PAGE whenever we are stationary.
576        // Calculating "next page" this way ensures that you scroll to whatever page you tap on
577        final int current = (mNextPage == INVALID_PAGE) ? mCurrentPage : mNextPage;
578
579        // Only allow tap to next page on large devices, where there's significant margin outside
580        // the active workspace
581        return LauncherApplication.isScreenLarge() && hitsPage(current - 1, x, y);
582    }
583
584    @Override
585    protected boolean hitsNextPage(float x, float y) {
586        // mNextPage is set to INVALID_PAGE whenever we are stationary.
587        // Calculating "next page" this way ensures that you scroll to whatever page you tap on
588        final int current = (mNextPage == INVALID_PAGE) ? mCurrentPage : mNextPage;
589
590        // Only allow tap to next page on large devices, where there's significant margin outside
591        // the active workspace
592        return LauncherApplication.isScreenLarge() && hitsPage(current + 1, x, y);
593    }
594
595    /**
596     * Called directly from a CellLayout (not by the framework), after we've been added as a
597     * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
598     * that it should intercept touch events, which is not something that is normally supported.
599     */
600    @Override
601    public boolean onTouch(View v, MotionEvent event) {
602        return (isSmall() || !isFinishedSwitchingState());
603    }
604
605    public boolean isSwitchingState() {
606        return mIsSwitchingState;
607    }
608
609    /** This differs from isSwitchingState in that we take into account how far the transition
610     *  has completed. */
611    public boolean isFinishedSwitchingState() {
612        return !mIsSwitchingState || (mTransitionProgress > 0.5f);
613    }
614
615    protected void onWindowVisibilityChanged (int visibility) {
616        mLauncher.onWindowVisibilityChanged(visibility);
617    }
618
619    @Override
620    public boolean dispatchUnhandledMove(View focused, int direction) {
621        if (isSmall() || !isFinishedSwitchingState()) {
622            // when the home screens are shrunken, shouldn't allow side-scrolling
623            return false;
624        }
625        return super.dispatchUnhandledMove(focused, direction);
626    }
627
628    @Override
629    public boolean onInterceptTouchEvent(MotionEvent ev) {
630        switch (ev.getAction() & MotionEvent.ACTION_MASK) {
631        case MotionEvent.ACTION_DOWN:
632            mXDown = ev.getX();
633            mYDown = ev.getY();
634            break;
635        case MotionEvent.ACTION_POINTER_UP:
636        case MotionEvent.ACTION_UP:
637            if (mTouchState == TOUCH_STATE_REST) {
638                final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
639                if (!currentPage.lastDownOnOccupiedCell()) {
640                    onWallpaperTap(ev);
641                }
642            }
643        }
644        return super.onInterceptTouchEvent(ev);
645    }
646
647    @Override
648    protected void determineScrollingStart(MotionEvent ev) {
649        if (isSmall()) return;
650        if (!isFinishedSwitchingState()) return;
651
652        float deltaX = Math.abs(ev.getX() - mXDown);
653        float deltaY = Math.abs(ev.getY() - mYDown);
654
655        if (Float.compare(deltaX, 0f) == 0) return;
656
657        float slope = deltaY / deltaX;
658        float theta = (float) Math.atan(slope);
659
660        if (deltaX > mTouchSlop || deltaY > mTouchSlop) {
661            cancelCurrentPageLongPress();
662        }
663
664        if (theta > MAX_SWIPE_ANGLE) {
665            // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
666            return;
667        } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
668            // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
669            // increase the touch slop to make it harder to begin scrolling the workspace. This
670            // results in vertically scrolling widgets to more easily. The higher the angle, the
671            // more we increase touch slop.
672            theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
673            float extraRatio = (float)
674                    Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
675            super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
676        } else {
677            // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
678            super.determineScrollingStart(ev);
679        }
680    }
681
682    @Override
683    protected boolean isScrollingIndicatorEnabled() {
684        return super.isScrollingIndicatorEnabled() && (mState != State.SPRING_LOADED);
685    }
686
687    protected void onPageBeginMoving() {
688        super.onPageBeginMoving();
689
690        if (isHardwareAccelerated()) {
691            updateChildrenLayersEnabled();
692        } else {
693            if (mNextPage != INVALID_PAGE) {
694                // we're snapping to a particular screen
695                enableChildrenCache(mCurrentPage, mNextPage);
696            } else {
697                // this is when user is actively dragging a particular screen, they might
698                // swipe it either left or right (but we won't advance by more than one screen)
699                enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
700            }
701        }
702
703        // Only show page outlines as we pan if we are on large screen
704        if (LauncherApplication.isScreenLarge()) {
705            showOutlines();
706            mIsStaticWallpaper = mWallpaperManager.getWallpaperInfo() == null;
707        }
708
709        // If we are not fading in adjacent screens, we still need to restore the alpha in case the
710        // user scrolls while we are transitioning (should not affect dispatchDraw optimizations)
711        if (!mFadeInAdjacentScreens) {
712            for (int i = 0; i < getChildCount(); ++i) {
713                ((CellLayout) getPageAt(i)).setShortcutAndWidgetAlpha(1f);
714            }
715        }
716
717        // Show the scroll indicator as you pan the page
718        showScrollingIndicator(false);
719    }
720
721    protected void onPageEndMoving() {
722        super.onPageEndMoving();
723
724        if (isHardwareAccelerated()) {
725            updateChildrenLayersEnabled();
726        } else {
727            clearChildrenCache();
728        }
729
730
731        if (mDragController.isDragging()) {
732            if (isSmall()) {
733                // If we are in springloaded mode, then force an event to check if the current touch
734                // is under a new page (to scroll to)
735                mDragController.forceMoveEvent();
736            }
737        } else {
738            // If we are not mid-dragging, hide the page outlines if we are on a large screen
739            if (LauncherApplication.isScreenLarge()) {
740                hideOutlines();
741            }
742
743            // Hide the scroll indicator as you pan the page
744            if (!mDragController.isDragging()) {
745                hideScrollingIndicator(false);
746            }
747        }
748        mOverScrollMaxBackgroundAlpha = 0.0f;
749        mOverScrollPageIndex = -1;
750
751        if (mDelayedResizeRunnable != null) {
752            mDelayedResizeRunnable.run();
753            mDelayedResizeRunnable = null;
754        }
755
756        if (mDelayedSnapToPageRunnable != null) {
757            mDelayedSnapToPageRunnable.run();
758            mDelayedSnapToPageRunnable = null;
759        }
760    }
761
762    @Override
763    protected void notifyPageSwitchListener() {
764        super.notifyPageSwitchListener();
765        Launcher.setScreen(mCurrentPage);
766    };
767
768    // As a ratio of screen height, the total distance we want the parallax effect to span
769    // horizontally
770    private float wallpaperTravelToScreenWidthRatio(int width, int height) {
771        float aspectRatio = width / (float) height;
772
773        // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
774        // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
775        // We will use these two data points to extrapolate how much the wallpaper parallax effect
776        // to span (ie travel) at any aspect ratio:
777
778        final float ASPECT_RATIO_LANDSCAPE = 16/10f;
779        final float ASPECT_RATIO_PORTRAIT = 10/16f;
780        final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
781        final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;
782
783        // To find out the desired width at different aspect ratios, we use the following two
784        // formulas, where the coefficient on x is the aspect ratio (width/height):
785        //   (16/10)x + y = 1.5
786        //   (10/16)x + y = 1.2
787        // We solve for x and y and end up with a final formula:
788        final float x =
789            (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
790            (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
791        final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
792        return x * aspectRatio + y;
793    }
794
795    // The range of scroll values for Workspace
796    private int getScrollRange() {
797        return getChildOffset(getChildCount() - 1) - getChildOffset(0);
798    }
799
800    protected void setWallpaperDimension() {
801        DisplayMetrics displayMetrics = new DisplayMetrics();
802        mLauncher.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
803        final int maxDim = Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels);
804        final int minDim = Math.min(displayMetrics.widthPixels, displayMetrics.heightPixels);
805
806        // We need to ensure that there is enough extra space in the wallpaper for the intended
807        // parallax effects
808        if (LauncherApplication.isScreenLarge()) {
809            mWallpaperWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));
810            mWallpaperHeight = maxDim;
811        } else {
812            mWallpaperWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
813            mWallpaperHeight = maxDim;
814        }
815        new Thread("setWallpaperDimension") {
816            public void run() {
817                mWallpaperManager.suggestDesiredDimensions(mWallpaperWidth, mWallpaperHeight);
818            }
819        }.start();
820    }
821
822    private float wallpaperOffsetForCurrentScroll() {
823        // Set wallpaper offset steps (1 / (number of screens - 1))
824        mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 1.0f);
825
826        // For the purposes of computing the scrollRange and overScrollOffset, we assume
827        // that mLayoutScale is 1. This means that when we're in spring-loaded mode,
828        // there's no discrepancy between the wallpaper offset for a given page.
829        float layoutScale = mLayoutScale;
830        mLayoutScale = 1f;
831        int scrollRange = getScrollRange();
832
833        // Again, we adjust the wallpaper offset to be consistent between values of mLayoutScale
834        float adjustedScrollX = Math.max(0, Math.min(getScrollX(), mMaxScrollX));
835        adjustedScrollX *= mWallpaperScrollRatio;
836        mLayoutScale = layoutScale;
837
838        float scrollProgress =
839            adjustedScrollX / (float) scrollRange;
840
841        if (LauncherApplication.isScreenLarge() && mIsStaticWallpaper) {
842            // The wallpaper travel width is how far, from left to right, the wallpaper will move
843            // at this orientation. On tablets in portrait mode we don't move all the way to the
844            // edges of the wallpaper, or otherwise the parallax effect would be too strong.
845            int wallpaperTravelWidth = Math.min(mWallpaperTravelWidth, mWallpaperWidth);
846
847            float offsetInDips = wallpaperTravelWidth * scrollProgress +
848                (mWallpaperWidth - wallpaperTravelWidth) / 2; // center it
849            float offset = offsetInDips / (float) mWallpaperWidth;
850            return offset;
851        } else {
852            return scrollProgress;
853        }
854    }
855
856    private void syncWallpaperOffsetWithScroll() {
857        final boolean enableWallpaperEffects = isHardwareAccelerated();
858        if (enableWallpaperEffects) {
859            mWallpaperOffset.setFinalX(wallpaperOffsetForCurrentScroll());
860        }
861    }
862
863    public void updateWallpaperOffsetImmediately() {
864        mUpdateWallpaperOffsetImmediately = true;
865    }
866
867    private void updateWallpaperOffsets() {
868        boolean updateNow = false;
869        boolean keepUpdating = true;
870        if (mUpdateWallpaperOffsetImmediately) {
871            updateNow = true;
872            keepUpdating = false;
873            mWallpaperOffset.jumpToFinal();
874            mUpdateWallpaperOffsetImmediately = false;
875        } else {
876            updateNow = keepUpdating = mWallpaperOffset.computeScrollOffset();
877        }
878        if (updateNow) {
879            if (mWindowToken != null) {
880                mWallpaperManager.setWallpaperOffsets(mWindowToken,
881                        mWallpaperOffset.getCurrX(), mWallpaperOffset.getCurrY());
882            }
883        }
884        if (keepUpdating) {
885            invalidate();
886        }
887    }
888
889    @Override
890    protected void updateCurrentPageScroll() {
891        super.updateCurrentPageScroll();
892        computeWallpaperScrollRatio(mCurrentPage);
893    }
894
895    @Override
896    protected void snapToPage(int whichPage) {
897        super.snapToPage(whichPage);
898        computeWallpaperScrollRatio(whichPage);
899    }
900
901    @Override
902    protected void snapToPage(int whichPage, int duration) {
903        super.snapToPage(whichPage, duration);
904        computeWallpaperScrollRatio(whichPage);
905    }
906
907    protected void snapToPage(int whichPage, Runnable r) {
908        if (mDelayedSnapToPageRunnable != null) {
909            mDelayedSnapToPageRunnable.run();
910        }
911        mDelayedSnapToPageRunnable = r;
912        snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION);
913    }
914
915    private void computeWallpaperScrollRatio(int page) {
916        // Here, we determine what the desired scroll would be with and without a layout scale,
917        // and compute a ratio between the two. This allows us to adjust the wallpaper offset
918        // as though there is no layout scale.
919        float layoutScale = mLayoutScale;
920        int scaled = getChildOffset(page) - getRelativeChildOffset(page);
921        mLayoutScale = 1.0f;
922        float unscaled = getChildOffset(page) - getRelativeChildOffset(page);
923        mLayoutScale = layoutScale;
924        if (scaled > 0) {
925            mWallpaperScrollRatio = (1.0f * unscaled) / scaled;
926        } else {
927            mWallpaperScrollRatio = 1f;
928        }
929    }
930
931    class WallpaperOffsetInterpolator {
932        float mFinalHorizontalWallpaperOffset = 0.0f;
933        float mFinalVerticalWallpaperOffset = 0.5f;
934        float mHorizontalWallpaperOffset = 0.0f;
935        float mVerticalWallpaperOffset = 0.5f;
936        long mLastWallpaperOffsetUpdateTime;
937        boolean mIsMovingFast;
938        boolean mOverrideHorizontalCatchupConstant;
939        float mHorizontalCatchupConstant = 0.35f;
940        float mVerticalCatchupConstant = 0.35f;
941
942        public WallpaperOffsetInterpolator() {
943        }
944
945        public void setOverrideHorizontalCatchupConstant(boolean override) {
946            mOverrideHorizontalCatchupConstant = override;
947        }
948
949        public void setHorizontalCatchupConstant(float f) {
950            mHorizontalCatchupConstant = f;
951        }
952
953        public void setVerticalCatchupConstant(float f) {
954            mVerticalCatchupConstant = f;
955        }
956
957        public boolean computeScrollOffset() {
958            if (Float.compare(mHorizontalWallpaperOffset, mFinalHorizontalWallpaperOffset) == 0 &&
959                    Float.compare(mVerticalWallpaperOffset, mFinalVerticalWallpaperOffset) == 0) {
960                mIsMovingFast = false;
961                return false;
962            }
963            boolean isLandscape = mDisplaySize.x > mDisplaySize.y;
964
965            long currentTime = System.currentTimeMillis();
966            long timeSinceLastUpdate = currentTime - mLastWallpaperOffsetUpdateTime;
967            timeSinceLastUpdate = Math.min((long) (1000/30f), timeSinceLastUpdate);
968            timeSinceLastUpdate = Math.max(1L, timeSinceLastUpdate);
969
970            float xdiff = Math.abs(mFinalHorizontalWallpaperOffset - mHorizontalWallpaperOffset);
971            if (!mIsMovingFast && xdiff > 0.07) {
972                mIsMovingFast = true;
973            }
974
975            float fractionToCatchUpIn1MsHorizontal;
976            if (mOverrideHorizontalCatchupConstant) {
977                fractionToCatchUpIn1MsHorizontal = mHorizontalCatchupConstant;
978            } else if (mIsMovingFast) {
979                fractionToCatchUpIn1MsHorizontal = isLandscape ? 0.5f : 0.75f;
980            } else {
981                // slow
982                fractionToCatchUpIn1MsHorizontal = isLandscape ? 0.27f : 0.5f;
983            }
984            float fractionToCatchUpIn1MsVertical = mVerticalCatchupConstant;
985
986            fractionToCatchUpIn1MsHorizontal /= 33f;
987            fractionToCatchUpIn1MsVertical /= 33f;
988
989            final float UPDATE_THRESHOLD = 0.00001f;
990            float hOffsetDelta = mFinalHorizontalWallpaperOffset - mHorizontalWallpaperOffset;
991            float vOffsetDelta = mFinalVerticalWallpaperOffset - mVerticalWallpaperOffset;
992            boolean jumpToFinalValue = Math.abs(hOffsetDelta) < UPDATE_THRESHOLD &&
993                Math.abs(vOffsetDelta) < UPDATE_THRESHOLD;
994
995            // Don't have any lag between workspace and wallpaper on non-large devices
996            if (!LauncherApplication.isScreenLarge() || jumpToFinalValue) {
997                mHorizontalWallpaperOffset = mFinalHorizontalWallpaperOffset;
998                mVerticalWallpaperOffset = mFinalVerticalWallpaperOffset;
999            } else {
1000                float percentToCatchUpVertical =
1001                    Math.min(1.0f, timeSinceLastUpdate * fractionToCatchUpIn1MsVertical);
1002                float percentToCatchUpHorizontal =
1003                    Math.min(1.0f, timeSinceLastUpdate * fractionToCatchUpIn1MsHorizontal);
1004                mHorizontalWallpaperOffset += percentToCatchUpHorizontal * hOffsetDelta;
1005                mVerticalWallpaperOffset += percentToCatchUpVertical * vOffsetDelta;
1006            }
1007
1008            mLastWallpaperOffsetUpdateTime = System.currentTimeMillis();
1009            return true;
1010        }
1011
1012        public float getCurrX() {
1013            return mHorizontalWallpaperOffset;
1014        }
1015
1016        public float getFinalX() {
1017            return mFinalHorizontalWallpaperOffset;
1018        }
1019
1020        public float getCurrY() {
1021            return mVerticalWallpaperOffset;
1022        }
1023
1024        public float getFinalY() {
1025            return mFinalVerticalWallpaperOffset;
1026        }
1027
1028        public void setFinalX(float x) {
1029            mFinalHorizontalWallpaperOffset = Math.max(0f, Math.min(x, 1.0f));
1030        }
1031
1032        public void setFinalY(float y) {
1033            mFinalVerticalWallpaperOffset = Math.max(0f, Math.min(y, 1.0f));
1034        }
1035
1036        public void jumpToFinal() {
1037            mHorizontalWallpaperOffset = mFinalHorizontalWallpaperOffset;
1038            mVerticalWallpaperOffset = mFinalVerticalWallpaperOffset;
1039        }
1040    }
1041
1042    @Override
1043    public void computeScroll() {
1044        super.computeScroll();
1045        syncWallpaperOffsetWithScroll();
1046    }
1047
1048    void showOutlines() {
1049        if (!isSmall() && !mIsSwitchingState) {
1050            if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
1051            if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
1052            mChildrenOutlineFadeInAnimation = ObjectAnimator.ofFloat(this, "childrenOutlineAlpha", 1.0f);
1053            mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
1054            mChildrenOutlineFadeInAnimation.start();
1055        }
1056    }
1057
1058    void hideOutlines() {
1059        if (!isSmall() && !mIsSwitchingState) {
1060            if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
1061            if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
1062            mChildrenOutlineFadeOutAnimation = ObjectAnimator.ofFloat(this, "childrenOutlineAlpha", 0.0f);
1063            mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
1064            mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
1065            mChildrenOutlineFadeOutAnimation.start();
1066        }
1067    }
1068
1069    public void showOutlinesTemporarily() {
1070        if (!mIsPageMoving && !isTouchActive()) {
1071            snapToPage(mCurrentPage);
1072        }
1073    }
1074
1075    public void setChildrenOutlineAlpha(float alpha) {
1076        mChildrenOutlineAlpha = alpha;
1077        for (int i = 0; i < getChildCount(); i++) {
1078            CellLayout cl = (CellLayout) getChildAt(i);
1079            cl.setBackgroundAlpha(alpha);
1080        }
1081    }
1082
1083    public float getChildrenOutlineAlpha() {
1084        return mChildrenOutlineAlpha;
1085    }
1086
1087    void disableBackground() {
1088        mDrawBackground = false;
1089    }
1090    void enableBackground() {
1091        mDrawBackground = true;
1092    }
1093
1094    private void animateBackgroundGradient(float finalAlpha, boolean animated) {
1095        if (mBackground == null) return;
1096        if (mBackgroundFadeInAnimation != null) {
1097            mBackgroundFadeInAnimation.cancel();
1098            mBackgroundFadeInAnimation = null;
1099        }
1100        if (mBackgroundFadeOutAnimation != null) {
1101            mBackgroundFadeOutAnimation.cancel();
1102            mBackgroundFadeOutAnimation = null;
1103        }
1104        float startAlpha = getBackgroundAlpha();
1105        if (finalAlpha != startAlpha) {
1106            if (animated) {
1107                mBackgroundFadeOutAnimation = ValueAnimator.ofFloat(startAlpha, finalAlpha);
1108                mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() {
1109                    public void onAnimationUpdate(ValueAnimator animation) {
1110                        setBackgroundAlpha(((Float) animation.getAnimatedValue()).floatValue());
1111                    }
1112                });
1113                mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
1114                mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
1115                mBackgroundFadeOutAnimation.start();
1116            } else {
1117                setBackgroundAlpha(finalAlpha);
1118            }
1119        }
1120    }
1121
1122    public void setBackgroundAlpha(float alpha) {
1123        if (alpha != mBackgroundAlpha) {
1124            mBackgroundAlpha = alpha;
1125            invalidate();
1126        }
1127    }
1128
1129    public float getBackgroundAlpha() {
1130        return mBackgroundAlpha;
1131    }
1132
1133    /**
1134     * Due to 3D transformations, if two CellLayouts are theoretically touching each other,
1135     * on the xy plane, when one is rotated along the y-axis, the gap between them is perceived
1136     * as being larger. This method computes what offset the rotated view should be translated
1137     * in order to minimize this perceived gap.
1138     * @param degrees Angle of the view
1139     * @param width Width of the view
1140     * @param height Height of the view
1141     * @return Offset to be used in a View.setTranslationX() call
1142     */
1143    private float getOffsetXForRotation(float degrees, int width, int height) {
1144        mMatrix.reset();
1145        mCamera.save();
1146        mCamera.rotateY(Math.abs(degrees));
1147        mCamera.getMatrix(mMatrix);
1148        mCamera.restore();
1149
1150        mMatrix.preTranslate(-width * 0.5f, -height * 0.5f);
1151        mMatrix.postTranslate(width * 0.5f, height * 0.5f);
1152        mTempFloat2[0] = width;
1153        mTempFloat2[1] = height;
1154        mMatrix.mapPoints(mTempFloat2);
1155        return (width - mTempFloat2[0]) * (degrees > 0.0f ? 1.0f : -1.0f);
1156    }
1157
1158    float backgroundAlphaInterpolator(float r) {
1159        float pivotA = 0.1f;
1160        float pivotB = 0.4f;
1161        if (r < pivotA) {
1162            return 0;
1163        } else if (r > pivotB) {
1164            return 1.0f;
1165        } else {
1166            return (r - pivotA)/(pivotB - pivotA);
1167        }
1168    }
1169
1170    float overScrollBackgroundAlphaInterpolator(float r) {
1171        float threshold = 0.08f;
1172
1173        if (r > mOverScrollMaxBackgroundAlpha) {
1174            mOverScrollMaxBackgroundAlpha = r;
1175        } else if (r < mOverScrollMaxBackgroundAlpha) {
1176            r = mOverScrollMaxBackgroundAlpha;
1177        }
1178
1179        return Math.min(r / threshold, 1.0f);
1180    }
1181
1182    private void screenScrolledLargeUI(int screenCenter) {
1183        if (isSwitchingState()) return;
1184        boolean isInOverscroll = false;
1185        for (int i = 0; i < getChildCount(); i++) {
1186            CellLayout cl = (CellLayout) getChildAt(i);
1187            if (cl != null) {
1188                float scrollProgress = getScrollProgress(screenCenter, cl, i);
1189                float rotation = WORKSPACE_ROTATION * scrollProgress;
1190                float translationX = getOffsetXForRotation(rotation, cl.getWidth(), cl.getHeight());
1191
1192                // If the current page (i) is being over scrolled, we use a different
1193                // set of rules for setting the background alpha multiplier.
1194                if (!isSmall()) {
1195                    if ((mOverScrollX < 0 && i == 0) || (mOverScrollX > mMaxScrollX &&
1196                            i == getChildCount() -1)) {
1197                        isInOverscroll = true;
1198                        rotation *= -1;
1199                        cl.setBackgroundAlphaMultiplier(
1200                                overScrollBackgroundAlphaInterpolator(Math.abs(scrollProgress)));
1201                        mOverScrollPageIndex = i;
1202                        cl.setOverScrollAmount(Math.abs(scrollProgress), i == 0);
1203                        if (!mOverscrollTransformsSet) {
1204                            mOverscrollTransformsSet = true;
1205                            cl.setPivotX(cl.getMeasuredWidth() * (i == 0 ? 0.75f : 0.25f));
1206                            cl.setPivotY(cl.getMeasuredHeight() * 0.5f);
1207                            cl.setOverscrollTransformsDirty(true);
1208                        }
1209                    } else if (mOverScrollPageIndex != i) {
1210                        cl.setBackgroundAlphaMultiplier(
1211                                backgroundAlphaInterpolator(Math.abs(scrollProgress)));
1212                    }
1213                }
1214                cl.setTranslationX(translationX);
1215                cl.setRotationY(rotation);
1216                if (mFadeInAdjacentScreens && !isSmall()) {
1217                    float alpha = 1 - Math.abs(scrollProgress);
1218                    cl.setShortcutAndWidgetAlpha(alpha);
1219                }
1220            }
1221        }
1222        if (mOverscrollTransformsSet && !isInOverscroll) {
1223            mOverscrollTransformsSet = false;
1224            ((CellLayout) getChildAt(0)).resetOverscrollTransforms();
1225            ((CellLayout) getChildAt(getChildCount() - 1)).resetOverscrollTransforms();
1226        }
1227        invalidate();
1228    }
1229
1230    private void screenScrolledStandardUI(int screenCenter) {
1231        if (mOverScrollX < 0 || mOverScrollX > mMaxScrollX) {
1232            int index = mOverScrollX < 0 ? 0 : getChildCount() - 1;
1233            CellLayout cl = (CellLayout) getChildAt(index);
1234            float scrollProgress = getScrollProgress(screenCenter, cl, index);
1235            cl.setOverScrollAmount(Math.abs(scrollProgress), index == 0);
1236            float rotation = - WORKSPACE_OVERSCROLL_ROTATION * scrollProgress;
1237            cl.setRotationY(rotation);
1238            setFadeForOverScroll(Math.abs(scrollProgress));
1239            if (!mOverscrollTransformsSet) {
1240                mOverscrollTransformsSet = true;
1241                cl.setCameraDistance(mDensity * CAMERA_DISTANCE);
1242                cl.setPivotX(cl.getMeasuredWidth() * (index == 0 ? 0.75f : 0.25f));
1243                cl.setPivotY(cl.getMeasuredHeight() * 0.5f);
1244                cl.setOverscrollTransformsDirty(true);
1245            }
1246        } else {
1247            if (mOverscrollFade != 0) {
1248                setFadeForOverScroll(0);
1249            }
1250            if (mOverscrollTransformsSet) {
1251                mOverscrollTransformsSet = false;
1252                ((CellLayout) getChildAt(0)).resetOverscrollTransforms();
1253                ((CellLayout) getChildAt(getChildCount() - 1)).resetOverscrollTransforms();
1254            }
1255        }
1256    }
1257
1258    @Override
1259    protected void screenScrolled(int screenCenter) {
1260        if (LauncherApplication.isScreenLarge()) {
1261            // We don't call super.screenScrolled() here because we handle the adjacent pages alpha
1262            // ourselves (for efficiency), and there are no scrolling indicators to update.
1263            screenScrolledLargeUI(screenCenter);
1264        } else {
1265            super.screenScrolled(screenCenter);
1266            screenScrolledStandardUI(screenCenter);
1267        }
1268    }
1269
1270    @Override
1271    protected void overScroll(float amount) {
1272        if (LauncherApplication.isScreenLarge()) {
1273            dampedOverScroll(amount);
1274        } else {
1275            acceleratedOverScroll(amount);
1276        }
1277    }
1278
1279    protected void onAttachedToWindow() {
1280        super.onAttachedToWindow();
1281        mWindowToken = getWindowToken();
1282        computeScroll();
1283        mDragController.setWindowToken(mWindowToken);
1284    }
1285
1286    protected void onDetachedFromWindow() {
1287        mWindowToken = null;
1288    }
1289
1290    @Override
1291    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1292        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
1293            mUpdateWallpaperOffsetImmediately = true;
1294        }
1295        super.onLayout(changed, left, top, right, bottom);
1296    }
1297
1298    @Override
1299    protected void onDraw(Canvas canvas) {
1300        updateWallpaperOffsets();
1301
1302        // Draw the background gradient if necessary
1303        if (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground) {
1304            int alpha = (int) (mBackgroundAlpha * 255);
1305            mBackground.setAlpha(alpha);
1306            mBackground.setBounds(getScrollX(), 0, getScrollX() + getMeasuredWidth(),
1307                    getMeasuredHeight());
1308            mBackground.draw(canvas);
1309        }
1310
1311        super.onDraw(canvas);
1312    }
1313
1314    boolean isDrawingBackgroundGradient() {
1315        return (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground);
1316    }
1317
1318    @Override
1319    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1320        if (!mLauncher.isAllAppsVisible()) {
1321            final Folder openFolder = getOpenFolder();
1322            if (openFolder != null) {
1323                return openFolder.requestFocus(direction, previouslyFocusedRect);
1324            } else {
1325                return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
1326            }
1327        }
1328        return false;
1329    }
1330
1331    @Override
1332    public int getDescendantFocusability() {
1333        if (isSmall()) {
1334            return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
1335        }
1336        return super.getDescendantFocusability();
1337    }
1338
1339    @Override
1340    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1341        if (!mLauncher.isAllAppsVisible()) {
1342            final Folder openFolder = getOpenFolder();
1343            if (openFolder != null) {
1344                openFolder.addFocusables(views, direction);
1345            } else {
1346                super.addFocusables(views, direction, focusableMode);
1347            }
1348        }
1349    }
1350
1351    public boolean isSmall() {
1352        return mState == State.SMALL || mState == State.SPRING_LOADED;
1353    }
1354
1355    void enableChildrenCache(int fromPage, int toPage) {
1356        if (fromPage > toPage) {
1357            final int temp = fromPage;
1358            fromPage = toPage;
1359            toPage = temp;
1360        }
1361
1362        final int screenCount = getChildCount();
1363
1364        fromPage = Math.max(fromPage, 0);
1365        toPage = Math.min(toPage, screenCount - 1);
1366
1367        for (int i = fromPage; i <= toPage; i++) {
1368            final CellLayout layout = (CellLayout) getChildAt(i);
1369            layout.setChildrenDrawnWithCacheEnabled(true);
1370            layout.setChildrenDrawingCacheEnabled(true);
1371        }
1372    }
1373
1374    void clearChildrenCache() {
1375        final int screenCount = getChildCount();
1376        for (int i = 0; i < screenCount; i++) {
1377            final CellLayout layout = (CellLayout) getChildAt(i);
1378            layout.setChildrenDrawnWithCacheEnabled(false);
1379            // In software mode, we don't want the items to continue to be drawn into bitmaps
1380            if (!isHardwareAccelerated()) {
1381                layout.setChildrenDrawingCacheEnabled(false);
1382            }
1383        }
1384    }
1385
1386    private void updateChildrenLayersEnabled() {
1387        boolean small = mState == State.SMALL || mIsSwitchingState;
1388        boolean enableChildrenLayers = small || mAnimatingViewIntoPlace || isPageMoving();
1389
1390        if (enableChildrenLayers != mChildrenLayersEnabled) {
1391            mChildrenLayersEnabled = enableChildrenLayers;
1392            for (int i = 0; i < getPageCount(); i++) {
1393                ((ViewGroup)getChildAt(i)).setChildrenLayersEnabled(mChildrenLayersEnabled);
1394            }
1395        }
1396    }
1397
1398    protected void onWallpaperTap(MotionEvent ev) {
1399        final int[] position = mTempCell;
1400        getLocationOnScreen(position);
1401
1402        int pointerIndex = ev.getActionIndex();
1403        position[0] += (int) ev.getX(pointerIndex);
1404        position[1] += (int) ev.getY(pointerIndex);
1405
1406        mWallpaperManager.sendWallpaperCommand(getWindowToken(),
1407                ev.getAction() == MotionEvent.ACTION_UP
1408                        ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
1409                position[0], position[1], 0, null);
1410    }
1411
1412    /*
1413     * This interpolator emulates the rate at which the perceived scale of an object changes
1414     * as its distance from a camera increases. When this interpolator is applied to a scale
1415     * animation on a view, it evokes the sense that the object is shrinking due to moving away
1416     * from the camera.
1417     */
1418    static class ZInterpolator implements TimeInterpolator {
1419        private float focalLength;
1420
1421        public ZInterpolator(float foc) {
1422            focalLength = foc;
1423        }
1424
1425        public float getInterpolation(float input) {
1426            return (1.0f - focalLength / (focalLength + input)) /
1427                (1.0f - focalLength / (focalLength + 1.0f));
1428        }
1429    }
1430
1431    /*
1432     * The exact reverse of ZInterpolator.
1433     */
1434    static class InverseZInterpolator implements TimeInterpolator {
1435        private ZInterpolator zInterpolator;
1436        public InverseZInterpolator(float foc) {
1437            zInterpolator = new ZInterpolator(foc);
1438        }
1439        public float getInterpolation(float input) {
1440            return 1 - zInterpolator.getInterpolation(1 - input);
1441        }
1442    }
1443
1444    /*
1445     * ZInterpolator compounded with an ease-out.
1446     */
1447    static class ZoomOutInterpolator implements TimeInterpolator {
1448        private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75f);
1449        private final ZInterpolator zInterpolator = new ZInterpolator(0.13f);
1450
1451        public float getInterpolation(float input) {
1452            return decelerate.getInterpolation(zInterpolator.getInterpolation(input));
1453        }
1454    }
1455
1456    /*
1457     * InvereZInterpolator compounded with an ease-out.
1458     */
1459    static class ZoomInInterpolator implements TimeInterpolator {
1460        private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
1461        private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
1462
1463        public float getInterpolation(float input) {
1464            return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
1465        }
1466    }
1467
1468    private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
1469
1470    /*
1471    *
1472    * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we
1473    * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
1474    *
1475    * These methods mark the appropriate pages as accepting drops (which alters their visual
1476    * appearance).
1477    *
1478    */
1479    public void onDragStartedWithItem(View v) {
1480        final Canvas canvas = new Canvas();
1481
1482        // The outline is used to visualize where the item will land if dropped
1483        mDragOutline = createDragOutline(v, canvas, DRAG_BITMAP_PADDING);
1484    }
1485
1486    public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, Paint alphaClipPaint) {
1487        final Canvas canvas = new Canvas();
1488
1489        int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
1490
1491        // The outline is used to visualize where the item will land if dropped
1492        mDragOutline = createDragOutline(b, canvas, DRAG_BITMAP_PADDING, size[0],
1493                size[1], alphaClipPaint);
1494    }
1495
1496    public void exitWidgetResizeMode() {
1497        DragLayer dragLayer = mLauncher.getDragLayer();
1498        dragLayer.clearAllResizeFrames();
1499    }
1500
1501    private void initAnimationArrays() {
1502        final int childCount = getChildCount();
1503        if (mOldTranslationXs != null) return;
1504        mOldTranslationXs = new float[childCount];
1505        mOldTranslationYs = new float[childCount];
1506        mOldScaleXs = new float[childCount];
1507        mOldScaleYs = new float[childCount];
1508        mOldBackgroundAlphas = new float[childCount];
1509        mOldBackgroundAlphaMultipliers = new float[childCount];
1510        mOldAlphas = new float[childCount];
1511        mOldRotationYs = new float[childCount];
1512        mNewTranslationXs = new float[childCount];
1513        mNewTranslationYs = new float[childCount];
1514        mNewScaleXs = new float[childCount];
1515        mNewScaleYs = new float[childCount];
1516        mNewBackgroundAlphas = new float[childCount];
1517        mNewBackgroundAlphaMultipliers = new float[childCount];
1518        mNewAlphas = new float[childCount];
1519        mNewRotationYs = new float[childCount];
1520    }
1521
1522    Animator getChangeStateAnimation(final State state, boolean animated) {
1523        return getChangeStateAnimation(state, animated, 0);
1524    }
1525
1526    Animator getChangeStateAnimation(final State state, boolean animated, int delay) {
1527        if (mState == state) {
1528            return null;
1529        }
1530
1531        // Initialize animation arrays for the first time if necessary
1532        initAnimationArrays();
1533
1534        AnimatorSet anim = animated ? new AnimatorSet() : null;
1535
1536        // Stop any scrolling, move to the current page right away
1537        setCurrentPage(getNextPage());
1538
1539        final State oldState = mState;
1540        final boolean oldStateIsNormal = (oldState == State.NORMAL);
1541        final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED);
1542        final boolean oldStateIsSmall = (oldState == State.SMALL);
1543        mState = state;
1544        final boolean stateIsNormal = (state == State.NORMAL);
1545        final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED);
1546        final boolean stateIsSmall = (state == State.SMALL);
1547        float finalScaleFactor = 1.0f;
1548        float finalBackgroundAlpha = stateIsSpringLoaded ? 1.0f : 0f;
1549        float translationX = 0;
1550        float translationY = 0;
1551        boolean zoomIn = true;
1552
1553        if (state != State.NORMAL) {
1554            finalScaleFactor = mSpringLoadedShrinkFactor - (stateIsSmall ? 0.1f : 0);
1555            if (oldStateIsNormal && stateIsSmall) {
1556                zoomIn = false;
1557                setLayoutScale(finalScaleFactor);
1558                updateChildrenLayersEnabled();
1559            } else {
1560                finalBackgroundAlpha = 1.0f;
1561                setLayoutScale(finalScaleFactor);
1562            }
1563        } else {
1564            setLayoutScale(1.0f);
1565        }
1566
1567        final int duration = zoomIn ?
1568                getResources().getInteger(R.integer.config_workspaceUnshrinkTime) :
1569                getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
1570        for (int i = 0; i < getChildCount(); i++) {
1571            final CellLayout cl = (CellLayout) getChildAt(i);
1572            float rotation = 0f;
1573            float initialAlpha = cl.getAlpha();
1574            float finalAlphaMultiplierValue = 1f;
1575            float finalAlpha = (!mFadeInAdjacentScreens || stateIsSpringLoaded ||
1576                    (i == mCurrentPage)) ? 1f : 0f;
1577
1578            // Determine the pages alpha during the state transition
1579            if ((oldStateIsSmall && stateIsNormal) ||
1580                (oldStateIsNormal && stateIsSmall)) {
1581                // To/from workspace - only show the current page unless the transition is not
1582                //                     animated and the animation end callback below doesn't run;
1583                //                     or, if we're in spring-loaded mode
1584                if (i == mCurrentPage || !animated || oldStateIsSpringLoaded) {
1585                    finalAlpha = 1f;
1586                    finalAlphaMultiplierValue = 0f;
1587                } else {
1588                    initialAlpha = 0f;
1589                    finalAlpha = 0f;
1590                }
1591            }
1592
1593            // Update the rotation of the screen (don't apply rotation on Phone UI)
1594            if (LauncherApplication.isScreenLarge()) {
1595                if (i < mCurrentPage) {
1596                    rotation = WORKSPACE_ROTATION;
1597                } else if (i > mCurrentPage) {
1598                    rotation = -WORKSPACE_ROTATION;
1599                }
1600            }
1601
1602            // If the screen is not xlarge, then don't rotate the CellLayouts
1603            // NOTE: If we don't update the side pages alpha, then we should not hide the side
1604            //       pages. see unshrink().
1605            if (LauncherApplication.isScreenLarge()) {
1606                translationX = getOffsetXForRotation(rotation, cl.getWidth(), cl.getHeight());
1607            }
1608
1609            mOldAlphas[i] = initialAlpha;
1610            mNewAlphas[i] = finalAlpha;
1611            if (animated) {
1612                mOldTranslationXs[i] = cl.getTranslationX();
1613                mOldTranslationYs[i] = cl.getTranslationY();
1614                mOldScaleXs[i] = cl.getScaleX();
1615                mOldScaleYs[i] = cl.getScaleY();
1616                mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
1617                mOldBackgroundAlphaMultipliers[i] = cl.getBackgroundAlphaMultiplier();
1618                mOldRotationYs[i] = cl.getRotationY();
1619
1620                mNewTranslationXs[i] = translationX;
1621                mNewTranslationYs[i] = translationY;
1622                mNewScaleXs[i] = finalScaleFactor;
1623                mNewScaleYs[i] = finalScaleFactor;
1624                mNewBackgroundAlphas[i] = finalBackgroundAlpha;
1625                mNewBackgroundAlphaMultipliers[i] = finalAlphaMultiplierValue;
1626                mNewRotationYs[i] = rotation;
1627            } else {
1628                cl.setTranslationX(translationX);
1629                cl.setTranslationY(translationY);
1630                cl.setScaleX(finalScaleFactor);
1631                cl.setScaleY(finalScaleFactor);
1632                cl.setBackgroundAlpha(finalBackgroundAlpha);
1633                cl.setBackgroundAlphaMultiplier(finalAlphaMultiplierValue);
1634                cl.setShortcutAndWidgetAlpha(finalAlpha);
1635                cl.setRotationY(rotation);
1636            }
1637        }
1638
1639        if (animated) {
1640            for (int index = 0; index < getChildCount(); index++) {
1641                final int i = index;
1642                final CellLayout cl = (CellLayout) getChildAt(i);
1643                if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) {
1644                    cl.setTranslationX(mNewTranslationXs[i]);
1645                    cl.setTranslationY(mNewTranslationYs[i]);
1646                    cl.setScaleX(mNewScaleXs[i]);
1647                    cl.setScaleY(mNewScaleYs[i]);
1648                    cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
1649                    cl.setBackgroundAlphaMultiplier(mNewBackgroundAlphaMultipliers[i]);
1650                    cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
1651                    cl.setRotationY(mNewRotationYs[i]);
1652                } else {
1653                    LauncherViewPropertyAnimator a = new LauncherViewPropertyAnimator(cl);
1654                    a.translationX(mNewTranslationXs[i])
1655                        .translationY(mNewTranslationYs[i])
1656                        .scaleX(mNewScaleXs[i])
1657                        .scaleY(mNewScaleYs[i])
1658                        .setDuration(duration)
1659                        .setInterpolator(mZoomInInterpolator);
1660                    anim.play(a);
1661
1662                    if (mOldAlphas[i] != mNewAlphas[i]) {
1663                        LauncherViewPropertyAnimator alphaAnim =
1664                            new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
1665                        alphaAnim.alpha(mNewAlphas[i])
1666                            .setDuration(duration)
1667                            .setInterpolator(mZoomInInterpolator);
1668                        anim.play(alphaAnim);
1669                    }
1670                    if (mOldRotationYs[i] != 0 || mNewRotationYs[i] != 0) {
1671                        ValueAnimator rotate = ValueAnimator.ofFloat(0f, 1f).setDuration(duration);
1672                        rotate.setInterpolator(new DecelerateInterpolator(2.0f));
1673                        rotate.addUpdateListener(new LauncherAnimatorUpdateListener() {
1674                                public void onAnimationUpdate(float a, float b) {
1675                                    cl.setRotationY(a * mOldRotationYs[i] + b * mNewRotationYs[i]);
1676                                }
1677                            });
1678                        anim.play(rotate);
1679                    }
1680                    if (mOldBackgroundAlphas[i] != 0 ||
1681                        mNewBackgroundAlphas[i] != 0 ||
1682                        mOldBackgroundAlphaMultipliers[i] != 0 ||
1683                        mNewBackgroundAlphaMultipliers[i] != 0) {
1684                        ValueAnimator bgAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(duration);
1685                        bgAnim.setInterpolator(mZoomInInterpolator);
1686                        bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
1687                                public void onAnimationUpdate(float a, float b) {
1688                                    cl.setBackgroundAlpha(
1689                                            a * mOldBackgroundAlphas[i] +
1690                                            b * mNewBackgroundAlphas[i]);
1691                                    cl.setBackgroundAlphaMultiplier(
1692                                            a * mOldBackgroundAlphaMultipliers[i] +
1693                                            b * mNewBackgroundAlphaMultipliers[i]);
1694                                }
1695                            });
1696                        anim.play(bgAnim);
1697                    }
1698                }
1699            }
1700            buildPageHardwareLayers();
1701            anim.setStartDelay(delay);
1702        }
1703
1704        if (stateIsSpringLoaded) {
1705            // Right now we're covered by Apps Customize
1706            // Show the background gradient immediately, so the gradient will
1707            // be showing once AppsCustomize disappears
1708            animateBackgroundGradient(getResources().getInteger(
1709                    R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, false);
1710        } else {
1711            // Fade the background gradient away
1712            animateBackgroundGradient(0f, true);
1713        }
1714        return anim;
1715    }
1716
1717    @Override
1718    public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
1719        mIsSwitchingState = true;
1720        cancelScrollingIndicatorAnimations();
1721    }
1722
1723    @Override
1724    public void onLauncherTransitionStep(Launcher l, float t) {
1725        mTransitionProgress = t;
1726    }
1727
1728    @Override
1729    public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
1730        mIsSwitchingState = false;
1731        mWallpaperOffset.setOverrideHorizontalCatchupConstant(false);
1732        updateChildrenLayersEnabled();
1733        // The code in getChangeStateAnimation to determine initialAlpha and finalAlpha will ensure
1734        // ensure that only the current page is visible during (and subsequently, after) the
1735        // transition animation.  If fade adjacent pages is disabled, then re-enable the page
1736        // visibility after the transition animation.
1737        if (!mFadeInAdjacentScreens) {
1738            for (int i = 0; i < getChildCount(); i++) {
1739                final CellLayout cl = (CellLayout) getChildAt(i);
1740                cl.setShortcutAndWidgetAlpha(1f);
1741            }
1742        }
1743    }
1744
1745    @Override
1746    public View getContent() {
1747        return this;
1748    }
1749
1750    /**
1751     * Draw the View v into the given Canvas.
1752     *
1753     * @param v the view to draw
1754     * @param destCanvas the canvas to draw on
1755     * @param padding the horizontal and vertical padding to use when drawing
1756     */
1757    private void drawDragView(View v, Canvas destCanvas, int padding, boolean pruneToDrawable) {
1758        final Rect clipRect = mTempRect;
1759        v.getDrawingRect(clipRect);
1760
1761        boolean textVisible = false;
1762
1763        destCanvas.save();
1764        if (v instanceof TextView && pruneToDrawable) {
1765            Drawable d = ((TextView) v).getCompoundDrawables()[1];
1766            clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding);
1767            destCanvas.translate(padding / 2, padding / 2);
1768            d.draw(destCanvas);
1769        } else {
1770            if (v instanceof FolderIcon) {
1771                // For FolderIcons the text can bleed into the icon area, and so we need to
1772                // hide the text completely (which can't be achieved by clipping).
1773                if (((FolderIcon) v).getTextVisible()) {
1774                    ((FolderIcon) v).setTextVisible(false);
1775                    textVisible = true;
1776                }
1777            } else if (v instanceof BubbleTextView) {
1778                final BubbleTextView tv = (BubbleTextView) v;
1779                clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V +
1780                        tv.getLayout().getLineTop(0);
1781            } else if (v instanceof TextView) {
1782                final TextView tv = (TextView) v;
1783                clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() +
1784                        tv.getLayout().getLineTop(0);
1785            }
1786            destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
1787            destCanvas.clipRect(clipRect, Op.REPLACE);
1788            v.draw(destCanvas);
1789
1790            // Restore text visibility of FolderIcon if necessary
1791            if (textVisible) {
1792                ((FolderIcon) v).setTextVisible(true);
1793            }
1794        }
1795        destCanvas.restore();
1796    }
1797
1798    /**
1799     * Returns a new bitmap to show when the given View is being dragged around.
1800     * Responsibility for the bitmap is transferred to the caller.
1801     */
1802    public Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
1803        Bitmap b;
1804
1805        if (v instanceof TextView) {
1806            Drawable d = ((TextView) v).getCompoundDrawables()[1];
1807            b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding,
1808                    d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888);
1809        } else {
1810            b = Bitmap.createBitmap(
1811                    v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
1812        }
1813
1814        canvas.setBitmap(b);
1815        drawDragView(v, canvas, padding, true);
1816        canvas.setBitmap(null);
1817
1818        return b;
1819    }
1820
1821    /**
1822     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
1823     * Responsibility for the bitmap is transferred to the caller.
1824     */
1825    private Bitmap createDragOutline(View v, Canvas canvas, int padding) {
1826        final int outlineColor = getResources().getColor(android.R.color.holo_blue_light);
1827        final Bitmap b = Bitmap.createBitmap(
1828                v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
1829
1830        canvas.setBitmap(b);
1831        drawDragView(v, canvas, padding, true);
1832        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
1833        canvas.setBitmap(null);
1834        return b;
1835    }
1836
1837    /**
1838     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
1839     * Responsibility for the bitmap is transferred to the caller.
1840     */
1841    private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding, int w, int h,
1842            Paint alphaClipPaint) {
1843        final int outlineColor = getResources().getColor(android.R.color.holo_blue_light);
1844        final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
1845        canvas.setBitmap(b);
1846
1847        Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
1848        float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(),
1849                (h - padding) / (float) orig.getHeight());
1850        int scaledWidth = (int) (scaleFactor * orig.getWidth());
1851        int scaledHeight = (int) (scaleFactor * orig.getHeight());
1852        Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
1853
1854        // center the image
1855        dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
1856
1857        canvas.drawBitmap(orig, src, dst, null);
1858        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor,
1859                alphaClipPaint);
1860        canvas.setBitmap(null);
1861
1862        return b;
1863    }
1864
1865    void startDrag(CellLayout.CellInfo cellInfo) {
1866        View child = cellInfo.cell;
1867
1868        // Make sure the drag was started by a long press as opposed to a long click.
1869        if (!child.isInTouchMode()) {
1870            return;
1871        }
1872
1873        mDragInfo = cellInfo;
1874        child.setVisibility(INVISIBLE);
1875        CellLayout layout = (CellLayout) child.getParent().getParent();
1876        layout.prepareChildForDrag(child);
1877
1878        child.clearFocus();
1879        child.setPressed(false);
1880
1881        final Canvas canvas = new Canvas();
1882
1883        // The outline is used to visualize where the item will land if dropped
1884        mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING);
1885        beginDragShared(child, this);
1886    }
1887
1888    public void beginDragShared(View child, DragSource source) {
1889        Resources r = getResources();
1890
1891        // The drag bitmap follows the touch point around on the screen
1892        final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING);
1893
1894        final int bmpWidth = b.getWidth();
1895        final int bmpHeight = b.getHeight();
1896
1897        mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
1898        int dragLayerX =
1899                Math.round(mTempXY[0] - (bmpWidth - child.getScaleX() * child.getWidth()) / 2);
1900        int dragLayerY =
1901                Math.round(mTempXY[1] - (bmpHeight - child.getScaleY() * bmpHeight) / 2
1902                        - DRAG_BITMAP_PADDING / 2);
1903
1904        Point dragVisualizeOffset = null;
1905        Rect dragRect = null;
1906        if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
1907            int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size);
1908            int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top);
1909            int top = child.getPaddingTop();
1910            int left = (bmpWidth - iconSize) / 2;
1911            int right = left + iconSize;
1912            int bottom = top + iconSize;
1913            dragLayerY += top;
1914            // Note: The drag region is used to calculate drag layer offsets, but the
1915            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
1916            dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2,
1917                    iconPaddingTop - DRAG_BITMAP_PADDING / 2);
1918            dragRect = new Rect(left, top, right, bottom);
1919        } else if (child instanceof FolderIcon) {
1920            int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size);
1921            dragRect = new Rect(0, 0, child.getWidth(), previewSize);
1922        }
1923
1924        // Clear the pressed state if necessary
1925        if (child instanceof BubbleTextView) {
1926            BubbleTextView icon = (BubbleTextView) child;
1927            icon.clearPressedOrFocusedBackground();
1928        }
1929
1930        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
1931                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, child.getScaleX());
1932        b.recycle();
1933
1934        // Show the scrolling indicator when you pick up an item
1935        showScrollingIndicator(false);
1936    }
1937
1938    void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, int screen,
1939            int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
1940        View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
1941
1942        final int[] cellXY = new int[2];
1943        target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
1944        addInScreen(view, container, screen, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
1945        LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen, cellXY[0],
1946                cellXY[1]);
1947    }
1948
1949    public boolean transitionStateShouldAllowDrop() {
1950        return ((!isSwitchingState() || mTransitionProgress > 0.5f) && mState != State.SMALL);
1951    }
1952
1953    /**
1954     * {@inheritDoc}
1955     */
1956    public boolean acceptDrop(DragObject d) {
1957        // If it's an external drop (e.g. from All Apps), check if it should be accepted
1958        CellLayout dropTargetLayout = mDropToLayout;
1959        if (d.dragSource != this) {
1960            // Don't accept the drop if we're not over a screen at time of drop
1961            if (dropTargetLayout == null) {
1962                return false;
1963            }
1964            if (!transitionStateShouldAllowDrop()) return false;
1965
1966            mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
1967                    d.dragView, mDragViewVisualCenter);
1968
1969            // We want the point to be mapped to the dragTarget.
1970            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
1971                mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter);
1972            } else {
1973                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
1974            }
1975
1976            int spanX = 1;
1977            int spanY = 1;
1978            if (mDragInfo != null) {
1979                final CellLayout.CellInfo dragCellInfo = mDragInfo;
1980                spanX = dragCellInfo.spanX;
1981                spanY = dragCellInfo.spanY;
1982            } else {
1983                final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
1984                spanX = dragInfo.spanX;
1985                spanY = dragInfo.spanY;
1986            }
1987
1988            int minSpanX = spanX;
1989            int minSpanY = spanY;
1990            if (d.dragInfo instanceof PendingAddWidgetInfo) {
1991                minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
1992                minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
1993            }
1994
1995            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
1996                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
1997                    mTargetCell);
1998            float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
1999                    mDragViewVisualCenter[1], mTargetCell);
2000            if (willCreateUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
2001                    mTargetCell, distance, true)) {
2002                return true;
2003            }
2004            if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
2005                    mTargetCell, distance)) {
2006                return true;
2007            }
2008
2009            int[] resultSpan = new int[2];
2010            mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
2011                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2012                    null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
2013            boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2014
2015            // Don't accept the drop if there's no room for the item
2016            if (!foundCell) {
2017                // Don't show the message if we are dropping on the AllApps button and the hotseat
2018                // is full
2019                boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2020                if (mTargetCell != null && isHotseat) {
2021                    Hotseat hotseat = mLauncher.getHotseat();
2022                    if (hotseat.isAllAppsButtonRank(
2023                            hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
2024                        return false;
2025                    }
2026                }
2027
2028                mLauncher.showOutOfSpaceMessage(isHotseat);
2029                return false;
2030            }
2031        }
2032        return true;
2033    }
2034
2035    boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float
2036            distance, boolean considerTimeout) {
2037        if (distance > mMaxDistanceForFolderCreation) return false;
2038        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2039
2040        if (dropOverView != null) {
2041            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2042            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2043                return false;
2044            }
2045        }
2046
2047        boolean hasntMoved = false;
2048        if (mDragInfo != null) {
2049            hasntMoved = dropOverView == mDragInfo.cell;
2050        }
2051
2052        if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
2053            return false;
2054        }
2055
2056        boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
2057        boolean willBecomeShortcut =
2058                (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
2059                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
2060
2061        return (aboveShortcut && willBecomeShortcut);
2062    }
2063
2064    boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
2065            float distance) {
2066        if (distance > mMaxDistanceForFolderCreation) return false;
2067        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2068
2069        if (dropOverView != null) {
2070            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2071            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2072                return false;
2073            }
2074        }
2075
2076        if (dropOverView instanceof FolderIcon) {
2077            FolderIcon fi = (FolderIcon) dropOverView;
2078            if (fi.acceptDrop(dragInfo)) {
2079                return true;
2080            }
2081        }
2082        return false;
2083    }
2084
2085    boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
2086            int[] targetCell, float distance, boolean external, DragView dragView,
2087            Runnable postAnimationRunnable) {
2088        if (distance > mMaxDistanceForFolderCreation) return false;
2089        View v = target.getChildAt(targetCell[0], targetCell[1]);
2090
2091        boolean hasntMoved = false;
2092        if (mDragInfo != null) {
2093            CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
2094            hasntMoved = (mDragInfo.cellX == targetCell[0] &&
2095                    mDragInfo.cellY == targetCell[1]) && (cellParent == target);
2096        }
2097
2098        if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
2099        mCreateUserFolderOnDrop = false;
2100        final int screen = (targetCell == null) ? mDragInfo.screen : indexOfChild(target);
2101
2102        boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
2103        boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
2104
2105        if (aboveShortcut && willBecomeShortcut) {
2106            ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
2107            ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
2108            // if the drag started here, we need to remove it from the workspace
2109            if (!external) {
2110                getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2111            }
2112
2113            Rect folderLocation = new Rect();
2114            float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
2115            target.removeView(v);
2116
2117            FolderIcon fi =
2118                mLauncher.addFolder(target, container, screen, targetCell[0], targetCell[1]);
2119            destInfo.cellX = -1;
2120            destInfo.cellY = -1;
2121            sourceInfo.cellX = -1;
2122            sourceInfo.cellY = -1;
2123
2124            // If the dragView is null, we can't animate
2125            boolean animate = dragView != null;
2126            if (animate) {
2127                fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
2128                        postAnimationRunnable);
2129            } else {
2130                fi.addItem(destInfo);
2131                fi.addItem(sourceInfo);
2132            }
2133            return true;
2134        }
2135        return false;
2136    }
2137
2138    boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
2139            float distance, DragObject d, boolean external) {
2140        if (distance > mMaxDistanceForFolderCreation) return false;
2141
2142        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2143        if (!mAddToExistingFolderOnDrop) return false;
2144        mAddToExistingFolderOnDrop = false;
2145
2146        if (dropOverView instanceof FolderIcon) {
2147            FolderIcon fi = (FolderIcon) dropOverView;
2148            if (fi.acceptDrop(d.dragInfo)) {
2149                fi.onDrop(d);
2150
2151                // if the drag started here, we need to remove it from the workspace
2152                if (!external) {
2153                    getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2154                }
2155                return true;
2156            }
2157        }
2158        return false;
2159    }
2160
2161    public void onDrop(final DragObject d) {
2162        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
2163                mDragViewVisualCenter);
2164
2165        CellLayout dropTargetLayout = mDropToLayout;
2166
2167        // We want the point to be mapped to the dragTarget.
2168        if (dropTargetLayout != null) {
2169            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2170                mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter);
2171            } else {
2172                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2173            }
2174        }
2175
2176        int snapScreen = -1;
2177        boolean resizeOnDrop = false;
2178        if (d.dragSource != this) {
2179            final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
2180                    (int) mDragViewVisualCenter[1] };
2181            onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
2182        } else if (mDragInfo != null) {
2183            final View cell = mDragInfo.cell;
2184
2185            Runnable resizeRunnable = null;
2186            if (dropTargetLayout != null) {
2187                // Move internally
2188                boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
2189                boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2190                long container = hasMovedIntoHotseat ?
2191                        LauncherSettings.Favorites.CONTAINER_HOTSEAT :
2192                        LauncherSettings.Favorites.CONTAINER_DESKTOP;
2193                int screen = (mTargetCell[0] < 0) ?
2194                        mDragInfo.screen : indexOfChild(dropTargetLayout);
2195                int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
2196                int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
2197                // First we find the cell nearest to point at which the item is
2198                // dropped, without any consideration to whether there is an item there.
2199
2200                mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
2201                        mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
2202                float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2203                        mDragViewVisualCenter[1], mTargetCell);
2204
2205                // If the item being dropped is a shortcut and the nearest drop
2206                // cell also contains a shortcut, then create a folder with the two shortcuts.
2207                if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
2208                        dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
2209                    return;
2210                }
2211
2212                if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
2213                        distance, d, false)) {
2214                    return;
2215                }
2216
2217                // Aside from the special case where we're dropping a shortcut onto a shortcut,
2218                // we need to find the nearest cell location that is vacant
2219                ItemInfo item = (ItemInfo) d.dragInfo;
2220                int minSpanX = item.spanX;
2221                int minSpanY = item.spanY;
2222                if (item.minSpanX > 0 && item.minSpanY > 0) {
2223                    minSpanX = item.minSpanX;
2224                    minSpanY = item.minSpanY;
2225                }
2226
2227                int[] resultSpan = new int[2];
2228                mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
2229                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
2230                        mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
2231
2232                boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2233                if (foundCell && (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
2234                    resizeOnDrop = true;
2235                    item.spanX = resultSpan[0];
2236                    item.spanY = resultSpan[1];
2237                }
2238
2239                if (mCurrentPage != screen && !hasMovedIntoHotseat) {
2240                    snapScreen = screen;
2241                    snapToPage(screen);
2242                }
2243
2244                if (foundCell) {
2245                    final ItemInfo info = (ItemInfo) cell.getTag();
2246                    if (hasMovedLayouts) {
2247                        // Reparent the view
2248                        getParentCellLayoutForView(cell).removeView(cell);
2249                        addInScreen(cell, container, screen, mTargetCell[0], mTargetCell[1],
2250                                info.spanX, info.spanY);
2251                    }
2252
2253                    // update the item's position after drop
2254                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2255                    lp.cellX = lp.tmpCellX = mTargetCell[0];
2256                    lp.cellY = lp.tmpCellY = mTargetCell[1];
2257                    lp.cellHSpan = item.spanX;
2258                    lp.cellVSpan = item.spanY;
2259                    lp.isLockedToGrid = true;
2260                    cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screen,
2261                            mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
2262
2263                    if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
2264                            cell instanceof LauncherAppWidgetHostView) {
2265                        final CellLayout cellLayout = dropTargetLayout;
2266                        // We post this call so that the widget has a chance to be placed
2267                        // in its final location
2268
2269                        final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
2270                        AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
2271                        if (pinfo != null &&
2272                                pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
2273                            final Runnable addResizeFrame = new Runnable() {
2274                                public void run() {
2275                                    DragLayer dragLayer = mLauncher.getDragLayer();
2276                                    dragLayer.addResizeFrame(info, hostView, cellLayout);
2277                                }
2278                            };
2279                            resizeRunnable = (new Runnable() {
2280                                public void run() {
2281                                    if (!isPageMoving()) {
2282                                        addResizeFrame.run();
2283                                    } else {
2284                                        mDelayedResizeRunnable = addResizeFrame;
2285                                    }
2286                                }
2287                            });
2288                        }
2289                    }
2290
2291                    LauncherModel.moveItemInDatabase(mLauncher, info, container, screen, lp.cellX,
2292                            lp.cellY);
2293                } else {
2294                    // If we can't find a drop location, we return the item to its original position
2295                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2296                    mTargetCell[0] = lp.cellX;
2297                    mTargetCell[1] = lp.cellY;
2298                }
2299            }
2300
2301            final CellLayout parent = (CellLayout) cell.getParent().getParent();
2302            final Runnable finalResizeRunnable = resizeRunnable;
2303            // Prepare it to be animated into its new position
2304            // This must be called after the view has been re-parented
2305            final Runnable onCompleteRunnable = new Runnable() {
2306                @Override
2307                public void run() {
2308                    mAnimatingViewIntoPlace = false;
2309                    updateChildrenLayersEnabled();
2310                    if (finalResizeRunnable != null) {
2311                        finalResizeRunnable.run();
2312                    }
2313                }
2314            };
2315            mAnimatingViewIntoPlace = true;
2316            if (d.dragView.hasDrawn()) {
2317                final ItemInfo info = (ItemInfo) cell.getTag();
2318                if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
2319                    int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
2320                            ANIMATE_INTO_POSITION_AND_DISAPPEAR;
2321                    animateWidgetDrop(info, parent, d.dragView,
2322                            onCompleteRunnable, animationType, cell, false);
2323                } else {
2324                    int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
2325                    mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
2326                            onCompleteRunnable, this);
2327                }
2328            } else {
2329                d.deferDragViewCleanupPostAnimation = false;
2330                cell.setVisibility(VISIBLE);
2331            }
2332            parent.onDropChild(cell);
2333        }
2334    }
2335
2336    public void setFinalScrollForPageChange(int screen) {
2337        if (screen >= 0) {
2338            mSavedScrollX = getScrollX();
2339            CellLayout cl = (CellLayout) getChildAt(screen);
2340            mSavedTranslationX = cl.getTranslationX();
2341            mSavedRotationY = cl.getRotationY();
2342            final int newX = getChildOffset(screen) - getRelativeChildOffset(screen);
2343            setScrollX(newX);
2344            cl.setTranslationX(0f);
2345            cl.setRotationY(0f);
2346        }
2347    }
2348
2349    public void resetFinalScrollForPageChange(int screen) {
2350        if (screen >= 0) {
2351            CellLayout cl = (CellLayout) getChildAt(screen);
2352            setScrollX(mSavedScrollX);
2353            cl.setTranslationX(mSavedTranslationX);
2354            cl.setRotationY(mSavedRotationY);
2355        }
2356    }
2357
2358    public void getViewLocationRelativeToSelf(View v, int[] location) {
2359        getLocationInWindow(location);
2360        int x = location[0];
2361        int y = location[1];
2362
2363        v.getLocationInWindow(location);
2364        int vX = location[0];
2365        int vY = location[1];
2366
2367        location[0] = vX - x;
2368        location[1] = vY - y;
2369    }
2370
2371    public void onDragEnter(DragObject d) {
2372        mDragEnforcer.onDragEnter();
2373        mCreateUserFolderOnDrop = false;
2374        mAddToExistingFolderOnDrop = false;
2375
2376        mDropToLayout = null;
2377        CellLayout layout = getCurrentDropLayout();
2378        setCurrentDropLayout(layout);
2379        setCurrentDragOverlappingLayout(layout);
2380
2381        // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we
2382        // don't need to show the outlines
2383        if (LauncherApplication.isScreenLarge()) {
2384            showOutlines();
2385        }
2386    }
2387
2388    public void onDragExit(DragObject d) {
2389        mDragEnforcer.onDragExit();
2390
2391        // Here we store the final page that will be dropped to, if the workspace in fact
2392        // receives the drop
2393        if (mInScrollArea) {
2394            mDropToLayout = mDragOverlappingLayout;
2395        } else {
2396            mDropToLayout = mDragTargetLayout;
2397        }
2398
2399        if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
2400            mCreateUserFolderOnDrop = true;
2401        } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
2402            mAddToExistingFolderOnDrop = true;
2403        }
2404
2405        // Reset the scroll area and previous drag target
2406        onResetScrollArea();
2407        setCurrentDropLayout(null);
2408        setCurrentDragOverlappingLayout(null);
2409
2410        mSpringLoadedDragController.cancel();
2411
2412        if (!mIsPageMoving) {
2413            hideOutlines();
2414        }
2415    }
2416
2417    void setCurrentDropLayout(CellLayout layout) {
2418        if (mDragTargetLayout != null) {
2419            mDragTargetLayout.revertTempState();
2420            mDragTargetLayout.onDragExit();
2421        }
2422        mDragTargetLayout = layout;
2423        if (mDragTargetLayout != null) {
2424            mDragTargetLayout.onDragEnter();
2425        }
2426        cleanupReorder(true);
2427        cleanupFolderCreation();
2428        setCurrentDropOverCell(-1, -1);
2429    }
2430
2431    void setCurrentDragOverlappingLayout(CellLayout layout) {
2432        if (mDragOverlappingLayout != null) {
2433            mDragOverlappingLayout.setIsDragOverlapping(false);
2434        }
2435        mDragOverlappingLayout = layout;
2436        if (mDragOverlappingLayout != null) {
2437            mDragOverlappingLayout.setIsDragOverlapping(true);
2438        }
2439        invalidate();
2440    }
2441
2442    void setCurrentDropOverCell(int x, int y) {
2443        if (x != mDragOverX || y != mDragOverY) {
2444            mDragOverX = x;
2445            mDragOverY = y;
2446            setDragMode(DRAG_MODE_NONE);
2447        }
2448    }
2449
2450    void setDragMode(int dragMode) {
2451        if (dragMode != mDragMode) {
2452            if (dragMode == DRAG_MODE_NONE) {
2453                cleanupAddToFolder();
2454                // We don't want to cancel the re-order alarm every time the target cell changes
2455                // as this feels to slow / unresponsive.
2456                cleanupReorder(false);
2457                cleanupFolderCreation();
2458            } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
2459                cleanupReorder(true);
2460                cleanupFolderCreation();
2461            } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
2462                cleanupAddToFolder();
2463                cleanupReorder(true);
2464            } else if (dragMode == DRAG_MODE_REORDER) {
2465                cleanupAddToFolder();
2466                cleanupFolderCreation();
2467            }
2468            mDragMode = dragMode;
2469        }
2470    }
2471
2472    private void cleanupFolderCreation() {
2473        if (mDragFolderRingAnimator != null) {
2474            mDragFolderRingAnimator.animateToNaturalState();
2475        }
2476        mFolderCreationAlarm.cancelAlarm();
2477    }
2478
2479    private void cleanupAddToFolder() {
2480        if (mDragOverFolderIcon != null) {
2481            mDragOverFolderIcon.onDragExit(null);
2482            mDragOverFolderIcon = null;
2483        }
2484    }
2485
2486    private void cleanupReorder(boolean cancelAlarm) {
2487        // Any pending reorders are canceled
2488        if (cancelAlarm) {
2489            mReorderAlarm.cancelAlarm();
2490        }
2491        mLastReorderX = -1;
2492        mLastReorderY = -1;
2493    }
2494
2495    public DropTarget getDropTargetDelegate(DragObject d) {
2496        return null;
2497    }
2498
2499    /*
2500    *
2501    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
2502    * coordinate space. The argument xy is modified with the return result.
2503    *
2504    */
2505   void mapPointFromSelfToChild(View v, float[] xy) {
2506       mapPointFromSelfToChild(v, xy, null);
2507   }
2508
2509   /*
2510    *
2511    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
2512    * coordinate space. The argument xy is modified with the return result.
2513    *
2514    * if cachedInverseMatrix is not null, this method will just use that matrix instead of
2515    * computing it itself; we use this to avoid redundant matrix inversions in
2516    * findMatchingPageForDragOver
2517    *
2518    */
2519   void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
2520       if (cachedInverseMatrix == null) {
2521           v.getMatrix().invert(mTempInverseMatrix);
2522           cachedInverseMatrix = mTempInverseMatrix;
2523       }
2524       int scrollX = getScrollX();
2525       if (mNextPage != INVALID_PAGE) {
2526           scrollX = mScroller.getFinalX();
2527       }
2528       xy[0] = xy[0] + scrollX - v.getLeft();
2529       xy[1] = xy[1] + getScrollY() - v.getTop();
2530       cachedInverseMatrix.mapPoints(xy);
2531   }
2532
2533   /*
2534    * Maps a point from the Workspace's coordinate system to another sibling view's. (Workspace
2535    * covers the full screen)
2536    */
2537   void mapPointFromSelfToSibling(View v, float[] xy) {
2538       xy[0] = xy[0] - v.getLeft();
2539       xy[1] = xy[1] - v.getTop();
2540   }
2541
2542   void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
2543       xy[0] = xy[0] - hotseat.getLeft() - hotseat.getLayout().getLeft();
2544       xy[1] = xy[1] - hotseat.getTop() - hotseat.getLayout().getTop();
2545   }
2546
2547   /*
2548    *
2549    * Convert the 2D coordinate xy from this CellLayout's coordinate space to
2550    * the parent View's coordinate space. The argument xy is modified with the return result.
2551    *
2552    */
2553   void mapPointFromChildToSelf(View v, float[] xy) {
2554       v.getMatrix().mapPoints(xy);
2555       int scrollX = getScrollX();
2556       if (mNextPage != INVALID_PAGE) {
2557           scrollX = mScroller.getFinalX();
2558       }
2559       xy[0] -= (scrollX - v.getLeft());
2560       xy[1] -= (getScrollY() - v.getTop());
2561   }
2562
2563   static private float squaredDistance(float[] point1, float[] point2) {
2564        float distanceX = point1[0] - point2[0];
2565        float distanceY = point2[1] - point2[1];
2566        return distanceX * distanceX + distanceY * distanceY;
2567   }
2568
2569    /*
2570     *
2571     * Returns true if the passed CellLayout cl overlaps with dragView
2572     *
2573     */
2574    boolean overlaps(CellLayout cl, DragView dragView,
2575            int dragViewX, int dragViewY, Matrix cachedInverseMatrix) {
2576        // Transform the coordinates of the item being dragged to the CellLayout's coordinates
2577        final float[] draggedItemTopLeft = mTempDragCoordinates;
2578        draggedItemTopLeft[0] = dragViewX;
2579        draggedItemTopLeft[1] = dragViewY;
2580        final float[] draggedItemBottomRight = mTempDragBottomRightCoordinates;
2581        draggedItemBottomRight[0] = draggedItemTopLeft[0] + dragView.getDragRegionWidth();
2582        draggedItemBottomRight[1] = draggedItemTopLeft[1] + dragView.getDragRegionHeight();
2583
2584        // Transform the dragged item's top left coordinates
2585        // to the CellLayout's local coordinates
2586        mapPointFromSelfToChild(cl, draggedItemTopLeft, cachedInverseMatrix);
2587        float overlapRegionLeft = Math.max(0f, draggedItemTopLeft[0]);
2588        float overlapRegionTop = Math.max(0f, draggedItemTopLeft[1]);
2589
2590        if (overlapRegionLeft <= cl.getWidth() && overlapRegionTop >= 0) {
2591            // Transform the dragged item's bottom right coordinates
2592            // to the CellLayout's local coordinates
2593            mapPointFromSelfToChild(cl, draggedItemBottomRight, cachedInverseMatrix);
2594            float overlapRegionRight = Math.min(cl.getWidth(), draggedItemBottomRight[0]);
2595            float overlapRegionBottom = Math.min(cl.getHeight(), draggedItemBottomRight[1]);
2596
2597            if (overlapRegionRight >= 0 && overlapRegionBottom <= cl.getHeight()) {
2598                float overlap = (overlapRegionRight - overlapRegionLeft) *
2599                         (overlapRegionBottom - overlapRegionTop);
2600                if (overlap > 0) {
2601                    return true;
2602                }
2603             }
2604        }
2605        return false;
2606    }
2607
2608    /*
2609     *
2610     * This method returns the CellLayout that is currently being dragged to. In order to drag
2611     * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
2612     * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
2613     *
2614     * Return null if no CellLayout is currently being dragged over
2615     *
2616     */
2617    private CellLayout findMatchingPageForDragOver(
2618            DragView dragView, float originX, float originY, boolean exact) {
2619        // We loop through all the screens (ie CellLayouts) and see which ones overlap
2620        // with the item being dragged and then choose the one that's closest to the touch point
2621        final int screenCount = getChildCount();
2622        CellLayout bestMatchingScreen = null;
2623        float smallestDistSoFar = Float.MAX_VALUE;
2624
2625        for (int i = 0; i < screenCount; i++) {
2626            CellLayout cl = (CellLayout) getChildAt(i);
2627
2628            final float[] touchXy = {originX, originY};
2629            // Transform the touch coordinates to the CellLayout's local coordinates
2630            // If the touch point is within the bounds of the cell layout, we can return immediately
2631            cl.getMatrix().invert(mTempInverseMatrix);
2632            mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
2633
2634            if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
2635                    touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
2636                return cl;
2637            }
2638
2639            if (!exact) {
2640                // Get the center of the cell layout in screen coordinates
2641                final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
2642                cellLayoutCenter[0] = cl.getWidth()/2;
2643                cellLayoutCenter[1] = cl.getHeight()/2;
2644                mapPointFromChildToSelf(cl, cellLayoutCenter);
2645
2646                touchXy[0] = originX;
2647                touchXy[1] = originY;
2648
2649                // Calculate the distance between the center of the CellLayout
2650                // and the touch point
2651                float dist = squaredDistance(touchXy, cellLayoutCenter);
2652
2653                if (dist < smallestDistSoFar) {
2654                    smallestDistSoFar = dist;
2655                    bestMatchingScreen = cl;
2656                }
2657            }
2658        }
2659        return bestMatchingScreen;
2660    }
2661
2662    // This is used to compute the visual center of the dragView. This point is then
2663    // used to visualize drop locations and determine where to drop an item. The idea is that
2664    // the visual center represents the user's interpretation of where the item is, and hence
2665    // is the appropriate point to use when determining drop location.
2666    private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
2667            DragView dragView, float[] recycle) {
2668        float res[];
2669        if (recycle == null) {
2670            res = new float[2];
2671        } else {
2672            res = recycle;
2673        }
2674
2675        // First off, the drag view has been shifted in a way that is not represented in the
2676        // x and y values or the x/yOffsets. Here we account for that shift.
2677        x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX);
2678        y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
2679
2680        // These represent the visual top and left of drag view if a dragRect was provided.
2681        // If a dragRect was not provided, then they correspond to the actual view left and
2682        // top, as the dragRect is in that case taken to be the entire dragView.
2683        // R.dimen.dragViewOffsetY.
2684        int left = x - xOffset;
2685        int top = y - yOffset;
2686
2687        // In order to find the visual center, we shift by half the dragRect
2688        res[0] = left + dragView.getDragRegion().width() / 2;
2689        res[1] = top + dragView.getDragRegion().height() / 2;
2690
2691        return res;
2692    }
2693
2694    private boolean isDragWidget(DragObject d) {
2695        return (d.dragInfo instanceof LauncherAppWidgetInfo ||
2696                d.dragInfo instanceof PendingAddWidgetInfo);
2697    }
2698    private boolean isExternalDragWidget(DragObject d) {
2699        return d.dragSource != this && isDragWidget(d);
2700    }
2701
2702    public void onDragOver(DragObject d) {
2703        // Skip drag over events while we are dragging over side pages
2704        if (mInScrollArea || mIsSwitchingState || mState == State.SMALL) return;
2705
2706        Rect r = new Rect();
2707        CellLayout layout = null;
2708        ItemInfo item = (ItemInfo) d.dragInfo;
2709
2710        // Ensure that we have proper spans for the item that we are dropping
2711        if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
2712        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
2713            d.dragView, mDragViewVisualCenter);
2714
2715        final View child = (mDragInfo == null) ? null : mDragInfo.cell;
2716        // Identify whether we have dragged over a side page
2717        if (isSmall()) {
2718            if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
2719                mLauncher.getHotseat().getHitRect(r);
2720                if (r.contains(d.x, d.y)) {
2721                    layout = mLauncher.getHotseat().getLayout();
2722                }
2723            }
2724            if (layout == null) {
2725                layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false);
2726            }
2727            if (layout != mDragTargetLayout) {
2728
2729                setCurrentDropLayout(layout);
2730                setCurrentDragOverlappingLayout(layout);
2731
2732                boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
2733                if (isInSpringLoadedMode) {
2734                    if (mLauncher.isHotseatLayout(layout)) {
2735                        mSpringLoadedDragController.cancel();
2736                    } else {
2737                        mSpringLoadedDragController.setAlarm(mDragTargetLayout);
2738                    }
2739                }
2740            }
2741        } else {
2742            // Test to see if we are over the hotseat otherwise just use the current page
2743            if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
2744                mLauncher.getHotseat().getHitRect(r);
2745                if (r.contains(d.x, d.y)) {
2746                    layout = mLauncher.getHotseat().getLayout();
2747                }
2748            }
2749            if (layout == null) {
2750                layout = getCurrentDropLayout();
2751            }
2752            if (layout != mDragTargetLayout) {
2753                setCurrentDropLayout(layout);
2754                setCurrentDragOverlappingLayout(layout);
2755            }
2756        }
2757
2758        // Handle the drag over
2759        if (mDragTargetLayout != null) {
2760            // We want the point to be mapped to the dragTarget.
2761            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
2762                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2763            } else {
2764                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
2765            }
2766
2767            ItemInfo info = (ItemInfo) d.dragInfo;
2768
2769            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2770                    (int) mDragViewVisualCenter[1], 1, 1, mDragTargetLayout, mTargetCell);
2771
2772            setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
2773
2774            float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
2775                    mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
2776
2777            final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0],
2778                    mTargetCell[1]);
2779
2780            manageFolderFeedback(info, mDragTargetLayout, mTargetCell,
2781                    targetCellDistance, dragOverView);
2782
2783            int minSpanX = item.spanX;
2784            int minSpanY = item.spanY;
2785            if (item.minSpanX > 0 && item.minSpanY > 0) {
2786                minSpanX = item.minSpanX;
2787                minSpanY = item.minSpanY;
2788            }
2789
2790            int[] reorderPosition = new int[2];
2791            reorderPosition = findNearestArea((int) mDragViewVisualCenter[0],
2792                    (int) mDragViewVisualCenter[1], item.spanX, item.spanY, mDragTargetLayout,
2793                    reorderPosition);
2794
2795            boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
2796                    mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
2797                    item.spanY, child, mTargetCell);
2798
2799            if (!nearestDropOccupied) {
2800                mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
2801                        (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
2802                        mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
2803                        d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
2804            } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
2805                    && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderPosition[0] ||
2806                    mLastReorderY != reorderPosition[1])) {
2807                // Otherwise, if we aren't adding to or creating a folder and there's no pending
2808                // reorder, then we schedule a reorder
2809                ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
2810                        minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child);
2811                mReorderAlarm.setOnAlarmListener(listener);
2812                mReorderAlarm.setAlarm(REORDER_TIMEOUT);
2813            }
2814
2815            if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
2816                    !nearestDropOccupied) {
2817                if (mDragTargetLayout != null) {
2818                    mDragTargetLayout.revertTempState();
2819                }
2820            }
2821        }
2822    }
2823
2824    private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
2825            int[] targetCell, float distance, View dragOverView) {
2826        boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
2827                false);
2828
2829        if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
2830                !mFolderCreationAlarm.alarmPending()) {
2831            mFolderCreationAlarm.setOnAlarmListener(new
2832                    FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
2833            mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
2834            return;
2835        }
2836
2837        boolean willAddToFolder =
2838                willAddToExistingUserFolder(info, targetLayout, targetCell, distance);
2839
2840        if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
2841            mDragOverFolderIcon = ((FolderIcon) dragOverView);
2842            mAddToExistingFolderOnDrop = true;
2843            mDragOverFolderIcon.onDragEnter(info);
2844            if (targetLayout != null) {
2845                targetLayout.clearDragOutlines();
2846            }
2847            setDragMode(DRAG_MODE_ADD_TO_FOLDER);
2848            return;
2849        }
2850
2851        if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
2852            setDragMode(DRAG_MODE_NONE);
2853        }
2854        if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
2855            setDragMode(DRAG_MODE_NONE);
2856        }
2857
2858        return;
2859    }
2860
2861    class FolderCreationAlarmListener implements OnAlarmListener {
2862        CellLayout layout;
2863        int cellX;
2864        int cellY;
2865
2866        public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
2867            this.layout = layout;
2868            this.cellX = cellX;
2869            this.cellY = cellY;
2870        }
2871
2872        public void onAlarm(Alarm alarm) {
2873            if (mDragFolderRingAnimator == null) {
2874                mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
2875            }
2876            mDragFolderRingAnimator.setCell(cellX, cellY);
2877            mDragFolderRingAnimator.setCellLayout(layout);
2878            mDragFolderRingAnimator.animateToAcceptState();
2879            layout.showFolderAccept(mDragFolderRingAnimator);
2880            layout.clearDragOutlines();
2881            setDragMode(DRAG_MODE_CREATE_FOLDER);
2882        }
2883    }
2884
2885    class ReorderAlarmListener implements OnAlarmListener {
2886        float[] dragViewCenter;
2887        int minSpanX, minSpanY, spanX, spanY;
2888        DragView dragView;
2889        View child;
2890
2891        public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
2892                int spanY, DragView dragView, View child) {
2893            this.dragViewCenter = dragViewCenter;
2894            this.minSpanX = minSpanX;
2895            this.minSpanY = minSpanY;
2896            this.spanX = spanX;
2897            this.spanY = spanY;
2898            this.child = child;
2899            this.dragView = dragView;
2900        }
2901
2902        public void onAlarm(Alarm alarm) {
2903            int[] resultSpan = new int[2];
2904            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2905                    (int) mDragViewVisualCenter[1], spanX, spanY, mDragTargetLayout, mTargetCell);
2906            mLastReorderX = mTargetCell[0];
2907            mLastReorderY = mTargetCell[1];
2908
2909            mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
2910                (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2911                child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
2912
2913            if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
2914                mDragTargetLayout.revertTempState();
2915            } else {
2916                setDragMode(DRAG_MODE_REORDER);
2917            }
2918
2919            boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
2920            mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
2921                (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
2922                mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize,
2923                dragView.getDragVisualizeOffset(), dragView.getDragRegion());
2924        }
2925    }
2926
2927    @Override
2928    public void getHitRect(Rect outRect) {
2929        // We want the workspace to have the whole area of the display (it will find the correct
2930        // cell layout to drop to in the existing drag/drop logic.
2931        outRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
2932    }
2933
2934    /**
2935     * Add the item specified by dragInfo to the given layout.
2936     * @return true if successful
2937     */
2938    public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
2939        if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
2940            onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
2941            return true;
2942        }
2943        mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
2944        return false;
2945    }
2946
2947    private void onDropExternal(int[] touchXY, Object dragInfo,
2948            CellLayout cellLayout, boolean insertAtFirst) {
2949        onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
2950    }
2951
2952    /**
2953     * Drop an item that didn't originate on one of the workspace screens.
2954     * It may have come from Launcher (e.g. from all apps or customize), or it may have
2955     * come from another app altogether.
2956     *
2957     * NOTE: This can also be called when we are outside of a drag event, when we want
2958     * to add an item to one of the workspace screens.
2959     */
2960    private void onDropExternal(final int[] touchXY, final Object dragInfo,
2961            final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
2962        final Runnable exitSpringLoadedRunnable = new Runnable() {
2963            @Override
2964            public void run() {
2965                mLauncher.exitSpringLoadedDragModeDelayed(true, false, null);
2966            }
2967        };
2968
2969        ItemInfo info = (ItemInfo) dragInfo;
2970        int spanX = info.spanX;
2971        int spanY = info.spanY;
2972        if (mDragInfo != null) {
2973            spanX = mDragInfo.spanX;
2974            spanY = mDragInfo.spanY;
2975        }
2976
2977        final long container = mLauncher.isHotseatLayout(cellLayout) ?
2978                LauncherSettings.Favorites.CONTAINER_HOTSEAT :
2979                    LauncherSettings.Favorites.CONTAINER_DESKTOP;
2980        final int screen = indexOfChild(cellLayout);
2981        if (!mLauncher.isHotseatLayout(cellLayout) && screen != mCurrentPage
2982                && mState != State.SPRING_LOADED) {
2983            snapToPage(screen);
2984        }
2985
2986        if (info instanceof PendingAddItemInfo) {
2987            final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
2988
2989            boolean findNearestVacantCell = true;
2990            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
2991                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
2992                        cellLayout, mTargetCell);
2993                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2994                        mDragViewVisualCenter[1], mTargetCell);
2995                if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell,
2996                        distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
2997                                cellLayout, mTargetCell, distance)) {
2998                    findNearestVacantCell = false;
2999                }
3000            }
3001
3002            final ItemInfo item = (ItemInfo) d.dragInfo;
3003            if (findNearestVacantCell) {
3004                int minSpanX = item.spanX;
3005                int minSpanY = item.spanY;
3006                if (item.minSpanX > 0 && item.minSpanY > 0) {
3007                    minSpanX = item.minSpanX;
3008                    minSpanY = item.minSpanY;
3009                }
3010                int[] resultSpan = new int[2];
3011                mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
3012                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
3013                        null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
3014                item.spanX = resultSpan[0];
3015                item.spanY = resultSpan[1];
3016            }
3017
3018            Runnable onAnimationCompleteRunnable = new Runnable() {
3019                @Override
3020                public void run() {
3021                    // When dragging and dropping from customization tray, we deal with creating
3022                    // widgets/shortcuts/folders in a slightly different way
3023                    switch (pendingInfo.itemType) {
3024                    case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
3025                        int span[] = new int[2];
3026                        span[0] = item.spanX;
3027                        span[1] = item.spanY;
3028                        mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
3029                                container, screen, mTargetCell, span, null);
3030                        break;
3031                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3032                        mLauncher.processShortcutFromDrop(pendingInfo.componentName,
3033                                container, screen, mTargetCell, null);
3034                        break;
3035                    default:
3036                        throw new IllegalStateException("Unknown item type: " +
3037                                pendingInfo.itemType);
3038                    }
3039                }
3040            };
3041            View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
3042                    ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
3043            int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3044            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
3045                    ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
3046                animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
3047            }
3048            animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
3049                    animationStyle, finalView, true);
3050        } else {
3051            // This is for other drag/drop cases, like dragging from All Apps
3052            View view = null;
3053
3054            switch (info.itemType) {
3055            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3056            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3057                if (info.container == NO_ID && info instanceof ApplicationInfo) {
3058                    // Came from all apps -- make a copy
3059                    info = new ShortcutInfo((ApplicationInfo) info);
3060                }
3061                view = mLauncher.createShortcut(R.layout.application, cellLayout,
3062                        (ShortcutInfo) info);
3063                break;
3064            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3065                view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
3066                        (FolderInfo) info, mIconCache);
3067                break;
3068            default:
3069                throw new IllegalStateException("Unknown item type: " + info.itemType);
3070            }
3071
3072            // First we find the cell nearest to point at which the item is
3073            // dropped, without any consideration to whether there is an item there.
3074            if (touchXY != null) {
3075                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3076                        cellLayout, mTargetCell);
3077                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3078                        mDragViewVisualCenter[1], mTargetCell);
3079                d.postAnimationRunnable = exitSpringLoadedRunnable;
3080                if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
3081                        true, d.dragView, d.postAnimationRunnable)) {
3082                    return;
3083                }
3084                if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
3085                        true)) {
3086                    return;
3087                }
3088            }
3089
3090            if (touchXY != null) {
3091                // when dragging and dropping, just find the closest free spot
3092                mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
3093                        (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
3094                        null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
3095            } else {
3096                cellLayout.findCellForSpan(mTargetCell, 1, 1);
3097            }
3098            addInScreen(view, container, screen, mTargetCell[0], mTargetCell[1], info.spanX,
3099                    info.spanY, insertAtFirst);
3100            cellLayout.onDropChild(view);
3101            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
3102            cellLayout.getShortcutsAndWidgets().measureChild(view);
3103
3104
3105            LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen,
3106                    lp.cellX, lp.cellY);
3107
3108            if (d.dragView != null) {
3109                // We wrap the animation call in the temporary set and reset of the current
3110                // cellLayout to its final transform -- this means we animate the drag view to
3111                // the correct final location.
3112                setFinalTransitionTransform(cellLayout);
3113                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
3114                        exitSpringLoadedRunnable);
3115                resetTransitionTransform(cellLayout);
3116            }
3117        }
3118    }
3119
3120    public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
3121        int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
3122                widgetInfo.spanY, widgetInfo, false);
3123        int visibility = layout.getVisibility();
3124        layout.setVisibility(VISIBLE);
3125
3126        int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
3127        int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
3128        Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
3129                Bitmap.Config.ARGB_8888);
3130        Canvas c = new Canvas(b);
3131
3132        layout.measure(width, height);
3133        layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
3134        layout.draw(c);
3135        c.setBitmap(null);
3136        layout.setVisibility(visibility);
3137        return b;
3138    }
3139
3140    private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
3141            DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, View finalView,
3142            boolean external) {
3143        // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
3144        // location and size on the home screen.
3145        int spanX = info.spanX;
3146        int spanY = info.spanY;
3147
3148        Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
3149        loc[0] = r.left;
3150        loc[1] = r.top;
3151
3152        setFinalTransitionTransform(layout);
3153        float cellLayoutScale =
3154                mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc);
3155        resetTransitionTransform(layout);
3156        float dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
3157        float dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
3158
3159        // The animation will scale the dragView about its center, so we need to center about
3160        // the final location.
3161        loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
3162        loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
3163
3164        scaleXY[0] = dragViewScaleX * cellLayoutScale;
3165        scaleXY[1] = dragViewScaleY * cellLayoutScale;
3166    }
3167
3168    public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
3169            final Runnable onCompleteRunnable, int animationType, final View finalView,
3170            boolean external) {
3171        Rect from = new Rect();
3172        mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
3173
3174        int[] finalPos = new int[2];
3175        float scaleXY[] = new float[2];
3176        getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
3177                finalView, external);
3178
3179        Resources res = mLauncher.getResources();
3180        int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
3181
3182        // In the case where we've prebound the widget, we remove it from the DragLayer
3183        if (finalView instanceof AppWidgetHostView && external) {
3184            mLauncher.getDragLayer().removeView(finalView);
3185        }
3186        if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
3187            Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
3188            dragView.setCrossFadeBitmap(crossFadeBitmap);
3189            dragView.crossFade((int) (duration * 0.8f));
3190        } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) {
3191            scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0],  scaleXY[1]);
3192        }
3193
3194        DragLayer dragLayer = mLauncher.getDragLayer();
3195        if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
3196            mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
3197                    DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
3198        } else {
3199            int endStyle;
3200            if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
3201                endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
3202            } else {
3203                endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
3204            }
3205
3206            Runnable onComplete = new Runnable() {
3207                @Override
3208                public void run() {
3209                    if (finalView != null) {
3210                        finalView.setVisibility(VISIBLE);
3211                    }
3212                    if (onCompleteRunnable != null) {
3213                        onCompleteRunnable.run();
3214                    }
3215                }
3216            };
3217            dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
3218                    finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
3219                    duration, this);
3220        }
3221    }
3222
3223    public void setFinalTransitionTransform(CellLayout layout) {
3224        if (isSwitchingState()) {
3225            int index = indexOfChild(layout);
3226            mCurrentScaleX = layout.getScaleX();
3227            mCurrentScaleY = layout.getScaleY();
3228            mCurrentTranslationX = layout.getTranslationX();
3229            mCurrentTranslationY = layout.getTranslationY();
3230            mCurrentRotationY = layout.getRotationY();
3231            layout.setScaleX(mNewScaleXs[index]);
3232            layout.setScaleY(mNewScaleYs[index]);
3233            layout.setTranslationX(mNewTranslationXs[index]);
3234            layout.setTranslationY(mNewTranslationYs[index]);
3235            layout.setRotationY(mNewRotationYs[index]);
3236        }
3237    }
3238    public void resetTransitionTransform(CellLayout layout) {
3239        if (isSwitchingState()) {
3240            mCurrentScaleX = layout.getScaleX();
3241            mCurrentScaleY = layout.getScaleY();
3242            mCurrentTranslationX = layout.getTranslationX();
3243            mCurrentTranslationY = layout.getTranslationY();
3244            mCurrentRotationY = layout.getRotationY();
3245            layout.setScaleX(mCurrentScaleX);
3246            layout.setScaleY(mCurrentScaleY);
3247            layout.setTranslationX(mCurrentTranslationX);
3248            layout.setTranslationY(mCurrentTranslationY);
3249            layout.setRotationY(mCurrentRotationY);
3250        }
3251    }
3252
3253    /**
3254     * Return the current {@link CellLayout}, correctly picking the destination
3255     * screen while a scroll is in progress.
3256     */
3257    public CellLayout getCurrentDropLayout() {
3258        return (CellLayout) getChildAt(getNextPage());
3259    }
3260
3261    /**
3262     * Return the current CellInfo describing our current drag; this method exists
3263     * so that Launcher can sync this object with the correct info when the activity is created/
3264     * destroyed
3265     *
3266     */
3267    public CellLayout.CellInfo getDragInfo() {
3268        return mDragInfo;
3269    }
3270
3271    /**
3272     * Calculate the nearest cell where the given object would be dropped.
3273     *
3274     * pixelX and pixelY should be in the coordinate system of layout
3275     */
3276    private int[] findNearestArea(int pixelX, int pixelY,
3277            int spanX, int spanY, CellLayout layout, int[] recycle) {
3278        return layout.findNearestArea(
3279                pixelX, pixelY, spanX, spanY, recycle);
3280    }
3281
3282    void setup(DragController dragController) {
3283        mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
3284        mDragController = dragController;
3285
3286        // hardware layers on children are enabled on startup, but should be disabled until
3287        // needed
3288        updateChildrenLayersEnabled();
3289        setWallpaperDimension();
3290    }
3291
3292    /**
3293     * Called at the end of a drag which originated on the workspace.
3294     */
3295    public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
3296            boolean success) {
3297        if (success) {
3298            if (target != this) {
3299                if (mDragInfo != null) {
3300                    getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
3301                    if (mDragInfo.cell instanceof DropTarget) {
3302                        mDragController.removeDropTarget((DropTarget) mDragInfo.cell);
3303                    }
3304                }
3305            }
3306        } else if (mDragInfo != null) {
3307            CellLayout cellLayout;
3308            if (mLauncher.isHotseatLayout(target)) {
3309                cellLayout = mLauncher.getHotseat().getLayout();
3310            } else {
3311                cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
3312            }
3313            cellLayout.onDropChild(mDragInfo.cell);
3314        }
3315        if (d.cancelled &&  mDragInfo.cell != null) {
3316                mDragInfo.cell.setVisibility(VISIBLE);
3317        }
3318        mDragOutline = null;
3319        mDragInfo = null;
3320
3321        // Hide the scrolling indicator after you pick up an item
3322        hideScrollingIndicator(false);
3323    }
3324
3325    void updateItemLocationsInDatabase(CellLayout cl) {
3326        int count = cl.getShortcutsAndWidgets().getChildCount();
3327
3328        int screen = indexOfChild(cl);
3329        int container = Favorites.CONTAINER_DESKTOP;
3330
3331        if (mLauncher.isHotseatLayout(cl)) {
3332            screen = -1;
3333            container = Favorites.CONTAINER_HOTSEAT;
3334        }
3335
3336        for (int i = 0; i < count; i++) {
3337            View v = cl.getShortcutsAndWidgets().getChildAt(i);
3338            ItemInfo info = (ItemInfo) v.getTag();
3339            // Null check required as the AllApps button doesn't have an item info
3340            if (info != null) {
3341                LauncherModel.modifyItemInDatabase(mLauncher, info, container, screen, info.cellX,
3342                        info.cellY, info.spanX, info.spanY);
3343            }
3344        }
3345    }
3346
3347    @Override
3348    public boolean supportsFlingToDelete() {
3349        return true;
3350    }
3351
3352    @Override
3353    public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
3354        // Do nothing
3355    }
3356
3357    @Override
3358    public void onFlingToDeleteCompleted() {
3359        // Do nothing
3360    }
3361
3362    public boolean isDropEnabled() {
3363        return true;
3364    }
3365
3366    @Override
3367    protected void onRestoreInstanceState(Parcelable state) {
3368        super.onRestoreInstanceState(state);
3369        Launcher.setScreen(mCurrentPage);
3370    }
3371
3372    @Override
3373    public void scrollLeft() {
3374        if (!isSmall() && !mIsSwitchingState) {
3375            super.scrollLeft();
3376        }
3377        Folder openFolder = getOpenFolder();
3378        if (openFolder != null) {
3379            openFolder.completeDragExit();
3380        }
3381    }
3382
3383    @Override
3384    public void scrollRight() {
3385        if (!isSmall() && !mIsSwitchingState) {
3386            super.scrollRight();
3387        }
3388        Folder openFolder = getOpenFolder();
3389        if (openFolder != null) {
3390            openFolder.completeDragExit();
3391        }
3392    }
3393
3394    @Override
3395    public boolean onEnterScrollArea(int x, int y, int direction) {
3396        // Ignore the scroll area if we are dragging over the hot seat
3397        boolean isPortrait = !LauncherApplication.isScreenLandscape(getContext());
3398        if (mLauncher.getHotseat() != null && isPortrait) {
3399            Rect r = new Rect();
3400            mLauncher.getHotseat().getHitRect(r);
3401            if (r.contains(x, y)) {
3402                return false;
3403            }
3404        }
3405
3406        boolean result = false;
3407        if (!isSmall() && !mIsSwitchingState) {
3408            mInScrollArea = true;
3409
3410            final int page = getNextPage() +
3411                       (direction == DragController.SCROLL_LEFT ? -1 : 1);
3412
3413            // We always want to exit the current layout to ensure parity of enter / exit
3414            setCurrentDropLayout(null);
3415
3416            if (0 <= page && page < getChildCount()) {
3417                CellLayout layout = (CellLayout) getChildAt(page);
3418                setCurrentDragOverlappingLayout(layout);
3419
3420                // Workspace is responsible for drawing the edge glow on adjacent pages,
3421                // so we need to redraw the workspace when this may have changed.
3422                invalidate();
3423                result = true;
3424            }
3425        }
3426        return result;
3427    }
3428
3429    @Override
3430    public boolean onExitScrollArea() {
3431        boolean result = false;
3432        if (mInScrollArea) {
3433            invalidate();
3434            CellLayout layout = getCurrentDropLayout();
3435            setCurrentDropLayout(layout);
3436            setCurrentDragOverlappingLayout(layout);
3437
3438            result = true;
3439            mInScrollArea = false;
3440        }
3441        return result;
3442    }
3443
3444    private void onResetScrollArea() {
3445        setCurrentDragOverlappingLayout(null);
3446        mInScrollArea = false;
3447    }
3448
3449    /**
3450     * Returns a specific CellLayout
3451     */
3452    CellLayout getParentCellLayoutForView(View v) {
3453        ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
3454        for (CellLayout layout : layouts) {
3455            if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
3456                return layout;
3457            }
3458        }
3459        return null;
3460    }
3461
3462    /**
3463     * Returns a list of all the CellLayouts in the workspace.
3464     */
3465    ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
3466        ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
3467        int screenCount = getChildCount();
3468        for (int screen = 0; screen < screenCount; screen++) {
3469            layouts.add(((CellLayout) getChildAt(screen)));
3470        }
3471        if (mLauncher.getHotseat() != null) {
3472            layouts.add(mLauncher.getHotseat().getLayout());
3473        }
3474        return layouts;
3475    }
3476
3477    /**
3478     * We should only use this to search for specific children.  Do not use this method to modify
3479     * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
3480     * the hotseat and workspace pages
3481     */
3482    ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
3483        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3484                new ArrayList<ShortcutAndWidgetContainer>();
3485        int screenCount = getChildCount();
3486        for (int screen = 0; screen < screenCount; screen++) {
3487            childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
3488        }
3489        if (mLauncher.getHotseat() != null) {
3490            childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
3491        }
3492        return childrenLayouts;
3493    }
3494
3495    public Folder getFolderForTag(Object tag) {
3496        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3497                getAllShortcutAndWidgetContainers();
3498        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
3499            int count = layout.getChildCount();
3500            for (int i = 0; i < count; i++) {
3501                View child = layout.getChildAt(i);
3502                if (child instanceof Folder) {
3503                    Folder f = (Folder) child;
3504                    if (f.getInfo() == tag && f.getInfo().opened) {
3505                        return f;
3506                    }
3507                }
3508            }
3509        }
3510        return null;
3511    }
3512
3513    public View getViewForTag(Object tag) {
3514        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3515                getAllShortcutAndWidgetContainers();
3516        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
3517            int count = layout.getChildCount();
3518            for (int i = 0; i < count; i++) {
3519                View child = layout.getChildAt(i);
3520                if (child.getTag() == tag) {
3521                    return child;
3522                }
3523            }
3524        }
3525        return null;
3526    }
3527
3528    void clearDropTargets() {
3529        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3530                getAllShortcutAndWidgetContainers();
3531        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
3532            int childCount = layout.getChildCount();
3533            for (int j = 0; j < childCount; j++) {
3534                View v = layout.getChildAt(j);
3535                if (v instanceof DropTarget) {
3536                    mDragController.removeDropTarget((DropTarget) v);
3537                }
3538            }
3539        }
3540    }
3541
3542    void removeItems(final ArrayList<ApplicationInfo> apps) {
3543        final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext());
3544
3545        final HashSet<String> packageNames = new HashSet<String>();
3546        final int appCount = apps.size();
3547        for (int i = 0; i < appCount; i++) {
3548            packageNames.add(apps.get(i).componentName.getPackageName());
3549        }
3550
3551        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
3552        for (final CellLayout layoutParent: cellLayouts) {
3553            final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
3554
3555            // Avoid ANRs by treating each screen separately
3556            post(new Runnable() {
3557                public void run() {
3558                    final ArrayList<View> childrenToRemove = new ArrayList<View>();
3559                    childrenToRemove.clear();
3560
3561                    int childCount = layout.getChildCount();
3562                    for (int j = 0; j < childCount; j++) {
3563                        final View view = layout.getChildAt(j);
3564                        Object tag = view.getTag();
3565
3566                        if (tag instanceof ShortcutInfo) {
3567                            final ShortcutInfo info = (ShortcutInfo) tag;
3568                            final Intent intent = info.intent;
3569                            final ComponentName name = intent.getComponent();
3570
3571                            if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
3572                                for (String packageName: packageNames) {
3573                                    if (packageName.equals(name.getPackageName())) {
3574                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
3575                                        childrenToRemove.add(view);
3576                                    }
3577                                }
3578                            }
3579                        } else if (tag instanceof FolderInfo) {
3580                            final FolderInfo info = (FolderInfo) tag;
3581                            final ArrayList<ShortcutInfo> contents = info.contents;
3582                            final int contentsCount = contents.size();
3583                            final ArrayList<ShortcutInfo> appsToRemoveFromFolder =
3584                                    new ArrayList<ShortcutInfo>();
3585
3586                            for (int k = 0; k < contentsCount; k++) {
3587                                final ShortcutInfo appInfo = contents.get(k);
3588                                final Intent intent = appInfo.intent;
3589                                final ComponentName name = intent.getComponent();
3590
3591                                if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
3592                                    for (String packageName: packageNames) {
3593                                        if (packageName.equals(name.getPackageName())) {
3594                                            appsToRemoveFromFolder.add(appInfo);
3595                                        }
3596                                    }
3597                                }
3598                            }
3599                            for (ShortcutInfo item: appsToRemoveFromFolder) {
3600                                info.remove(item);
3601                                LauncherModel.deleteItemFromDatabase(mLauncher, item);
3602                            }
3603                        } else if (tag instanceof LauncherAppWidgetInfo) {
3604                            final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag;
3605                            final AppWidgetProviderInfo provider =
3606                                    widgets.getAppWidgetInfo(info.appWidgetId);
3607                            if (provider != null) {
3608                                for (String packageName: packageNames) {
3609                                    if (packageName.equals(provider.provider.getPackageName())) {
3610                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
3611                                        childrenToRemove.add(view);
3612                                    }
3613                                }
3614                            }
3615                        }
3616                    }
3617
3618                    childCount = childrenToRemove.size();
3619                    for (int j = 0; j < childCount; j++) {
3620                        View child = childrenToRemove.get(j);
3621                        // Note: We can not remove the view directly from CellLayoutChildren as this
3622                        // does not re-mark the spaces as unoccupied.
3623                        layoutParent.removeViewInLayout(child);
3624                        if (child instanceof DropTarget) {
3625                            mDragController.removeDropTarget((DropTarget)child);
3626                        }
3627                    }
3628
3629                    if (childCount > 0) {
3630                        layout.requestLayout();
3631                        layout.invalidate();
3632                    }
3633                }
3634            });
3635        }
3636    }
3637
3638    void updateShortcuts(ArrayList<ApplicationInfo> apps) {
3639        ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers();
3640        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
3641            int childCount = layout.getChildCount();
3642            for (int j = 0; j < childCount; j++) {
3643                final View view = layout.getChildAt(j);
3644                Object tag = view.getTag();
3645                if (tag instanceof ShortcutInfo) {
3646                    ShortcutInfo info = (ShortcutInfo) tag;
3647                    // We need to check for ACTION_MAIN otherwise getComponent() might
3648                    // return null for some shortcuts (for instance, for shortcuts to
3649                    // web pages.)
3650                    final Intent intent = info.intent;
3651                    final ComponentName name = intent.getComponent();
3652                    if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
3653                            Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
3654                        final int appCount = apps.size();
3655                        for (int k = 0; k < appCount; k++) {
3656                            ApplicationInfo app = apps.get(k);
3657                            if (app.componentName.equals(name)) {
3658                                BubbleTextView shortcut = (BubbleTextView) view;
3659                                info.updateIcon(mIconCache);
3660                                info.title = app.title.toString();
3661                                shortcut.applyFromShortcutInfo(info, mIconCache);
3662                            }
3663                        }
3664                    }
3665                }
3666            }
3667        }
3668    }
3669
3670    void moveToDefaultScreen(boolean animate) {
3671        if (!isSmall()) {
3672            if (animate) {
3673                snapToPage(mDefaultPage);
3674            } else {
3675                setCurrentPage(mDefaultPage);
3676            }
3677        }
3678        getChildAt(mDefaultPage).requestFocus();
3679    }
3680
3681    @Override
3682    public void syncPages() {
3683    }
3684
3685    @Override
3686    public void syncPageItems(int page, boolean immediate) {
3687    }
3688
3689    @Override
3690    protected String getCurrentPageDescription() {
3691        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
3692        return String.format(getContext().getString(R.string.workspace_scroll_format),
3693                page + 1, getChildCount());
3694    }
3695
3696    public void getLocationInDragLayer(int[] loc) {
3697        mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
3698    }
3699
3700    void setFadeForOverScroll(float fade) {
3701        if (!isScrollingIndicatorEnabled()) return;
3702
3703        mOverscrollFade = fade;
3704        float reducedFade = 0.5f + 0.5f * (1 - fade);
3705        final ViewGroup parent = (ViewGroup) getParent();
3706        final ImageView qsbDivider = (ImageView) (parent.findViewById(R.id.qsb_divider));
3707        final ImageView dockDivider = (ImageView) (parent.findViewById(R.id.dock_divider));
3708        final View scrollIndicator = getScrollingIndicator();
3709
3710        cancelScrollingIndicatorAnimations();
3711        if (qsbDivider != null) qsbDivider.setAlpha(reducedFade);
3712        if (dockDivider != null) dockDivider.setAlpha(reducedFade);
3713        scrollIndicator.setAlpha(1 - fade);
3714    }
3715}
3716