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