Workspace.java revision 8b805b17158886035b38261eb611d8641701ae43
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 = 0;
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.lockScreenOrientationOnLargeUI();
379    }
380
381    public void onDragEnd() {
382        mIsDragOccuring = false;
383        updateChildrenLayersEnabled();
384        mLauncher.unlockScreenOrientationOnLargeUI();
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            anim.setStartDelay(delay);
1731        }
1732
1733        if (stateIsSpringLoaded) {
1734            // Right now we're covered by Apps Customize
1735            // Show the background gradient immediately, so the gradient will
1736            // be showing once AppsCustomize disappears
1737            animateBackgroundGradient(getResources().getInteger(
1738                    R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, false);
1739        } else {
1740            // Fade the background gradient away
1741            animateBackgroundGradient(0f, true);
1742        }
1743        return anim;
1744    }
1745
1746    @Override
1747    public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
1748        mIsSwitchingState = true;
1749        cancelScrollingIndicatorAnimations();
1750    }
1751
1752    @Override
1753    public void onLauncherTransitionStep(Launcher l, float t) {
1754        mTransitionProgress = t;
1755    }
1756
1757    @Override
1758    public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
1759        mIsSwitchingState = false;
1760        mWallpaperOffset.setOverrideHorizontalCatchupConstant(false);
1761        updateChildrenLayersEnabled();
1762        // The code in getChangeStateAnimation to determine initialAlpha and finalAlpha will ensure
1763        // ensure that only the current page is visible during (and subsequently, after) the
1764        // transition animation.  If fade adjacent pages is disabled, then re-enable the page
1765        // visibility after the transition animation.
1766        if (!mFadeInAdjacentScreens) {
1767            for (int i = 0; i < getChildCount(); i++) {
1768                final CellLayout cl = (CellLayout) getChildAt(i);
1769                cl.setShortcutAndWidgetAlpha(1f);
1770            }
1771        }
1772    }
1773
1774    @Override
1775    public View getContent() {
1776        return this;
1777    }
1778
1779    /**
1780     * Draw the View v into the given Canvas.
1781     *
1782     * @param v the view to draw
1783     * @param destCanvas the canvas to draw on
1784     * @param padding the horizontal and vertical padding to use when drawing
1785     */
1786    private void drawDragView(View v, Canvas destCanvas, int padding, boolean pruneToDrawable) {
1787        final Rect clipRect = mTempRect;
1788        v.getDrawingRect(clipRect);
1789
1790        boolean textVisible = false;
1791
1792        destCanvas.save();
1793        if (v instanceof TextView && pruneToDrawable) {
1794            Drawable d = ((TextView) v).getCompoundDrawables()[1];
1795            clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding);
1796            destCanvas.translate(padding / 2, padding / 2);
1797            d.draw(destCanvas);
1798        } else {
1799            if (v instanceof FolderIcon) {
1800                // For FolderIcons the text can bleed into the icon area, and so we need to
1801                // hide the text completely (which can't be achieved by clipping).
1802                if (((FolderIcon) v).getTextVisible()) {
1803                    ((FolderIcon) v).setTextVisible(false);
1804                    textVisible = true;
1805                }
1806            } else if (v instanceof BubbleTextView) {
1807                final BubbleTextView tv = (BubbleTextView) v;
1808                clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V +
1809                        tv.getLayout().getLineTop(0);
1810            } else if (v instanceof TextView) {
1811                final TextView tv = (TextView) v;
1812                clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() +
1813                        tv.getLayout().getLineTop(0);
1814            }
1815            destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
1816            destCanvas.clipRect(clipRect, Op.REPLACE);
1817            v.draw(destCanvas);
1818
1819            // Restore text visibility of FolderIcon if necessary
1820            if (textVisible) {
1821                ((FolderIcon) v).setTextVisible(true);
1822            }
1823        }
1824        destCanvas.restore();
1825    }
1826
1827    /**
1828     * Returns a new bitmap to show when the given View is being dragged around.
1829     * Responsibility for the bitmap is transferred to the caller.
1830     */
1831    public Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
1832        Bitmap b;
1833
1834        if (v instanceof TextView) {
1835            Drawable d = ((TextView) v).getCompoundDrawables()[1];
1836            b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding,
1837                    d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888);
1838        } else {
1839            b = Bitmap.createBitmap(
1840                    v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
1841        }
1842
1843        canvas.setBitmap(b);
1844        drawDragView(v, canvas, padding, true);
1845        canvas.setBitmap(null);
1846
1847        return b;
1848    }
1849
1850    /**
1851     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
1852     * Responsibility for the bitmap is transferred to the caller.
1853     */
1854    private Bitmap createDragOutline(View v, Canvas canvas, int padding) {
1855        final int outlineColor = getResources().getColor(android.R.color.holo_blue_light);
1856        final Bitmap b = Bitmap.createBitmap(
1857                v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
1858
1859        canvas.setBitmap(b);
1860        drawDragView(v, canvas, padding, true);
1861        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
1862        canvas.setBitmap(null);
1863        return b;
1864    }
1865
1866    /**
1867     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
1868     * Responsibility for the bitmap is transferred to the caller.
1869     */
1870    private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding, int w, int h,
1871            Paint alphaClipPaint) {
1872        final int outlineColor = getResources().getColor(android.R.color.holo_blue_light);
1873        final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
1874        canvas.setBitmap(b);
1875
1876        Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
1877        float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(),
1878                (h - padding) / (float) orig.getHeight());
1879        int scaledWidth = (int) (scaleFactor * orig.getWidth());
1880        int scaledHeight = (int) (scaleFactor * orig.getHeight());
1881        Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
1882
1883        // center the image
1884        dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
1885
1886        canvas.drawBitmap(orig, src, dst, null);
1887        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor,
1888                alphaClipPaint);
1889        canvas.setBitmap(null);
1890
1891        return b;
1892    }
1893
1894    void startDrag(CellLayout.CellInfo cellInfo) {
1895        View child = cellInfo.cell;
1896
1897        // Make sure the drag was started by a long press as opposed to a long click.
1898        if (!child.isInTouchMode()) {
1899            return;
1900        }
1901
1902        mDragInfo = cellInfo;
1903        child.setVisibility(INVISIBLE);
1904        CellLayout layout = (CellLayout) child.getParent().getParent();
1905        layout.prepareChildForDrag(child);
1906
1907        child.clearFocus();
1908        child.setPressed(false);
1909
1910        final Canvas canvas = new Canvas();
1911
1912        // The outline is used to visualize where the item will land if dropped
1913        mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING);
1914        beginDragShared(child, this);
1915    }
1916
1917    public void beginDragShared(View child, DragSource source) {
1918        Resources r = getResources();
1919
1920        // The drag bitmap follows the touch point around on the screen
1921        final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING);
1922
1923        final int bmpWidth = b.getWidth();
1924        final int bmpHeight = b.getHeight();
1925
1926        mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
1927        int dragLayerX =
1928                Math.round(mTempXY[0] - (bmpWidth - child.getScaleX() * child.getWidth()) / 2);
1929        int dragLayerY =
1930                Math.round(mTempXY[1] - (bmpHeight - child.getScaleY() * bmpHeight) / 2
1931                        - DRAG_BITMAP_PADDING / 2);
1932
1933        Point dragVisualizeOffset = null;
1934        Rect dragRect = null;
1935        if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
1936            int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size);
1937            int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top);
1938            int top = child.getPaddingTop();
1939            int left = (bmpWidth - iconSize) / 2;
1940            int right = left + iconSize;
1941            int bottom = top + iconSize;
1942            dragLayerY += top;
1943            // Note: The drag region is used to calculate drag layer offsets, but the
1944            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
1945            dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2,
1946                    iconPaddingTop - DRAG_BITMAP_PADDING / 2);
1947            dragRect = new Rect(left, top, right, bottom);
1948        } else if (child instanceof FolderIcon) {
1949            int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size);
1950            dragRect = new Rect(0, 0, child.getWidth(), previewSize);
1951        }
1952
1953        // Clear the pressed state if necessary
1954        if (child instanceof BubbleTextView) {
1955            BubbleTextView icon = (BubbleTextView) child;
1956            icon.clearPressedOrFocusedBackground();
1957        }
1958
1959        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
1960                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, child.getScaleX());
1961        b.recycle();
1962
1963        // Show the scrolling indicator when you pick up an item
1964        showScrollingIndicator(false);
1965    }
1966
1967    void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, int screen,
1968            int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
1969        View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
1970
1971        final int[] cellXY = new int[2];
1972        target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
1973        addInScreen(view, container, screen, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
1974        LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen, cellXY[0],
1975                cellXY[1]);
1976    }
1977
1978    public boolean transitionStateShouldAllowDrop() {
1979        return ((!isSwitchingState() || mTransitionProgress > 0.5f) && mState != State.SMALL);
1980    }
1981
1982    /**
1983     * {@inheritDoc}
1984     */
1985    public boolean acceptDrop(DragObject d) {
1986        // If it's an external drop (e.g. from All Apps), check if it should be accepted
1987        CellLayout dropTargetLayout = mDropToLayout;
1988        if (d.dragSource != this) {
1989            // Don't accept the drop if we're not over a screen at time of drop
1990            if (dropTargetLayout == null) {
1991                return false;
1992            }
1993            if (!transitionStateShouldAllowDrop()) return false;
1994
1995            mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
1996                    d.dragView, mDragViewVisualCenter);
1997
1998            // We want the point to be mapped to the dragTarget.
1999            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2000                mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter);
2001            } else {
2002                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2003            }
2004
2005            int spanX = 1;
2006            int spanY = 1;
2007            if (mDragInfo != null) {
2008                final CellLayout.CellInfo dragCellInfo = mDragInfo;
2009                spanX = dragCellInfo.spanX;
2010                spanY = dragCellInfo.spanY;
2011            } else {
2012                final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
2013                spanX = dragInfo.spanX;
2014                spanY = dragInfo.spanY;
2015            }
2016
2017            int minSpanX = spanX;
2018            int minSpanY = spanY;
2019            if (d.dragInfo instanceof PendingAddWidgetInfo) {
2020                minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
2021                minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
2022            }
2023
2024            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2025                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
2026                    mTargetCell);
2027            float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2028                    mDragViewVisualCenter[1], mTargetCell);
2029            if (willCreateUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
2030                    mTargetCell, distance, true)) {
2031                return true;
2032            }
2033            if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
2034                    mTargetCell, distance)) {
2035                return true;
2036            }
2037
2038            int[] resultSpan = new int[2];
2039            mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
2040                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2041                    null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
2042            boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2043
2044            // Don't accept the drop if there's no room for the item
2045            if (!foundCell) {
2046                // Don't show the message if we are dropping on the AllApps button and the hotseat
2047                // is full
2048                boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2049                if (mTargetCell != null && isHotseat) {
2050                    Hotseat hotseat = mLauncher.getHotseat();
2051                    if (hotseat.isAllAppsButtonRank(
2052                            hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
2053                        return false;
2054                    }
2055                }
2056
2057                mLauncher.showOutOfSpaceMessage(isHotseat);
2058                return false;
2059            }
2060        }
2061        return true;
2062    }
2063
2064    boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float
2065            distance, boolean considerTimeout) {
2066        if (distance > mMaxDistanceForFolderCreation) return false;
2067        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2068
2069        if (dropOverView != null) {
2070            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2071            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2072                return false;
2073            }
2074        }
2075
2076        boolean hasntMoved = false;
2077        if (mDragInfo != null) {
2078            hasntMoved = dropOverView == mDragInfo.cell;
2079        }
2080
2081        if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
2082            return false;
2083        }
2084
2085        boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
2086        boolean willBecomeShortcut =
2087                (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
2088                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
2089
2090        return (aboveShortcut && willBecomeShortcut);
2091    }
2092
2093    boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
2094            float distance) {
2095        if (distance > mMaxDistanceForFolderCreation) return false;
2096        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2097
2098        if (dropOverView != null) {
2099            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2100            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2101                return false;
2102            }
2103        }
2104
2105        if (dropOverView instanceof FolderIcon) {
2106            FolderIcon fi = (FolderIcon) dropOverView;
2107            if (fi.acceptDrop(dragInfo)) {
2108                return true;
2109            }
2110        }
2111        return false;
2112    }
2113
2114    boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
2115            int[] targetCell, float distance, boolean external, DragView dragView,
2116            Runnable postAnimationRunnable) {
2117        if (distance > mMaxDistanceForFolderCreation) return false;
2118        View v = target.getChildAt(targetCell[0], targetCell[1]);
2119
2120        boolean hasntMoved = false;
2121        if (mDragInfo != null) {
2122            CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
2123            hasntMoved = (mDragInfo.cellX == targetCell[0] &&
2124                    mDragInfo.cellY == targetCell[1]) && (cellParent == target);
2125        }
2126
2127        if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
2128        mCreateUserFolderOnDrop = false;
2129        final int screen = (targetCell == null) ? mDragInfo.screen : indexOfChild(target);
2130
2131        boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
2132        boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
2133
2134        if (aboveShortcut && willBecomeShortcut) {
2135            ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
2136            ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
2137            // if the drag started here, we need to remove it from the workspace
2138            if (!external) {
2139                getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2140            }
2141
2142            Rect folderLocation = new Rect();
2143            float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
2144            target.removeView(v);
2145
2146            FolderIcon fi =
2147                mLauncher.addFolder(target, container, screen, targetCell[0], targetCell[1]);
2148            destInfo.cellX = -1;
2149            destInfo.cellY = -1;
2150            sourceInfo.cellX = -1;
2151            sourceInfo.cellY = -1;
2152
2153            // If the dragView is null, we can't animate
2154            boolean animate = dragView != null;
2155            if (animate) {
2156                fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
2157                        postAnimationRunnable);
2158            } else {
2159                fi.addItem(destInfo);
2160                fi.addItem(sourceInfo);
2161            }
2162            return true;
2163        }
2164        return false;
2165    }
2166
2167    boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
2168            float distance, DragObject d, boolean external) {
2169        if (distance > mMaxDistanceForFolderCreation) return false;
2170
2171        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2172        if (!mAddToExistingFolderOnDrop) return false;
2173        mAddToExistingFolderOnDrop = false;
2174
2175        if (dropOverView instanceof FolderIcon) {
2176            FolderIcon fi = (FolderIcon) dropOverView;
2177            if (fi.acceptDrop(d.dragInfo)) {
2178                fi.onDrop(d);
2179
2180                // if the drag started here, we need to remove it from the workspace
2181                if (!external) {
2182                    getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2183                }
2184                return true;
2185            }
2186        }
2187        return false;
2188    }
2189
2190    public void onDrop(final DragObject d) {
2191        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
2192                mDragViewVisualCenter);
2193
2194        CellLayout dropTargetLayout = mDropToLayout;
2195
2196        // We want the point to be mapped to the dragTarget.
2197        if (dropTargetLayout != null) {
2198            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2199                mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter);
2200            } else {
2201                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2202            }
2203        }
2204
2205        int snapScreen = -1;
2206        boolean resizeOnDrop = false;
2207        if (d.dragSource != this) {
2208            final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
2209                    (int) mDragViewVisualCenter[1] };
2210            onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
2211        } else if (mDragInfo != null) {
2212            final View cell = mDragInfo.cell;
2213
2214            Runnable resizeRunnable = null;
2215            if (dropTargetLayout != null) {
2216                // Move internally
2217                boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
2218                boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2219                long container = hasMovedIntoHotseat ?
2220                        LauncherSettings.Favorites.CONTAINER_HOTSEAT :
2221                        LauncherSettings.Favorites.CONTAINER_DESKTOP;
2222                int screen = (mTargetCell[0] < 0) ?
2223                        mDragInfo.screen : indexOfChild(dropTargetLayout);
2224                int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
2225                int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
2226                // First we find the cell nearest to point at which the item is
2227                // dropped, without any consideration to whether there is an item there.
2228
2229                mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
2230                        mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
2231                float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2232                        mDragViewVisualCenter[1], mTargetCell);
2233
2234                // If the item being dropped is a shortcut and the nearest drop
2235                // cell also contains a shortcut, then create a folder with the two shortcuts.
2236                if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
2237                        dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
2238                    return;
2239                }
2240
2241                if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
2242                        distance, d, false)) {
2243                    return;
2244                }
2245
2246                // Aside from the special case where we're dropping a shortcut onto a shortcut,
2247                // we need to find the nearest cell location that is vacant
2248                ItemInfo item = (ItemInfo) d.dragInfo;
2249                int minSpanX = item.spanX;
2250                int minSpanY = item.spanY;
2251                if (item.minSpanX > 0 && item.minSpanY > 0) {
2252                    minSpanX = item.minSpanX;
2253                    minSpanY = item.minSpanY;
2254                }
2255
2256                int[] resultSpan = new int[2];
2257                mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
2258                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
2259                        mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
2260
2261                boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2262                if (foundCell && (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
2263                    resizeOnDrop = true;
2264                    item.spanX = resultSpan[0];
2265                    item.spanY = resultSpan[1];
2266                }
2267
2268                if (mCurrentPage != screen && !hasMovedIntoHotseat) {
2269                    snapScreen = screen;
2270                    snapToPage(screen);
2271                }
2272
2273                if (foundCell) {
2274                    final ItemInfo info = (ItemInfo) cell.getTag();
2275                    if (hasMovedLayouts) {
2276                        // Reparent the view
2277                        getParentCellLayoutForView(cell).removeView(cell);
2278                        addInScreen(cell, container, screen, mTargetCell[0], mTargetCell[1],
2279                                info.spanX, info.spanY);
2280                    }
2281
2282                    // update the item's position after drop
2283                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2284                    lp.cellX = lp.tmpCellX = mTargetCell[0];
2285                    lp.cellY = lp.tmpCellY = mTargetCell[1];
2286                    lp.cellHSpan = item.spanX;
2287                    lp.cellVSpan = item.spanY;
2288                    lp.isLockedToGrid = true;
2289                    cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screen,
2290                            mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
2291
2292                    if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
2293                            cell instanceof LauncherAppWidgetHostView) {
2294                        final CellLayout cellLayout = dropTargetLayout;
2295                        // We post this call so that the widget has a chance to be placed
2296                        // in its final location
2297
2298                        final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
2299                        AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
2300                        if (pinfo != null &&
2301                                pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
2302                            final Runnable addResizeFrame = new Runnable() {
2303                                public void run() {
2304                                    DragLayer dragLayer = mLauncher.getDragLayer();
2305                                    dragLayer.addResizeFrame(info, hostView, cellLayout);
2306                                }
2307                            };
2308                            resizeRunnable = (new Runnable() {
2309                                public void run() {
2310                                    if (!isPageMoving()) {
2311                                        addResizeFrame.run();
2312                                    } else {
2313                                        mDelayedResizeRunnable = addResizeFrame;
2314                                    }
2315                                }
2316                            });
2317                        }
2318                    }
2319
2320                    LauncherModel.moveItemInDatabase(mLauncher, info, container, screen, lp.cellX,
2321                            lp.cellY);
2322                } else {
2323                    // If we can't find a drop location, we return the item to its original position
2324                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2325                    mTargetCell[0] = lp.cellX;
2326                    mTargetCell[1] = lp.cellY;
2327                }
2328            }
2329
2330            final CellLayout parent = (CellLayout) cell.getParent().getParent();
2331            final Runnable finalResizeRunnable = resizeRunnable;
2332            // Prepare it to be animated into its new position
2333            // This must be called after the view has been re-parented
2334            final Runnable onCompleteRunnable = new Runnable() {
2335                @Override
2336                public void run() {
2337                    mAnimatingViewIntoPlace = false;
2338                    updateChildrenLayersEnabled();
2339                    if (finalResizeRunnable != null) {
2340                        finalResizeRunnable.run();
2341                    }
2342                }
2343            };
2344            mAnimatingViewIntoPlace = true;
2345            if (d.dragView.hasDrawn()) {
2346                final ItemInfo info = (ItemInfo) cell.getTag();
2347                if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
2348                    int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
2349                            ANIMATE_INTO_POSITION_AND_DISAPPEAR;
2350                    animateWidgetDrop(info, parent, d.dragView,
2351                            onCompleteRunnable, animationType, cell, false);
2352                } else {
2353                    int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
2354                    mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
2355                            onCompleteRunnable, this);
2356                }
2357            } else {
2358                d.deferDragViewCleanupPostAnimation = false;
2359                cell.setVisibility(VISIBLE);
2360            }
2361            parent.onDropChild(cell);
2362        }
2363    }
2364
2365    public void setFinalScrollForPageChange(int screen) {
2366        if (screen >= 0) {
2367            mSavedScrollX = getScrollX();
2368            CellLayout cl = (CellLayout) getChildAt(screen);
2369            mSavedTranslationX = cl.getTranslationX();
2370            mSavedRotationY = cl.getRotationY();
2371            final int newX = getChildOffset(screen) - getRelativeChildOffset(screen);
2372            setScrollX(newX);
2373            cl.setTranslationX(0f);
2374            cl.setRotationY(0f);
2375        }
2376    }
2377
2378    public void resetFinalScrollForPageChange(int screen) {
2379        if (screen >= 0) {
2380            CellLayout cl = (CellLayout) getChildAt(screen);
2381            setScrollX(mSavedScrollX);
2382            cl.setTranslationX(mSavedTranslationX);
2383            cl.setRotationY(mSavedRotationY);
2384        }
2385    }
2386
2387    public void getViewLocationRelativeToSelf(View v, int[] location) {
2388        getLocationInWindow(location);
2389        int x = location[0];
2390        int y = location[1];
2391
2392        v.getLocationInWindow(location);
2393        int vX = location[0];
2394        int vY = location[1];
2395
2396        location[0] = vX - x;
2397        location[1] = vY - y;
2398    }
2399
2400    public void onDragEnter(DragObject d) {
2401        mDragEnforcer.onDragEnter();
2402        mCreateUserFolderOnDrop = false;
2403        mAddToExistingFolderOnDrop = false;
2404
2405        mDropToLayout = null;
2406        CellLayout layout = getCurrentDropLayout();
2407        setCurrentDropLayout(layout);
2408        setCurrentDragOverlappingLayout(layout);
2409
2410        // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we
2411        // don't need to show the outlines
2412        if (LauncherApplication.isScreenLarge()) {
2413            showOutlines();
2414        }
2415    }
2416
2417    public void onDragExit(DragObject d) {
2418        mDragEnforcer.onDragExit();
2419
2420        // Here we store the final page that will be dropped to, if the workspace in fact
2421        // receives the drop
2422        if (mInScrollArea) {
2423            mDropToLayout = mDragOverlappingLayout;
2424        } else {
2425            mDropToLayout = mDragTargetLayout;
2426        }
2427
2428        if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
2429            mCreateUserFolderOnDrop = true;
2430        } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
2431            mAddToExistingFolderOnDrop = true;
2432        }
2433
2434        // Reset the scroll area and previous drag target
2435        onResetScrollArea();
2436        setCurrentDropLayout(null);
2437        setCurrentDragOverlappingLayout(null);
2438
2439        mSpringLoadedDragController.cancel();
2440
2441        if (!mIsPageMoving) {
2442            hideOutlines();
2443        }
2444    }
2445
2446    void setCurrentDropLayout(CellLayout layout) {
2447        if (mDragTargetLayout != null) {
2448            mDragTargetLayout.revertTempState();
2449            mDragTargetLayout.onDragExit();
2450        }
2451        mDragTargetLayout = layout;
2452        if (mDragTargetLayout != null) {
2453            mDragTargetLayout.onDragEnter();
2454        }
2455        cleanupReorder(true);
2456        cleanupFolderCreation();
2457        setCurrentDropOverCell(-1, -1);
2458    }
2459
2460    void setCurrentDragOverlappingLayout(CellLayout layout) {
2461        if (mDragOverlappingLayout != null) {
2462            mDragOverlappingLayout.setIsDragOverlapping(false);
2463        }
2464        mDragOverlappingLayout = layout;
2465        if (mDragOverlappingLayout != null) {
2466            mDragOverlappingLayout.setIsDragOverlapping(true);
2467        }
2468        invalidate();
2469    }
2470
2471    void setCurrentDropOverCell(int x, int y) {
2472        if (x != mDragOverX || y != mDragOverY) {
2473            mDragOverX = x;
2474            mDragOverY = y;
2475            setDragMode(DRAG_MODE_NONE);
2476        }
2477    }
2478
2479    void setDragMode(int dragMode) {
2480        if (dragMode != mDragMode) {
2481            if (dragMode == DRAG_MODE_NONE) {
2482                cleanupAddToFolder();
2483                // We don't want to cancel the re-order alarm every time the target cell changes
2484                // as this feels to slow / unresponsive.
2485                cleanupReorder(false);
2486                cleanupFolderCreation();
2487            } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
2488                cleanupReorder(true);
2489                cleanupFolderCreation();
2490            } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
2491                cleanupAddToFolder();
2492                cleanupReorder(true);
2493            } else if (dragMode == DRAG_MODE_REORDER) {
2494                cleanupAddToFolder();
2495                cleanupFolderCreation();
2496            }
2497            mDragMode = dragMode;
2498        }
2499    }
2500
2501    private void cleanupFolderCreation() {
2502        if (mDragFolderRingAnimator != null) {
2503            mDragFolderRingAnimator.animateToNaturalState();
2504        }
2505        mFolderCreationAlarm.cancelAlarm();
2506    }
2507
2508    private void cleanupAddToFolder() {
2509        if (mDragOverFolderIcon != null) {
2510            mDragOverFolderIcon.onDragExit(null);
2511            mDragOverFolderIcon = null;
2512        }
2513    }
2514
2515    private void cleanupReorder(boolean cancelAlarm) {
2516        // Any pending reorders are canceled
2517        if (cancelAlarm) {
2518            mReorderAlarm.cancelAlarm();
2519        }
2520        mLastReorderX = -1;
2521        mLastReorderY = -1;
2522    }
2523
2524    public DropTarget getDropTargetDelegate(DragObject d) {
2525        return null;
2526    }
2527
2528    /*
2529    *
2530    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
2531    * coordinate space. The argument xy is modified with the return result.
2532    *
2533    */
2534   void mapPointFromSelfToChild(View v, float[] xy) {
2535       mapPointFromSelfToChild(v, xy, null);
2536   }
2537
2538   /*
2539    *
2540    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
2541    * coordinate space. The argument xy is modified with the return result.
2542    *
2543    * if cachedInverseMatrix is not null, this method will just use that matrix instead of
2544    * computing it itself; we use this to avoid redundant matrix inversions in
2545    * findMatchingPageForDragOver
2546    *
2547    */
2548   void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
2549       if (cachedInverseMatrix == null) {
2550           v.getMatrix().invert(mTempInverseMatrix);
2551           cachedInverseMatrix = mTempInverseMatrix;
2552       }
2553       int scrollX = getScrollX();
2554       if (mNextPage != INVALID_PAGE) {
2555           scrollX = mScroller.getFinalX();
2556       }
2557       xy[0] = xy[0] + scrollX - v.getLeft();
2558       xy[1] = xy[1] + getScrollY() - v.getTop();
2559       cachedInverseMatrix.mapPoints(xy);
2560   }
2561
2562   /*
2563    * Maps a point from the Workspace's coordinate system to another sibling view's. (Workspace
2564    * covers the full screen)
2565    */
2566   void mapPointFromSelfToSibling(View v, float[] xy) {
2567       xy[0] = xy[0] - v.getLeft();
2568       xy[1] = xy[1] - v.getTop();
2569   }
2570
2571   void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
2572       xy[0] = xy[0] - hotseat.getLeft() - hotseat.getLayout().getLeft();
2573       xy[1] = xy[1] - hotseat.getTop() - hotseat.getLayout().getTop();
2574   }
2575
2576   /*
2577    *
2578    * Convert the 2D coordinate xy from this CellLayout's coordinate space to
2579    * the parent View's coordinate space. The argument xy is modified with the return result.
2580    *
2581    */
2582   void mapPointFromChildToSelf(View v, float[] xy) {
2583       v.getMatrix().mapPoints(xy);
2584       int scrollX = getScrollX();
2585       if (mNextPage != INVALID_PAGE) {
2586           scrollX = mScroller.getFinalX();
2587       }
2588       xy[0] -= (scrollX - v.getLeft());
2589       xy[1] -= (getScrollY() - v.getTop());
2590   }
2591
2592   static private float squaredDistance(float[] point1, float[] point2) {
2593        float distanceX = point1[0] - point2[0];
2594        float distanceY = point2[1] - point2[1];
2595        return distanceX * distanceX + distanceY * distanceY;
2596   }
2597
2598    /*
2599     *
2600     * Returns true if the passed CellLayout cl overlaps with dragView
2601     *
2602     */
2603    boolean overlaps(CellLayout cl, DragView dragView,
2604            int dragViewX, int dragViewY, Matrix cachedInverseMatrix) {
2605        // Transform the coordinates of the item being dragged to the CellLayout's coordinates
2606        final float[] draggedItemTopLeft = mTempDragCoordinates;
2607        draggedItemTopLeft[0] = dragViewX;
2608        draggedItemTopLeft[1] = dragViewY;
2609        final float[] draggedItemBottomRight = mTempDragBottomRightCoordinates;
2610        draggedItemBottomRight[0] = draggedItemTopLeft[0] + dragView.getDragRegionWidth();
2611        draggedItemBottomRight[1] = draggedItemTopLeft[1] + dragView.getDragRegionHeight();
2612
2613        // Transform the dragged item's top left coordinates
2614        // to the CellLayout's local coordinates
2615        mapPointFromSelfToChild(cl, draggedItemTopLeft, cachedInverseMatrix);
2616        float overlapRegionLeft = Math.max(0f, draggedItemTopLeft[0]);
2617        float overlapRegionTop = Math.max(0f, draggedItemTopLeft[1]);
2618
2619        if (overlapRegionLeft <= cl.getWidth() && overlapRegionTop >= 0) {
2620            // Transform the dragged item's bottom right coordinates
2621            // to the CellLayout's local coordinates
2622            mapPointFromSelfToChild(cl, draggedItemBottomRight, cachedInverseMatrix);
2623            float overlapRegionRight = Math.min(cl.getWidth(), draggedItemBottomRight[0]);
2624            float overlapRegionBottom = Math.min(cl.getHeight(), draggedItemBottomRight[1]);
2625
2626            if (overlapRegionRight >= 0 && overlapRegionBottom <= cl.getHeight()) {
2627                float overlap = (overlapRegionRight - overlapRegionLeft) *
2628                         (overlapRegionBottom - overlapRegionTop);
2629                if (overlap > 0) {
2630                    return true;
2631                }
2632             }
2633        }
2634        return false;
2635    }
2636
2637    /*
2638     *
2639     * This method returns the CellLayout that is currently being dragged to. In order to drag
2640     * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
2641     * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
2642     *
2643     * Return null if no CellLayout is currently being dragged over
2644     *
2645     */
2646    private CellLayout findMatchingPageForDragOver(
2647            DragView dragView, float originX, float originY, boolean exact) {
2648        // We loop through all the screens (ie CellLayouts) and see which ones overlap
2649        // with the item being dragged and then choose the one that's closest to the touch point
2650        final int screenCount = getChildCount();
2651        CellLayout bestMatchingScreen = null;
2652        float smallestDistSoFar = Float.MAX_VALUE;
2653
2654        for (int i = 0; i < screenCount; i++) {
2655            CellLayout cl = (CellLayout) getChildAt(i);
2656
2657            final float[] touchXy = {originX, originY};
2658            // Transform the touch coordinates to the CellLayout's local coordinates
2659            // If the touch point is within the bounds of the cell layout, we can return immediately
2660            cl.getMatrix().invert(mTempInverseMatrix);
2661            mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
2662
2663            if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
2664                    touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
2665                return cl;
2666            }
2667
2668            if (!exact) {
2669                // Get the center of the cell layout in screen coordinates
2670                final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
2671                cellLayoutCenter[0] = cl.getWidth()/2;
2672                cellLayoutCenter[1] = cl.getHeight()/2;
2673                mapPointFromChildToSelf(cl, cellLayoutCenter);
2674
2675                touchXy[0] = originX;
2676                touchXy[1] = originY;
2677
2678                // Calculate the distance between the center of the CellLayout
2679                // and the touch point
2680                float dist = squaredDistance(touchXy, cellLayoutCenter);
2681
2682                if (dist < smallestDistSoFar) {
2683                    smallestDistSoFar = dist;
2684                    bestMatchingScreen = cl;
2685                }
2686            }
2687        }
2688        return bestMatchingScreen;
2689    }
2690
2691    // This is used to compute the visual center of the dragView. This point is then
2692    // used to visualize drop locations and determine where to drop an item. The idea is that
2693    // the visual center represents the user's interpretation of where the item is, and hence
2694    // is the appropriate point to use when determining drop location.
2695    private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
2696            DragView dragView, float[] recycle) {
2697        float res[];
2698        if (recycle == null) {
2699            res = new float[2];
2700        } else {
2701            res = recycle;
2702        }
2703
2704        // First off, the drag view has been shifted in a way that is not represented in the
2705        // x and y values or the x/yOffsets. Here we account for that shift.
2706        x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX);
2707        y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
2708
2709        // These represent the visual top and left of drag view if a dragRect was provided.
2710        // If a dragRect was not provided, then they correspond to the actual view left and
2711        // top, as the dragRect is in that case taken to be the entire dragView.
2712        // R.dimen.dragViewOffsetY.
2713        int left = x - xOffset;
2714        int top = y - yOffset;
2715
2716        // In order to find the visual center, we shift by half the dragRect
2717        res[0] = left + dragView.getDragRegion().width() / 2;
2718        res[1] = top + dragView.getDragRegion().height() / 2;
2719
2720        return res;
2721    }
2722
2723    private boolean isDragWidget(DragObject d) {
2724        return (d.dragInfo instanceof LauncherAppWidgetInfo ||
2725                d.dragInfo instanceof PendingAddWidgetInfo);
2726    }
2727    private boolean isExternalDragWidget(DragObject d) {
2728        return d.dragSource != this && isDragWidget(d);
2729    }
2730
2731    public void onDragOver(DragObject d) {
2732        // Skip drag over events while we are dragging over side pages
2733        if (mInScrollArea || mIsSwitchingState || mState == State.SMALL) return;
2734
2735        Rect r = new Rect();
2736        CellLayout layout = null;
2737        ItemInfo item = (ItemInfo) d.dragInfo;
2738
2739        // Ensure that we have proper spans for the item that we are dropping
2740        if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
2741        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
2742            d.dragView, mDragViewVisualCenter);
2743
2744        final View child = (mDragInfo == null) ? null : mDragInfo.cell;
2745        // Identify whether we have dragged over a side page
2746        if (isSmall()) {
2747            if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
2748                mLauncher.getHotseat().getHitRect(r);
2749                if (r.contains(d.x, d.y)) {
2750                    layout = mLauncher.getHotseat().getLayout();
2751                }
2752            }
2753            if (layout == null) {
2754                layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false);
2755            }
2756            if (layout != mDragTargetLayout) {
2757
2758                setCurrentDropLayout(layout);
2759                setCurrentDragOverlappingLayout(layout);
2760
2761                boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
2762                if (isInSpringLoadedMode) {
2763                    if (mLauncher.isHotseatLayout(layout)) {
2764                        mSpringLoadedDragController.cancel();
2765                    } else {
2766                        mSpringLoadedDragController.setAlarm(mDragTargetLayout);
2767                    }
2768                }
2769            }
2770        } else {
2771            // Test to see if we are over the hotseat otherwise just use the current page
2772            if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
2773                mLauncher.getHotseat().getHitRect(r);
2774                if (r.contains(d.x, d.y)) {
2775                    layout = mLauncher.getHotseat().getLayout();
2776                }
2777            }
2778            if (layout == null) {
2779                layout = getCurrentDropLayout();
2780            }
2781            if (layout != mDragTargetLayout) {
2782                setCurrentDropLayout(layout);
2783                setCurrentDragOverlappingLayout(layout);
2784            }
2785        }
2786
2787        // Handle the drag over
2788        if (mDragTargetLayout != null) {
2789            // We want the point to be mapped to the dragTarget.
2790            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
2791                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2792            } else {
2793                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
2794            }
2795
2796            ItemInfo info = (ItemInfo) d.dragInfo;
2797
2798            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2799                    (int) mDragViewVisualCenter[1], 1, 1, mDragTargetLayout, mTargetCell);
2800
2801            setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
2802
2803            float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
2804                    mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
2805
2806            final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0],
2807                    mTargetCell[1]);
2808
2809            manageFolderFeedback(info, mDragTargetLayout, mTargetCell,
2810                    targetCellDistance, dragOverView);
2811
2812            int minSpanX = item.spanX;
2813            int minSpanY = item.spanY;
2814            if (item.minSpanX > 0 && item.minSpanY > 0) {
2815                minSpanX = item.minSpanX;
2816                minSpanY = item.minSpanY;
2817            }
2818
2819            int[] reorderPosition = new int[2];
2820            reorderPosition = findNearestArea((int) mDragViewVisualCenter[0],
2821                    (int) mDragViewVisualCenter[1], item.spanX, item.spanY, mDragTargetLayout,
2822                    reorderPosition);
2823
2824            boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
2825                    mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
2826                    item.spanY, child, mTargetCell);
2827
2828            if (!nearestDropOccupied) {
2829                mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
2830                        (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
2831                        mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
2832                        d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
2833            } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
2834                    && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderPosition[0] ||
2835                    mLastReorderY != reorderPosition[1])) {
2836                // Otherwise, if we aren't adding to or creating a folder and there's no pending
2837                // reorder, then we schedule a reorder
2838                ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
2839                        minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child);
2840                mReorderAlarm.setOnAlarmListener(listener);
2841                mReorderAlarm.setAlarm(REORDER_TIMEOUT);
2842            }
2843
2844            if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
2845                    !nearestDropOccupied) {
2846                if (mDragTargetLayout != null) {
2847                    mDragTargetLayout.revertTempState();
2848                }
2849            }
2850        }
2851    }
2852
2853    private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
2854            int[] targetCell, float distance, View dragOverView) {
2855        boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
2856                false);
2857
2858        if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
2859                !mFolderCreationAlarm.alarmPending()) {
2860            mFolderCreationAlarm.setOnAlarmListener(new
2861                    FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
2862            mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
2863            return;
2864        }
2865
2866        boolean willAddToFolder =
2867                willAddToExistingUserFolder(info, targetLayout, targetCell, distance);
2868
2869        if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
2870            mDragOverFolderIcon = ((FolderIcon) dragOverView);
2871            mAddToExistingFolderOnDrop = true;
2872            mDragOverFolderIcon.onDragEnter(info);
2873            if (targetLayout != null) {
2874                targetLayout.clearDragOutlines();
2875            }
2876            setDragMode(DRAG_MODE_ADD_TO_FOLDER);
2877            return;
2878        }
2879
2880        if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
2881            setDragMode(DRAG_MODE_NONE);
2882        }
2883        if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
2884            setDragMode(DRAG_MODE_NONE);
2885        }
2886
2887        return;
2888    }
2889
2890    class FolderCreationAlarmListener implements OnAlarmListener {
2891        CellLayout layout;
2892        int cellX;
2893        int cellY;
2894
2895        public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
2896            this.layout = layout;
2897            this.cellX = cellX;
2898            this.cellY = cellY;
2899        }
2900
2901        public void onAlarm(Alarm alarm) {
2902            if (mDragFolderRingAnimator == null) {
2903                mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
2904            }
2905            mDragFolderRingAnimator.setCell(cellX, cellY);
2906            mDragFolderRingAnimator.setCellLayout(layout);
2907            mDragFolderRingAnimator.animateToAcceptState();
2908            layout.showFolderAccept(mDragFolderRingAnimator);
2909            layout.clearDragOutlines();
2910            setDragMode(DRAG_MODE_CREATE_FOLDER);
2911        }
2912    }
2913
2914    class ReorderAlarmListener implements OnAlarmListener {
2915        float[] dragViewCenter;
2916        int minSpanX, minSpanY, spanX, spanY;
2917        DragView dragView;
2918        View child;
2919
2920        public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
2921                int spanY, DragView dragView, View child) {
2922            this.dragViewCenter = dragViewCenter;
2923            this.minSpanX = minSpanX;
2924            this.minSpanY = minSpanY;
2925            this.spanX = spanX;
2926            this.spanY = spanY;
2927            this.child = child;
2928            this.dragView = dragView;
2929        }
2930
2931        public void onAlarm(Alarm alarm) {
2932            int[] resultSpan = new int[2];
2933            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2934                    (int) mDragViewVisualCenter[1], spanX, spanY, mDragTargetLayout, mTargetCell);
2935            mLastReorderX = mTargetCell[0];
2936            mLastReorderY = mTargetCell[1];
2937
2938            mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
2939                (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2940                child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
2941
2942            if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
2943                mDragTargetLayout.revertTempState();
2944            } else {
2945                setDragMode(DRAG_MODE_REORDER);
2946            }
2947
2948            boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
2949            mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
2950                (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
2951                mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize,
2952                dragView.getDragVisualizeOffset(), dragView.getDragRegion());
2953        }
2954    }
2955
2956    @Override
2957    public void getHitRect(Rect outRect) {
2958        // We want the workspace to have the whole area of the display (it will find the correct
2959        // cell layout to drop to in the existing drag/drop logic.
2960        outRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
2961    }
2962
2963    /**
2964     * Add the item specified by dragInfo to the given layout.
2965     * @return true if successful
2966     */
2967    public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
2968        if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
2969            onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
2970            return true;
2971        }
2972        mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
2973        return false;
2974    }
2975
2976    private void onDropExternal(int[] touchXY, Object dragInfo,
2977            CellLayout cellLayout, boolean insertAtFirst) {
2978        onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
2979    }
2980
2981    /**
2982     * Drop an item that didn't originate on one of the workspace screens.
2983     * It may have come from Launcher (e.g. from all apps or customize), or it may have
2984     * come from another app altogether.
2985     *
2986     * NOTE: This can also be called when we are outside of a drag event, when we want
2987     * to add an item to one of the workspace screens.
2988     */
2989    private void onDropExternal(final int[] touchXY, final Object dragInfo,
2990            final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
2991        final Runnable exitSpringLoadedRunnable = new Runnable() {
2992            @Override
2993            public void run() {
2994                mLauncher.exitSpringLoadedDragModeDelayed(true, false, null);
2995            }
2996        };
2997
2998        ItemInfo info = (ItemInfo) dragInfo;
2999        int spanX = info.spanX;
3000        int spanY = info.spanY;
3001        if (mDragInfo != null) {
3002            spanX = mDragInfo.spanX;
3003            spanY = mDragInfo.spanY;
3004        }
3005
3006        final long container = mLauncher.isHotseatLayout(cellLayout) ?
3007                LauncherSettings.Favorites.CONTAINER_HOTSEAT :
3008                    LauncherSettings.Favorites.CONTAINER_DESKTOP;
3009        final int screen = indexOfChild(cellLayout);
3010        if (!mLauncher.isHotseatLayout(cellLayout) && screen != mCurrentPage
3011                && mState != State.SPRING_LOADED) {
3012            snapToPage(screen);
3013        }
3014
3015        if (info instanceof PendingAddItemInfo) {
3016            final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
3017
3018            boolean findNearestVacantCell = true;
3019            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
3020                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3021                        cellLayout, mTargetCell);
3022                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3023                        mDragViewVisualCenter[1], mTargetCell);
3024                if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell,
3025                        distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
3026                                cellLayout, mTargetCell, distance)) {
3027                    findNearestVacantCell = false;
3028                }
3029            }
3030
3031            final ItemInfo item = (ItemInfo) d.dragInfo;
3032            if (findNearestVacantCell) {
3033                int minSpanX = item.spanX;
3034                int minSpanY = item.spanY;
3035                if (item.minSpanX > 0 && item.minSpanY > 0) {
3036                    minSpanX = item.minSpanX;
3037                    minSpanY = item.minSpanY;
3038                }
3039                int[] resultSpan = new int[2];
3040                mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
3041                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
3042                        null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
3043                item.spanX = resultSpan[0];
3044                item.spanY = resultSpan[1];
3045            }
3046
3047            Runnable onAnimationCompleteRunnable = new Runnable() {
3048                @Override
3049                public void run() {
3050                    // When dragging and dropping from customization tray, we deal with creating
3051                    // widgets/shortcuts/folders in a slightly different way
3052                    switch (pendingInfo.itemType) {
3053                    case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
3054                        int span[] = new int[2];
3055                        span[0] = item.spanX;
3056                        span[1] = item.spanY;
3057                        mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
3058                                container, screen, mTargetCell, span, null);
3059                        break;
3060                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3061                        mLauncher.processShortcutFromDrop(pendingInfo.componentName,
3062                                container, screen, mTargetCell, null);
3063                        break;
3064                    default:
3065                        throw new IllegalStateException("Unknown item type: " +
3066                                pendingInfo.itemType);
3067                    }
3068                }
3069            };
3070            View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
3071                    ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
3072            int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3073            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
3074                    ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
3075                animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
3076            }
3077            animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
3078                    animationStyle, finalView, true);
3079        } else {
3080            // This is for other drag/drop cases, like dragging from All Apps
3081            View view = null;
3082
3083            switch (info.itemType) {
3084            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3085            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3086                if (info.container == NO_ID && info instanceof ApplicationInfo) {
3087                    // Came from all apps -- make a copy
3088                    info = new ShortcutInfo((ApplicationInfo) info);
3089                }
3090                view = mLauncher.createShortcut(R.layout.application, cellLayout,
3091                        (ShortcutInfo) info);
3092                break;
3093            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3094                view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
3095                        (FolderInfo) info, mIconCache);
3096                break;
3097            default:
3098                throw new IllegalStateException("Unknown item type: " + info.itemType);
3099            }
3100
3101            // First we find the cell nearest to point at which the item is
3102            // dropped, without any consideration to whether there is an item there.
3103            if (touchXY != null) {
3104                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3105                        cellLayout, mTargetCell);
3106                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3107                        mDragViewVisualCenter[1], mTargetCell);
3108                d.postAnimationRunnable = exitSpringLoadedRunnable;
3109                if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
3110                        true, d.dragView, d.postAnimationRunnable)) {
3111                    return;
3112                }
3113                if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
3114                        true)) {
3115                    return;
3116                }
3117            }
3118
3119            if (touchXY != null) {
3120                // when dragging and dropping, just find the closest free spot
3121                mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
3122                        (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
3123                        null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
3124            } else {
3125                cellLayout.findCellForSpan(mTargetCell, 1, 1);
3126            }
3127            addInScreen(view, container, screen, mTargetCell[0], mTargetCell[1], info.spanX,
3128                    info.spanY, insertAtFirst);
3129            cellLayout.onDropChild(view);
3130            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
3131            cellLayout.getShortcutsAndWidgets().measureChild(view);
3132
3133
3134            LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen,
3135                    lp.cellX, lp.cellY);
3136
3137            if (d.dragView != null) {
3138                // We wrap the animation call in the temporary set and reset of the current
3139                // cellLayout to its final transform -- this means we animate the drag view to
3140                // the correct final location.
3141                setFinalTransitionTransform(cellLayout);
3142                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
3143                        exitSpringLoadedRunnable);
3144                resetTransitionTransform(cellLayout);
3145            }
3146        }
3147    }
3148
3149    public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
3150        int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
3151                widgetInfo.spanY, widgetInfo, false);
3152        int visibility = layout.getVisibility();
3153        layout.setVisibility(VISIBLE);
3154
3155        int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
3156        int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
3157        Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
3158                Bitmap.Config.ARGB_8888);
3159        Canvas c = new Canvas(b);
3160
3161        layout.measure(width, height);
3162        layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
3163        layout.draw(c);
3164        c.setBitmap(null);
3165        layout.setVisibility(visibility);
3166        return b;
3167    }
3168
3169    private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
3170            DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, View finalView,
3171            boolean external) {
3172        // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
3173        // location and size on the home screen.
3174        int spanX = info.spanX;
3175        int spanY = info.spanY;
3176
3177        Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
3178        loc[0] = r.left;
3179        loc[1] = r.top;
3180
3181        setFinalTransitionTransform(layout);
3182        float cellLayoutScale =
3183                mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc);
3184        resetTransitionTransform(layout);
3185        float dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
3186        float dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
3187
3188        // The animation will scale the dragView about its center, so we need to center about
3189        // the final location.
3190        loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
3191        loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
3192
3193        scaleXY[0] = dragViewScaleX * cellLayoutScale;
3194        scaleXY[1] = dragViewScaleY * cellLayoutScale;
3195    }
3196
3197    public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
3198            final Runnable onCompleteRunnable, int animationType, final View finalView,
3199            boolean external) {
3200        Rect from = new Rect();
3201        mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
3202
3203        int[] finalPos = new int[2];
3204        float scaleXY[] = new float[2];
3205        getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
3206                finalView, external);
3207
3208        Resources res = mLauncher.getResources();
3209        int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
3210
3211        // In the case where we've prebound the widget, we remove it from the DragLayer
3212        if (finalView instanceof AppWidgetHostView && external) {
3213            mLauncher.getDragLayer().removeView(finalView);
3214        }
3215        if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
3216            Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
3217            dragView.setCrossFadeBitmap(crossFadeBitmap);
3218            dragView.crossFade((int) (duration * 0.8f));
3219        } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) {
3220            scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0],  scaleXY[1]);
3221        }
3222
3223        DragLayer dragLayer = mLauncher.getDragLayer();
3224        if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
3225            mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
3226                    DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
3227        } else {
3228            int endStyle;
3229            if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
3230                endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
3231            } else {
3232                endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
3233            }
3234
3235            Runnable onComplete = new Runnable() {
3236                @Override
3237                public void run() {
3238                    if (finalView != null) {
3239                        finalView.setVisibility(VISIBLE);
3240                    }
3241                    if (onCompleteRunnable != null) {
3242                        onCompleteRunnable.run();
3243                    }
3244                }
3245            };
3246            dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
3247                    finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
3248                    duration, this);
3249        }
3250    }
3251
3252    public void setFinalTransitionTransform(CellLayout layout) {
3253        if (isSwitchingState()) {
3254            int index = indexOfChild(layout);
3255            mCurrentScaleX = layout.getScaleX();
3256            mCurrentScaleY = layout.getScaleY();
3257            mCurrentTranslationX = layout.getTranslationX();
3258            mCurrentTranslationY = layout.getTranslationY();
3259            mCurrentRotationY = layout.getRotationY();
3260            layout.setScaleX(mNewScaleXs[index]);
3261            layout.setScaleY(mNewScaleYs[index]);
3262            layout.setTranslationX(mNewTranslationXs[index]);
3263            layout.setTranslationY(mNewTranslationYs[index]);
3264            layout.setRotationY(mNewRotationYs[index]);
3265        }
3266    }
3267    public void resetTransitionTransform(CellLayout layout) {
3268        if (isSwitchingState()) {
3269            mCurrentScaleX = layout.getScaleX();
3270            mCurrentScaleY = layout.getScaleY();
3271            mCurrentTranslationX = layout.getTranslationX();
3272            mCurrentTranslationY = layout.getTranslationY();
3273            mCurrentRotationY = layout.getRotationY();
3274            layout.setScaleX(mCurrentScaleX);
3275            layout.setScaleY(mCurrentScaleY);
3276            layout.setTranslationX(mCurrentTranslationX);
3277            layout.setTranslationY(mCurrentTranslationY);
3278            layout.setRotationY(mCurrentRotationY);
3279        }
3280    }
3281
3282    /**
3283     * Return the current {@link CellLayout}, correctly picking the destination
3284     * screen while a scroll is in progress.
3285     */
3286    public CellLayout getCurrentDropLayout() {
3287        return (CellLayout) getChildAt(mNextPage == INVALID_PAGE ? mCurrentPage : mNextPage);
3288    }
3289
3290    /**
3291     * Return the current CellInfo describing our current drag; this method exists
3292     * so that Launcher can sync this object with the correct info when the activity is created/
3293     * destroyed
3294     *
3295     */
3296    public CellLayout.CellInfo getDragInfo() {
3297        return mDragInfo;
3298    }
3299
3300    /**
3301     * Calculate the nearest cell where the given object would be dropped.
3302     *
3303     * pixelX and pixelY should be in the coordinate system of layout
3304     */
3305    private int[] findNearestArea(int pixelX, int pixelY,
3306            int spanX, int spanY, CellLayout layout, int[] recycle) {
3307        return layout.findNearestArea(
3308                pixelX, pixelY, spanX, spanY, recycle);
3309    }
3310
3311    void setup(DragController dragController) {
3312        mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
3313        mDragController = dragController;
3314
3315        // hardware layers on children are enabled on startup, but should be disabled until
3316        // needed
3317        updateChildrenLayersEnabled();
3318        setWallpaperDimension();
3319    }
3320
3321    /**
3322     * Called at the end of a drag which originated on the workspace.
3323     */
3324    public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
3325            boolean success) {
3326        if (success) {
3327            if (target != this) {
3328                if (mDragInfo != null) {
3329                    getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
3330                    if (mDragInfo.cell instanceof DropTarget) {
3331                        mDragController.removeDropTarget((DropTarget) mDragInfo.cell);
3332                    }
3333                }
3334            }
3335        } else if (mDragInfo != null) {
3336            CellLayout cellLayout;
3337            if (mLauncher.isHotseatLayout(target)) {
3338                cellLayout = mLauncher.getHotseat().getLayout();
3339            } else {
3340                cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
3341            }
3342            cellLayout.onDropChild(mDragInfo.cell);
3343        }
3344        if (d.cancelled &&  mDragInfo.cell != null) {
3345                mDragInfo.cell.setVisibility(VISIBLE);
3346        }
3347        mDragOutline = null;
3348        mDragInfo = null;
3349
3350        // Hide the scrolling indicator after you pick up an item
3351        hideScrollingIndicator(false);
3352    }
3353
3354    void updateItemLocationsInDatabase(CellLayout cl) {
3355        int count = cl.getShortcutsAndWidgets().getChildCount();
3356
3357        int screen = indexOfChild(cl);
3358        int container = Favorites.CONTAINER_DESKTOP;
3359
3360        if (mLauncher.isHotseatLayout(cl)) {
3361            screen = -1;
3362            container = Favorites.CONTAINER_HOTSEAT;
3363        }
3364
3365        for (int i = 0; i < count; i++) {
3366            View v = cl.getShortcutsAndWidgets().getChildAt(i);
3367            ItemInfo info = (ItemInfo) v.getTag();
3368            // Null check required as the AllApps button doesn't have an item info
3369            if (info != null) {
3370                LauncherModel.modifyItemInDatabase(mLauncher, info, container, screen, info.cellX,
3371                        info.cellY, info.spanX, info.spanY);
3372            }
3373        }
3374    }
3375
3376    @Override
3377    public boolean supportsFlingToDelete() {
3378        return true;
3379    }
3380
3381    @Override
3382    public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
3383        // Do nothing
3384    }
3385
3386    @Override
3387    public void onFlingToDeleteCompleted() {
3388        // Do nothing
3389    }
3390
3391    public boolean isDropEnabled() {
3392        return true;
3393    }
3394
3395    @Override
3396    protected void onRestoreInstanceState(Parcelable state) {
3397        super.onRestoreInstanceState(state);
3398        Launcher.setScreen(mCurrentPage);
3399    }
3400
3401    @Override
3402    public void scrollLeft() {
3403        if (!isSmall() && !mIsSwitchingState) {
3404            super.scrollLeft();
3405        }
3406        Folder openFolder = getOpenFolder();
3407        if (openFolder != null) {
3408            openFolder.completeDragExit();
3409        }
3410    }
3411
3412    @Override
3413    public void scrollRight() {
3414        if (!isSmall() && !mIsSwitchingState) {
3415            super.scrollRight();
3416        }
3417        Folder openFolder = getOpenFolder();
3418        if (openFolder != null) {
3419            openFolder.completeDragExit();
3420        }
3421    }
3422
3423    @Override
3424    public boolean onEnterScrollArea(int x, int y, int direction) {
3425        // Ignore the scroll area if we are dragging over the hot seat
3426        boolean isPortrait = !LauncherApplication.isScreenLandscape(getContext());
3427        if (mLauncher.getHotseat() != null && isPortrait) {
3428            Rect r = new Rect();
3429            mLauncher.getHotseat().getHitRect(r);
3430            if (r.contains(x, y)) {
3431                return false;
3432            }
3433        }
3434
3435        boolean result = false;
3436        if (!isSmall() && !mIsSwitchingState) {
3437            mInScrollArea = true;
3438
3439            final int page = (mNextPage != INVALID_PAGE ? mNextPage : mCurrentPage) +
3440                       (direction == DragController.SCROLL_LEFT ? -1 : 1);
3441
3442            // We always want to exit the current layout to ensure parity of enter / exit
3443            setCurrentDropLayout(null);
3444
3445            if (0 <= page && page < getChildCount()) {
3446                CellLayout layout = (CellLayout) getChildAt(page);
3447                setCurrentDragOverlappingLayout(layout);
3448
3449                // Workspace is responsible for drawing the edge glow on adjacent pages,
3450                // so we need to redraw the workspace when this may have changed.
3451                invalidate();
3452                result = true;
3453            }
3454        }
3455        return result;
3456    }
3457
3458    @Override
3459    public boolean onExitScrollArea() {
3460        boolean result = false;
3461        if (mInScrollArea) {
3462            invalidate();
3463            CellLayout layout = getCurrentDropLayout();
3464            setCurrentDropLayout(layout);
3465            setCurrentDragOverlappingLayout(layout);
3466
3467            result = true;
3468            mInScrollArea = false;
3469        }
3470        return result;
3471    }
3472
3473    private void onResetScrollArea() {
3474        setCurrentDragOverlappingLayout(null);
3475        mInScrollArea = false;
3476    }
3477
3478    /**
3479     * Returns a specific CellLayout
3480     */
3481    CellLayout getParentCellLayoutForView(View v) {
3482        ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
3483        for (CellLayout layout : layouts) {
3484            if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
3485                return layout;
3486            }
3487        }
3488        return null;
3489    }
3490
3491    /**
3492     * Returns a list of all the CellLayouts in the workspace.
3493     */
3494    ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
3495        ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
3496        int screenCount = getChildCount();
3497        for (int screen = 0; screen < screenCount; screen++) {
3498            layouts.add(((CellLayout) getChildAt(screen)));
3499        }
3500        if (mLauncher.getHotseat() != null) {
3501            layouts.add(mLauncher.getHotseat().getLayout());
3502        }
3503        return layouts;
3504    }
3505
3506    /**
3507     * We should only use this to search for specific children.  Do not use this method to modify
3508     * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
3509     * the hotseat and workspace pages
3510     */
3511    ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
3512        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3513                new ArrayList<ShortcutAndWidgetContainer>();
3514        int screenCount = getChildCount();
3515        for (int screen = 0; screen < screenCount; screen++) {
3516            childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
3517        }
3518        if (mLauncher.getHotseat() != null) {
3519            childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
3520        }
3521        return childrenLayouts;
3522    }
3523
3524    public Folder getFolderForTag(Object tag) {
3525        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3526                getAllShortcutAndWidgetContainers();
3527        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
3528            int count = layout.getChildCount();
3529            for (int i = 0; i < count; i++) {
3530                View child = layout.getChildAt(i);
3531                if (child instanceof Folder) {
3532                    Folder f = (Folder) child;
3533                    if (f.getInfo() == tag && f.getInfo().opened) {
3534                        return f;
3535                    }
3536                }
3537            }
3538        }
3539        return null;
3540    }
3541
3542    public View getViewForTag(Object tag) {
3543        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3544                getAllShortcutAndWidgetContainers();
3545        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
3546            int count = layout.getChildCount();
3547            for (int i = 0; i < count; i++) {
3548                View child = layout.getChildAt(i);
3549                if (child.getTag() == tag) {
3550                    return child;
3551                }
3552            }
3553        }
3554        return null;
3555    }
3556
3557    void clearDropTargets() {
3558        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3559                getAllShortcutAndWidgetContainers();
3560        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
3561            int childCount = layout.getChildCount();
3562            for (int j = 0; j < childCount; j++) {
3563                View v = layout.getChildAt(j);
3564                if (v instanceof DropTarget) {
3565                    mDragController.removeDropTarget((DropTarget) v);
3566                }
3567            }
3568        }
3569    }
3570
3571    void removeItems(final ArrayList<ApplicationInfo> apps) {
3572        final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext());
3573
3574        final HashSet<String> packageNames = new HashSet<String>();
3575        final int appCount = apps.size();
3576        for (int i = 0; i < appCount; i++) {
3577            packageNames.add(apps.get(i).componentName.getPackageName());
3578        }
3579
3580        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
3581        for (final CellLayout layoutParent: cellLayouts) {
3582            final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
3583
3584            // Avoid ANRs by treating each screen separately
3585            post(new Runnable() {
3586                public void run() {
3587                    final ArrayList<View> childrenToRemove = new ArrayList<View>();
3588                    childrenToRemove.clear();
3589
3590                    int childCount = layout.getChildCount();
3591                    for (int j = 0; j < childCount; j++) {
3592                        final View view = layout.getChildAt(j);
3593                        Object tag = view.getTag();
3594
3595                        if (tag instanceof ShortcutInfo) {
3596                            final ShortcutInfo info = (ShortcutInfo) tag;
3597                            final Intent intent = info.intent;
3598                            final ComponentName name = intent.getComponent();
3599
3600                            if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
3601                                for (String packageName: packageNames) {
3602                                    if (packageName.equals(name.getPackageName())) {
3603                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
3604                                        childrenToRemove.add(view);
3605                                    }
3606                                }
3607                            }
3608                        } else if (tag instanceof FolderInfo) {
3609                            final FolderInfo info = (FolderInfo) tag;
3610                            final ArrayList<ShortcutInfo> contents = info.contents;
3611                            final int contentsCount = contents.size();
3612                            final ArrayList<ShortcutInfo> appsToRemoveFromFolder =
3613                                    new ArrayList<ShortcutInfo>();
3614
3615                            for (int k = 0; k < contentsCount; k++) {
3616                                final ShortcutInfo appInfo = contents.get(k);
3617                                final Intent intent = appInfo.intent;
3618                                final ComponentName name = intent.getComponent();
3619
3620                                if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
3621                                    for (String packageName: packageNames) {
3622                                        if (packageName.equals(name.getPackageName())) {
3623                                            appsToRemoveFromFolder.add(appInfo);
3624                                        }
3625                                    }
3626                                }
3627                            }
3628                            for (ShortcutInfo item: appsToRemoveFromFolder) {
3629                                info.remove(item);
3630                                LauncherModel.deleteItemFromDatabase(mLauncher, item);
3631                            }
3632                        } else if (tag instanceof LauncherAppWidgetInfo) {
3633                            final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag;
3634                            final AppWidgetProviderInfo provider =
3635                                    widgets.getAppWidgetInfo(info.appWidgetId);
3636                            if (provider != null) {
3637                                for (String packageName: packageNames) {
3638                                    if (packageName.equals(provider.provider.getPackageName())) {
3639                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
3640                                        childrenToRemove.add(view);
3641                                    }
3642                                }
3643                            }
3644                        }
3645                    }
3646
3647                    childCount = childrenToRemove.size();
3648                    for (int j = 0; j < childCount; j++) {
3649                        View child = childrenToRemove.get(j);
3650                        // Note: We can not remove the view directly from CellLayoutChildren as this
3651                        // does not re-mark the spaces as unoccupied.
3652                        layoutParent.removeViewInLayout(child);
3653                        if (child instanceof DropTarget) {
3654                            mDragController.removeDropTarget((DropTarget)child);
3655                        }
3656                    }
3657
3658                    if (childCount > 0) {
3659                        layout.requestLayout();
3660                        layout.invalidate();
3661                    }
3662                }
3663            });
3664        }
3665    }
3666
3667    void updateShortcuts(ArrayList<ApplicationInfo> apps) {
3668        ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers();
3669        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
3670            int childCount = layout.getChildCount();
3671            for (int j = 0; j < childCount; j++) {
3672                final View view = layout.getChildAt(j);
3673                Object tag = view.getTag();
3674                if (tag instanceof ShortcutInfo) {
3675                    ShortcutInfo info = (ShortcutInfo) tag;
3676                    // We need to check for ACTION_MAIN otherwise getComponent() might
3677                    // return null for some shortcuts (for instance, for shortcuts to
3678                    // web pages.)
3679                    final Intent intent = info.intent;
3680                    final ComponentName name = intent.getComponent();
3681                    if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
3682                            Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
3683                        final int appCount = apps.size();
3684                        for (int k = 0; k < appCount; k++) {
3685                            ApplicationInfo app = apps.get(k);
3686                            if (app.componentName.equals(name)) {
3687                                BubbleTextView shortcut = (BubbleTextView) view;
3688                                info.updateIcon(mIconCache);
3689                                info.title = app.title.toString();
3690                                shortcut.applyFromShortcutInfo(info, mIconCache);
3691                            }
3692                        }
3693                    }
3694                }
3695            }
3696        }
3697    }
3698
3699    void moveToDefaultScreen(boolean animate) {
3700        if (!isSmall()) {
3701            if (animate) {
3702                snapToPage(mDefaultPage);
3703            } else {
3704                setCurrentPage(mDefaultPage);
3705            }
3706        }
3707        getChildAt(mDefaultPage).requestFocus();
3708    }
3709
3710    @Override
3711    public void syncPages() {
3712    }
3713
3714    @Override
3715    public void syncPageItems(int page, boolean immediate) {
3716    }
3717
3718    @Override
3719    protected String getCurrentPageDescription() {
3720        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
3721        return String.format(getContext().getString(R.string.workspace_scroll_format),
3722                page + 1, getChildCount());
3723    }
3724
3725    public void getLocationInDragLayer(int[] loc) {
3726        mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
3727    }
3728
3729    void setFadeForOverScroll(float fade) {
3730        if (!isScrollingIndicatorEnabled()) return;
3731
3732        mOverscrollFade = fade;
3733        float reducedFade = 0.5f + 0.5f * (1 - fade);
3734        final ViewGroup parent = (ViewGroup) getParent();
3735        final ImageView qsbDivider = (ImageView) (parent.findViewById(R.id.qsb_divider));
3736        final ImageView dockDivider = (ImageView) (parent.findViewById(R.id.dock_divider));
3737        final View scrollIndicator = getScrollingIndicator();
3738
3739        cancelScrollingIndicatorAnimations();
3740        if (qsbDivider != null) qsbDivider.setAlpha(reducedFade);
3741        if (dockDivider != null) dockDivider.setAlpha(reducedFade);
3742        scrollIndicator.setAlpha(1 - fade);
3743    }
3744}
3745