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