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