Workspace.java revision 8edd75c8bb0729a10cb39f614183e3e9ae4288e8
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 com.android.launcher.R;
20import com.android.launcher2.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
21
22import android.animation.Animator;
23import android.animation.AnimatorListenerAdapter;
24import android.animation.AnimatorSet;
25import android.animation.ObjectAnimator;
26import android.animation.PropertyValuesHolder;
27import android.animation.TimeInterpolator;
28import android.animation.ValueAnimator;
29import android.animation.Animator.AnimatorListener;
30import android.animation.ValueAnimator.AnimatorUpdateListener;
31import android.app.AlertDialog;
32import android.app.WallpaperManager;
33import android.appwidget.AppWidgetManager;
34import android.appwidget.AppWidgetProviderInfo;
35import android.content.ClipData;
36import android.content.ClipDescription;
37import android.content.ComponentName;
38import android.content.Context;
39import android.content.Intent;
40import android.content.pm.PackageManager;
41import android.content.pm.ProviderInfo;
42import android.content.res.Resources;
43import android.content.res.TypedArray;
44import android.graphics.Bitmap;
45import android.graphics.Camera;
46import android.graphics.Canvas;
47import android.graphics.Matrix;
48import android.graphics.Paint;
49import android.graphics.Rect;
50import android.graphics.RectF;
51import android.graphics.Region.Op;
52import android.graphics.drawable.Drawable;
53import android.net.Uri;
54import android.os.IBinder;
55import android.os.Parcelable;
56import android.util.AttributeSet;
57import android.util.Log;
58import android.util.Pair;
59import android.view.Display;
60import android.view.DragEvent;
61import android.view.MotionEvent;
62import android.view.View;
63import android.view.animation.DecelerateInterpolator;
64import android.widget.TextView;
65import android.widget.Toast;
66
67import java.util.ArrayList;
68import java.util.HashSet;
69import java.util.List;
70
71/**
72 * The workspace is a wide area with a wallpaper and a finite number of pages.
73 * Each page contains a number of icons, folders or widgets the user can
74 * interact with. A workspace is meant to be used with a fixed width only.
75 */
76public class Workspace extends SmoothPagedView
77        implements DropTarget, DragSource, DragScroller, View.OnTouchListener {
78    @SuppressWarnings({"UnusedDeclaration"})
79    private static final String TAG = "Launcher.Workspace";
80
81    // This is how much the workspace shrinks when we enter all apps or
82    // customization mode
83    private static final float SHRINK_FACTOR = 0.16f;
84
85    // How much the screens shrink when we enter spring loaded drag mode
86    private static final float SPRING_LOADED_DRAG_SHRINK_FACTOR = 0.7f;
87
88    // Y rotation to apply to the workspace screens
89    private static final float WORKSPACE_ROTATION = 12.5f;
90    private static final float WORKSPACE_TRANSLATION = 50.0f;
91
92    // These are extra scale factors to apply to the mini home screens
93    // so as to achieve the desired transform
94    private static final float EXTRA_SCALE_FACTOR_0 = 0.972f;
95    private static final float EXTRA_SCALE_FACTOR_1 = 1.0f;
96    private static final float EXTRA_SCALE_FACTOR_2 = 1.10f;
97
98    private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
99    private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
100    private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
101
102    private static final int BACKGROUND_FADE_OUT_DURATION = 350;
103    private static final int BACKGROUND_FADE_IN_DURATION = 350;
104
105    // These animators are used to fade the children's outlines
106    private ObjectAnimator mChildrenOutlineFadeInAnimation;
107    private ObjectAnimator mChildrenOutlineFadeOutAnimation;
108    private float mChildrenOutlineAlpha = 0;
109
110    // These properties refer to the background protection gradient used for AllApps and Customize
111    private ObjectAnimator mBackgroundFadeInAnimation;
112    private ObjectAnimator mBackgroundFadeOutAnimation;
113    private Drawable mBackground;
114    private Drawable mCustomizeTrayBackground;
115    private boolean mDrawCustomizeTrayBackground;
116    private float mBackgroundAlpha = 0;
117    private float mOverScrollMaxBackgroundAlpha = 0.0f;
118    private int mOverScrollPageIndex = -1;
119
120    private View mCustomizationDrawer;
121    private View mCustomizationDrawerContent;
122    private int[] mCustomizationDrawerPos = new int[2];
123    private float[] mCustomizationDrawerTransformedPos = new float[2];
124
125    private final WallpaperManager mWallpaperManager;
126
127    private int mDefaultPage;
128
129    private boolean mPageMoving = false;
130    private boolean mIsDragInProcess = false;
131
132    /**
133     * CellInfo for the cell that is currently being dragged
134     */
135    private CellLayout.CellInfo mDragInfo;
136
137    /**
138     * Target drop area calculated during last acceptDrop call.
139     */
140    private int[] mTargetCell = null;
141
142    /**
143     * The CellLayout that is currently being dragged over
144     */
145    private CellLayout mDragTargetLayout = null;
146
147    private Launcher mLauncher;
148    private IconCache mIconCache;
149    private DragController mDragController;
150
151    // These are temporary variables to prevent having to allocate a new object just to
152    // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
153    private int[] mTempCell = new int[2];
154    private int[] mTempEstimate = new int[2];
155    private float[] mTempOriginXY = new float[2];
156    private float[] mTempDragCoordinates = new float[2];
157    private float[] mTempTouchCoordinates = new float[2];
158    private float[] mTempCellLayoutCenterCoordinates = new float[2];
159    private float[] mTempDragBottomRightCoordinates = new float[2];
160    private Matrix mTempInverseMatrix = new Matrix();
161
162    private SpringLoadedDragController mSpringLoadedDragController;
163
164    private static final int DEFAULT_CELL_COUNT_X = 4;
165    private static final int DEFAULT_CELL_COUNT_Y = 4;
166
167    private Drawable mPreviousIndicator;
168    private Drawable mNextIndicator;
169
170    // State variable that indicates whether the pages are small (ie when you're
171    // in all apps or customize mode)
172    private boolean mIsSmall = false;
173    private boolean mIsInUnshrinkAnimation = false;
174    private AnimatorListener mShrinkAnimationListener, mUnshrinkAnimationListener;
175    enum ShrinkState { TOP, SPRING_LOADED, MIDDLE, BOTTOM_HIDDEN, BOTTOM_VISIBLE };
176    private ShrinkState mShrinkState;
177    private boolean mWasSpringLoadedOnDragExit = false;
178    private boolean mWaitingToShrink = false;
179    private ShrinkState mWaitingToShrinkState;
180    private AnimatorSet mAnimator;
181
182    /** Is the user is dragging an item near the edge of a page? */
183    private boolean mInScrollArea = false;
184
185    /** If mInScrollArea is true, the direction of the scroll. */
186    private int mPendingScrollDirection = DragController.SCROLL_NONE;
187
188    private final HolographicOutlineHelper mOutlineHelper = new HolographicOutlineHelper();
189    private Bitmap mDragOutline = null;
190    private final Rect mTempRect = new Rect();
191    private final int[] mTempXY = new int[2];
192
193    private ValueAnimator mDropAnim = null;
194    private TimeInterpolator mQuintEaseOutInterpolator = new DecelerateInterpolator(2.5f);
195    private View mDropView = null;
196    private int[] mDropViewPos = new int[] { -1, -1 };
197
198    // Paint used to draw external drop outline
199    private final Paint mExternalDragOutlinePaint = new Paint();
200
201    /** Used to trigger an animation as soon as the workspace stops scrolling. */
202    private Animator mAnimOnPageEndMoving = null;
203
204    // Camera and Matrix used to determine the final position of a neighboring CellLayout
205    private final Matrix mMatrix = new Matrix();
206    private final Camera mCamera = new Camera();
207    private final float mTempFloat2[] = new float[2];
208
209    /**
210     * Used to inflate the Workspace from XML.
211     *
212     * @param context The application's context.
213     * @param attrs The attributes set containing the Workspace's customization values.
214     */
215    public Workspace(Context context, AttributeSet attrs) {
216        this(context, attrs, 0);
217    }
218
219    /**
220     * Used to inflate the Workspace from XML.
221     *
222     * @param context The application's context.
223     * @param attrs The attributes set containing the Workspace's customization values.
224     * @param defStyle Unused.
225     */
226    public Workspace(Context context, AttributeSet attrs, int defStyle) {
227        super(context, attrs, defStyle);
228        mContentIsRefreshable = false;
229
230        if (!LauncherApplication.isScreenXLarge()) {
231            mFadeInAdjacentScreens = false;
232        }
233
234        mWallpaperManager = WallpaperManager.getInstance(context);
235
236        TypedArray a = context.obtainStyledAttributes(attrs,
237                R.styleable.Workspace, defStyle, 0);
238        int cellCountX = a.getInt(R.styleable.Workspace_cellCountX, DEFAULT_CELL_COUNT_X);
239        int cellCountY = a.getInt(R.styleable.Workspace_cellCountY, DEFAULT_CELL_COUNT_Y);
240        mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
241        a.recycle();
242
243        LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY);
244        setHapticFeedbackEnabled(false);
245
246        initWorkspace();
247    }
248
249    /**
250     * Initializes various states for this workspace.
251     */
252    protected void initWorkspace() {
253        Context context = getContext();
254        mCurrentPage = mDefaultPage;
255        Launcher.setScreen(mCurrentPage);
256        LauncherApplication app = (LauncherApplication)context.getApplicationContext();
257        mIconCache = app.getIconCache();
258        mExternalDragOutlinePaint.setAntiAlias(true);
259        setWillNotDraw(false);
260
261        try {
262            final Resources res = getResources();
263            mBackground = res.getDrawable(R.drawable.all_apps_bg_gradient);
264            mCustomizeTrayBackground = res.getDrawable(R.drawable.customize_bg_gradient);
265        } catch (Resources.NotFoundException e) {
266            // In this case, we will skip drawing background protection
267        }
268
269        mUnshrinkAnimationListener = new AnimatorListenerAdapter() {
270            @Override
271            public void onAnimationStart(Animator animation) {
272                mIsInUnshrinkAnimation = true;
273            }
274            @Override
275            public void onAnimationEnd(Animator animation) {
276                mIsInUnshrinkAnimation = false;
277                if (mShrinkState != ShrinkState.SPRING_LOADED) {
278                    mDrawCustomizeTrayBackground = false;
279                }
280            }
281        };
282        mSnapVelocity = 600;
283    }
284
285    @Override
286    protected int getScrollMode() {
287        if (LauncherApplication.isScreenXLarge()) {
288            return SmoothPagedView.X_LARGE_MODE;
289        } else {
290            return SmoothPagedView.DEFAULT_MODE;
291        }
292    }
293
294    @Override
295    public void addView(View child, int index, LayoutParams params) {
296        if (!(child instanceof CellLayout)) {
297            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
298        }
299        ((CellLayout) child).setOnInterceptTouchListener(this);
300        super.addView(child, index, params);
301    }
302
303    @Override
304    public void addView(View child) {
305        if (!(child instanceof CellLayout)) {
306            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
307        }
308        ((CellLayout) child).setOnInterceptTouchListener(this);
309        super.addView(child);
310    }
311
312    @Override
313    public void addView(View child, int index) {
314        if (!(child instanceof CellLayout)) {
315            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
316        }
317        ((CellLayout) child).setOnInterceptTouchListener(this);
318        super.addView(child, index);
319    }
320
321    @Override
322    public void addView(View child, int width, int height) {
323        if (!(child instanceof CellLayout)) {
324            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
325        }
326        ((CellLayout) child).setOnInterceptTouchListener(this);
327        super.addView(child, width, height);
328    }
329
330    @Override
331    public void addView(View child, LayoutParams params) {
332        if (!(child instanceof CellLayout)) {
333            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
334        }
335        ((CellLayout) child).setOnInterceptTouchListener(this);
336        super.addView(child, params);
337    }
338
339    /**
340     * @return The open folder on the current screen, or null if there is none
341     */
342    Folder getOpenFolder() {
343        CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
344        int count = currentPage.getChildCount();
345        for (int i = 0; i < count; i++) {
346            View child = currentPage.getChildAt(i);
347            if (child instanceof Folder) {
348                Folder folder = (Folder) child;
349                if (folder.getInfo().opened)
350                    return folder;
351            }
352        }
353        return null;
354    }
355
356    ArrayList<Folder> getOpenFolders() {
357        final int screenCount = getChildCount();
358        ArrayList<Folder> folders = new ArrayList<Folder>(screenCount);
359
360        for (int screen = 0; screen < screenCount; screen++) {
361            CellLayout currentPage = (CellLayout) getChildAt(screen);
362            int count = currentPage.getChildCount();
363            for (int i = 0; i < count; i++) {
364                View child = currentPage.getChildAt(i);
365                if (child instanceof Folder) {
366                    Folder folder = (Folder) child;
367                    if (folder.getInfo().opened)
368                        folders.add(folder);
369                    break;
370                }
371            }
372        }
373        return folders;
374    }
375
376    boolean isDefaultPageShowing() {
377        return mCurrentPage == mDefaultPage;
378    }
379
380    /**
381     * Sets the current screen.
382     *
383     * @param currentPage
384     */
385    @Override
386    void setCurrentPage(int currentPage) {
387        super.setCurrentPage(currentPage);
388        updateWallpaperOffset(mScrollX);
389    }
390
391    /**
392     * Adds the specified child in the specified screen. The position and dimension of
393     * the child are defined by x, y, spanX and spanY.
394     *
395     * @param child The child to add in one of the workspace's screens.
396     * @param screen The screen in which to add the child.
397     * @param x The X position of the child in the screen's grid.
398     * @param y The Y position of the child in the screen's grid.
399     * @param spanX The number of cells spanned horizontally by the child.
400     * @param spanY The number of cells spanned vertically by the child.
401     */
402    void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) {
403        addInScreen(child, screen, x, y, spanX, spanY, false);
404    }
405
406    void addInFullScreen(View child, int screen) {
407        addInScreen(child, screen, 0, 0, -1, -1);
408    }
409
410    /**
411     * Adds the specified child in the specified screen. The position and dimension of
412     * the child are defined by x, y, spanX and spanY.
413     *
414     * @param child The child to add in one of the workspace's screens.
415     * @param screen The screen in which to add the child.
416     * @param x The X position of the child in the screen's grid.
417     * @param y The Y position of the child in the screen's grid.
418     * @param spanX The number of cells spanned horizontally by the child.
419     * @param spanY The number of cells spanned vertically by the child.
420     * @param insert When true, the child is inserted at the beginning of the children list.
421     */
422    void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) {
423        if (screen < 0 || screen >= getChildCount()) {
424            Log.e(TAG, "The screen must be >= 0 and < " + getChildCount()
425                + " (was " + screen + "); skipping child");
426            return;
427        }
428
429        final CellLayout group = (CellLayout) getChildAt(screen);
430        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
431        if (lp == null) {
432            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
433        } else {
434            lp.cellX = x;
435            lp.cellY = y;
436            lp.cellHSpan = spanX;
437            lp.cellVSpan = spanY;
438        }
439
440        // Get the canonical child id to uniquely represent this view in this screen
441        int childId = LauncherModel.getCellLayoutChildId(-1, screen, x, y, spanX, spanY);
442        boolean markCellsAsOccupied = !(child instanceof Folder);
443        if (!group.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
444            // TODO: This branch occurs when the workspace is adding views
445            // outside of the defined grid
446            // maybe we should be deleting these items from the LauncherModel?
447            Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
448        }
449
450        if (!(child instanceof Folder)) {
451            child.setHapticFeedbackEnabled(false);
452            child.setOnLongClickListener(mLongClickListener);
453        }
454        if (child instanceof DropTarget) {
455            mDragController.addDropTarget((DropTarget) child);
456        }
457    }
458
459    public boolean onTouch(View v, MotionEvent event) {
460        // this is an intercepted event being forwarded from a cell layout
461        if (mIsSmall || mIsInUnshrinkAnimation) {
462            // Only allow clicks on a CellLayout if it is visible
463            if (mShrinkState != ShrinkState.BOTTOM_HIDDEN) {
464                mLauncher.onWorkspaceClick((CellLayout) v);
465            }
466            return true;
467        } else if (!mPageMoving) {
468            if (v == getChildAt(mCurrentPage - 1)) {
469                snapToPage(mCurrentPage - 1);
470                return true;
471            } else if (v == getChildAt(mCurrentPage + 1)) {
472                snapToPage(mCurrentPage + 1);
473                return true;
474            }
475        }
476        return false;
477    }
478
479    protected void onWindowVisibilityChanged (int visibility) {
480        mLauncher.onWindowVisibilityChanged(visibility);
481    }
482
483    @Override
484    public boolean dispatchUnhandledMove(View focused, int direction) {
485        if (mIsSmall || mIsInUnshrinkAnimation) {
486            // when the home screens are shrunken, shouldn't allow side-scrolling
487            return false;
488        }
489        return super.dispatchUnhandledMove(focused, direction);
490    }
491
492    @Override
493    public boolean onInterceptTouchEvent(MotionEvent ev) {
494        if (mIsSmall || mIsInUnshrinkAnimation) {
495            if (mLauncher.isAllAppsVisible() &&
496                    mShrinkState == ShrinkState.BOTTOM_HIDDEN) {
497                // Intercept this event so we can show the workspace in full view
498                // when it is clicked on and it is small
499                return true;
500            }
501            return false;
502        }
503        return super.onInterceptTouchEvent(ev);
504    }
505
506    @Override
507    protected void determineScrollingStart(MotionEvent ev) {
508        if (!mIsSmall && !mIsInUnshrinkAnimation) super.determineScrollingStart(ev);
509    }
510
511    protected void onPageBeginMoving() {
512        if (mNextPage != INVALID_PAGE) {
513            // we're snapping to a particular screen
514            enableChildrenCache(mCurrentPage, mNextPage);
515        } else {
516            // this is when user is actively dragging a particular screen, they might
517            // swipe it either left or right (but we won't advance by more than one screen)
518            enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
519        }
520        showOutlines();
521        mPageMoving = true;
522    }
523
524    protected void onPageEndMoving() {
525        clearChildrenCache();
526        // Hide the outlines, as long as we're not dragging
527        if (!mDragController.dragging()) {
528            hideOutlines();
529        }
530        // Check for an animation that's waiting to be started
531        if (mAnimOnPageEndMoving != null) {
532            mAnimOnPageEndMoving.start();
533            mAnimOnPageEndMoving = null;
534        }
535        mOverScrollMaxBackgroundAlpha = 0.0f;
536        mOverScrollPageIndex = -1;
537        mPageMoving = false;
538    }
539
540    @Override
541    protected void notifyPageSwitchListener() {
542        super.notifyPageSwitchListener();
543
544        if (mPreviousIndicator != null) {
545            // if we know the next page, we show the indication for it right away; it looks
546            // weird if the indicators are lagging
547            int page = mNextPage;
548            if (page == INVALID_PAGE) {
549                page = mCurrentPage;
550            }
551            mPreviousIndicator.setLevel(page);
552            mNextIndicator.setLevel(page);
553        }
554        Launcher.setScreen(mCurrentPage);
555    };
556
557    private void updateWallpaperOffset(int scrollRange) {
558        final boolean isStaticWallpaper = (mWallpaperManager != null) &&
559                (mWallpaperManager.getWallpaperInfo() == null);
560        if (LauncherApplication.isScreenXLarge() && !isStaticWallpaper) {
561            IBinder token = getWindowToken();
562            if (token != null) {
563                mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 );
564                mWallpaperManager.setWallpaperOffsets(getWindowToken(),
565                        Math.max(0.f, Math.min(mScrollX/(float)scrollRange, 1.f)), 0);
566            }
567        }
568    }
569
570    public void showOutlines() {
571        if (!mIsSmall && !mIsInUnshrinkAnimation) {
572            if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
573            if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
574            mChildrenOutlineFadeInAnimation = ObjectAnimator.ofFloat(this, "childrenOutlineAlpha", 1.0f);
575            mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
576            mChildrenOutlineFadeInAnimation.start();
577        }
578    }
579
580    public void hideOutlines() {
581        if (!mIsSmall && !mIsInUnshrinkAnimation) {
582            if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
583            if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
584            mChildrenOutlineFadeOutAnimation = ObjectAnimator.ofFloat(this, "childrenOutlineAlpha", 0.0f);
585            mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
586            mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
587            mChildrenOutlineFadeOutAnimation.start();
588        }
589    }
590
591    public void setChildrenOutlineAlpha(float alpha) {
592        mChildrenOutlineAlpha = alpha;
593        for (int i = 0; i < getChildCount(); i++) {
594            CellLayout cl = (CellLayout) getChildAt(i);
595            cl.setBackgroundAlpha(alpha);
596        }
597    }
598
599    public float getChildrenOutlineAlpha() {
600        return mChildrenOutlineAlpha;
601    }
602
603    private void showBackgroundGradientForAllApps() {
604        showBackgroundGradient();
605        mDrawCustomizeTrayBackground = false;
606    }
607
608    private void showBackgroundGradientForCustomizeTray() {
609        showBackgroundGradient();
610        mDrawCustomizeTrayBackground = true;
611    }
612
613    private void showBackgroundGradient() {
614        if (mBackground == null) return;
615        if (mBackgroundFadeOutAnimation != null) mBackgroundFadeOutAnimation.cancel();
616        if (mBackgroundFadeInAnimation != null) mBackgroundFadeInAnimation.cancel();
617        mBackgroundFadeInAnimation = ObjectAnimator.ofFloat(this, "backgroundAlpha", 1.0f);
618        mBackgroundFadeInAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
619        mBackgroundFadeInAnimation.setDuration(BACKGROUND_FADE_IN_DURATION);
620        mBackgroundFadeInAnimation.start();
621    }
622
623    private void hideBackgroundGradient() {
624        if (mBackground == null) return;
625        if (mBackgroundFadeInAnimation != null) mBackgroundFadeInAnimation.cancel();
626        if (mBackgroundFadeOutAnimation != null) mBackgroundFadeOutAnimation.cancel();
627        mBackgroundFadeOutAnimation = ObjectAnimator.ofFloat(this, "backgroundAlpha", 0.0f);
628        mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
629        mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
630        mBackgroundFadeOutAnimation.start();
631    }
632
633    public void setBackgroundAlpha(float alpha) {
634        mBackgroundAlpha = alpha;
635        invalidate();
636    }
637
638    public float getBackgroundAlpha() {
639        return mBackgroundAlpha;
640    }
641
642    /**
643     * Due to 3D transformations, if two CellLayouts are theoretically touching each other,
644     * on the xy plane, when one is rotated along the y-axis, the gap between them is perceived
645     * as being larger. This method computes what offset the rotated view should be translated
646     * in order to minimize this perceived gap.
647     * @param degrees Angle of the view
648     * @param width Width of the view
649     * @param height Height of the view
650     * @return Offset to be used in a View.setTranslationX() call
651     */
652    private float getOffsetXForRotation(float degrees, int width, int height) {
653        mMatrix.reset();
654        mCamera.save();
655        mCamera.rotateY(Math.abs(degrees));
656        mCamera.getMatrix(mMatrix);
657        mCamera.restore();
658
659        mMatrix.preTranslate(-width * 0.5f, -height * 0.5f);
660        mMatrix.postTranslate(width * 0.5f, height * 0.5f);
661        mTempFloat2[0] = width;
662        mTempFloat2[1] = height;
663        mMatrix.mapPoints(mTempFloat2);
664        return (width - mTempFloat2[0]) * (degrees > 0.0f ? 1.0f : -1.0f);
665    }
666
667    float backgroundAlphaInterpolator(float r) {
668        float pivotA = 0.1f;
669        float pivotB = 0.4f;
670        if (r < pivotA) {
671            return 0;
672        } else if (r > pivotB) {
673            return 1.0f;
674        } else {
675            return (r - pivotA)/(pivotB - pivotA);
676        }
677    }
678
679    float overScrollBackgroundAlphaInterpolator(float r) {
680        float threshold = 0.08f;
681
682        if (r > mOverScrollMaxBackgroundAlpha) {
683            mOverScrollMaxBackgroundAlpha = r;
684        } else if (r < mOverScrollMaxBackgroundAlpha) {
685            r = mOverScrollMaxBackgroundAlpha;
686        }
687
688        return Math.min(r / threshold, 1.0f);
689    }
690
691    @Override
692    protected void screenScrolled(int screenCenter) {
693        final int halfScreenSize = getMeasuredWidth() / 2;
694
695        for (int i = 0; i < getChildCount(); i++) {
696            CellLayout cl = (CellLayout) getChildAt(i);
697            if (cl != null) {
698                int totalDistance = getScaledMeasuredWidth(cl) + mPageSpacing;
699                int delta = screenCenter - (getChildOffset(i) -
700                        getRelativeChildOffset(i) + halfScreenSize);
701
702                float scrollProgress = delta / (totalDistance * 1.0f);
703                scrollProgress = Math.min(scrollProgress, 1.0f);
704                scrollProgress = Math.max(scrollProgress, -1.0f);
705
706                // If the current page (i) is being overscrolled, we use a different
707                // set of rules for setting the background alpha multiplier.
708                if ((mScrollX < 0 && i == 0) || (mScrollX > mMaxScrollX &&
709                        i == getChildCount() -1 )) {
710                    cl.setBackgroundAlphaMultiplier(
711                            overScrollBackgroundAlphaInterpolator(Math.abs(scrollProgress)));
712                    mOverScrollPageIndex = i;
713                } else if (mOverScrollPageIndex != i) {
714                    cl.setBackgroundAlphaMultiplier(
715                            backgroundAlphaInterpolator(Math.abs(scrollProgress)));
716
717                }
718
719                float rotation = WORKSPACE_ROTATION * scrollProgress;
720                float translationX = getOffsetXForRotation(rotation, cl.getWidth(), cl.getHeight());
721                cl.setTranslationX(translationX);
722
723                cl.setRotationY(rotation);
724            }
725        }
726    }
727
728    protected void onAttachedToWindow() {
729        super.onAttachedToWindow();
730        computeScroll();
731        mDragController.setWindowToken(getWindowToken());
732    }
733
734    @Override
735    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
736        super.onLayout(changed, left, top, right, bottom);
737
738        // if shrinkToBottom() is called on initialization, it has to be deferred
739        // until after the first call to onLayout so that it has the correct width
740        if (mWaitingToShrink) {
741            // shrink can trigger a synchronous onLayout call, so we
742            // post this to avoid a stack overflow / tangled onLayout calls
743            post(new Runnable() {
744                public void run() {
745                    shrink(mWaitingToShrinkState, false);
746                    mWaitingToShrink = false;
747                }
748            });
749        }
750
751        if (LauncherApplication.isInPlaceRotationEnabled()) {
752            // When the device is rotated, the scroll position of the current screen
753            // needs to be refreshed
754            setCurrentPage(getCurrentPage());
755        }
756    }
757
758    @Override
759    protected void onDraw(Canvas canvas) {
760        // Draw the background gradient if necessary
761        if (mBackground != null && mBackgroundAlpha > 0.0f) {
762            int alpha = (int) (mBackgroundAlpha * 255);
763            mBackground.setAlpha(alpha);
764            mBackground.setBounds(mScrollX, 0, mScrollX + getMeasuredWidth(), getMeasuredHeight());
765            mBackground.draw(canvas);
766            if (mDrawCustomizeTrayBackground) {
767                // Find out where to offset the gradient for the customization tray content
768                mCustomizationDrawer.getLocationOnScreen(mCustomizationDrawerPos);
769                final Matrix m = mCustomizationDrawer.getMatrix();
770                mCustomizationDrawerTransformedPos[0] = 0.0f;
771                mCustomizationDrawerTransformedPos[1] = mCustomizationDrawerContent.getTop();
772                m.mapPoints(mCustomizationDrawerTransformedPos);
773
774                // Draw the bg glow behind the gradient
775                mCustomizeTrayBackground.setAlpha(alpha);
776                mCustomizeTrayBackground.setBounds(mScrollX, 0, mScrollX + getMeasuredWidth(),
777                        getMeasuredHeight());
778                mCustomizeTrayBackground.draw(canvas);
779
780                // Draw the bg gradient
781                final int  offset = (int) (mCustomizationDrawerPos[1] +
782                        mCustomizationDrawerTransformedPos[1]);
783                mBackground.setBounds(mScrollX, offset, mScrollX + getMeasuredWidth(),
784                        offset + getMeasuredHeight());
785                mBackground.draw(canvas);
786            }
787        }
788        super.onDraw(canvas);
789    }
790
791    @Override
792    protected void dispatchDraw(Canvas canvas) {
793        if (mIsSmall || mIsInUnshrinkAnimation) {
794            // Draw all the workspaces if we're small
795            final int pageCount = getChildCount();
796            final long drawingTime = getDrawingTime();
797            for (int i = 0; i < pageCount; i++) {
798                final View page = (View) getChildAt(i);
799
800                drawChild(canvas, page, drawingTime);
801            }
802        } else {
803            super.dispatchDraw(canvas);
804
805            final int width = getWidth();
806            final int height = getHeight();
807
808            // In portrait orientation, draw the glowing edge when dragging to adjacent screens
809            if (mInScrollArea && (height > width)) {
810                final int pageHeight = getChildAt(0).getHeight();
811
812                // This determines the height of the glowing edge: 90% of the page height
813                final int padding = (int) ((height - pageHeight) * 0.5f + pageHeight * 0.1f);
814
815                final CellLayout leftPage = (CellLayout) getChildAt(mCurrentPage - 1);
816                final CellLayout rightPage = (CellLayout) getChildAt(mCurrentPage + 1);
817
818                if (leftPage != null && leftPage.getHover()) {
819                    final Drawable d = getResources().getDrawable(R.drawable.page_hover_left);
820                    d.setBounds(mScrollX, padding, mScrollX + d.getIntrinsicWidth(), height - padding);
821                    d.draw(canvas);
822                } else if (rightPage != null && rightPage.getHover()) {
823                    final Drawable d = getResources().getDrawable(R.drawable.page_hover_right);
824                    d.setBounds(mScrollX + width - d.getIntrinsicWidth(), padding, mScrollX + width, height - padding);
825                    d.draw(canvas);
826                }
827            }
828
829            if (mDropView != null) {
830                // We are animating an item that was just dropped on the home screen.
831                // Render its View in the current animation position.
832                canvas.save(Canvas.MATRIX_SAVE_FLAG);
833                final int xPos = mDropViewPos[0] - mDropView.getScrollX();
834                final int yPos = mDropViewPos[1] - mDropView.getScrollY();
835                canvas.translate(xPos, yPos);
836                mDropView.draw(canvas);
837                canvas.restore();
838            }
839        }
840    }
841
842    @Override
843    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
844        if (!mLauncher.isAllAppsVisible()) {
845            final Folder openFolder = getOpenFolder();
846            if (openFolder != null) {
847                return openFolder.requestFocus(direction, previouslyFocusedRect);
848            } else {
849                return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
850            }
851        }
852        return false;
853    }
854
855    @Override
856    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
857        if (!mLauncher.isAllAppsVisible()) {
858            final Folder openFolder = getOpenFolder();
859            if (openFolder != null) {
860                openFolder.addFocusables(views, direction);
861            } else {
862                super.addFocusables(views, direction, focusableMode);
863            }
864        }
865    }
866
867    @Override
868    public boolean dispatchTouchEvent(MotionEvent ev) {
869        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
870            // (In XLarge mode, the workspace is shrunken below all apps, and responds to taps
871            // ie when you click on a mini-screen, it zooms back to that screen)
872            if (!LauncherApplication.isScreenXLarge() && mLauncher.isAllAppsVisible()) {
873                return false;
874            }
875        }
876
877        return super.dispatchTouchEvent(ev);
878    }
879
880    void enableChildrenCache(int fromPage, int toPage) {
881        if (fromPage > toPage) {
882            final int temp = fromPage;
883            fromPage = toPage;
884            toPage = temp;
885        }
886
887        final int screenCount = getChildCount();
888
889        fromPage = Math.max(fromPage, 0);
890        toPage = Math.min(toPage, screenCount - 1);
891
892        for (int i = fromPage; i <= toPage; i++) {
893            final CellLayout layout = (CellLayout) getChildAt(i);
894            layout.setChildrenDrawnWithCacheEnabled(true);
895            layout.setChildrenDrawingCacheEnabled(true);
896        }
897    }
898
899    void clearChildrenCache() {
900        final int screenCount = getChildCount();
901        for (int i = 0; i < screenCount; i++) {
902            final CellLayout layout = (CellLayout) getChildAt(i);
903            layout.setChildrenDrawnWithCacheEnabled(false);
904        }
905    }
906
907    @Override
908    public boolean onTouchEvent(MotionEvent ev) {
909        if (mLauncher.isAllAppsVisible()) {
910            // Cancel any scrolling that is in progress.
911            if (!mScroller.isFinished()) {
912                mScroller.abortAnimation();
913            }
914            setCurrentPage(mCurrentPage);
915
916            if (mShrinkState == ShrinkState.BOTTOM_HIDDEN) {
917                mLauncher.showWorkspace(true);
918                // Let the events fall through to the CellLayouts because if they are not
919                // hit, then we get a crash due to a missing ACTION_DOWN touch event
920            }
921
922            return false; // We don't want the events
923        }
924
925        return super.onTouchEvent(ev);
926    }
927
928    @Override
929    protected void onWallpaperTap(MotionEvent ev) {
930        final int[] position = mTempCell;
931        getLocationOnScreen(position);
932
933        int pointerIndex = ev.getActionIndex();
934        position[0] += (int) ev.getX(pointerIndex);
935        position[1] += (int) ev.getY(pointerIndex);
936
937        mWallpaperManager.sendWallpaperCommand(getWindowToken(),
938                ev.getAction() == MotionEvent.ACTION_UP
939                        ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
940                position[0], position[1], 0, null);
941    }
942
943    public boolean isSmall() {
944        return mIsSmall;
945    }
946
947    private float getYScaleForScreen(int screen) {
948        int x = Math.abs(screen - 2);
949
950        // TODO: This should be generalized for use with arbitrary rotation angles.
951        switch(x) {
952            case 0: return EXTRA_SCALE_FACTOR_0;
953            case 1: return EXTRA_SCALE_FACTOR_1;
954            case 2: return EXTRA_SCALE_FACTOR_2;
955        }
956        return 1.0f;
957    }
958
959    public void shrink(ShrinkState shrinkState) {
960        shrink(shrinkState, true);
961    }
962
963    // we use this to shrink the workspace for the all apps view and the customize view
964    public void shrink(ShrinkState shrinkState, boolean animated) {
965        if (mFirstLayout) {
966            // (mFirstLayout == "first layout has not happened yet")
967            // if we get a call to shrink() as part of our initialization (for example, if
968            // Launcher is started in All Apps mode) then we need to wait for a layout call
969            // to get our width so we can layout the mini-screen views correctly
970            mWaitingToShrink = true;
971            mWaitingToShrinkState = shrinkState;
972            return;
973        }
974        mIsSmall = true;
975        mShrinkState = shrinkState;
976
977        // Stop any scrolling, move to the current page right away
978        setCurrentPage((mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage);
979        if (!mIsDragInProcess) {
980            updateWhichPagesAcceptDrops(mShrinkState);
981        }
982
983        // we intercept and reject all touch events when we're small, so be sure to reset the state
984        mTouchState = TOUCH_STATE_REST;
985        mActivePointerId = INVALID_POINTER;
986
987        CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
988        if (currentPage.getBackgroundAlphaMultiplier() < 1.0f) {
989            currentPage.setBackgroundAlpha(0.0f);
990        }
991        currentPage.setBackgroundAlphaMultiplier(1.0f);
992
993        final Resources res = getResources();
994        final int screenWidth = getWidth();
995        final int screenHeight = getHeight();
996
997        // Making the assumption that all pages have the same width as the 0th
998        final int pageWidth = getChildAt(0).getMeasuredWidth();
999        final int pageHeight = getChildAt(0).getMeasuredHeight();
1000
1001        final int scaledPageWidth = (int) (SHRINK_FACTOR * pageWidth);
1002        final int scaledPageHeight = (int) (SHRINK_FACTOR * pageHeight);
1003        final float extraScaledSpacing = res.getDimension(R.dimen.smallScreenExtraSpacing);
1004
1005        final int screenCount = getChildCount();
1006        float totalWidth = screenCount * scaledPageWidth + (screenCount - 1) * extraScaledSpacing;
1007
1008        boolean isPortrait = getMeasuredHeight() > getMeasuredWidth();
1009        float newY = (isPortrait ?
1010                getResources().getDimension(R.dimen.allAppsSmallScreenVerticalMarginPortrait) :
1011                getResources().getDimension(R.dimen.allAppsSmallScreenVerticalMarginLandscape));
1012        float finalAlpha = 1.0f;
1013        float extraShrinkFactor = 1.0f;
1014        if (shrinkState == ShrinkState.BOTTOM_VISIBLE) {
1015             newY = screenHeight - newY - scaledPageHeight;
1016        } else if (shrinkState == ShrinkState.BOTTOM_HIDDEN) {
1017
1018            // We shrink and disappear to nothing in the case of all apps
1019            // (which is when we shrink to the bottom)
1020            newY = screenHeight - newY - scaledPageHeight;
1021            finalAlpha = 0.0f;
1022        } else if (shrinkState == ShrinkState.MIDDLE) {
1023            newY = screenHeight / 2 - scaledPageHeight / 2;
1024            finalAlpha = 1.0f;
1025        } else if (shrinkState == ShrinkState.TOP) {
1026            newY = (isPortrait ?
1027                getResources().getDimension(R.dimen.customizeSmallScreenVerticalMarginPortrait) :
1028                getResources().getDimension(R.dimen.customizeSmallScreenVerticalMarginLandscape));
1029        }
1030
1031        // We animate all the screens to the centered position in workspace
1032        // At the same time, the screens become greyed/dimmed
1033
1034        // newX is initialized to the left-most position of the centered screens
1035        float newX = mScroller.getFinalX() + screenWidth / 2 - totalWidth / 2;
1036
1037        // We are going to scale about the center of the view, so we need to adjust the positions
1038        // of the views accordingly
1039        newX -= (pageWidth - scaledPageWidth) / 2.0f;
1040        newY -= (pageHeight - scaledPageHeight) / 2.0f;
1041
1042        if (mAnimator != null) {
1043            mAnimator.cancel();
1044        }
1045        mAnimator = new AnimatorSet();
1046        for (int i = 0; i < screenCount; i++) {
1047            CellLayout cl = (CellLayout) getChildAt(i);
1048
1049            float rotation = (-i + 2) * WORKSPACE_ROTATION;
1050            float rotationScaleX = (float) (1.0f / Math.cos(Math.PI * rotation / 180.0f));
1051            float rotationScaleY = getYScaleForScreen(i);
1052
1053            if (animated) {
1054                final int duration = res.getInteger(R.integer.config_workspaceShrinkTime);
1055
1056                ObjectAnimator animWithInterpolator = ObjectAnimator.ofPropertyValuesHolder(cl,
1057                        PropertyValuesHolder.ofFloat("x", newX),
1058                        PropertyValuesHolder.ofFloat("y", newY),
1059                        PropertyValuesHolder.ofFloat("scaleX",
1060                                SHRINK_FACTOR * rotationScaleX * extraShrinkFactor),
1061                        PropertyValuesHolder.ofFloat("scaleY",
1062                                SHRINK_FACTOR * rotationScaleY * extraShrinkFactor),
1063                        PropertyValuesHolder.ofFloat("backgroundAlpha", finalAlpha),
1064                        PropertyValuesHolder.ofFloat("alpha", finalAlpha),
1065                        PropertyValuesHolder.ofFloat("rotationY", rotation));
1066
1067                animWithInterpolator.setDuration(duration);
1068                animWithInterpolator.setInterpolator(mZoomOutInterpolator);
1069                mAnimator.playTogether(animWithInterpolator);
1070            } else {
1071                cl.setX((int)newX);
1072                cl.setY((int)newY);
1073                cl.setScaleX(SHRINK_FACTOR * rotationScaleX * extraShrinkFactor);
1074                cl.setScaleY(SHRINK_FACTOR * rotationScaleY * extraShrinkFactor);
1075                cl.setBackgroundAlpha(finalAlpha);
1076                cl.setAlpha(finalAlpha);
1077                cl.setRotationY(rotation);
1078            }
1079            // increment newX for the next screen
1080            newX += scaledPageWidth + extraScaledSpacing;
1081        }
1082        setLayoutScale(1.0f);
1083        if (animated) {
1084            mAnimator.start();
1085        }
1086        setChildrenDrawnWithCacheEnabled(true);
1087
1088        if (shrinkState == ShrinkState.TOP) {
1089            showBackgroundGradientForCustomizeTray();
1090        } else {
1091            showBackgroundGradientForAllApps();
1092        }
1093    }
1094
1095    /*
1096     * This interpolator emulates the rate at which the perceived scale of an object changes
1097     * as its distance from a camera increases. When this interpolator is applied to a scale
1098     * animation on a view, it evokes the sense that the object is shrinking due to moving away
1099     * from the camera.
1100     */
1101    static class ZInterpolator implements TimeInterpolator {
1102        private float focalLength;
1103
1104        public ZInterpolator(float foc) {
1105            focalLength = foc;
1106        }
1107
1108        public float getInterpolation(float input) {
1109            return (1.0f - focalLength / (focalLength + input)) /
1110                (1.0f - focalLength / (focalLength + 1.0f));
1111        }
1112    }
1113
1114    /*
1115     * The exact reverse of ZInterpolator.
1116     */
1117    static class InverseZInterpolator implements TimeInterpolator {
1118        private ZInterpolator zInterpolator;
1119        public InverseZInterpolator(float foc) {
1120            zInterpolator = new ZInterpolator(foc);
1121        }
1122        public float getInterpolation(float input) {
1123            return 1 - zInterpolator.getInterpolation(1 - input);
1124        }
1125    }
1126
1127    /*
1128     * ZInterpolator compounded with an ease-out.
1129     */
1130    static class ZoomOutInterpolator implements TimeInterpolator {
1131        private final ZInterpolator zInterpolator = new ZInterpolator(0.2f);
1132        private final DecelerateInterpolator decelerate = new DecelerateInterpolator(1.5f);
1133
1134        public float getInterpolation(float input) {
1135            return decelerate.getInterpolation(zInterpolator.getInterpolation(input));
1136        }
1137    }
1138
1139    /*
1140     * InvereZInterpolator compounded with an ease-out.
1141     */
1142    static class ZoomInInterpolator implements TimeInterpolator {
1143        private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
1144        private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
1145
1146        public float getInterpolation(float input) {
1147            return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
1148        }
1149    }
1150
1151    private final ZoomOutInterpolator mZoomOutInterpolator = new ZoomOutInterpolator();
1152    private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
1153
1154    private void updateWhichPagesAcceptDrops(ShrinkState state) {
1155        updateWhichPagesAcceptDropsHelper(state, false, 1, 1);
1156    }
1157
1158    private void updateWhichPagesAcceptDropsDuringDrag(ShrinkState state, int spanX, int spanY) {
1159        updateWhichPagesAcceptDropsHelper(state, true, spanX, spanY);
1160    }
1161
1162    private void updateWhichPagesAcceptDropsHelper(
1163            ShrinkState state, boolean isDragHappening, int spanX, int spanY) {
1164        final int screenCount = getChildCount();
1165        for (int i = 0; i < screenCount; i++) {
1166            CellLayout cl = (CellLayout) getChildAt(i);
1167
1168            switch (state) {
1169                case TOP:
1170                    if (!isDragHappening) {
1171                        boolean showDropHighlight = i == mCurrentPage;
1172                        cl.setAcceptsDrops(showDropHighlight);
1173                        break;
1174                    }
1175                    // otherwise, fall through below and mark non-full screens as accepting drops
1176                case BOTTOM_HIDDEN:
1177                case BOTTOM_VISIBLE:
1178                    if (!isDragHappening) {
1179                        // even if a drag isn't happening, we don't want to show a screen as
1180                        // accepting drops if it doesn't have at least one free cell
1181                        spanX = 1;
1182                        spanY = 1;
1183                    }
1184                    // the page accepts drops if we can find at least one empty spot
1185                    cl.setAcceptsDrops(cl.findCellForSpan(null, spanX, spanY));
1186                    break;
1187                default:
1188                     throw new RuntimeException(
1189                             "updateWhichPagesAcceptDropsHelper passed an unhandled ShrinkState");
1190            }
1191        }
1192    }
1193
1194    /*
1195     *
1196     * We call these methods (onDragStartedWithItemSpans/onDragStartedWithItemMinSize) whenever we
1197     * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
1198     *
1199     * These methods mark the appropriate pages as accepting drops (which alters their visual
1200     * appearance).
1201     *
1202     */
1203    public void onDragStartedWithItemSpans(int spanX, int spanY, Bitmap b) {
1204        mIsDragInProcess = true;
1205
1206        final Canvas canvas = new Canvas();
1207
1208        // We need to add extra padding to the bitmap to make room for the glow effect
1209        final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;
1210
1211        // The outline is used to visualize where the item will land if dropped
1212        mDragOutline = createDragOutline(b, canvas, bitmapPadding);
1213
1214        updateWhichPagesAcceptDropsDuringDrag(mShrinkState, spanX, spanY);
1215    }
1216
1217    // we call this method whenever a drag and drop in Launcher finishes, even if Workspace was
1218    // never dragged over
1219    public void onDragStopped() {
1220        mIsDragInProcess = false;
1221        updateWhichPagesAcceptDrops(mShrinkState);
1222    }
1223
1224    @Override
1225    protected boolean handlePagingClicks() {
1226        return true;
1227    }
1228
1229    // We call this when we trigger an unshrink by clicking on the CellLayout cl
1230    public void unshrink(CellLayout clThatWasClicked) {
1231        unshrink(clThatWasClicked, false);
1232    }
1233
1234    public void unshrink(CellLayout clThatWasClicked, boolean springLoaded) {
1235        int newCurrentPage = indexOfChild(clThatWasClicked);
1236        if (mIsSmall) {
1237            if (springLoaded) {
1238                setLayoutScale(SPRING_LOADED_DRAG_SHRINK_FACTOR);
1239            }
1240            moveToNewPageWithoutMovingCellLayouts(newCurrentPage);
1241            unshrink(true, springLoaded);
1242        }
1243    }
1244
1245
1246    public void enterSpringLoadedDragMode(CellLayout clThatWasClicked) {
1247        mShrinkState = ShrinkState.SPRING_LOADED;
1248        unshrink(clThatWasClicked, true);
1249        mDragTargetLayout.onDragEnter();
1250    }
1251
1252    public void exitSpringLoadedDragMode(ShrinkState shrinkState) {
1253        shrink(shrinkState);
1254        if (mDragTargetLayout != null) {
1255            mDragTargetLayout.onDragExit();
1256        }
1257    }
1258
1259    void unshrink(boolean animated) {
1260        unshrink(animated, false);
1261    }
1262
1263    void unshrink(boolean animated, boolean springLoaded) {
1264        if (mIsSmall) {
1265            float finalScaleFactor = 1.0f;
1266            float finalBackgroundAlpha = 0.0f;
1267            if (springLoaded) {
1268                finalScaleFactor = SPRING_LOADED_DRAG_SHRINK_FACTOR;
1269                finalBackgroundAlpha = 1.0f;
1270            } else {
1271                mIsSmall = false;
1272            }
1273            if (mAnimator != null) {
1274                mAnimator.cancel();
1275            }
1276
1277            mAnimator = new AnimatorSet();
1278            final int screenCount = getChildCount();
1279
1280            final int duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime);
1281            for (int i = 0; i < screenCount; i++) {
1282                final CellLayout cl = (CellLayout)getChildAt(i);
1283                float finalAlphaValue = (i == mCurrentPage) ? 1.0f : 0.0f;
1284                float rotation = 0.0f;
1285
1286                if (i < mCurrentPage) {
1287                    rotation = WORKSPACE_ROTATION;
1288                } else if (i > mCurrentPage) {
1289                    rotation = -WORKSPACE_ROTATION;
1290                }
1291
1292                float translation = getOffsetXForRotation(rotation, cl.getWidth(), cl.getHeight());
1293
1294                if (animated) {
1295                    ObjectAnimator animWithInterpolator = ObjectAnimator.ofPropertyValuesHolder(cl,
1296                            PropertyValuesHolder.ofFloat("translationX", translation),
1297                            PropertyValuesHolder.ofFloat("translationY", 0.0f),
1298                            PropertyValuesHolder.ofFloat("scaleX", finalScaleFactor),
1299                            PropertyValuesHolder.ofFloat("scaleY", finalScaleFactor),
1300                            PropertyValuesHolder.ofFloat("backgroundAlpha", finalBackgroundAlpha),
1301                            PropertyValuesHolder.ofFloat("alpha", finalAlphaValue),
1302                            PropertyValuesHolder.ofFloat("rotationY", rotation));
1303                    animWithInterpolator.setDuration(duration);
1304                    animWithInterpolator.setInterpolator(mZoomInInterpolator);
1305                    mAnimator.playTogether(animWithInterpolator);
1306                } else {
1307                    cl.setTranslationX(translation);
1308                    cl.setTranslationY(0.0f);
1309                    cl.setScaleX(finalScaleFactor);
1310                    cl.setScaleY(finalScaleFactor);
1311                    cl.setBackgroundAlpha(0.0f);
1312                    cl.setAlpha(finalAlphaValue);
1313                    cl.setRotationY(rotation);
1314                }
1315            }
1316
1317            if (animated) {
1318                // If we call this when we're not animated, onAnimationEnd is never called on
1319                // the listener; make sure we only use the listener when we're actually animating
1320                mAnimator.addListener(mUnshrinkAnimationListener);
1321                mAnimator.start();
1322            }
1323        }
1324
1325        if (!springLoaded) {
1326            hideBackgroundGradient();
1327        }
1328    }
1329
1330    /**
1331     * Draw the View v into the given Canvas.
1332     *
1333     * @param v the view to draw
1334     * @param destCanvas the canvas to draw on
1335     * @param padding the horizontal and vertical padding to use when drawing
1336     */
1337    private void drawDragView(View v, Canvas destCanvas, int padding) {
1338        final Rect clipRect = mTempRect;
1339        v.getDrawingRect(clipRect);
1340
1341        // For a TextView, adjust the clip rect so that we don't include the text label
1342        if (v instanceof BubbleTextView) {
1343            final BubbleTextView tv = (BubbleTextView) v;
1344            clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V +
1345                    tv.getLayout().getLineTop(0);
1346        } else if (v instanceof TextView) {
1347            final TextView tv = (TextView) v;
1348            clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() +
1349                    tv.getLayout().getLineTop(0);
1350        }
1351
1352        // Draw the View into the bitmap.
1353        // The translate of scrollX and scrollY is necessary when drawing TextViews, because
1354        // they set scrollX and scrollY to large values to achieve centered text
1355
1356        destCanvas.save();
1357        destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
1358        destCanvas.clipRect(clipRect, Op.REPLACE);
1359        v.draw(destCanvas);
1360        destCanvas.restore();
1361    }
1362
1363    /**
1364     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
1365     * Responsibility for the bitmap is transferred to the caller.
1366     */
1367    private Bitmap createDragOutline(View v, Canvas canvas, int padding) {
1368        final int outlineColor = getResources().getColor(R.color.drag_outline_color);
1369        final Bitmap b = Bitmap.createBitmap(
1370                v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
1371
1372        canvas.setBitmap(b);
1373        drawDragView(v, canvas, padding);
1374        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
1375        return b;
1376    }
1377
1378    /**
1379     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
1380     * Responsibility for the bitmap is transferred to the caller.
1381     */
1382    private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding) {
1383        final int outlineColor = getResources().getColor(R.color.drag_outline_color);
1384        final Bitmap b = Bitmap.createBitmap(
1385                orig.getWidth() + padding, orig.getHeight() + padding, Bitmap.Config.ARGB_8888);
1386
1387        canvas.setBitmap(b);
1388        canvas.drawBitmap(orig, 0, 0, new Paint());
1389        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
1390
1391        return b;
1392    }
1393
1394    /**
1395     * Creates a drag outline to represent a drop (that we don't have the actual information for
1396     * yet).  May be changed in the future to alter the drop outline slightly depending on the
1397     * clip description mime data.
1398     */
1399    private Bitmap createExternalDragOutline(Canvas canvas, int padding) {
1400        Resources r = getResources();
1401        final int outlineColor = r.getColor(R.color.drag_outline_color);
1402        final int iconWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
1403        final int iconHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
1404        final int rectRadius = r.getDimensionPixelSize(R.dimen.external_drop_icon_rect_radius);
1405        final int inset = (int) (Math.min(iconWidth, iconHeight) * 0.2f);
1406        final Bitmap b = Bitmap.createBitmap(
1407                iconWidth + padding, iconHeight + padding, Bitmap.Config.ARGB_8888);
1408
1409        canvas.setBitmap(b);
1410        canvas.drawRoundRect(new RectF(inset, inset, iconWidth - inset, iconHeight - inset),
1411                rectRadius, rectRadius, mExternalDragOutlinePaint);
1412        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
1413        return b;
1414    }
1415
1416    /**
1417     * Returns a new bitmap to show when the given View is being dragged around.
1418     * Responsibility for the bitmap is transferred to the caller.
1419     */
1420    private Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
1421        final int outlineColor = getResources().getColor(R.color.drag_outline_color);
1422        final Bitmap b = Bitmap.createBitmap(
1423                mDragOutline.getWidth(), mDragOutline.getHeight(), Bitmap.Config.ARGB_8888);
1424
1425        canvas.setBitmap(b);
1426        canvas.drawBitmap(mDragOutline, 0, 0, null);
1427        drawDragView(v, canvas, padding);
1428        mOutlineHelper.applyOuterBlur(b, canvas, outlineColor);
1429
1430        return b;
1431    }
1432
1433    void startDrag(CellLayout.CellInfo cellInfo) {
1434        View child = cellInfo.cell;
1435
1436        // Make sure the drag was started by a long press as opposed to a long click.
1437        if (!child.isInTouchMode()) {
1438            return;
1439        }
1440
1441        mDragInfo = cellInfo;
1442        mDragInfo.screen = mCurrentPage;
1443
1444        CellLayout current = getCurrentDropLayout();
1445
1446        current.onDragChild(child);
1447
1448        child.clearFocus();
1449        child.setPressed(false);
1450
1451        final Canvas canvas = new Canvas();
1452
1453        // We need to add extra padding to the bitmap to make room for the glow effect
1454        final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;
1455
1456        // The outline is used to visualize where the item will land if dropped
1457        mDragOutline = createDragOutline(child, canvas, bitmapPadding);
1458
1459        // The drag bitmap follows the touch point around on the screen
1460        final Bitmap b = createDragBitmap(child, canvas, bitmapPadding);
1461
1462        final int bmpWidth = b.getWidth();
1463        final int bmpHeight = b.getHeight();
1464        child.getLocationOnScreen(mTempXY);
1465        final int screenX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2;
1466        final int screenY = (int) mTempXY[1] + (child.getHeight() - bmpHeight) / 2;
1467        mDragController.startDrag(b, screenX, screenY, 0, 0, bmpWidth, bmpHeight, this,
1468                child.getTag(), DragController.DRAG_ACTION_MOVE, null);
1469        b.recycle();
1470    }
1471
1472    void addApplicationShortcut(ShortcutInfo info, int screen, int cellX, int cellY,
1473            boolean insertAtFirst, int intersectX, int intersectY) {
1474        final CellLayout cellLayout = (CellLayout) getChildAt(screen);
1475        View view = mLauncher.createShortcut(R.layout.application, cellLayout, (ShortcutInfo) info);
1476
1477        final int[] cellXY = new int[2];
1478        cellLayout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
1479        addInScreen(view, screen, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
1480        LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
1481                LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
1482                cellXY[0], cellXY[1]);
1483    }
1484
1485    private void setPositionForDropAnimation(
1486            View dragView, int dragViewX, int dragViewY, View parent, View child) {
1487        final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
1488
1489        // Based on the position of the drag view, find the top left of the original view
1490        int viewX = dragViewX + (dragView.getWidth() - child.getWidth()) / 2;
1491        int viewY = dragViewY + (dragView.getHeight() - child.getHeight()) / 2;
1492        viewX += getResources().getInteger(R.integer.config_dragViewOffsetX);
1493        viewY += getResources().getInteger(R.integer.config_dragViewOffsetY);
1494
1495        // Set its old pos (in the new parent's coordinates); it will be animated
1496        // in animateViewIntoPosition after the next layout pass
1497        lp.oldX = viewX - (parent.getLeft() - mScrollX);
1498        lp.oldY = viewY - (parent.getTop() - mScrollY);
1499    }
1500
1501    public void animateViewIntoPosition(final View view) {
1502        final CellLayout parent = (CellLayout) view.getParent();
1503        final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
1504
1505        // Convert the animation params to be relative to the Workspace, not the CellLayout
1506        final int fromX = lp.oldX + parent.getLeft();
1507        final int fromY = lp.oldY + parent.getTop();
1508
1509        final int dx = lp.x - lp.oldX;
1510        final int dy = lp.y - lp.oldY;
1511
1512        // Calculate the duration of the animation based on the object's distance
1513        final float dist = (float) Math.sqrt(dx*dx + dy*dy);
1514        final Resources res = getResources();
1515        final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
1516        int duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
1517        if (dist < maxDist) {
1518            duration *= mQuintEaseOutInterpolator.getInterpolation(dist / maxDist);
1519        }
1520
1521        if (mDropAnim != null) {
1522            mDropAnim.end();
1523        }
1524        mDropAnim = new ValueAnimator();
1525        mDropAnim.setInterpolator(mQuintEaseOutInterpolator);
1526
1527        // The view is invisible during the animation; we render it manually.
1528        mDropAnim.addListener(new AnimatorListenerAdapter() {
1529            public void onAnimationStart(Animator animation) {
1530                // Set this here so that we don't render it until the animation begins
1531                mDropView = view;
1532            }
1533
1534            public void onAnimationEnd(Animator animation) {
1535                if (mDropView != null) {
1536                    mDropView.setVisibility(View.VISIBLE);
1537                    mDropView = null;
1538                }
1539            }
1540        });
1541
1542        mDropAnim.setDuration(duration);
1543        mDropAnim.setFloatValues(0.0f, 1.0f);
1544        mDropAnim.removeAllUpdateListeners();
1545        mDropAnim.addUpdateListener(new AnimatorUpdateListener() {
1546            public void onAnimationUpdate(ValueAnimator animation) {
1547                final float percent = (Float) animation.getAnimatedValue();
1548                // Invalidate the old position
1549                invalidate(mDropViewPos[0], mDropViewPos[1],
1550                        mDropViewPos[0] + view.getWidth(), mDropViewPos[1] + view.getHeight());
1551
1552                mDropViewPos[0] = fromX + (int) (percent * dx + 0.5f);
1553                mDropViewPos[1] = fromY + (int) (percent * dy + 0.5f);
1554                invalidate(mDropViewPos[0], mDropViewPos[1],
1555                        mDropViewPos[0] + view.getWidth(), mDropViewPos[1] + view.getHeight());
1556            }
1557        });
1558
1559        view.setVisibility(View.INVISIBLE);
1560
1561        if (!mScroller.isFinished()) {
1562            mAnimOnPageEndMoving = mDropAnim;
1563        } else {
1564            mDropAnim.start();
1565        }
1566    }
1567
1568    /**
1569     * {@inheritDoc}
1570     */
1571    public boolean acceptDrop(DragSource source, int x, int y,
1572            int xOffset, int yOffset, DragView dragView, Object dragInfo) {
1573
1574        // If it's an external drop (e.g. from All Apps), check if it should be accepted
1575        if (source != this) {
1576            // Don't accept the drop if we're not over a screen at time of drop
1577            if (mDragTargetLayout == null || !mDragTargetLayout.getAcceptsDrops()) {
1578                return false;
1579            }
1580
1581            final CellLayout.CellInfo dragCellInfo = mDragInfo;
1582            final int spanX = dragCellInfo == null ? 1 : dragCellInfo.spanX;
1583            final int spanY = dragCellInfo == null ? 1 : dragCellInfo.spanY;
1584
1585            final View ignoreView = dragCellInfo == null ? null : dragCellInfo.cell;
1586
1587            // Don't accept the drop if there's no room for the item
1588            if (!mDragTargetLayout.findCellForSpanIgnoring(null, spanX, spanY, ignoreView)) {
1589                mLauncher.showOutOfSpaceMessage();
1590                return false;
1591            }
1592        }
1593        return true;
1594    }
1595
1596    public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
1597            DragView dragView, Object dragInfo) {
1598
1599        int originX = x - xOffset;
1600        int originY = y - yOffset;
1601
1602        if (mIsSmall || mIsInUnshrinkAnimation) {
1603            // get originX and originY in the local coordinate system of the screen
1604            mTempOriginXY[0] = originX;
1605            mTempOriginXY[1] = originY;
1606            mapPointFromSelfToChild(mDragTargetLayout, mTempOriginXY);
1607            originX = (int)mTempOriginXY[0];
1608            originY = (int)mTempOriginXY[1];
1609        }
1610
1611        if (source != this) {
1612            if (!mIsSmall || mWasSpringLoadedOnDragExit) {
1613                onDropExternal(originX, originY, dragInfo, mDragTargetLayout, false);
1614            } else {
1615                // if we drag and drop to small screens, don't pass the touch x/y coords (when we
1616                // enable spring-loaded adding, however, we do want to pass the touch x/y coords)
1617                onDropExternal(-1, -1, dragInfo, mDragTargetLayout, false);
1618            }
1619        } else if (mDragInfo != null) {
1620            final View cell = mDragInfo.cell;
1621            CellLayout dropTargetLayout = mDragTargetLayout;
1622
1623            // Handle the case where the user drops when in the scroll area.
1624            // This is treated as a drop on the adjacent page.
1625            if (dropTargetLayout == null && mInScrollArea) {
1626                if (mPendingScrollDirection == DragController.SCROLL_LEFT) {
1627                    dropTargetLayout = (CellLayout) getChildAt(mCurrentPage - 1);
1628                } else if (mPendingScrollDirection == DragController.SCROLL_RIGHT) {
1629                    dropTargetLayout = (CellLayout) getChildAt(mCurrentPage + 1);
1630                }
1631            }
1632
1633            if (dropTargetLayout != null) {
1634                // Move internally
1635                mTargetCell = findNearestVacantArea(originX, originY,
1636                        mDragInfo.spanX, mDragInfo.spanY, cell, dropTargetLayout,
1637                        mTargetCell);
1638
1639                final int screen = (mTargetCell == null) ?
1640                        mDragInfo.screen : indexOfChild(dropTargetLayout);
1641
1642                if (screen != mCurrentPage) {
1643                    snapToPage(screen);
1644                }
1645
1646                if (mTargetCell != null) {
1647                    if (screen != mDragInfo.screen) {
1648                        // Reparent the view
1649                        ((CellLayout) getChildAt(mDragInfo.screen)).removeView(cell);
1650                        addInScreen(cell, screen, mTargetCell[0], mTargetCell[1],
1651                                mDragInfo.spanX, mDragInfo.spanY);
1652                    }
1653
1654                    // update the item's position after drop
1655                    final ItemInfo info = (ItemInfo) cell.getTag();
1656                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
1657                    dropTargetLayout.onMove(cell, mTargetCell[0], mTargetCell[1]);
1658                    lp.cellX = mTargetCell[0];
1659                    lp.cellY = mTargetCell[1];
1660                    cell.setId(LauncherModel.getCellLayoutChildId(-1, mDragInfo.screen,
1661                            mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
1662
1663                    LauncherModel.moveItemInDatabase(mLauncher, info,
1664                            LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
1665                            lp.cellX, lp.cellY);
1666                }
1667            }
1668
1669            final CellLayout parent = (CellLayout) cell.getParent();
1670
1671            // Prepare it to be animated into its new position
1672            // This must be called after the view has been re-parented
1673            setPositionForDropAnimation(dragView, originX, originY, parent, cell);
1674            boolean animateDrop = !mWasSpringLoadedOnDragExit;
1675            parent.onDropChild(cell, animateDrop);
1676        }
1677    }
1678
1679    public void onDragEnter(DragSource source, int x, int y, int xOffset,
1680            int yOffset, DragView dragView, Object dragInfo) {
1681        mDragTargetLayout = null; // Reset the drag state
1682
1683        if (!mIsSmall) {
1684            mDragTargetLayout = getCurrentDropLayout();
1685            mDragTargetLayout.onDragEnter();
1686            showOutlines();
1687        }
1688    }
1689
1690    public DropTarget getDropTargetDelegate(DragSource source, int x, int y,
1691            int xOffset, int yOffset, DragView dragView, Object dragInfo) {
1692
1693        if (mIsSmall || mIsInUnshrinkAnimation) {
1694            // If we're shrunken, don't let anyone drag on folders/etc that are on the mini-screens
1695            return null;
1696        }
1697        // We may need to delegate the drag to a child view. If a 1x1 item
1698        // would land in a cell occupied by a DragTarget (e.g. a Folder),
1699        // then drag events should be handled by that child.
1700
1701        ItemInfo item = (ItemInfo)dragInfo;
1702        CellLayout currentLayout = getCurrentDropLayout();
1703
1704        int dragPointX, dragPointY;
1705        if (item.spanX == 1 && item.spanY == 1) {
1706            // For a 1x1, calculate the drop cell exactly as in onDragOver
1707            dragPointX = x - xOffset;
1708            dragPointY = y - yOffset;
1709        } else {
1710            // Otherwise, use the exact drag coordinates
1711            dragPointX = x;
1712            dragPointY = y;
1713        }
1714        dragPointX += mScrollX - currentLayout.getLeft();
1715        dragPointY += mScrollY - currentLayout.getTop();
1716
1717        // If we are dragging over a cell that contains a DropTarget that will
1718        // accept the drop, delegate to that DropTarget.
1719        final int[] cellXY = mTempCell;
1720        currentLayout.estimateDropCell(dragPointX, dragPointY, item.spanX, item.spanY, cellXY);
1721        View child = currentLayout.getChildAt(cellXY[0], cellXY[1]);
1722        if (child instanceof DropTarget) {
1723            DropTarget target = (DropTarget)child;
1724            if (target.acceptDrop(source, x, y, xOffset, yOffset, dragView, dragInfo)) {
1725                return target;
1726            }
1727        }
1728        return null;
1729    }
1730
1731    /**
1732     * Tests to see if the drop will be accepted by Launcher, and if so, includes additional data
1733     * in the returned structure related to the widgets that match the drop (or a null list if it is
1734     * a shortcut drop).  If the drop is not accepted then a null structure is returned.
1735     */
1736    private Pair<Integer, List<WidgetMimeTypeHandlerData>> validateDrag(DragEvent event) {
1737        final LauncherModel model = mLauncher.getModel();
1738        final ClipDescription desc = event.getClipDescription();
1739        final int mimeTypeCount = desc.getMimeTypeCount();
1740        for (int i = 0; i < mimeTypeCount; ++i) {
1741            final String mimeType = desc.getMimeType(i);
1742            if (mimeType.equals(InstallShortcutReceiver.SHORTCUT_MIMETYPE)) {
1743                return new Pair<Integer, List<WidgetMimeTypeHandlerData>>(i, null);
1744            } else {
1745                final List<WidgetMimeTypeHandlerData> widgets =
1746                    model.resolveWidgetsForMimeType(mContext, mimeType);
1747                if (widgets.size() > 0) {
1748                    return new Pair<Integer, List<WidgetMimeTypeHandlerData>>(i, widgets);
1749                }
1750            }
1751        }
1752        return null;
1753    }
1754
1755    /**
1756     * Global drag and drop handler
1757     */
1758    @Override
1759    public boolean onDragEvent(DragEvent event) {
1760        final ClipDescription desc = event.getClipDescription();
1761        final CellLayout layout = (CellLayout) getChildAt(mCurrentPage);
1762        final int[] pos = new int[2];
1763        layout.getLocationOnScreen(pos);
1764        // We need to offset the drag coordinates to layout coordinate space
1765        final int x = (int) event.getX() - pos[0];
1766        final int y = (int) event.getY() - pos[1];
1767
1768        switch (event.getAction()) {
1769        case DragEvent.ACTION_DRAG_STARTED: {
1770            // Validate this drag
1771            Pair<Integer, List<WidgetMimeTypeHandlerData>> test = validateDrag(event);
1772            if (test != null) {
1773                boolean isShortcut = (test.second == null);
1774                if (isShortcut) {
1775                    // Check if we have enough space on this screen to add a new shortcut
1776                    if (!layout.findCellForSpan(pos, 1, 1)) {
1777                        Toast.makeText(mContext, mContext.getString(R.string.out_of_space),
1778                                Toast.LENGTH_SHORT).show();
1779                        return false;
1780                    }
1781                }
1782            } else {
1783                // Show error message if we couldn't accept any of the items
1784                Toast.makeText(mContext, mContext.getString(R.string.external_drop_widget_error),
1785                        Toast.LENGTH_SHORT).show();
1786                return false;
1787            }
1788
1789            // Create the drag outline
1790            // We need to add extra padding to the bitmap to make room for the glow effect
1791            final Canvas canvas = new Canvas();
1792            final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;
1793            mDragOutline = createExternalDragOutline(canvas, bitmapPadding);
1794
1795            // Show the current page outlines to indicate that we can accept this drop
1796            showOutlines();
1797            layout.setHover(true);
1798            layout.onDragEnter();
1799            layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1);
1800
1801            return true;
1802        }
1803        case DragEvent.ACTION_DRAG_LOCATION:
1804            // Visualize the drop location
1805            layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1);
1806            return true;
1807        case DragEvent.ACTION_DROP: {
1808            // Try and add any shortcuts
1809            int newDropCount = 0;
1810            final LauncherModel model = mLauncher.getModel();
1811            final ClipData data = event.getClipData();
1812
1813            // We assume that the mime types are ordered in descending importance of
1814            // representation. So we enumerate the list of mime types and alert the
1815            // user if any widgets can handle the drop.  Only the most preferred
1816            // representation will be handled.
1817            pos[0] = x;
1818            pos[1] = y;
1819            Pair<Integer, List<WidgetMimeTypeHandlerData>> test = validateDrag(event);
1820            if (test != null) {
1821                final int index = test.first;
1822                final List<WidgetMimeTypeHandlerData> widgets = test.second;
1823                final boolean isShortcut = (widgets == null);
1824                final String mimeType = desc.getMimeType(index);
1825                if (isShortcut) {
1826                    final Intent intent = data.getItem(index).getIntent();
1827                    Object info = model.infoFromShortcutIntent(mContext, intent, data.getIcon());
1828                    onDropExternal(x, y, info, layout, false);
1829                } else {
1830                    if (widgets.size() == 1) {
1831                        // If there is only one item, then go ahead and add and configure
1832                        // that widget
1833                        final AppWidgetProviderInfo widgetInfo = widgets.get(0).widgetInfo;
1834                        final PendingAddWidgetInfo createInfo =
1835                                new PendingAddWidgetInfo(widgetInfo, mimeType, data);
1836                        mLauncher.addAppWidgetFromDrop(createInfo, mCurrentPage, pos);
1837                    } else {
1838                        // Show the widget picker dialog if there is more than one widget
1839                        // that can handle this data type
1840                        final InstallWidgetReceiver.WidgetListAdapter adapter =
1841                            new InstallWidgetReceiver.WidgetListAdapter(mLauncher, mimeType,
1842                                    data, widgets, layout, mCurrentPage, pos);
1843                        final AlertDialog.Builder builder =
1844                            new AlertDialog.Builder(mContext);
1845                        builder.setAdapter(adapter, adapter);
1846                        builder.setCancelable(true);
1847                        builder.setTitle(mContext.getString(
1848                                R.string.external_drop_widget_pick_title));
1849                        builder.setIcon(R.drawable.ic_no_applications);
1850                        builder.show();
1851                    }
1852                }
1853            }
1854            return true;
1855        }
1856        case DragEvent.ACTION_DRAG_ENDED:
1857            // Hide the page outlines after the drop
1858            layout.setHover(false);
1859            layout.onDragExit();
1860            hideOutlines();
1861            return true;
1862        }
1863        return super.onDragEvent(event);
1864    }
1865
1866    /*
1867    *
1868    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
1869    * coordinate space. The argument xy is modified with the return result.
1870    *
1871    */
1872   void mapPointFromSelfToChild(View v, float[] xy) {
1873       mapPointFromSelfToChild(v, xy, null);
1874   }
1875
1876   /*
1877    *
1878    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
1879    * coordinate space. The argument xy is modified with the return result.
1880    *
1881    * if cachedInverseMatrix is not null, this method will just use that matrix instead of
1882    * computing it itself; we use this to avoid redundant matrix inversions in
1883    * findMatchingPageForDragOver
1884    *
1885    */
1886   void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
1887       if (cachedInverseMatrix == null) {
1888           v.getMatrix().invert(mTempInverseMatrix);
1889           cachedInverseMatrix = mTempInverseMatrix;
1890       }
1891       xy[0] = xy[0] + mScrollX - v.getLeft();
1892       xy[1] = xy[1] + mScrollY - v.getTop();
1893       cachedInverseMatrix.mapPoints(xy);
1894   }
1895
1896   /*
1897    *
1898    * Convert the 2D coordinate xy from this CellLayout's coordinate space to
1899    * the parent View's coordinate space. The argument xy is modified with the return result.
1900    *
1901    */
1902   void mapPointFromChildToSelf(View v, float[] xy) {
1903       v.getMatrix().mapPoints(xy);
1904       xy[0] -= (mScrollX - v.getLeft());
1905       xy[1] -= (mScrollY - v.getTop());
1906   }
1907
1908    static private float squaredDistance(float[] point1, float[] point2) {
1909        float distanceX = point1[0] - point2[0];
1910        float distanceY = point2[1] - point2[1];
1911        return distanceX * distanceX + distanceY * distanceY;
1912    }
1913
1914    /*
1915     *
1916     * Returns true if the passed CellLayout cl overlaps with dragView
1917     *
1918     */
1919    boolean overlaps(CellLayout cl, DragView dragView,
1920            int dragViewX, int dragViewY, Matrix cachedInverseMatrix) {
1921        // Transform the coordinates of the item being dragged to the CellLayout's coordinates
1922        final float[] draggedItemTopLeft = mTempDragCoordinates;
1923        draggedItemTopLeft[0] = dragViewX + dragView.getScaledDragRegionXOffset();
1924        draggedItemTopLeft[1] = dragViewY + dragView.getScaledDragRegionYOffset();
1925        final float[] draggedItemBottomRight = mTempDragBottomRightCoordinates;
1926        draggedItemBottomRight[0] = draggedItemTopLeft[0] + dragView.getScaledDragRegionWidth();
1927        draggedItemBottomRight[1] = draggedItemTopLeft[1] + dragView.getScaledDragRegionHeight();
1928
1929        // Transform the dragged item's top left coordinates
1930        // to the CellLayout's local coordinates
1931        mapPointFromSelfToChild(cl, draggedItemTopLeft, cachedInverseMatrix);
1932        float overlapRegionLeft = Math.max(0f, draggedItemTopLeft[0]);
1933        float overlapRegionTop = Math.max(0f, draggedItemTopLeft[1]);
1934
1935        if (overlapRegionLeft <= cl.getWidth() && overlapRegionTop >= 0) {
1936            // Transform the dragged item's bottom right coordinates
1937            // to the CellLayout's local coordinates
1938            mapPointFromSelfToChild(cl, draggedItemBottomRight, cachedInverseMatrix);
1939            float overlapRegionRight = Math.min(cl.getWidth(), draggedItemBottomRight[0]);
1940            float overlapRegionBottom = Math.min(cl.getHeight(), draggedItemBottomRight[1]);
1941
1942            if (overlapRegionRight >= 0 && overlapRegionBottom <= cl.getHeight()) {
1943                float overlap = (overlapRegionRight - overlapRegionLeft) *
1944                         (overlapRegionBottom - overlapRegionTop);
1945                if (overlap > 0) {
1946                    return true;
1947                }
1948             }
1949        }
1950        return false;
1951    }
1952
1953    /*
1954     *
1955     * This method returns the CellLayout that is currently being dragged to. In order to drag
1956     * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
1957     * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
1958     *
1959     * Return null if no CellLayout is currently being dragged over
1960     *
1961     */
1962    private CellLayout findMatchingPageForDragOver(
1963            DragView dragView, int originX, int originY, int offsetX, int offsetY) {
1964        // We loop through all the screens (ie CellLayouts) and see which ones overlap
1965        // with the item being dragged and then choose the one that's closest to the touch point
1966        final int screenCount = getChildCount();
1967        CellLayout bestMatchingScreen = null;
1968        float smallestDistSoFar = Float.MAX_VALUE;
1969
1970        for (int i = 0; i < screenCount; i++) {
1971            CellLayout cl = (CellLayout)getChildAt(i);
1972
1973            final float[] touchXy = mTempTouchCoordinates;
1974            touchXy[0] = originX + offsetX;
1975            touchXy[1] = originY + offsetY;
1976
1977            // Transform the touch coordinates to the CellLayout's local coordinates
1978            // If the touch point is within the bounds of the cell layout, we can return immediately
1979            cl.getMatrix().invert(mTempInverseMatrix);
1980            mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
1981
1982            if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
1983                    touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
1984                return cl;
1985            }
1986
1987            if (overlaps(cl, dragView, originX, originY, mTempInverseMatrix)) {
1988                // Get the center of the cell layout in screen coordinates
1989                final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
1990                cellLayoutCenter[0] = cl.getWidth()/2;
1991                cellLayoutCenter[1] = cl.getHeight()/2;
1992                mapPointFromChildToSelf(cl, cellLayoutCenter);
1993
1994                touchXy[0] = originX + offsetX;
1995                touchXy[1] = originY + offsetY;
1996
1997                // Calculate the distance between the center of the CellLayout
1998                // and the touch point
1999                float dist = squaredDistance(touchXy, cellLayoutCenter);
2000
2001                if (dist < smallestDistSoFar) {
2002                    smallestDistSoFar = dist;
2003                    bestMatchingScreen = cl;
2004                }
2005            }
2006        }
2007        return bestMatchingScreen;
2008    }
2009
2010    public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
2011            DragView dragView, Object dragInfo) {
2012        // When touch is inside the scroll area, skip dragOver actions for the current screen
2013        if (!mInScrollArea) {
2014            CellLayout layout;
2015            int originX = x - xOffset;
2016            int originY = y - yOffset;
2017            boolean shrunken = mIsSmall || mIsInUnshrinkAnimation;
2018            if (shrunken) {
2019                layout = findMatchingPageForDragOver(
2020                        dragView, originX, originY, xOffset, yOffset);
2021
2022                if (layout != mDragTargetLayout) {
2023                    if (mDragTargetLayout != null) {
2024                        mDragTargetLayout.setHover(false);
2025                        mSpringLoadedDragController.onDragExit();
2026                    }
2027                    mDragTargetLayout = layout;
2028                    if (mDragTargetLayout != null && mDragTargetLayout.getAcceptsDrops()) {
2029                        mDragTargetLayout.setHover(true);
2030                        mSpringLoadedDragController.onDragEnter(mDragTargetLayout);
2031                    }
2032                }
2033            } else {
2034                layout = getCurrentDropLayout();
2035                if (layout != mDragTargetLayout) {
2036                    if (mDragTargetLayout != null) {
2037                        mDragTargetLayout.onDragExit();
2038                    }
2039                    layout.onDragEnter();
2040                    mDragTargetLayout = layout;
2041                }
2042            }
2043            if (!shrunken || mShrinkState == ShrinkState.SPRING_LOADED) {
2044                layout = getCurrentDropLayout();
2045
2046                final ItemInfo item = (ItemInfo)dragInfo;
2047                if (dragInfo instanceof LauncherAppWidgetInfo) {
2048                    LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo)dragInfo;
2049
2050                    if (widgetInfo.spanX == -1) {
2051                        // Calculate the grid spans needed to fit this widget
2052                        int[] spans = layout.rectToCell(
2053                                widgetInfo.minWidth, widgetInfo.minHeight, null);
2054                        item.spanX = spans[0];
2055                        item.spanY = spans[1];
2056                    }
2057                }
2058
2059                if (source instanceof AllAppsPagedView) {
2060                    // This is a hack to fix the point used to determine which cell an icon from
2061                    // the all apps screen is over
2062                    if (item != null && item.spanX == 1 && layout != null) {
2063                        int dragRegionLeft = (dragView.getWidth() - layout.getCellWidth()) / 2;
2064
2065                        originX += dragRegionLeft - dragView.getDragRegionLeft();
2066                        if (dragView.getDragRegionWidth() != layout.getCellWidth()) {
2067                            dragView.setDragRegion(dragView.getDragRegionLeft(),
2068                                    dragView.getDragRegionTop(),
2069                                    layout.getCellWidth(),
2070                                    dragView.getDragRegionHeight());
2071                        }
2072                    }
2073                }
2074
2075                if (mDragTargetLayout != null) {
2076                    final View child = (mDragInfo == null) ? null : mDragInfo.cell;
2077                    float[] localOrigin = { originX, originY };
2078                    mapPointFromSelfToChild(mDragTargetLayout, localOrigin, null);
2079                    mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
2080                            (int) localOrigin[0], (int) localOrigin[1], item.spanX, item.spanY);
2081                }
2082            }
2083        }
2084    }
2085
2086    public void onDragExit(DragSource source, int x, int y, int xOffset,
2087            int yOffset, DragView dragView, Object dragInfo) {
2088        mWasSpringLoadedOnDragExit = mShrinkState == ShrinkState.SPRING_LOADED;
2089        if (mDragTargetLayout != null) {
2090            mDragTargetLayout.onDragExit();
2091        }
2092        if (!mIsPageMoving) {
2093            hideOutlines();
2094        }
2095        if (mShrinkState == ShrinkState.SPRING_LOADED) {
2096            mLauncher.exitSpringLoadedDragMode();
2097        }
2098        clearAllHovers();
2099    }
2100
2101    @Override
2102    public void getHitRect(Rect outRect) {
2103        // We want the workspace to have the whole area of the display (it will find the correct
2104        // cell layout to drop to in the existing drag/drop logic.
2105        final Display d = mLauncher.getWindowManager().getDefaultDisplay();
2106        outRect.set(0, 0, d.getWidth(), d.getHeight());
2107    }
2108
2109    /**
2110     * Add the item specified by dragInfo to the given layout.
2111     * @return true if successful
2112     */
2113    public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
2114        if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
2115            onDropExternal(-1, -1, (ItemInfo) dragInfo, (CellLayout) layout, false);
2116            return true;
2117        }
2118        mLauncher.showOutOfSpaceMessage();
2119        return false;
2120    }
2121
2122    /**
2123     * Drop an item that didn't originate on one of the workspace screens.
2124     * It may have come from Launcher (e.g. from all apps or customize), or it may have
2125     * come from another app altogether.
2126     *
2127     * NOTE: This can also be called when we are outside of a drag event, when we want
2128     * to add an item to one of the workspace screens.
2129     */
2130    private void onDropExternal(int x, int y, Object dragInfo,
2131            CellLayout cellLayout, boolean insertAtFirst) {
2132        int screen = indexOfChild(cellLayout);
2133        if (dragInfo instanceof PendingAddItemInfo) {
2134            PendingAddItemInfo info = (PendingAddItemInfo) dragInfo;
2135            // When dragging and dropping from customization tray, we deal with creating
2136            // widgets/shortcuts/folders in a slightly different way
2137            // Only set touchXY if you are supporting spring loaded adding of items
2138            int[] touchXY = new int[2];
2139            touchXY[0] = x;
2140            touchXY[1] = y;
2141            switch (info.itemType) {
2142                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2143                    mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) info, screen, touchXY);
2144                    break;
2145                case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
2146                    mLauncher.addLiveFolderFromDrop(info.componentName, screen, touchXY);
2147                    break;
2148                case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
2149                    mLauncher.processShortcutFromDrop(info.componentName, screen, touchXY);
2150                    break;
2151                default:
2152                    throw new IllegalStateException("Unknown item type: " + info.itemType);
2153            }
2154            cellLayout.onDragExit();
2155        } else {
2156            // This is for other drag/drop cases, like dragging from All Apps
2157            ItemInfo info = (ItemInfo) dragInfo;
2158            View view = null;
2159
2160            switch (info.itemType) {
2161            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
2162            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
2163                if (info.container == NO_ID && info instanceof ApplicationInfo) {
2164                    // Came from all apps -- make a copy
2165                    info = new ShortcutInfo((ApplicationInfo) info);
2166                }
2167                view = mLauncher.createShortcut(R.layout.application, cellLayout,
2168                        (ShortcutInfo) info);
2169                break;
2170            case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
2171                view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher,
2172                        cellLayout, (UserFolderInfo) info, mIconCache);
2173                break;
2174            default:
2175                throw new IllegalStateException("Unknown item type: " + info.itemType);
2176            }
2177
2178            mTargetCell = new int[2];
2179            if (x != -1 && y != -1) {
2180                // when dragging and dropping, just find the closest free spot
2181                cellLayout.findNearestVacantArea(x, y, 1, 1, mTargetCell);
2182            } else {
2183                cellLayout.findCellForSpan(mTargetCell, 1, 1);
2184            }
2185            addInScreen(view, indexOfChild(cellLayout), mTargetCell[0],
2186                    mTargetCell[1], info.spanX, info.spanY, insertAtFirst);
2187            boolean animateDrop = !mWasSpringLoadedOnDragExit;
2188            cellLayout.onDropChild(view, animateDrop);
2189            cellLayout.animateDrop();
2190            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
2191
2192            LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
2193                    LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
2194                    lp.cellX, lp.cellY);
2195        }
2196    }
2197
2198    /**
2199     * Return the current {@link CellLayout}, correctly picking the destination
2200     * screen while a scroll is in progress.
2201     */
2202    private CellLayout getCurrentDropLayout() {
2203        // if we're currently small, use findMatchingPageForDragOver instead
2204        if (mIsSmall) return null;
2205        int index = mScroller.isFinished() ? mCurrentPage : mNextPage;
2206        return (CellLayout) getChildAt(index);
2207    }
2208
2209    /**
2210     * Return the current CellInfo describing our current drag; this method exists
2211     * so that Launcher can sync this object with the correct info when the activity is created/
2212     * destroyed
2213     *
2214     */
2215    public CellLayout.CellInfo getDragInfo() {
2216        return mDragInfo;
2217    }
2218
2219    /**
2220     * Calculate the nearest cell where the given object would be dropped.
2221     */
2222    private int[] findNearestVacantArea(int pixelX, int pixelY,
2223            int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
2224
2225        int localPixelX = pixelX - (layout.getLeft() - mScrollX);
2226        int localPixelY = pixelY - (layout.getTop() - mScrollY);
2227
2228        // Find the best target drop location
2229        return layout.findNearestVacantArea(
2230                localPixelX, localPixelY, spanX, spanY, ignoreView, recycle);
2231    }
2232
2233    /**
2234     * Estimate the size that a child with the given dimensions will take in the current screen.
2235     */
2236    void estimateChildSize(int minWidth, int minHeight, int[] result) {
2237        ((CellLayout)getChildAt(mCurrentPage)).estimateChildSize(minWidth, minHeight, result);
2238    }
2239
2240    void setLauncher(Launcher launcher) {
2241        mLauncher = launcher;
2242        mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
2243
2244        mCustomizationDrawer = mLauncher.findViewById(R.id.customization_drawer);
2245        if (mCustomizationDrawer != null) {
2246            mCustomizationDrawerContent =
2247                mCustomizationDrawer.findViewById(com.android.internal.R.id.tabcontent);
2248        }
2249    }
2250
2251    public void setDragController(DragController dragController) {
2252        mDragController = dragController;
2253    }
2254
2255    public void onDropCompleted(View target, boolean success) {
2256        if (success) {
2257            if (target != this && mDragInfo != null) {
2258                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
2259                cellLayout.removeView(mDragInfo.cell);
2260                if (mDragInfo.cell instanceof DropTarget) {
2261                    mDragController.removeDropTarget((DropTarget)mDragInfo.cell);
2262                }
2263                // final Object tag = mDragInfo.cell.getTag();
2264            }
2265        } else if (mDragInfo != null) {
2266            boolean animateDrop = !mWasSpringLoadedOnDragExit;
2267            ((CellLayout) getChildAt(mDragInfo.screen)).onDropChild(mDragInfo.cell, animateDrop);
2268        }
2269
2270        mDragOutline = null;
2271        mDragInfo = null;
2272    }
2273
2274    @Override
2275    public void onDragViewVisible() {
2276        ((View) mDragInfo.cell).setVisibility(View.GONE);
2277    }
2278
2279    public boolean isDropEnabled() {
2280        return true;
2281    }
2282
2283    @Override
2284    protected void onRestoreInstanceState(Parcelable state) {
2285        super.onRestoreInstanceState(state);
2286        Launcher.setScreen(mCurrentPage);
2287    }
2288
2289    @Override
2290    public void scrollLeft() {
2291        if (!mIsSmall && !mIsInUnshrinkAnimation) {
2292            super.scrollLeft();
2293        }
2294    }
2295
2296    @Override
2297    public void scrollRight() {
2298        if (!mIsSmall && !mIsInUnshrinkAnimation) {
2299            super.scrollRight();
2300        }
2301    }
2302
2303    @Override
2304    public void onEnterScrollArea(int direction) {
2305        if (!mIsSmall && !mIsInUnshrinkAnimation) {
2306            mInScrollArea = true;
2307            mPendingScrollDirection = direction;
2308
2309            final int page = mCurrentPage + (direction == DragController.SCROLL_LEFT ? -1 : 1);
2310            final CellLayout layout = (CellLayout) getChildAt(page);
2311
2312            if (layout != null) {
2313                layout.setHover(true);
2314
2315                if (mDragTargetLayout != null) {
2316                    mDragTargetLayout.onDragExit();
2317                    mDragTargetLayout = null;
2318                }
2319            }
2320        }
2321    }
2322
2323    private void clearAllHovers() {
2324        final int childCount = getChildCount();
2325        for (int i = 0; i < childCount; i++) {
2326            ((CellLayout) getChildAt(i)).setHover(false);
2327        }
2328        mSpringLoadedDragController.onDragExit();
2329    }
2330
2331    @Override
2332    public void onExitScrollArea() {
2333        if (mInScrollArea) {
2334            mInScrollArea = false;
2335            mPendingScrollDirection = DragController.SCROLL_NONE;
2336            clearAllHovers();
2337        }
2338    }
2339
2340    public Folder getFolderForTag(Object tag) {
2341        final int screenCount = getChildCount();
2342        for (int screen = 0; screen < screenCount; screen++) {
2343            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
2344            int count = currentScreen.getChildCount();
2345            for (int i = 0; i < count; i++) {
2346                View child = currentScreen.getChildAt(i);
2347                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
2348                if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
2349                    Folder f = (Folder) child;
2350                    if (f.getInfo() == tag && f.getInfo().opened) {
2351                        return f;
2352                    }
2353                }
2354            }
2355        }
2356        return null;
2357    }
2358
2359    public View getViewForTag(Object tag) {
2360        int screenCount = getChildCount();
2361        for (int screen = 0; screen < screenCount; screen++) {
2362            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
2363            int count = currentScreen.getChildCount();
2364            for (int i = 0; i < count; i++) {
2365                View child = currentScreen.getChildAt(i);
2366                if (child.getTag() == tag) {
2367                    return child;
2368                }
2369            }
2370        }
2371        return null;
2372    }
2373
2374
2375    void removeItems(final ArrayList<ApplicationInfo> apps) {
2376        final int screenCount = getChildCount();
2377        final PackageManager manager = getContext().getPackageManager();
2378        final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext());
2379
2380        final HashSet<String> packageNames = new HashSet<String>();
2381        final int appCount = apps.size();
2382        for (int i = 0; i < appCount; i++) {
2383            packageNames.add(apps.get(i).componentName.getPackageName());
2384        }
2385
2386        for (int i = 0; i < screenCount; i++) {
2387            final CellLayout layout = (CellLayout) getChildAt(i);
2388
2389            // Avoid ANRs by treating each screen separately
2390            post(new Runnable() {
2391                public void run() {
2392                    final ArrayList<View> childrenToRemove = new ArrayList<View>();
2393                    childrenToRemove.clear();
2394
2395                    int childCount = layout.getChildCount();
2396                    for (int j = 0; j < childCount; j++) {
2397                        final View view = layout.getChildAt(j);
2398                        Object tag = view.getTag();
2399
2400                        if (tag instanceof ShortcutInfo) {
2401                            final ShortcutInfo info = (ShortcutInfo) tag;
2402                            final Intent intent = info.intent;
2403                            final ComponentName name = intent.getComponent();
2404
2405                            if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
2406                                for (String packageName: packageNames) {
2407                                    if (packageName.equals(name.getPackageName())) {
2408                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
2409                                        childrenToRemove.add(view);
2410                                    }
2411                                }
2412                            }
2413                        } else if (tag instanceof UserFolderInfo) {
2414                            final UserFolderInfo info = (UserFolderInfo) tag;
2415                            final ArrayList<ShortcutInfo> contents = info.contents;
2416                            final ArrayList<ShortcutInfo> toRemove = new ArrayList<ShortcutInfo>(1);
2417                            final int contentsCount = contents.size();
2418                            boolean removedFromFolder = false;
2419
2420                            for (int k = 0; k < contentsCount; k++) {
2421                                final ShortcutInfo appInfo = contents.get(k);
2422                                final Intent intent = appInfo.intent;
2423                                final ComponentName name = intent.getComponent();
2424
2425                                if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
2426                                    for (String packageName: packageNames) {
2427                                        if (packageName.equals(name.getPackageName())) {
2428                                            toRemove.add(appInfo);
2429                                            LauncherModel.deleteItemFromDatabase(mLauncher, appInfo);
2430                                            removedFromFolder = true;
2431                                        }
2432                                    }
2433                                }
2434                            }
2435
2436                            contents.removeAll(toRemove);
2437                            if (removedFromFolder) {
2438                                final Folder folder = getOpenFolder();
2439                                if (folder != null)
2440                                    folder.notifyDataSetChanged();
2441                            }
2442                        } else if (tag instanceof LiveFolderInfo) {
2443                            final LiveFolderInfo info = (LiveFolderInfo) tag;
2444                            final Uri uri = info.uri;
2445                            final ProviderInfo providerInfo = manager.resolveContentProvider(
2446                                    uri.getAuthority(), 0);
2447
2448                            if (providerInfo != null) {
2449                                for (String packageName: packageNames) {
2450                                    if (packageName.equals(providerInfo.packageName)) {
2451                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
2452                                        childrenToRemove.add(view);
2453                                    }
2454                                }
2455                            }
2456                        } else if (tag instanceof LauncherAppWidgetInfo) {
2457                            final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag;
2458                            final AppWidgetProviderInfo provider =
2459                                    widgets.getAppWidgetInfo(info.appWidgetId);
2460                            if (provider != null) {
2461                                for (String packageName: packageNames) {
2462                                    if (packageName.equals(provider.provider.getPackageName())) {
2463                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
2464                                        childrenToRemove.add(view);
2465                                    }
2466                                }
2467                            }
2468                        }
2469                    }
2470
2471                    childCount = childrenToRemove.size();
2472                    for (int j = 0; j < childCount; j++) {
2473                        View child = childrenToRemove.get(j);
2474                        layout.removeViewInLayout(child);
2475                        if (child instanceof DropTarget) {
2476                            mDragController.removeDropTarget((DropTarget)child);
2477                        }
2478                    }
2479
2480                    if (childCount > 0) {
2481                        layout.requestLayout();
2482                        layout.invalidate();
2483                    }
2484                }
2485            });
2486        }
2487    }
2488
2489    void updateShortcuts(ArrayList<ApplicationInfo> apps) {
2490        final int screenCount = getChildCount();
2491        for (int i = 0; i < screenCount; i++) {
2492            final CellLayout layout = (CellLayout) getChildAt(i);
2493            int childCount = layout.getChildCount();
2494            for (int j = 0; j < childCount; j++) {
2495                final View view = layout.getChildAt(j);
2496                Object tag = view.getTag();
2497                if (tag instanceof ShortcutInfo) {
2498                    ShortcutInfo info = (ShortcutInfo)tag;
2499                    // We need to check for ACTION_MAIN otherwise getComponent() might
2500                    // return null for some shortcuts (for instance, for shortcuts to
2501                    // web pages.)
2502                    final Intent intent = info.intent;
2503                    final ComponentName name = intent.getComponent();
2504                    if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
2505                            Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
2506                        final int appCount = apps.size();
2507                        for (int k = 0; k < appCount; k++) {
2508                            ApplicationInfo app = apps.get(k);
2509                            if (app.componentName.equals(name)) {
2510                                info.setIcon(mIconCache.getIcon(info.intent));
2511                                ((TextView)view).setCompoundDrawablesWithIntrinsicBounds(null,
2512                                        new FastBitmapDrawable(info.getIcon(mIconCache)),
2513                                        null, null);
2514                                }
2515                        }
2516                    }
2517                }
2518            }
2519        }
2520    }
2521
2522    void moveToDefaultScreen(boolean animate) {
2523        if (mIsSmall || mIsInUnshrinkAnimation) {
2524            mLauncher.showWorkspace(animate, (CellLayout)getChildAt(mDefaultPage));
2525        } else if (animate) {
2526            snapToPage(mDefaultPage);
2527        } else {
2528            setCurrentPage(mDefaultPage);
2529        }
2530        getChildAt(mDefaultPage).requestFocus();
2531    }
2532
2533    void setIndicators(Drawable previous, Drawable next) {
2534        mPreviousIndicator = previous;
2535        mNextIndicator = next;
2536        previous.setLevel(mCurrentPage);
2537        next.setLevel(mCurrentPage);
2538    }
2539
2540    @Override
2541    public void syncPages() {
2542    }
2543
2544    @Override
2545    public void syncPageItems(int page) {
2546    }
2547
2548}
2549