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