Workspace.java revision 55f57c49cf333a6015befe38131ef8a78a091171
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.launcher2;
18
19import android.animation.Animator;
20import android.animation.Animator.AnimatorListener;
21import android.animation.AnimatorListenerAdapter;
22import android.animation.AnimatorSet;
23import android.animation.ObjectAnimator;
24import android.animation.TimeInterpolator;
25import android.animation.ValueAnimator;
26import android.animation.ValueAnimator.AnimatorUpdateListener;
27import android.app.AlertDialog;
28import android.app.WallpaperManager;
29import android.appwidget.AppWidgetManager;
30import android.appwidget.AppWidgetProviderInfo;
31import android.content.ClipData;
32import android.content.ClipDescription;
33import android.content.ComponentName;
34import android.content.Context;
35import android.content.Intent;
36import android.content.res.Resources;
37import android.content.res.TypedArray;
38import android.graphics.Bitmap;
39import android.graphics.Camera;
40import android.graphics.Canvas;
41import android.graphics.Matrix;
42import android.graphics.Paint;
43import android.graphics.Point;
44import android.graphics.PorterDuff;
45import android.graphics.Rect;
46import android.graphics.RectF;
47import android.graphics.Region.Op;
48import android.graphics.drawable.Drawable;
49import android.os.IBinder;
50import android.os.Parcelable;
51import android.util.AttributeSet;
52import android.util.DisplayMetrics;
53import android.util.Log;
54import android.util.Pair;
55import android.view.Display;
56import android.view.DragEvent;
57import android.view.MotionEvent;
58import android.view.View;
59import android.view.ViewGroup;
60import android.view.animation.DecelerateInterpolator;
61import android.widget.ImageView;
62import android.widget.TextView;
63import android.widget.Toast;
64
65import com.android.launcher.R;
66import com.android.launcher2.FolderIcon.FolderRingAnimator;
67import com.android.launcher2.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
68
69import java.util.ArrayList;
70import java.util.HashSet;
71import java.util.List;
72
73/**
74 * The workspace is a wide area with a wallpaper and a finite number of pages.
75 * Each page contains a number of icons, folders or widgets the user can
76 * interact with. A workspace is meant to be used with a fixed width only.
77 */
78public class Workspace extends SmoothPagedView
79        implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
80        DragController.DragListener {
81    @SuppressWarnings({"UnusedDeclaration"})
82    private static final String TAG = "Launcher.Workspace";
83
84    // Y rotation to apply to the workspace screens
85    private static final float WORKSPACE_ROTATION = 12.5f;
86    private static final float WORKSPACE_OVERSCROLL_ROTATION = 24f;
87    private static float CAMERA_DISTANCE = 6500;
88
89    private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
90    private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
91    private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
92
93    private static final int BACKGROUND_FADE_OUT_DURATION = 350;
94    private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
95
96    // These animators are used to fade the children's outlines
97    private ObjectAnimator mChildrenOutlineFadeInAnimation;
98    private ObjectAnimator mChildrenOutlineFadeOutAnimation;
99    private float mChildrenOutlineAlpha = 0;
100
101    // These properties refer to the background protection gradient used for AllApps and Customize
102    private ValueAnimator mBackgroundFadeInAnimation;
103    private ValueAnimator mBackgroundFadeOutAnimation;
104    private Drawable mBackground;
105    boolean mDrawBackground = true;
106    private float mBackgroundAlpha = 0;
107    private float mOverScrollMaxBackgroundAlpha = 0.0f;
108    private int mOverScrollPageIndex = -1;
109    private AnimatorSet mDividerAnimator;
110
111    private float mWallpaperScrollRatio = 1.0f;
112
113    private final WallpaperManager mWallpaperManager;
114    private IBinder mWindowToken;
115    private static final float WALLPAPER_SCREENS_SPAN = 2f;
116
117    private int mDefaultPage;
118
119    /**
120     * CellInfo for the cell that is currently being dragged
121     */
122    private CellLayout.CellInfo mDragInfo;
123
124    /**
125     * Target drop area calculated during last acceptDrop call.
126     */
127    private int[] mTargetCell = new int[2];
128
129    /**
130     * The CellLayout that is currently being dragged over
131     */
132    private CellLayout mDragTargetLayout = null;
133
134    private Launcher mLauncher;
135    private IconCache mIconCache;
136    private DragController mDragController;
137
138    // These are temporary variables to prevent having to allocate a new object just to
139    // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
140    private int[] mTempCell = new int[2];
141    private int[] mTempEstimate = new int[2];
142    private float[] mDragViewVisualCenter = new float[2];
143    private float[] mTempDragCoordinates = new float[2];
144    private float[] mTempCellLayoutCenterCoordinates = new float[2];
145    private float[] mTempDragBottomRightCoordinates = new float[2];
146    private Matrix mTempInverseMatrix = new Matrix();
147
148    private SpringLoadedDragController mSpringLoadedDragController;
149    private float mSpringLoadedShrinkFactor;
150
151    private static final int DEFAULT_CELL_COUNT_X = 4;
152    private static final int DEFAULT_CELL_COUNT_Y = 4;
153
154    // State variable that indicates whether the pages are small (ie when you're
155    // in all apps or customize mode)
156
157    enum State { NORMAL, SPRING_LOADED, SMALL };
158    private State mState = State.NORMAL;
159    private boolean mIsSwitchingState = false;
160    private boolean mSwitchStateAfterFirstLayout = false;
161    private State mStateAfterFirstLayout;
162
163    private AnimatorSet mAnimator;
164    private AnimatorListener mChangeStateAnimationListener;
165
166    boolean mAnimatingViewIntoPlace = false;
167    boolean mIsDragOccuring = false;
168    boolean mChildrenLayersEnabled = true;
169
170    /** Is the user is dragging an item near the edge of a page? */
171    private boolean mInScrollArea = false;
172
173    private final HolographicOutlineHelper mOutlineHelper = new HolographicOutlineHelper();
174    private Bitmap mDragOutline = null;
175    private final Rect mTempRect = new Rect();
176    private final int[] mTempXY = new int[2];
177    private int mDragViewMultiplyColor;
178
179    // Paint used to draw external drop outline
180    private final Paint mExternalDragOutlinePaint = new Paint();
181
182    // Camera and Matrix used to determine the final position of a neighboring CellLayout
183    private final Matrix mMatrix = new Matrix();
184    private final Camera mCamera = new Camera();
185    private final float mTempFloat2[] = new float[2];
186
187    enum WallpaperVerticalOffset { TOP, MIDDLE, BOTTOM };
188    int mWallpaperWidth;
189    int mWallpaperHeight;
190    WallpaperOffsetInterpolator mWallpaperOffset;
191    boolean mUpdateWallpaperOffsetImmediately = false;
192    private Runnable mDelayedResizeRunnable;
193    private int mDisplayWidth;
194    private int mDisplayHeight;
195    private int mWallpaperTravelWidth;
196
197    // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
198    private static final int FOLDER_CREATION_TIMEOUT = 250;
199    private final Alarm mFolderCreationAlarm = new Alarm();
200    private FolderRingAnimator mDragFolderRingAnimator = null;
201    private View mLastDragOverView = null;
202    private boolean mCreateUserFolderOnDrop = false;
203
204    // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
205    private float mXDown;
206    private float mYDown;
207    final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
208    final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
209    final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
210
211    // These variables are used for storing the initial and final values during workspace animations
212    private int mSavedScrollX;
213    private float mSavedRotationY;
214    private float mSavedTranslationX;
215    private float mCurrentScaleX;
216    private float mCurrentScaleY;
217    private float mCurrentRotationY;
218    private float mCurrentTranslationX;
219    private float mCurrentTranslationY;
220    private float[] mOldTranslationXs;
221    private float[] mOldTranslationYs;
222    private float[] mOldScaleXs;
223    private float[] mOldScaleYs;
224    private float[] mOldBackgroundAlphas;
225    private float[] mOldBackgroundAlphaMultipliers;
226    private float[] mOldAlphas;
227    private float[] mOldRotationYs;
228    private float[] mNewTranslationXs;
229    private float[] mNewTranslationYs;
230    private float[] mNewScaleXs;
231    private float[] mNewScaleYs;
232    private float[] mNewBackgroundAlphas;
233    private float[] mNewBackgroundAlphaMultipliers;
234    private float[] mNewAlphas;
235    private float[] mNewRotationYs;
236    private float mTransitionProgress;
237
238    /**
239     * Used to inflate the Workspace from XML.
240     *
241     * @param context The application's context.
242     * @param attrs The attributes set containing the Workspace's customization values.
243     */
244    public Workspace(Context context, AttributeSet attrs) {
245        this(context, attrs, 0);
246    }
247
248    /**
249     * Used to inflate the Workspace from XML.
250     *
251     * @param context The application's context.
252     * @param attrs The attributes set containing the Workspace's customization values.
253     * @param defStyle Unused.
254     */
255    public Workspace(Context context, AttributeSet attrs, int defStyle) {
256        super(context, attrs, defStyle);
257        mContentIsRefreshable = false;
258
259        // With workspace, data is available straight from the get-go
260        setDataIsReady();
261
262        mFadeInAdjacentScreens =
263            getResources().getBoolean(R.bool.config_workspaceFadeAdjacentScreens);
264        mWallpaperManager = WallpaperManager.getInstance(context);
265
266        int cellCountX = DEFAULT_CELL_COUNT_X;
267        int cellCountY = DEFAULT_CELL_COUNT_Y;
268
269        TypedArray a = context.obtainStyledAttributes(attrs,
270                R.styleable.Workspace, defStyle, 0);
271
272        final Resources res = context.getResources();
273        if (LauncherApplication.isScreenLarge()) {
274            // Determine number of rows/columns dynamically
275            // TODO: This code currently fails on tablets with an aspect ratio < 1.3.
276            // Around that ratio we should make cells the same size in portrait and
277            // landscape
278            TypedArray actionBarSizeTypedArray =
279                context.obtainStyledAttributes(new int[] { android.R.attr.actionBarSize });
280            final float actionBarHeight = actionBarSizeTypedArray.getDimension(0, 0f);
281            final float systemBarHeight = res.getDimension(R.dimen.status_bar_height);
282            final float smallestScreenDim = res.getConfiguration().smallestScreenWidthDp;
283
284            cellCountX = 1;
285            while (CellLayout.widthInPortrait(res, cellCountX + 1) <= smallestScreenDim) {
286                cellCountX++;
287            }
288
289            cellCountY = 1;
290            while (actionBarHeight + CellLayout.heightInLandscape(res, cellCountY + 1)
291                <= smallestScreenDim - systemBarHeight) {
292                cellCountY++;
293            }
294        }
295
296        mSpringLoadedShrinkFactor =
297            res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
298        mDragViewMultiplyColor = res.getColor(R.color.drag_view_multiply_color);
299
300        // if the value is manually specified, use that instead
301        cellCountX = a.getInt(R.styleable.Workspace_cellCountX, cellCountX);
302        cellCountY = a.getInt(R.styleable.Workspace_cellCountY, cellCountY);
303        mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
304        a.recycle();
305
306        LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY);
307        setHapticFeedbackEnabled(false);
308
309        mLauncher = (Launcher) context;
310        initWorkspace();
311
312        // Disable multitouch across the workspace/all apps/customize tray
313        setMotionEventSplittingEnabled(true);
314    }
315
316    public void buildPageHardwareLayers() {
317        if (getWindowToken() != null) {
318            final int childCount = getChildCount();
319            for (int i = 0; i < childCount; i++) {
320                CellLayout cl = (CellLayout) getChildAt(i);
321                cl.buildChildrenLayer();
322            }
323        }
324    }
325
326    public void onDragStart(DragSource source, Object info, int dragAction) {
327        mIsDragOccuring = true;
328        updateChildrenLayersEnabled();
329        mLauncher.lockScreenOrientationOnLargeUI();
330    }
331
332    public void onDragEnd() {
333        mIsDragOccuring = false;
334        updateChildrenLayersEnabled();
335        mLauncher.unlockScreenOrientationOnLargeUI();
336    }
337
338    /**
339     * Initializes various states for this workspace.
340     */
341    protected void initWorkspace() {
342        Context context = getContext();
343        mCurrentPage = mDefaultPage;
344        Launcher.setScreen(mCurrentPage);
345        LauncherApplication app = (LauncherApplication)context.getApplicationContext();
346        mIconCache = app.getIconCache();
347        mExternalDragOutlinePaint.setAntiAlias(true);
348        setWillNotDraw(false);
349        setChildrenDrawnWithCacheEnabled(true);
350
351        try {
352            final Resources res = getResources();
353            mBackground = res.getDrawable(R.drawable.apps_customize_bg);
354        } catch (Resources.NotFoundException e) {
355            // In this case, we will skip drawing background protection
356        }
357
358        mChangeStateAnimationListener = new AnimatorListenerAdapter() {
359            @Override
360            public void onAnimationStart(Animator animation) {
361                mIsSwitchingState = true;
362            }
363
364            @Override
365            public void onAnimationEnd(Animator animation) {
366                mIsSwitchingState = false;
367                mWallpaperOffset.setOverrideHorizontalCatchupConstant(false);
368                mAnimator = null;
369                updateChildrenLayersEnabled();
370            }
371        };
372
373        mSnapVelocity = 600;
374        mWallpaperOffset = new WallpaperOffsetInterpolator();
375        Display display = mLauncher.getWindowManager().getDefaultDisplay();
376        mDisplayWidth = display.getWidth();
377        mDisplayHeight = display.getHeight();
378        mWallpaperTravelWidth = (int) (mDisplayWidth *
379                wallpaperTravelToScreenWidthRatio(mDisplayWidth, mDisplayHeight));
380    }
381
382    @Override
383    protected int getScrollMode() {
384        return SmoothPagedView.X_LARGE_MODE;
385    }
386
387    @Override
388    protected void onViewAdded(View child) {
389        super.onViewAdded(child);
390        if (!(child instanceof CellLayout)) {
391            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
392        }
393        CellLayout cl = ((CellLayout) child);
394        cl.setOnInterceptTouchListener(this);
395        cl.setClickable(true);
396        cl.enableHardwareLayers();
397    }
398
399    /**
400     * @return The open folder on the current screen, or null if there is none
401     */
402    Folder getOpenFolder() {
403        DragLayer dragLayer = mLauncher.getDragLayer();
404        int count = dragLayer.getChildCount();
405        for (int i = 0; i < count; i++) {
406            View child = dragLayer.getChildAt(i);
407            if (child instanceof Folder) {
408                Folder folder = (Folder) child;
409                if (folder.getInfo().opened)
410                    return folder;
411            }
412        }
413        return null;
414    }
415
416    boolean isTouchActive() {
417        return mTouchState != TOUCH_STATE_REST;
418    }
419
420    /**
421     * Adds the specified child in the specified screen. The position and dimension of
422     * the child are defined by x, y, spanX and spanY.
423     *
424     * @param child The child to add in one of the workspace's screens.
425     * @param screen The screen in which to add the child.
426     * @param x The X position of the child in the screen's grid.
427     * @param y The Y position of the child in the screen's grid.
428     * @param spanX The number of cells spanned horizontally by the child.
429     * @param spanY The number of cells spanned vertically by the child.
430     */
431    void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY) {
432        addInScreen(child, container, screen, x, y, spanX, spanY, false);
433    }
434
435    /**
436     * Adds the specified child in the specified screen. The position and dimension of
437     * the child are defined by x, y, spanX and spanY.
438     *
439     * @param child The child to add in one of the workspace's screens.
440     * @param screen The screen in which to add the child.
441     * @param x The X position of the child in the screen's grid.
442     * @param y The Y position of the child in the screen's grid.
443     * @param spanX The number of cells spanned horizontally by the child.
444     * @param spanY The number of cells spanned vertically by the child.
445     * @param insert When true, the child is inserted at the beginning of the children list.
446     */
447    void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY,
448            boolean insert) {
449        if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
450            if (screen < 0 || screen >= getChildCount()) {
451                Log.e(TAG, "The screen must be >= 0 and < " + getChildCount()
452                    + " (was " + screen + "); skipping child");
453                return;
454            }
455        }
456
457        final CellLayout layout;
458        if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
459            layout = mLauncher.getHotseat().getLayout();
460            child.setOnKeyListener(null);
461
462            // Hide folder title in the hotseat
463            if (child instanceof FolderIcon) {
464                ((FolderIcon) child).setTextVisible(false);
465            }
466
467            if (screen < 0) {
468                screen = mLauncher.getHotseat().getOrderInHotseat(x, y);
469            } else {
470                // Note: We do this to ensure that the hotseat is always laid out in the orientation
471                // of the hotseat in order regardless of which orientation they were added
472                x = mLauncher.getHotseat().getCellXFromOrder(screen);
473                y = mLauncher.getHotseat().getCellYFromOrder(screen);
474            }
475        } else {
476            // Show folder title if not in the hotseat
477            if (child instanceof FolderIcon) {
478                ((FolderIcon) child).setTextVisible(true);
479            }
480
481            layout = (CellLayout) getChildAt(screen);
482            child.setOnKeyListener(new IconKeyEventListener());
483        }
484
485        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
486        if (lp == null) {
487            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
488        } else {
489            lp.cellX = x;
490            lp.cellY = y;
491            lp.cellHSpan = spanX;
492            lp.cellVSpan = spanY;
493        }
494
495        if (spanX < 0 && spanY < 0) {
496            lp.isLockedToGrid = false;
497        }
498
499        // Get the canonical child id to uniquely represent this view in this screen
500        int childId = LauncherModel.getCellLayoutChildId(container, screen, x, y, spanX, spanY);
501        boolean markCellsAsOccupied = !(child instanceof Folder);
502        if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
503            // TODO: This branch occurs when the workspace is adding views
504            // outside of the defined grid
505            // maybe we should be deleting these items from the LauncherModel?
506            Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
507        }
508
509        if (!(child instanceof Folder)) {
510            child.setHapticFeedbackEnabled(false);
511            child.setOnLongClickListener(mLongClickListener);
512        }
513        if (child instanceof DropTarget) {
514            mDragController.addDropTarget((DropTarget) child);
515        }
516    }
517
518    /**
519     * Check if the point (x, y) hits a given page.
520     */
521    private boolean hitsPage(int index, float x, float y) {
522        final View page = getChildAt(index);
523        if (page != null) {
524            float[] localXY = { x, y };
525            mapPointFromSelfToChild(page, localXY);
526            return (localXY[0] >= 0 && localXY[0] < page.getWidth()
527                    && localXY[1] >= 0 && localXY[1] < page.getHeight());
528        }
529        return false;
530    }
531
532    @Override
533    protected boolean hitsPreviousPage(float x, float y) {
534        // mNextPage is set to INVALID_PAGE whenever we are stationary.
535        // Calculating "next page" this way ensures that you scroll to whatever page you tap on
536        final int current = (mNextPage == INVALID_PAGE) ? mCurrentPage : mNextPage;
537
538        // Only allow tap to next page on large devices, where there's significant margin outside
539        // the active workspace
540        return LauncherApplication.isScreenLarge() && hitsPage(current - 1, x, y);
541    }
542
543    @Override
544    protected boolean hitsNextPage(float x, float y) {
545        // mNextPage is set to INVALID_PAGE whenever we are stationary.
546        // Calculating "next page" this way ensures that you scroll to whatever page you tap on
547        final int current = (mNextPage == INVALID_PAGE) ? mCurrentPage : mNextPage;
548
549        // Only allow tap to next page on large devices, where there's significant margin outside
550        // the active workspace
551        return LauncherApplication.isScreenLarge() && hitsPage(current + 1, x, y);
552    }
553
554    /**
555     * Called directly from a CellLayout (not by the framework), after we've been added as a
556     * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
557     * that it should intercept touch events, which is not something that is normally supported.
558     */
559    @Override
560    public boolean onTouch(View v, MotionEvent event) {
561        return (isSmall() || mIsSwitchingState);
562    }
563
564    public boolean isSwitchingState() {
565        return mIsSwitchingState;
566    }
567
568    protected void onWindowVisibilityChanged (int visibility) {
569        mLauncher.onWindowVisibilityChanged(visibility);
570    }
571
572    @Override
573    public boolean dispatchUnhandledMove(View focused, int direction) {
574        if (isSmall() || mIsSwitchingState) {
575            // when the home screens are shrunken, shouldn't allow side-scrolling
576            return false;
577        }
578        return super.dispatchUnhandledMove(focused, direction);
579    }
580
581    @Override
582    public boolean onInterceptTouchEvent(MotionEvent ev) {
583        switch (ev.getAction() & MotionEvent.ACTION_MASK) {
584        case MotionEvent.ACTION_DOWN:
585            mXDown = ev.getX();
586            mYDown = ev.getY();
587            break;
588        case MotionEvent.ACTION_POINTER_UP:
589        case MotionEvent.ACTION_UP:
590            if (mTouchState == TOUCH_STATE_REST) {
591                final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
592                if (!currentPage.lastDownOnOccupiedCell()) {
593                    onWallpaperTap(ev);
594                }
595            }
596        }
597        return super.onInterceptTouchEvent(ev);
598    }
599
600    @Override
601    protected void determineScrollingStart(MotionEvent ev) {
602        if (!isSmall() && !mIsSwitchingState) {
603            float deltaX = Math.abs(ev.getX() - mXDown);
604            float deltaY = Math.abs(ev.getY() - mYDown);
605
606            if (Float.compare(deltaX, 0f) == 0) return;
607
608            float slope = deltaY / deltaX;
609            float theta = (float) Math.atan(slope);
610
611            if (deltaX > mTouchSlop || deltaY > mTouchSlop) {
612                cancelCurrentPageLongPress();
613            }
614
615            if (theta > MAX_SWIPE_ANGLE) {
616                // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
617                return;
618            } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
619                // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
620                // increase the touch slop to make it harder to begin scrolling the workspace. This
621                // results in vertically scrolling widgets to more easily. The higher the angle, the
622                // more we increase touch slop.
623                theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
624                float extraRatio = (float)
625                        Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
626                super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
627            } else {
628                // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
629                super.determineScrollingStart(ev);
630            }
631        }
632    }
633
634    @Override
635    protected boolean isScrollingIndicatorEnabled() {
636        return mState != State.SPRING_LOADED;
637    }
638
639    protected void onPageBeginMoving() {
640        super.onPageBeginMoving();
641
642        if (isHardwareAccelerated()) {
643            updateChildrenLayersEnabled();
644        } else {
645            if (mNextPage != INVALID_PAGE) {
646                // we're snapping to a particular screen
647                enableChildrenCache(mCurrentPage, mNextPage);
648            } else {
649                // this is when user is actively dragging a particular screen, they might
650                // swipe it either left or right (but we won't advance by more than one screen)
651                enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
652            }
653        }
654
655        // Only show page outlines as we pan if we are on large screen
656        if (LauncherApplication.isScreenLarge()) {
657            showOutlines();
658        }
659    }
660
661    protected void onPageEndMoving() {
662        super.onPageEndMoving();
663
664        if (isHardwareAccelerated()) {
665            updateChildrenLayersEnabled();
666        } else {
667            clearChildrenCache();
668        }
669
670        // Hide the outlines, as long as we're not dragging
671        if (!mDragController.dragging()) {
672            // Only hide page outlines as we pan if we are on large screen
673            if (LauncherApplication.isScreenLarge()) {
674                hideOutlines();
675            }
676        }
677        mOverScrollMaxBackgroundAlpha = 0.0f;
678        mOverScrollPageIndex = -1;
679
680        if (mDelayedResizeRunnable != null) {
681            mDelayedResizeRunnable.run();
682            mDelayedResizeRunnable = null;
683        }
684    }
685
686    @Override
687    protected void notifyPageSwitchListener() {
688        super.notifyPageSwitchListener();
689        Launcher.setScreen(mCurrentPage);
690    };
691
692    // As a ratio of screen height, the total distance we want the parallax effect to span
693    // horizontally
694    private float wallpaperTravelToScreenWidthRatio(int width, int height) {
695        float aspectRatio = width / (float) height;
696
697        // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
698        // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
699        // We will use these two data points to extrapolate how much the wallpaper parallax effect
700        // to span (ie travel) at any aspect ratio:
701
702        final float ASPECT_RATIO_LANDSCAPE = 16/10f;
703        final float ASPECT_RATIO_PORTRAIT = 10/16f;
704        final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
705        final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;
706
707        // To find out the desired width at different aspect ratios, we use the following two
708        // formulas, where the coefficient on x is the aspect ratio (width/height):
709        //   (16/10)x + y = 1.5
710        //   (10/16)x + y = 1.2
711        // We solve for x and y and end up with a final formula:
712        final float x =
713            (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
714            (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
715        final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
716        return x * aspectRatio + y;
717    }
718
719    // The range of scroll values for Workspace
720    private int getScrollRange() {
721        return getChildOffset(getChildCount() - 1) - getChildOffset(0);
722    }
723
724    protected void setWallpaperDimension() {
725        DisplayMetrics displayMetrics = new DisplayMetrics();
726        mLauncher.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
727        final int maxDim = Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels);
728        final int minDim = Math.min(displayMetrics.widthPixels, displayMetrics.heightPixels);
729
730        // We need to ensure that there is enough extra space in the wallpaper for the intended
731        // parallax effects
732        if (LauncherApplication.isScreenLarge()) {
733            mWallpaperWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));
734            mWallpaperHeight = maxDim;
735        } else {
736            mWallpaperWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
737            mWallpaperHeight = maxDim;
738        }
739        new Thread("setWallpaperDimension") {
740            public void run() {
741                mWallpaperManager.suggestDesiredDimensions(mWallpaperWidth, mWallpaperHeight);
742            }
743        }.start();
744    }
745
746    public void setVerticalWallpaperOffset(float offset) {
747        mWallpaperOffset.setFinalY(offset);
748    }
749    public float getVerticalWallpaperOffset() {
750        return mWallpaperOffset.getCurrY();
751    }
752    public void setHorizontalWallpaperOffset(float offset) {
753        mWallpaperOffset.setFinalX(offset);
754    }
755    public float getHorizontalWallpaperOffset() {
756        return mWallpaperOffset.getCurrX();
757    }
758
759    private float wallpaperOffsetForCurrentScroll() {
760        // The wallpaper travel width is how far, from left to right, the wallpaper will move
761        // at this orientation. On tablets in portrait mode we don't move all the way to the
762        // edges of the wallpaper, or otherwise the parallax effect would be too strong.
763        int wallpaperTravelWidth = mWallpaperWidth;
764        if (LauncherApplication.isScreenLarge()) {
765            wallpaperTravelWidth = mWallpaperTravelWidth;
766        }
767
768        // Set wallpaper offset steps (1 / (number of screens - 1))
769        mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 1.0f);
770
771        // For the purposes of computing the scrollRange and overScrollOffset, we assume
772        // that mLayoutScale is 1. This means that when we're in spring-loaded mode,
773        // there's no discrepancy between the wallpaper offset for a given page.
774        float layoutScale = mLayoutScale;
775        mLayoutScale = 1f;
776        int scrollRange = getScrollRange();
777
778        // Again, we adjust the wallpaper offset to be consistent between values of mLayoutScale
779        float adjustedScrollX = Math.max(0, Math.min(mScrollX, mMaxScrollX));
780        adjustedScrollX *= mWallpaperScrollRatio;
781        mLayoutScale = layoutScale;
782
783        float scrollProgress =
784            adjustedScrollX / (float) scrollRange;
785        float offsetInDips = wallpaperTravelWidth * scrollProgress +
786            (mWallpaperWidth - wallpaperTravelWidth) / 2; // center it
787        float offset = offsetInDips / (float) mWallpaperWidth;
788        return offset;
789    }
790    private void syncWallpaperOffsetWithScroll() {
791        final boolean enableWallpaperEffects = isHardwareAccelerated();
792        if (enableWallpaperEffects) {
793            mWallpaperOffset.setFinalX(wallpaperOffsetForCurrentScroll());
794        }
795    }
796
797    public void updateWallpaperOffsetImmediately() {
798        mUpdateWallpaperOffsetImmediately = true;
799    }
800
801    private void updateWallpaperOffsets() {
802        boolean updateNow = false;
803        boolean keepUpdating = true;
804        if (mUpdateWallpaperOffsetImmediately) {
805            updateNow = true;
806            keepUpdating = false;
807            mWallpaperOffset.jumpToFinal();
808            mUpdateWallpaperOffsetImmediately = false;
809        } else {
810            updateNow = keepUpdating = mWallpaperOffset.computeScrollOffset();
811        }
812        if (updateNow) {
813            if (mWindowToken != null) {
814                mWallpaperManager.setWallpaperOffsets(mWindowToken,
815                        mWallpaperOffset.getCurrX(), mWallpaperOffset.getCurrY());
816            }
817        }
818        if (keepUpdating) {
819            fastInvalidate();
820        }
821    }
822
823    @Override
824    protected void updateCurrentPageScroll() {
825        super.updateCurrentPageScroll();
826        computeWallpaperScrollRatio(mCurrentPage);
827    }
828
829    @Override
830    protected void snapToPage(int whichPage) {
831        super.snapToPage(whichPage);
832        computeWallpaperScrollRatio(whichPage);
833    }
834
835    private void computeWallpaperScrollRatio(int page) {
836        // Here, we determine what the desired scroll would be with and without a layout scale,
837        // and compute a ratio between the two. This allows us to adjust the wallpaper offset
838        // as though there is no layout scale.
839        float layoutScale = mLayoutScale;
840        int scaled = getChildOffset(page) - getRelativeChildOffset(page);
841        mLayoutScale = 1.0f;
842        float unscaled = getChildOffset(page) - getRelativeChildOffset(page);
843        mLayoutScale = layoutScale;
844        if (scaled > 0) {
845            mWallpaperScrollRatio = (1.0f * unscaled) / scaled;
846        } else {
847            mWallpaperScrollRatio = 1f;
848        }
849    }
850
851    class WallpaperOffsetInterpolator {
852        float mFinalHorizontalWallpaperOffset = 0.0f;
853        float mFinalVerticalWallpaperOffset = 0.5f;
854        float mHorizontalWallpaperOffset = 0.0f;
855        float mVerticalWallpaperOffset = 0.5f;
856        long mLastWallpaperOffsetUpdateTime;
857        boolean mIsMovingFast;
858        boolean mOverrideHorizontalCatchupConstant;
859        float mHorizontalCatchupConstant = 0.35f;
860        float mVerticalCatchupConstant = 0.35f;
861
862        public WallpaperOffsetInterpolator() {
863        }
864
865        public void setOverrideHorizontalCatchupConstant(boolean override) {
866            mOverrideHorizontalCatchupConstant = override;
867        }
868
869        public void setHorizontalCatchupConstant(float f) {
870            mHorizontalCatchupConstant = f;
871        }
872
873        public void setVerticalCatchupConstant(float f) {
874            mVerticalCatchupConstant = f;
875        }
876
877        public boolean computeScrollOffset() {
878            if (Float.compare(mHorizontalWallpaperOffset, mFinalHorizontalWallpaperOffset) == 0 &&
879                    Float.compare(mVerticalWallpaperOffset, mFinalVerticalWallpaperOffset) == 0) {
880                mIsMovingFast = false;
881                return false;
882            }
883            boolean isLandscape = mDisplayWidth > mDisplayHeight;
884
885            long currentTime = System.currentTimeMillis();
886            long timeSinceLastUpdate = currentTime - mLastWallpaperOffsetUpdateTime;
887            timeSinceLastUpdate = Math.min((long) (1000/30f), timeSinceLastUpdate);
888            timeSinceLastUpdate = Math.max(1L, timeSinceLastUpdate);
889
890            float xdiff = Math.abs(mFinalHorizontalWallpaperOffset - mHorizontalWallpaperOffset);
891            if (!mIsMovingFast && xdiff > 0.07) {
892                mIsMovingFast = true;
893            }
894
895            float fractionToCatchUpIn1MsHorizontal;
896            if (mOverrideHorizontalCatchupConstant) {
897                fractionToCatchUpIn1MsHorizontal = mHorizontalCatchupConstant;
898            } else if (mIsMovingFast) {
899                fractionToCatchUpIn1MsHorizontal = isLandscape ? 0.5f : 0.75f;
900            } else {
901                // slow
902                fractionToCatchUpIn1MsHorizontal = isLandscape ? 0.27f : 0.5f;
903            }
904            float fractionToCatchUpIn1MsVertical = mVerticalCatchupConstant;
905
906            fractionToCatchUpIn1MsHorizontal /= 33f;
907            fractionToCatchUpIn1MsVertical /= 33f;
908
909            final float UPDATE_THRESHOLD = 0.00001f;
910            float hOffsetDelta = mFinalHorizontalWallpaperOffset - mHorizontalWallpaperOffset;
911            float vOffsetDelta = mFinalVerticalWallpaperOffset - mVerticalWallpaperOffset;
912            boolean jumpToFinalValue = Math.abs(hOffsetDelta) < UPDATE_THRESHOLD &&
913                Math.abs(vOffsetDelta) < UPDATE_THRESHOLD;
914
915            // Don't have any lag between workspace and wallpaper on non-large devices
916            if (!LauncherApplication.isScreenLarge() || jumpToFinalValue) {
917                mHorizontalWallpaperOffset = mFinalHorizontalWallpaperOffset;
918                mVerticalWallpaperOffset = mFinalVerticalWallpaperOffset;
919            } else {
920                float percentToCatchUpVertical =
921                    Math.min(1.0f, timeSinceLastUpdate * fractionToCatchUpIn1MsVertical);
922                float percentToCatchUpHorizontal =
923                    Math.min(1.0f, timeSinceLastUpdate * fractionToCatchUpIn1MsHorizontal);
924                mHorizontalWallpaperOffset += percentToCatchUpHorizontal * hOffsetDelta;
925                mVerticalWallpaperOffset += percentToCatchUpVertical * vOffsetDelta;
926            }
927
928            mLastWallpaperOffsetUpdateTime = System.currentTimeMillis();
929            return true;
930        }
931
932        public float getCurrX() {
933            return mHorizontalWallpaperOffset;
934        }
935
936        public float getFinalX() {
937            return mFinalHorizontalWallpaperOffset;
938        }
939
940        public float getCurrY() {
941            return mVerticalWallpaperOffset;
942        }
943
944        public float getFinalY() {
945            return mFinalVerticalWallpaperOffset;
946        }
947
948        public void setFinalX(float x) {
949            mFinalHorizontalWallpaperOffset = Math.max(0f, Math.min(x, 1.0f));
950        }
951
952        public void setFinalY(float y) {
953            mFinalVerticalWallpaperOffset = Math.max(0f, Math.min(y, 1.0f));
954        }
955
956        public void jumpToFinal() {
957            mHorizontalWallpaperOffset = mFinalHorizontalWallpaperOffset;
958            mVerticalWallpaperOffset = mFinalVerticalWallpaperOffset;
959        }
960    }
961
962    @Override
963    public void computeScroll() {
964        super.computeScroll();
965        syncWallpaperOffsetWithScroll();
966    }
967
968    void showOutlines() {
969        if (!isSmall() && !mIsSwitchingState) {
970            if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
971            if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
972            mChildrenOutlineFadeInAnimation = ObjectAnimator.ofFloat(this, "childrenOutlineAlpha", 1.0f);
973            mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
974            mChildrenOutlineFadeInAnimation.start();
975        }
976    }
977
978    void hideOutlines() {
979        if (!isSmall() && !mIsSwitchingState) {
980            if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
981            if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
982            mChildrenOutlineFadeOutAnimation = ObjectAnimator.ofFloat(this, "childrenOutlineAlpha", 0.0f);
983            mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
984            mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
985            mChildrenOutlineFadeOutAnimation.start();
986        }
987    }
988
989    public void showOutlinesTemporarily() {
990        if (!mIsPageMoving && !isTouchActive()) {
991            snapToPage(mCurrentPage);
992        }
993    }
994
995    public void setChildrenOutlineAlpha(float alpha) {
996        mChildrenOutlineAlpha = alpha;
997        for (int i = 0; i < getChildCount(); i++) {
998            CellLayout cl = (CellLayout) getChildAt(i);
999            cl.setBackgroundAlpha(alpha);
1000        }
1001    }
1002
1003    public float getChildrenOutlineAlpha() {
1004        return mChildrenOutlineAlpha;
1005    }
1006
1007    void disableBackground() {
1008        mDrawBackground = false;
1009    }
1010    void enableBackground() {
1011        mDrawBackground = true;
1012    }
1013
1014    private void animateBackgroundGradient(float finalAlpha, boolean animated) {
1015        if (mBackground == null) return;
1016        if (mBackgroundFadeInAnimation != null) {
1017            mBackgroundFadeInAnimation.cancel();
1018            mBackgroundFadeInAnimation = null;
1019        }
1020        if (mBackgroundFadeOutAnimation != null) {
1021            mBackgroundFadeOutAnimation.cancel();
1022            mBackgroundFadeOutAnimation = null;
1023        }
1024        float startAlpha = getBackgroundAlpha();
1025        if (finalAlpha != startAlpha) {
1026            if (animated) {
1027                mBackgroundFadeOutAnimation = ValueAnimator.ofFloat(startAlpha, finalAlpha);
1028                mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() {
1029                    public void onAnimationUpdate(ValueAnimator animation) {
1030                        setBackgroundAlpha(((Float) animation.getAnimatedValue()).floatValue());
1031                    }
1032                });
1033                mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
1034                mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
1035                mBackgroundFadeOutAnimation.start();
1036            } else {
1037                setBackgroundAlpha(finalAlpha);
1038            }
1039        }
1040    }
1041
1042    public void setBackgroundAlpha(float alpha) {
1043        if (alpha != mBackgroundAlpha) {
1044            mBackgroundAlpha = alpha;
1045            invalidate();
1046        }
1047    }
1048
1049    public float getBackgroundAlpha() {
1050        return mBackgroundAlpha;
1051    }
1052
1053    /**
1054     * Due to 3D transformations, if two CellLayouts are theoretically touching each other,
1055     * on the xy plane, when one is rotated along the y-axis, the gap between them is perceived
1056     * as being larger. This method computes what offset the rotated view should be translated
1057     * in order to minimize this perceived gap.
1058     * @param degrees Angle of the view
1059     * @param width Width of the view
1060     * @param height Height of the view
1061     * @return Offset to be used in a View.setTranslationX() call
1062     */
1063    private float getOffsetXForRotation(float degrees, int width, int height) {
1064        mMatrix.reset();
1065        mCamera.save();
1066        mCamera.rotateY(Math.abs(degrees));
1067        mCamera.getMatrix(mMatrix);
1068        mCamera.restore();
1069
1070        mMatrix.preTranslate(-width * 0.5f, -height * 0.5f);
1071        mMatrix.postTranslate(width * 0.5f, height * 0.5f);
1072        mTempFloat2[0] = width;
1073        mTempFloat2[1] = height;
1074        mMatrix.mapPoints(mTempFloat2);
1075        return (width - mTempFloat2[0]) * (degrees > 0.0f ? 1.0f : -1.0f);
1076    }
1077
1078    float backgroundAlphaInterpolator(float r) {
1079        float pivotA = 0.1f;
1080        float pivotB = 0.4f;
1081        if (r < pivotA) {
1082            return 0;
1083        } else if (r > pivotB) {
1084            return 1.0f;
1085        } else {
1086            return (r - pivotA)/(pivotB - pivotA);
1087        }
1088    }
1089
1090    float overScrollBackgroundAlphaInterpolator(float r) {
1091        float threshold = 0.08f;
1092
1093        if (r > mOverScrollMaxBackgroundAlpha) {
1094            mOverScrollMaxBackgroundAlpha = r;
1095        } else if (r < mOverScrollMaxBackgroundAlpha) {
1096            r = mOverScrollMaxBackgroundAlpha;
1097        }
1098
1099        return Math.min(r / threshold, 1.0f);
1100    }
1101
1102    private void screenScrolledLargeUI(int screenCenter) {
1103        for (int i = 0; i < getChildCount(); i++) {
1104            CellLayout cl = (CellLayout) getChildAt(i);
1105            if (cl != null) {
1106                float scrollProgress = getScrollProgress(screenCenter, cl, i);
1107                float rotation = WORKSPACE_ROTATION * scrollProgress;
1108                float translationX = getOffsetXForRotation(rotation, cl.getWidth(), cl.getHeight());
1109
1110                // If the current page (i) is being over scrolled, we use a different
1111                // set of rules for setting the background alpha multiplier.
1112                if (!isSmall()) {
1113                    if ((mScrollX < 0 && i == 0) || (mScrollX > mMaxScrollX &&
1114                            i == getChildCount() -1 )) {
1115                        cl.setBackgroundAlphaMultiplier(
1116                                overScrollBackgroundAlphaInterpolator(Math.abs(scrollProgress)));
1117                        mOverScrollPageIndex = i;
1118                    } else if (mOverScrollPageIndex != i) {
1119                        cl.setBackgroundAlphaMultiplier(
1120                                backgroundAlphaInterpolator(Math.abs(scrollProgress)));
1121                    }
1122                }
1123                cl.setFastTranslationX(translationX);
1124                cl.setFastRotationY(rotation);
1125                if (mFadeInAdjacentScreens && !isSmall()) {
1126                    float alpha = 1 - Math.abs(scrollProgress);
1127                    cl.setFastAlpha(alpha);
1128                }
1129                cl.fastInvalidate();
1130            }
1131        }
1132        invalidate();
1133    }
1134
1135    private void resetCellLayoutTransforms(CellLayout cl, boolean left) {
1136        cl.setTranslationX(0);
1137        cl.setRotationY(0);
1138        cl.setOverScrollAmount(0, left);
1139        cl.setPivotX(cl.getMeasuredWidth() / 2);
1140        cl.setPivotY(cl.getMeasuredHeight() / 2);
1141    }
1142
1143    private void screenScrolledStandardUI(int screenCenter) {
1144        if (mScrollX < 0 || mScrollX > mMaxScrollX) {
1145            int index = mScrollX < 0 ? 0 : getChildCount() - 1;
1146            CellLayout cl = (CellLayout) getChildAt(index);
1147            float scrollProgress = getScrollProgress(screenCenter, cl, index);
1148            cl.setOverScrollAmount(Math.abs(scrollProgress), index == 0);
1149            float translationX = index == 0 ? mScrollX : - (mMaxScrollX - mScrollX);
1150            float rotation = - WORKSPACE_OVERSCROLL_ROTATION * scrollProgress;
1151            cl.setCameraDistance(mDensity * CAMERA_DISTANCE);
1152            cl.setPivotX(cl.getMeasuredWidth() * (index == 0 ? 0.75f : 0.25f));
1153            cl.setTranslationX(translationX);
1154            cl.setRotationY(rotation);
1155        } else {
1156            // We don't want to mess with the translations during transitions
1157            if (!isSwitchingState()) {
1158                resetCellLayoutTransforms((CellLayout) getChildAt(0), true);
1159                resetCellLayoutTransforms((CellLayout) getChildAt(getChildCount() - 1), false);
1160            }
1161        }
1162    }
1163
1164    @Override
1165    protected void screenScrolled(int screenCenter) {
1166        if (LauncherApplication.isScreenLarge()) {
1167            // We don't call super.screenScrolled() here because we handle the adjacent pages alpha
1168            // ourselves (for efficiency), and there are no scrolling indicators to update.
1169            screenScrolledLargeUI(screenCenter);
1170        } else {
1171            super.screenScrolled(screenCenter);
1172            screenScrolledStandardUI(screenCenter);
1173        }
1174    }
1175
1176    @Override
1177    protected void overScroll(float amount) {
1178        if (LauncherApplication.isScreenLarge()) {
1179            dampedOverScroll(amount);
1180        } else {
1181            acceleratedOverScroll(amount);
1182        }
1183    }
1184
1185    protected void onAttachedToWindow() {
1186        super.onAttachedToWindow();
1187        mWindowToken = getWindowToken();
1188        computeScroll();
1189        mDragController.setWindowToken(mWindowToken);
1190    }
1191
1192    protected void onDetachedFromWindow() {
1193        mWindowToken = null;
1194    }
1195
1196    @Override
1197    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1198        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
1199            mUpdateWallpaperOffsetImmediately = true;
1200        }
1201        super.onLayout(changed, left, top, right, bottom);
1202
1203        // if shrinkToBottom() is called on initialization, it has to be deferred
1204        // until after the first call to onLayout so that it has the correct width
1205        if (mSwitchStateAfterFirstLayout) {
1206            mSwitchStateAfterFirstLayout = false;
1207            // shrink can trigger a synchronous onLayout call, so we
1208            // post this to avoid a stack overflow / tangled onLayout calls
1209            post(new Runnable() {
1210                public void run() {
1211                    changeState(mStateAfterFirstLayout, false);
1212                }
1213            });
1214        }
1215    }
1216
1217    @Override
1218    protected void onDraw(Canvas canvas) {
1219        updateWallpaperOffsets();
1220
1221        // Draw the background gradient if necessary
1222        if (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground) {
1223            int alpha = (int) (mBackgroundAlpha * 255);
1224            mBackground.setAlpha(alpha);
1225            mBackground.setBounds(mScrollX, 0, mScrollX + getMeasuredWidth(),
1226                    getMeasuredHeight());
1227            mBackground.draw(canvas);
1228        }
1229
1230        super.onDraw(canvas);
1231    }
1232
1233    @Override
1234    protected void dispatchDraw(Canvas canvas) {
1235        super.dispatchDraw(canvas);
1236
1237        if (mInScrollArea && !LauncherApplication.isScreenLarge()) {
1238            final int width = getWidth();
1239            final int height = getHeight();
1240            final int pageHeight = getChildAt(0).getHeight();
1241
1242            // Set the height of the outline to be the height of the page
1243            final int offset = (height - pageHeight - mPaddingTop - mPaddingBottom) / 2;
1244            final int paddingTop = mPaddingTop + offset;
1245            final int paddingBottom = mPaddingBottom + offset;
1246
1247            final CellLayout leftPage = (CellLayout) getChildAt(mCurrentPage - 1);
1248            final CellLayout rightPage = (CellLayout) getChildAt(mCurrentPage + 1);
1249
1250            if (leftPage != null && leftPage.getIsDragOverlapping()) {
1251                final Drawable d = getResources().getDrawable(R.drawable.page_hover_left_holo);
1252                d.setBounds(mScrollX, paddingTop, mScrollX + d.getIntrinsicWidth(),
1253                        height - paddingBottom);
1254                d.draw(canvas);
1255            } else if (rightPage != null && rightPage.getIsDragOverlapping()) {
1256                final Drawable d = getResources().getDrawable(R.drawable.page_hover_right_holo);
1257                d.setBounds(mScrollX + width - d.getIntrinsicWidth(), paddingTop, mScrollX + width,
1258                        height - paddingBottom);
1259                d.draw(canvas);
1260            }
1261        }
1262    }
1263
1264    @Override
1265    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1266        if (!mLauncher.isAllAppsVisible()) {
1267            final Folder openFolder = getOpenFolder();
1268            if (openFolder != null) {
1269                return openFolder.requestFocus(direction, previouslyFocusedRect);
1270            } else {
1271                return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
1272            }
1273        }
1274        return false;
1275    }
1276
1277    @Override
1278    public int getDescendantFocusability() {
1279        if (isSmall()) {
1280            return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
1281        }
1282        return super.getDescendantFocusability();
1283    }
1284
1285    @Override
1286    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1287        if (!mLauncher.isAllAppsVisible()) {
1288            final Folder openFolder = getOpenFolder();
1289            if (openFolder != null) {
1290                openFolder.addFocusables(views, direction);
1291            } else {
1292                super.addFocusables(views, direction, focusableMode);
1293            }
1294        }
1295    }
1296
1297    public boolean isSmall() {
1298        return mState == State.SMALL || mState == State.SPRING_LOADED;
1299    }
1300
1301    void enableChildrenCache(int fromPage, int toPage) {
1302        if (fromPage > toPage) {
1303            final int temp = fromPage;
1304            fromPage = toPage;
1305            toPage = temp;
1306        }
1307
1308        final int screenCount = getChildCount();
1309
1310        fromPage = Math.max(fromPage, 0);
1311        toPage = Math.min(toPage, screenCount - 1);
1312
1313        for (int i = fromPage; i <= toPage; i++) {
1314            final CellLayout layout = (CellLayout) getChildAt(i);
1315            layout.setChildrenDrawnWithCacheEnabled(true);
1316            layout.setChildrenDrawingCacheEnabled(true);
1317        }
1318    }
1319
1320    void clearChildrenCache() {
1321        final int screenCount = getChildCount();
1322        for (int i = 0; i < screenCount; i++) {
1323            final CellLayout layout = (CellLayout) getChildAt(i);
1324            layout.setChildrenDrawnWithCacheEnabled(false);
1325            // In software mode, we don't want the items to continue to be drawn into bitmaps
1326            if (!isHardwareAccelerated()) {
1327                layout.setChildrenDrawingCacheEnabled(false);
1328            }
1329        }
1330    }
1331
1332    private void updateChildrenLayersEnabled() {
1333        boolean small = isSmall() || mIsSwitchingState;
1334        boolean dragging = mAnimatingViewIntoPlace || mIsDragOccuring;
1335        boolean enableChildrenLayers = small || dragging || isPageMoving();
1336
1337        if (enableChildrenLayers != mChildrenLayersEnabled) {
1338            mChildrenLayersEnabled = enableChildrenLayers;
1339            for (int i = 0; i < getPageCount(); i++) {
1340                ((ViewGroup)getChildAt(i)).setChildrenLayersEnabled(enableChildrenLayers);
1341            }
1342        }
1343    }
1344
1345    protected void onWallpaperTap(MotionEvent ev) {
1346        final int[] position = mTempCell;
1347        getLocationOnScreen(position);
1348
1349        int pointerIndex = ev.getActionIndex();
1350        position[0] += (int) ev.getX(pointerIndex);
1351        position[1] += (int) ev.getY(pointerIndex);
1352
1353        mWallpaperManager.sendWallpaperCommand(getWindowToken(),
1354                ev.getAction() == MotionEvent.ACTION_UP
1355                        ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
1356                position[0], position[1], 0, null);
1357    }
1358
1359    /*
1360     * This interpolator emulates the rate at which the perceived scale of an object changes
1361     * as its distance from a camera increases. When this interpolator is applied to a scale
1362     * animation on a view, it evokes the sense that the object is shrinking due to moving away
1363     * from the camera.
1364     */
1365    static class ZInterpolator implements TimeInterpolator {
1366        private float focalLength;
1367
1368        public ZInterpolator(float foc) {
1369            focalLength = foc;
1370        }
1371
1372        public float getInterpolation(float input) {
1373            return (1.0f - focalLength / (focalLength + input)) /
1374                (1.0f - focalLength / (focalLength + 1.0f));
1375        }
1376    }
1377
1378    /*
1379     * The exact reverse of ZInterpolator.
1380     */
1381    static class InverseZInterpolator implements TimeInterpolator {
1382        private ZInterpolator zInterpolator;
1383        public InverseZInterpolator(float foc) {
1384            zInterpolator = new ZInterpolator(foc);
1385        }
1386        public float getInterpolation(float input) {
1387            return 1 - zInterpolator.getInterpolation(1 - input);
1388        }
1389    }
1390
1391    /*
1392     * ZInterpolator compounded with an ease-out.
1393     */
1394    static class ZoomOutInterpolator implements TimeInterpolator {
1395        private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75f);
1396        private final ZInterpolator zInterpolator = new ZInterpolator(0.13f);
1397
1398        public float getInterpolation(float input) {
1399            return decelerate.getInterpolation(zInterpolator.getInterpolation(input));
1400        }
1401    }
1402
1403    /*
1404     * InvereZInterpolator compounded with an ease-out.
1405     */
1406    static class ZoomInInterpolator implements TimeInterpolator {
1407        private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
1408        private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
1409
1410        public float getInterpolation(float input) {
1411            return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
1412        }
1413    }
1414
1415    private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
1416
1417    /*
1418    *
1419    * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we
1420    * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
1421    *
1422    * These methods mark the appropriate pages as accepting drops (which alters their visual
1423    * appearance).
1424    *
1425    */
1426    public void onDragStartedWithItem(View v) {
1427        final Canvas canvas = new Canvas();
1428
1429        // We need to add extra padding to the bitmap to make room for the glow effect
1430        final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;
1431
1432        // The outline is used to visualize where the item will land if dropped
1433        mDragOutline = createDragOutline(v, canvas, bitmapPadding);
1434    }
1435
1436    public void onDragStartedWithItemSpans(int spanX, int spanY, Bitmap b) {
1437        onDragStartedWithItemSpans(spanX, spanY, b, null);
1438    }
1439
1440    public void onDragStartedWithItemSpans(int spanX, int spanY, Bitmap b, Paint alphaClipPaint) {
1441        final Canvas canvas = new Canvas();
1442
1443        // We need to add extra padding to the bitmap to make room for the glow effect
1444        final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;
1445
1446        CellLayout cl = (CellLayout) getChildAt(0);
1447
1448        int[] size = cl.cellSpansToSize(spanX, spanY);
1449
1450        // The outline is used to visualize where the item will land if dropped
1451        mDragOutline = createDragOutline(b, canvas, bitmapPadding, size[0], size[1], alphaClipPaint);
1452    }
1453
1454    // we call this method whenever a drag and drop in Launcher finishes, even if Workspace was
1455    // never dragged over
1456    public void onDragStopped(boolean success) {
1457        // In the success case, DragController has already called onDragExit()
1458        if (!success) {
1459            doDragExit(null);
1460        }
1461    }
1462
1463    public void exitWidgetResizeMode() {
1464        DragLayer dragLayer = mLauncher.getDragLayer();
1465        dragLayer.clearAllResizeFrames();
1466    }
1467
1468    private void initAnimationArrays() {
1469        final int childCount = getChildCount();
1470        if (mOldTranslationXs != null) return;
1471        mOldTranslationXs = new float[childCount];
1472        mOldTranslationYs = new float[childCount];
1473        mOldScaleXs = new float[childCount];
1474        mOldScaleYs = new float[childCount];
1475        mOldBackgroundAlphas = new float[childCount];
1476        mOldBackgroundAlphaMultipliers = new float[childCount];
1477        mOldAlphas = new float[childCount];
1478        mOldRotationYs = new float[childCount];
1479        mNewTranslationXs = new float[childCount];
1480        mNewTranslationYs = new float[childCount];
1481        mNewScaleXs = new float[childCount];
1482        mNewScaleYs = new float[childCount];
1483        mNewBackgroundAlphas = new float[childCount];
1484        mNewBackgroundAlphaMultipliers = new float[childCount];
1485        mNewAlphas = new float[childCount];
1486        mNewRotationYs = new float[childCount];
1487    }
1488
1489    public void changeState(State shrinkState) {
1490        changeState(shrinkState, true);
1491    }
1492
1493    void changeState(final State state, boolean animated) {
1494        changeState(state, animated, 0);
1495    }
1496
1497    void changeState(final State state, boolean animated, int delay) {
1498        if (mState == state) {
1499            return;
1500        }
1501        if (mFirstLayout) {
1502            // (mFirstLayout == "first layout has not happened yet")
1503            // cancel any pending shrinks that were set earlier
1504            mSwitchStateAfterFirstLayout = false;
1505            mStateAfterFirstLayout = state;
1506            return;
1507        }
1508
1509        // Initialize animation arrays for the first time if necessary
1510        initAnimationArrays();
1511
1512        // Cancel any running transition animations
1513        if (mAnimator != null) mAnimator.cancel();
1514        mAnimator = new AnimatorSet();
1515
1516        // Stop any scrolling, move to the current page right away
1517        setCurrentPage((mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage);
1518
1519        final State oldState = mState;
1520        final boolean oldStateIsNormal = (oldState == State.NORMAL);
1521        final boolean oldStateIsSmall = (oldState == State.SMALL);
1522        mState = state;
1523        final boolean stateIsNormal = (state == State.NORMAL);
1524        final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED);
1525        final boolean stateIsSmall = (state == State.SMALL);
1526        float finalScaleFactor = 1.0f;
1527        float finalBackgroundAlpha = stateIsSpringLoaded ? 1.0f : 0f;
1528        float translationX = 0;
1529        float translationY = 0;
1530        boolean zoomIn = true;
1531
1532        if (state != State.NORMAL) {
1533            finalScaleFactor = mSpringLoadedShrinkFactor - (stateIsSmall ? 0.1f : 0);
1534            if (oldStateIsNormal && stateIsSmall) {
1535                zoomIn = false;
1536                setLayoutScale(finalScaleFactor);
1537                updateChildrenLayersEnabled();
1538            } else {
1539                finalBackgroundAlpha = 1.0f;
1540                setLayoutScale(finalScaleFactor);
1541            }
1542        } else {
1543            setLayoutScale(1.0f);
1544        }
1545
1546        final int duration = zoomIn ?
1547                getResources().getInteger(R.integer.config_workspaceUnshrinkTime) :
1548                getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
1549        for (int i = 0; i < getChildCount(); i++) {
1550            final CellLayout cl = (CellLayout) getChildAt(i);
1551            float rotation = 0f;
1552            float initialAlpha = cl.getAlpha();
1553            float finalAlphaMultiplierValue = 1f;
1554            float finalAlpha = (!mFadeInAdjacentScreens || stateIsSpringLoaded ||
1555                    (i == mCurrentPage)) ? 1f : 0f;
1556
1557            // Determine the pages alpha during the state transition
1558            if ((oldStateIsSmall && stateIsNormal) ||
1559                (oldStateIsNormal && stateIsSmall)) {
1560                // To/from workspace - only show the current page unless the transition is not
1561                //                     animated and the animation end callback below doesn't run
1562                if (i == mCurrentPage || !animated) {
1563                    finalAlpha = 1f;
1564                    finalAlphaMultiplierValue = 0f;
1565                } else {
1566                    initialAlpha = 0f;
1567                    finalAlpha = 0f;
1568                }
1569            }
1570
1571            // Update the rotation of the screen (don't apply rotation on Phone UI)
1572            if (LauncherApplication.isScreenLarge()) {
1573                if (i < mCurrentPage) {
1574                    rotation = WORKSPACE_ROTATION;
1575                } else if (i > mCurrentPage) {
1576                    rotation = -WORKSPACE_ROTATION;
1577                }
1578            }
1579
1580            // If the screen is not xlarge, then don't rotate the CellLayouts
1581            // NOTE: If we don't update the side pages alpha, then we should not hide the side
1582            //       pages. see unshrink().
1583            if (LauncherApplication.isScreenLarge()) {
1584                translationX = getOffsetXForRotation(rotation, cl.getWidth(), cl.getHeight());
1585            }
1586
1587            mOldAlphas[i] = initialAlpha;
1588            mNewAlphas[i] = finalAlpha;
1589            if (animated) {
1590                mOldTranslationXs[i] = cl.getTranslationX();
1591                mOldTranslationYs[i] = cl.getTranslationY();
1592                mOldScaleXs[i] = cl.getScaleX();
1593                mOldScaleYs[i] = cl.getScaleY();
1594                mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
1595                mOldBackgroundAlphaMultipliers[i] = cl.getBackgroundAlphaMultiplier();
1596                mOldRotationYs[i] = cl.getRotationY();
1597
1598                mNewTranslationXs[i] = translationX;
1599                mNewTranslationYs[i] = translationY;
1600                mNewScaleXs[i] = finalScaleFactor;
1601                mNewScaleYs[i] = finalScaleFactor;
1602                mNewBackgroundAlphas[i] = finalBackgroundAlpha;
1603                mNewBackgroundAlphaMultipliers[i] = finalAlphaMultiplierValue;
1604                mNewRotationYs[i] = rotation;
1605            } else {
1606                cl.setTranslationX(translationX);
1607                cl.setTranslationY(translationY);
1608                cl.setScaleX(finalScaleFactor);
1609                cl.setScaleY(finalScaleFactor);
1610                cl.setBackgroundAlpha(finalBackgroundAlpha);
1611                cl.setBackgroundAlphaMultiplier(finalAlphaMultiplierValue);
1612                cl.setAlpha(finalAlpha);
1613                cl.setRotationY(rotation);
1614                mChangeStateAnimationListener.onAnimationEnd(null);
1615            }
1616        }
1617
1618        if (animated) {
1619            ValueAnimator animWithInterpolator =
1620                ValueAnimator.ofFloat(0f, 1f).setDuration(duration);
1621
1622            if (zoomIn) {
1623                animWithInterpolator.setInterpolator(mZoomInInterpolator);
1624            }
1625
1626            animWithInterpolator.addListener(new AnimatorListenerAdapter() {
1627                @Override
1628                public void onAnimationEnd(android.animation.Animator animation) {
1629                    // The above code to determine initialAlpha and finalAlpha will ensure that only
1630                    // the current page is visible during (and subsequently, after) the transition
1631                    // animation.  If fade adjacent pages is disabled, then re-enable the page
1632                    // visibility after the transition animation.
1633                    if (!mFadeInAdjacentScreens && stateIsNormal && oldStateIsSmall) {
1634                        for (int i = 0; i < getChildCount(); i++) {
1635                            final CellLayout cl = (CellLayout) getChildAt(i);
1636                            cl.setAlpha(1f);
1637                        }
1638                    }
1639                }
1640            });
1641            animWithInterpolator.addUpdateListener(new LauncherAnimatorUpdateListener() {
1642                public void onAnimationUpdate(float a, float b) {
1643                    mTransitionProgress = b;
1644                    if (b == 0f) {
1645                        // an optimization, but not required
1646                        return;
1647                    }
1648                    invalidate();
1649                    for (int i = 0; i < getChildCount(); i++) {
1650                        final CellLayout cl = (CellLayout) getChildAt(i);
1651                        cl.fastInvalidate();
1652                        cl.setFastTranslationX(a * mOldTranslationXs[i] + b * mNewTranslationXs[i]);
1653                        cl.setFastTranslationY(a * mOldTranslationYs[i] + b * mNewTranslationYs[i]);
1654                        cl.setFastScaleX(a * mOldScaleXs[i] + b * mNewScaleXs[i]);
1655                        cl.setFastScaleY(a * mOldScaleYs[i] + b * mNewScaleYs[i]);
1656                        cl.setFastBackgroundAlpha(
1657                                a * mOldBackgroundAlphas[i] + b * mNewBackgroundAlphas[i]);
1658                        cl.setBackgroundAlphaMultiplier(a * mOldBackgroundAlphaMultipliers[i] +
1659                                b * mNewBackgroundAlphaMultipliers[i]);
1660                        cl.setFastAlpha(a * mOldAlphas[i] + b * mNewAlphas[i]);
1661                    }
1662                }
1663            });
1664
1665            ValueAnimator rotationAnim =
1666                ValueAnimator.ofFloat(0f, 1f).setDuration(duration);
1667            rotationAnim.setInterpolator(new DecelerateInterpolator(2.0f));
1668            rotationAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
1669                public void onAnimationUpdate(float a, float b) {
1670                    if (b == 0f) {
1671                        // an optimization, but not required
1672                        return;
1673                    }
1674                    for (int i = 0; i < getChildCount(); i++) {
1675                        final CellLayout cl = (CellLayout) getChildAt(i);
1676                        cl.setFastRotationY(a * mOldRotationYs[i] + b * mNewRotationYs[i]);
1677                    }
1678                }
1679            });
1680
1681            mAnimator.playTogether(animWithInterpolator, rotationAnim);
1682            mAnimator.setStartDelay(delay);
1683            // If we call this when we're not animated, onAnimationEnd is never called on
1684            // the listener; make sure we only use the listener when we're actually animating
1685            mAnimator.addListener(mChangeStateAnimationListener);
1686            mAnimator.start();
1687        }
1688
1689        if (stateIsSpringLoaded) {
1690            // Right now we're covered by Apps Customize
1691            // Show the background gradient immediately, so the gradient will
1692            // be showing once AppsCustomize disappears
1693            animateBackgroundGradient(getResources().getInteger(
1694                    R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, false);
1695        } else {
1696            // Fade the background gradient away
1697            animateBackgroundGradient(0f, true);
1698        }
1699    }
1700
1701    /**
1702     * Draw the View v into the given Canvas.
1703     *
1704     * @param v the view to draw
1705     * @param destCanvas the canvas to draw on
1706     * @param padding the horizontal and vertical padding to use when drawing
1707     */
1708    private void drawDragView(View v, Canvas destCanvas, int padding, boolean pruneToDrawable) {
1709        final Rect clipRect = mTempRect;
1710        v.getDrawingRect(clipRect);
1711
1712        boolean textVisible = false;
1713
1714        destCanvas.save();
1715        if (v instanceof TextView && pruneToDrawable) {
1716            Drawable d = ((TextView) v).getCompoundDrawables()[1];
1717            clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding);
1718            destCanvas.translate(padding / 2, padding / 2);
1719            d.draw(destCanvas);
1720        } else {
1721            if (v instanceof FolderIcon) {
1722                // For FolderIcons the text can bleed into the icon area, and so we need to
1723                // hide the text completely (which can't be achieved by clipping).
1724                if (((FolderIcon) v).getTextVisible()) {
1725                    ((FolderIcon) v).setTextVisible(false);
1726                    textVisible = true;
1727                }
1728            } else if (v instanceof BubbleTextView) {
1729                final BubbleTextView tv = (BubbleTextView) v;
1730                clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V +
1731                        tv.getLayout().getLineTop(0);
1732            } else if (v instanceof TextView) {
1733                final TextView tv = (TextView) v;
1734                clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() +
1735                        tv.getLayout().getLineTop(0);
1736            }
1737            destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
1738            destCanvas.clipRect(clipRect, Op.REPLACE);
1739            v.draw(destCanvas);
1740
1741            // Restore text visibility of FolderIcon if necessary
1742            if (textVisible) {
1743                ((FolderIcon) v).setTextVisible(true);
1744            }
1745        }
1746        destCanvas.restore();
1747    }
1748
1749    /**
1750     * Returns a new bitmap to show when the given View is being dragged around.
1751     * Responsibility for the bitmap is transferred to the caller.
1752     */
1753    public Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
1754        final int outlineColor = getResources().getColor(android.R.color.holo_blue_light);
1755        Bitmap b;
1756
1757        if (v instanceof TextView) {
1758            Drawable d = ((TextView) v).getCompoundDrawables()[1];
1759            b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding,
1760                    d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888);
1761        } else {
1762            b = Bitmap.createBitmap(
1763                    v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
1764        }
1765
1766        canvas.setBitmap(b);
1767        drawDragView(v, canvas, padding, true);
1768        mOutlineHelper.applyOuterBlur(b, canvas, outlineColor);
1769        canvas.drawColor(mDragViewMultiplyColor, PorterDuff.Mode.MULTIPLY);
1770        canvas.setBitmap(null);
1771
1772        return b;
1773    }
1774
1775    /**
1776     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
1777     * Responsibility for the bitmap is transferred to the caller.
1778     */
1779    private Bitmap createDragOutline(View v, Canvas canvas, int padding) {
1780        final int outlineColor = getResources().getColor(android.R.color.holo_blue_light);
1781        final Bitmap b = Bitmap.createBitmap(
1782                v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
1783
1784        canvas.setBitmap(b);
1785        drawDragView(v, canvas, padding, true);
1786        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
1787        canvas.setBitmap(null);
1788        return b;
1789    }
1790
1791    /**
1792     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
1793     * Responsibility for the bitmap is transferred to the caller.
1794     */
1795    private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding, int w, int h) {
1796        return createDragOutline(orig, canvas, padding, w, h, null);
1797    }
1798    private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding, int w, int h,
1799            Paint alphaClipPaint) {
1800        final int outlineColor = getResources().getColor(android.R.color.holo_blue_light);
1801        final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
1802        canvas.setBitmap(b);
1803
1804        Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
1805        float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(),
1806                (h - padding) / (float) orig.getHeight());
1807        int scaledWidth = (int) (scaleFactor * orig.getWidth());
1808        int scaledHeight = (int) (scaleFactor * orig.getHeight());
1809        Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
1810
1811        // center the image
1812        dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
1813
1814        Paint p = new Paint();
1815        p.setFilterBitmap(true);
1816        canvas.drawBitmap(orig, src, dst, p);
1817        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor,
1818                alphaClipPaint);
1819        canvas.setBitmap(null);
1820
1821        return b;
1822    }
1823
1824    /**
1825     * Creates a drag outline to represent a drop (that we don't have the actual information for
1826     * yet).  May be changed in the future to alter the drop outline slightly depending on the
1827     * clip description mime data.
1828     */
1829    private Bitmap createExternalDragOutline(Canvas canvas, int padding) {
1830        Resources r = getResources();
1831        final int outlineColor = r.getColor(android.R.color.holo_blue_light);
1832        final int iconWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
1833        final int iconHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
1834        final int rectRadius = r.getDimensionPixelSize(R.dimen.external_drop_icon_rect_radius);
1835        final int inset = (int) (Math.min(iconWidth, iconHeight) * 0.2f);
1836        final Bitmap b = Bitmap.createBitmap(
1837                iconWidth + padding, iconHeight + padding, Bitmap.Config.ARGB_8888);
1838
1839        canvas.setBitmap(b);
1840        canvas.drawRoundRect(new RectF(inset, inset, iconWidth - inset, iconHeight - inset),
1841                rectRadius, rectRadius, mExternalDragOutlinePaint);
1842        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
1843        canvas.setBitmap(null);
1844        return b;
1845    }
1846
1847    void startDrag(CellLayout.CellInfo cellInfo) {
1848        View child = cellInfo.cell;
1849
1850        // Make sure the drag was started by a long press as opposed to a long click.
1851        if (!child.isInTouchMode()) {
1852            return;
1853        }
1854
1855        mDragInfo = cellInfo;
1856        child.setVisibility(GONE);
1857
1858        child.clearFocus();
1859        child.setPressed(false);
1860
1861        final Canvas canvas = new Canvas();
1862
1863        // We need to add extra padding to the bitmap to make room for the glow effect
1864        final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;
1865
1866        // The outline is used to visualize where the item will land if dropped
1867        mDragOutline = createDragOutline(child, canvas, bitmapPadding);
1868        beginDragShared(child, this);
1869    }
1870
1871    public void beginDragShared(View child, DragSource source) {
1872        Resources r = getResources();
1873
1874        // We need to add extra padding to the bitmap to make room for the glow effect
1875        final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;
1876
1877        // The drag bitmap follows the touch point around on the screen
1878        final Bitmap b = createDragBitmap(child, new Canvas(), bitmapPadding);
1879
1880        final int bmpWidth = b.getWidth();
1881
1882        mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
1883        final int dragLayerX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2;
1884        int dragLayerY = mTempXY[1] - bitmapPadding / 2;
1885
1886        Point dragVisualizeOffset = null;
1887        Rect dragRect = null;
1888        if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
1889            int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size);
1890            int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top);
1891            int top = child.getPaddingTop();
1892            int left = (bmpWidth - iconSize) / 2;
1893            int right = left + iconSize;
1894            int bottom = top + iconSize;
1895            dragLayerY += top;
1896            // Note: The drag region is used to calculate drag layer offsets, but the
1897            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
1898            dragVisualizeOffset = new Point(-bitmapPadding / 2, iconPaddingTop - bitmapPadding / 2);
1899            dragRect = new Rect(left, top, right, bottom);
1900        } else if (child instanceof FolderIcon) {
1901            int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size);
1902            dragRect = new Rect(0, 0, child.getWidth(), previewSize);
1903        }
1904
1905        // Clear the pressed state if necessary
1906        if (child instanceof BubbleTextView) {
1907            BubbleTextView icon = (BubbleTextView) child;
1908            icon.clearPressedOrFocusedBackground();
1909        }
1910
1911        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
1912                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect);
1913        b.recycle();
1914    }
1915
1916    void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, int screen,
1917            int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
1918        View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
1919
1920        final int[] cellXY = new int[2];
1921        target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
1922        addInScreen(view, container, screen, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
1923        LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen, cellXY[0],
1924                cellXY[1]);
1925    }
1926
1927    public boolean transitionStateShouldAllowDrop() {
1928        return (!isSwitchingState() || mTransitionProgress > 0.5f);
1929    }
1930
1931    /**
1932     * {@inheritDoc}
1933     */
1934    public boolean acceptDrop(DragObject d) {
1935        // If it's an external drop (e.g. from All Apps), check if it should be accepted
1936        if (d.dragSource != this) {
1937            // Don't accept the drop if we're not over a screen at time of drop
1938            if (mDragTargetLayout == null) {
1939                return false;
1940            }
1941            if (!transitionStateShouldAllowDrop()) return false;
1942
1943            mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
1944                    d.dragView, mDragViewVisualCenter);
1945
1946            // We want the point to be mapped to the dragTarget.
1947            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
1948                mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter);
1949            } else {
1950                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
1951            }
1952
1953            int spanX = 1;
1954            int spanY = 1;
1955            View ignoreView = null;
1956            if (mDragInfo != null) {
1957                final CellLayout.CellInfo dragCellInfo = mDragInfo;
1958                spanX = dragCellInfo.spanX;
1959                spanY = dragCellInfo.spanY;
1960                ignoreView = dragCellInfo.cell;
1961            } else {
1962                final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
1963                spanX = dragInfo.spanX;
1964                spanY = dragInfo.spanY;
1965            }
1966
1967            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
1968                    (int) mDragViewVisualCenter[1], spanX, spanY, mDragTargetLayout, mTargetCell);
1969            if (willCreateUserFolder((ItemInfo) d.dragInfo, mDragTargetLayout, mTargetCell, true)) {
1970                return true;
1971            }
1972            if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, mDragTargetLayout,
1973                    mTargetCell)) {
1974                return true;
1975            }
1976
1977
1978            // Don't accept the drop if there's no room for the item
1979            if (!mDragTargetLayout.findCellForSpanIgnoring(null, spanX, spanY, ignoreView)) {
1980                mLauncher.showOutOfSpaceMessage();
1981                return false;
1982            }
1983        }
1984        return true;
1985    }
1986
1987    boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell,
1988            boolean considerTimeout) {
1989        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
1990
1991        boolean hasntMoved = false;
1992        if (mDragInfo != null) {
1993            CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
1994            hasntMoved = (mDragInfo.cellX == targetCell[0] &&
1995                    mDragInfo.cellY == targetCell[1]) && (cellParent == target);
1996        }
1997
1998        if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
1999            return false;
2000        }
2001
2002        boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
2003        boolean willBecomeShortcut =
2004                (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
2005                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
2006
2007        return (aboveShortcut && willBecomeShortcut);
2008    }
2009
2010    boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell) {
2011        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2012        if (dropOverView instanceof FolderIcon) {
2013            FolderIcon fi = (FolderIcon) dropOverView;
2014            if (fi.acceptDrop(dragInfo)) {
2015                return true;
2016            }
2017        }
2018        return false;
2019    }
2020
2021    boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
2022            int[] targetCell, boolean external, DragView dragView, Runnable postAnimationRunnable) {
2023        View v = target.getChildAt(targetCell[0], targetCell[1]);
2024        boolean hasntMoved = false;
2025        if (mDragInfo != null) {
2026            CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
2027            hasntMoved = (mDragInfo.cellX == targetCell[0] &&
2028                    mDragInfo.cellY == targetCell[1]) && (cellParent == target);
2029        }
2030
2031        if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
2032        mCreateUserFolderOnDrop = false;
2033        final int screen = (targetCell == null) ? mDragInfo.screen : indexOfChild(target);
2034
2035        boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
2036        boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
2037
2038        if (aboveShortcut && willBecomeShortcut) {
2039            ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
2040            ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
2041            // if the drag started here, we need to remove it from the workspace
2042            if (!external) {
2043                getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2044            }
2045
2046            Rect folderLocation = new Rect();
2047            float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
2048            target.removeView(v);
2049
2050            FolderIcon fi =
2051                mLauncher.addFolder(target, container, screen, targetCell[0], targetCell[1]);
2052            destInfo.cellX = -1;
2053            destInfo.cellY = -1;
2054            sourceInfo.cellX = -1;
2055            sourceInfo.cellY = -1;
2056
2057            // If the dragView is null, we can't animate
2058            boolean animate = dragView != null;
2059            if (animate) {
2060                fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
2061                        postAnimationRunnable);
2062            } else {
2063                fi.addItem(destInfo);
2064                fi.addItem(sourceInfo);
2065            }
2066            return true;
2067        }
2068        return false;
2069    }
2070
2071    boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
2072            DragObject d, boolean external) {
2073        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2074        if (dropOverView instanceof FolderIcon) {
2075            FolderIcon fi = (FolderIcon) dropOverView;
2076            if (fi.acceptDrop(d.dragInfo)) {
2077                fi.onDrop(d);
2078
2079                // if the drag started here, we need to remove it from the workspace
2080                if (!external) {
2081                    getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2082                }
2083                return true;
2084            }
2085        }
2086        return false;
2087    }
2088
2089    public void onDrop(DragObject d) {
2090        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
2091                mDragViewVisualCenter);
2092
2093        // We want the point to be mapped to the dragTarget.
2094        if (mDragTargetLayout != null) {
2095            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
2096                mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter);
2097            } else {
2098                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
2099            }
2100        }
2101
2102        CellLayout dropTargetLayout = mDragTargetLayout;
2103
2104        int snapScreen = -1;
2105        if (d.dragSource != this) {
2106            final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
2107                    (int) mDragViewVisualCenter[1] };
2108            onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
2109        } else if (mDragInfo != null) {
2110            final View cell = mDragInfo.cell;
2111
2112            if (dropTargetLayout != null) {
2113                // Move internally
2114                boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
2115                boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2116                long container = hasMovedIntoHotseat ?
2117                        LauncherSettings.Favorites.CONTAINER_HOTSEAT :
2118                        LauncherSettings.Favorites.CONTAINER_DESKTOP;
2119                int screen = (mTargetCell[0] < 0) ?
2120                        mDragInfo.screen : indexOfChild(dropTargetLayout);
2121                int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
2122                int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
2123                // First we find the cell nearest to point at which the item is
2124                // dropped, without any consideration to whether there is an item there.
2125                mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
2126                        mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
2127                // If the item being dropped is a shortcut and the nearest drop
2128                // cell also contains a shortcut, then create a folder with the two shortcuts.
2129                if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
2130                        dropTargetLayout, mTargetCell, false, d.dragView, null)) {
2131                    return;
2132                }
2133
2134                if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, d, false)) {
2135                    return;
2136                }
2137
2138                // Aside from the special case where we're dropping a shortcut onto a shortcut,
2139                // we need to find the nearest cell location that is vacant
2140                mTargetCell = findNearestVacantArea((int) mDragViewVisualCenter[0],
2141                        (int) mDragViewVisualCenter[1], mDragInfo.spanX, mDragInfo.spanY, cell,
2142                        dropTargetLayout, mTargetCell);
2143
2144                if (mCurrentPage != screen && !hasMovedIntoHotseat) {
2145                    snapScreen = screen;
2146                    snapToPage(screen);
2147                }
2148
2149                if (mTargetCell[0] >= 0 && mTargetCell[1] >= 0) {
2150                    if (hasMovedLayouts) {
2151                        // Reparent the view
2152                        getParentCellLayoutForView(cell).removeView(cell);
2153                        addInScreen(cell, container, screen, mTargetCell[0], mTargetCell[1],
2154                                mDragInfo.spanX, mDragInfo.spanY);
2155                    }
2156
2157                    // update the item's position after drop
2158                    final ItemInfo info = (ItemInfo) cell.getTag();
2159                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2160                    dropTargetLayout.onMove(cell, mTargetCell[0], mTargetCell[1]);
2161                    lp.cellX = mTargetCell[0];
2162                    lp.cellY = mTargetCell[1];
2163                    cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screen,
2164                            mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
2165
2166                    if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
2167                            cell instanceof LauncherAppWidgetHostView) {
2168                        final CellLayout cellLayout = dropTargetLayout;
2169                        // We post this call so that the widget has a chance to be placed
2170                        // in its final location
2171
2172                        final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
2173                        AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
2174                        if (pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
2175                            final Runnable resizeRunnable = new Runnable() {
2176                                public void run() {
2177                                    DragLayer dragLayer = mLauncher.getDragLayer();
2178                                    dragLayer.addResizeFrame(info, hostView, cellLayout);
2179                                }
2180                            };
2181                            post(new Runnable() {
2182                                public void run() {
2183                                    if (!isPageMoving()) {
2184                                        resizeRunnable.run();
2185                                    } else {
2186                                        mDelayedResizeRunnable = resizeRunnable;
2187                                    }
2188                                }
2189                            });
2190                        }
2191                    }
2192
2193                    LauncherModel.moveItemInDatabase(mLauncher, info, container, screen, lp.cellX,
2194                            lp.cellY);
2195                }
2196            }
2197
2198            final CellLayout parent = (CellLayout) cell.getParent().getParent();
2199
2200            // Prepare it to be animated into its new position
2201            // This must be called after the view has been re-parented
2202            final Runnable disableHardwareLayersRunnable = new Runnable() {
2203                @Override
2204                public void run() {
2205                    mAnimatingViewIntoPlace = false;
2206                    updateChildrenLayersEnabled();
2207                }
2208            };
2209            mAnimatingViewIntoPlace = true;
2210            if (d.dragView.hasDrawn()) {
2211                int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
2212                setFinalScrollForPageChange(snapScreen);
2213                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
2214                        disableHardwareLayersRunnable);
2215                resetFinalScrollForPageChange(snapScreen);
2216            } else {
2217                cell.setVisibility(VISIBLE);
2218            }
2219            parent.onDropChild(cell);
2220        }
2221    }
2222
2223    public void setFinalScrollForPageChange(int screen) {
2224        if (screen >= 0) {
2225            mSavedScrollX = getScrollX();
2226            CellLayout cl = (CellLayout) getChildAt(screen);
2227            mSavedTranslationX = cl.getTranslationX();
2228            mSavedRotationY = cl.getRotationY();
2229            final int newX = getChildOffset(screen) - getRelativeChildOffset(screen);
2230            setScrollX(newX);
2231            cl.setTranslationX(0f);
2232            cl.setRotationY(0f);
2233        }
2234    }
2235
2236    public void resetFinalScrollForPageChange(int screen) {
2237        if (screen >= 0) {
2238            CellLayout cl = (CellLayout) getChildAt(screen);
2239            setScrollX(mSavedScrollX);
2240            cl.setTranslationX(mSavedTranslationX);
2241            cl.setRotationY(mSavedRotationY);
2242        }
2243    }
2244
2245    public void getViewLocationRelativeToSelf(View v, int[] location) {
2246        getLocationInWindow(location);
2247        int x = location[0];
2248        int y = location[1];
2249
2250        v.getLocationInWindow(location);
2251        int vX = location[0];
2252        int vY = location[1];
2253
2254        location[0] = vX - x;
2255        location[1] = vY - y;
2256    }
2257
2258    public void onDragEnter(DragObject d) {
2259        if (mDragTargetLayout != null) {
2260            mDragTargetLayout.setIsDragOverlapping(false);
2261            mDragTargetLayout.onDragExit();
2262        }
2263        mDragTargetLayout = getCurrentDropLayout();
2264        mDragTargetLayout.setIsDragOverlapping(true);
2265        mDragTargetLayout.onDragEnter();
2266
2267        // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we
2268        // don't need to show the outlines
2269        if (LauncherApplication.isScreenLarge()) {
2270            showOutlines();
2271        }
2272    }
2273
2274    private void doDragExit(DragObject d) {
2275        // Clean up folders
2276        cleanupFolderCreation(d);
2277
2278        // Reset the scroll area and previous drag target
2279        onResetScrollArea();
2280
2281        if (mDragTargetLayout != null) {
2282            mDragTargetLayout.setIsDragOverlapping(false);
2283            mDragTargetLayout.onDragExit();
2284        }
2285        mLastDragOverView = null;
2286
2287        if (!mIsPageMoving) {
2288            hideOutlines();
2289        }
2290    }
2291
2292    public void onDragExit(DragObject d) {
2293        doDragExit(d);
2294    }
2295
2296    public DropTarget getDropTargetDelegate(DragObject d) {
2297        return null;
2298    }
2299
2300    /**
2301     * Tests to see if the drop will be accepted by Launcher, and if so, includes additional data
2302     * in the returned structure related to the widgets that match the drop (or a null list if it is
2303     * a shortcut drop).  If the drop is not accepted then a null structure is returned.
2304     */
2305    private Pair<Integer, List<WidgetMimeTypeHandlerData>> validateDrag(DragEvent event) {
2306        final LauncherModel model = mLauncher.getModel();
2307        final ClipDescription desc = event.getClipDescription();
2308        final int mimeTypeCount = desc.getMimeTypeCount();
2309        for (int i = 0; i < mimeTypeCount; ++i) {
2310            final String mimeType = desc.getMimeType(i);
2311            if (mimeType.equals(InstallShortcutReceiver.SHORTCUT_MIMETYPE)) {
2312                return new Pair<Integer, List<WidgetMimeTypeHandlerData>>(i, null);
2313            } else {
2314                final List<WidgetMimeTypeHandlerData> widgets =
2315                    model.resolveWidgetsForMimeType(mContext, mimeType);
2316                if (widgets.size() > 0) {
2317                    return new Pair<Integer, List<WidgetMimeTypeHandlerData>>(i, widgets);
2318                }
2319            }
2320        }
2321        return null;
2322    }
2323
2324    /**
2325     * Global drag and drop handler
2326     */
2327    @Override
2328    public boolean onDragEvent(DragEvent event) {
2329        final ClipDescription desc = event.getClipDescription();
2330        final CellLayout layout = (CellLayout) getChildAt(mCurrentPage);
2331        final int[] pos = new int[2];
2332        layout.getLocationOnScreen(pos);
2333        // We need to offset the drag coordinates to layout coordinate space
2334        final int x = (int) event.getX() - pos[0];
2335        final int y = (int) event.getY() - pos[1];
2336
2337        switch (event.getAction()) {
2338        case DragEvent.ACTION_DRAG_STARTED: {
2339            // Validate this drag
2340            Pair<Integer, List<WidgetMimeTypeHandlerData>> test = validateDrag(event);
2341            if (test != null) {
2342                boolean isShortcut = (test.second == null);
2343                if (isShortcut) {
2344                    // Check if we have enough space on this screen to add a new shortcut
2345                    if (!layout.findCellForSpan(pos, 1, 1)) {
2346                        mLauncher.showOutOfSpaceMessage();
2347                        return false;
2348                    }
2349                }
2350            } else {
2351                // Show error message if we couldn't accept any of the items
2352                Toast.makeText(mContext, mContext.getString(R.string.external_drop_widget_error),
2353                        Toast.LENGTH_SHORT).show();
2354                return false;
2355            }
2356
2357            // Create the drag outline
2358            // We need to add extra padding to the bitmap to make room for the glow effect
2359            final Canvas canvas = new Canvas();
2360            final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;
2361            mDragOutline = createExternalDragOutline(canvas, bitmapPadding);
2362
2363            // Show the current page outlines to indicate that we can accept this drop
2364            showOutlines();
2365            layout.onDragEnter();
2366            layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1, null, null);
2367
2368            return true;
2369        }
2370        case DragEvent.ACTION_DRAG_LOCATION:
2371            // Visualize the drop location
2372            layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1, null, null);
2373            return true;
2374        case DragEvent.ACTION_DROP: {
2375            // Try and add any shortcuts
2376            final LauncherModel model = mLauncher.getModel();
2377            final ClipData data = event.getClipData();
2378
2379            // We assume that the mime types are ordered in descending importance of
2380            // representation. So we enumerate the list of mime types and alert the
2381            // user if any widgets can handle the drop.  Only the most preferred
2382            // representation will be handled.
2383            pos[0] = x;
2384            pos[1] = y;
2385            Pair<Integer, List<WidgetMimeTypeHandlerData>> test = validateDrag(event);
2386            if (test != null) {
2387                final int index = test.first;
2388                final List<WidgetMimeTypeHandlerData> widgets = test.second;
2389                final boolean isShortcut = (widgets == null);
2390                final String mimeType = desc.getMimeType(index);
2391                if (isShortcut) {
2392                    final Intent intent = data.getItemAt(index).getIntent();
2393                    Object info = model.infoFromShortcutIntent(mContext, intent, data.getIcon());
2394                    onDropExternal(new int[] { x, y }, info, layout, false);
2395                } else {
2396                    if (widgets.size() == 1) {
2397                        // If there is only one item, then go ahead and add and configure
2398                        // that widget
2399                        final AppWidgetProviderInfo widgetInfo = widgets.get(0).widgetInfo;
2400                        final PendingAddWidgetInfo createInfo =
2401                                new PendingAddWidgetInfo(widgetInfo, mimeType, data);
2402                        mLauncher.addAppWidgetFromDrop(createInfo,
2403                            LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentPage, null, pos);
2404                    } else {
2405                        // Show the widget picker dialog if there is more than one widget
2406                        // that can handle this data type
2407                        final InstallWidgetReceiver.WidgetListAdapter adapter =
2408                            new InstallWidgetReceiver.WidgetListAdapter(mLauncher, mimeType,
2409                                    data, widgets, layout, mCurrentPage, pos);
2410                        final AlertDialog.Builder builder =
2411                            new AlertDialog.Builder(mContext);
2412                        builder.setAdapter(adapter, adapter);
2413                        builder.setCancelable(true);
2414                        builder.setTitle(mContext.getString(
2415                                R.string.external_drop_widget_pick_title));
2416                        builder.setIcon(R.drawable.ic_no_applications);
2417                        builder.show();
2418                    }
2419                }
2420            }
2421            return true;
2422        }
2423        case DragEvent.ACTION_DRAG_ENDED:
2424            // Hide the page outlines after the drop
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