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