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