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