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