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