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