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