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