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