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