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