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