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