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