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