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