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