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