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