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