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