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