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