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