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