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