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