Workspace.java revision 7777d967a60ecfd34ad2d94141d598fca4021058
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            int spanX = 1;
1830            int spanY = 1;
1831            View ignoreView = null;
1832            if (mDragInfo != null) {
1833                final CellLayout.CellInfo dragCellInfo = mDragInfo;
1834                spanX = dragCellInfo.spanX;
1835                spanY = dragCellInfo.spanY;
1836                ignoreView = dragCellInfo.cell;
1837            } else {
1838                final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
1839                spanX = dragInfo.spanX;
1840                spanY = dragInfo.spanY;
1841            }
1842
1843            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
1844                    (int) mDragViewVisualCenter[1], spanX, spanY, mDragTargetLayout, mTargetCell);
1845            if (willCreateUserFolder((ItemInfo) d.dragInfo, mDragTargetLayout, mTargetCell, true)) {
1846                return true;
1847            }
1848            if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, mDragTargetLayout,
1849                    mTargetCell)) {
1850                return true;
1851            }
1852
1853
1854            // Don't accept the drop if there's no room for the item
1855            if (!mDragTargetLayout.findCellForSpanIgnoring(null, spanX, spanY, ignoreView)) {
1856                mLauncher.showOutOfSpaceMessage();
1857                return false;
1858            }
1859        }
1860        return true;
1861    }
1862
1863    boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell,
1864            boolean considerTimeout) {
1865        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
1866
1867        boolean hasntMoved = false;
1868        if (mDragInfo != null) {
1869            CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
1870            hasntMoved = (mDragInfo.cellX == targetCell[0] &&
1871                    mDragInfo.cellY == targetCell[1]) && (cellParent == target);
1872        }
1873
1874        if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
1875            return false;
1876        }
1877
1878        boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
1879        boolean willBecomeShortcut =
1880                (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
1881                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
1882
1883        return (aboveShortcut && willBecomeShortcut);
1884    }
1885
1886    boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell) {
1887        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
1888        if (dropOverView instanceof FolderIcon) {
1889            FolderIcon fi = (FolderIcon) dropOverView;
1890            if (fi.acceptDrop(dragInfo)) {
1891                return true;
1892            }
1893        }
1894        return false;
1895    }
1896
1897    boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
1898            int[] targetCell, boolean external, DragView dragView, Runnable postAnimationRunnable) {
1899        View v = target.getChildAt(targetCell[0], targetCell[1]);
1900        boolean hasntMoved = false;
1901        if (mDragInfo != null) {
1902            CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
1903            hasntMoved = (mDragInfo.cellX == targetCell[0] &&
1904                    mDragInfo.cellY == targetCell[1]) && (cellParent == target);
1905        }
1906
1907        if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
1908        mCreateUserFolderOnDrop = false;
1909        final int screen = (targetCell == null) ? mDragInfo.screen : indexOfChild(target);
1910
1911        boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
1912        boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
1913
1914        if (aboveShortcut && willBecomeShortcut) {
1915            ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
1916            ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
1917            // if the drag started here, we need to remove it from the workspace
1918            if (!external) {
1919                getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
1920            }
1921
1922            Rect folderLocation = new Rect();
1923            float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
1924            target.removeView(v);
1925
1926            FolderIcon fi =
1927                mLauncher.addFolder(target, container, screen, targetCell[0], targetCell[1]);
1928            destInfo.cellX = -1;
1929            destInfo.cellY = -1;
1930            sourceInfo.cellX = -1;
1931            sourceInfo.cellY = -1;
1932
1933            // If the dragView is null, we can't animate
1934            boolean animate = dragView != null;
1935            if (animate) {
1936                fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
1937                        postAnimationRunnable);
1938            } else {
1939                fi.addItem(destInfo);
1940                fi.addItem(sourceInfo);
1941            }
1942            return true;
1943        }
1944        return false;
1945    }
1946
1947    boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
1948            DragObject d, boolean external) {
1949        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
1950        if (dropOverView instanceof FolderIcon) {
1951            FolderIcon fi = (FolderIcon) dropOverView;
1952            if (fi.acceptDrop(d.dragInfo)) {
1953                fi.onDrop(d);
1954
1955                // if the drag started here, we need to remove it from the workspace
1956                if (!external) {
1957                    getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
1958                }
1959                return true;
1960            }
1961        }
1962        return false;
1963    }
1964
1965    public void onDrop(DragObject d) {
1966        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
1967                mDragViewVisualCenter);
1968
1969        // We want the point to be mapped to the dragTarget.
1970        if (mDragTargetLayout != null) {
1971            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
1972                mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter);
1973            } else {
1974                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
1975            }
1976        }
1977
1978        CellLayout dropTargetLayout = mDragTargetLayout;
1979
1980        int snapScreen = -1;
1981        if (d.dragSource != this) {
1982            final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
1983                    (int) mDragViewVisualCenter[1] };
1984            onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
1985        } else if (mDragInfo != null) {
1986            final View cell = mDragInfo.cell;
1987
1988            if (dropTargetLayout != null) {
1989                // Move internally
1990                boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
1991                boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
1992                long container = hasMovedIntoHotseat ?
1993                        LauncherSettings.Favorites.CONTAINER_HOTSEAT :
1994                        LauncherSettings.Favorites.CONTAINER_DESKTOP;
1995                int screen = (mTargetCell[0] < 0) ?
1996                        mDragInfo.screen : indexOfChild(dropTargetLayout);
1997                int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
1998                int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
1999                // First we find the cell nearest to point at which the item is
2000                // dropped, without any consideration to whether there is an item there.
2001                mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
2002                        mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
2003                // If the item being dropped is a shortcut and the nearest drop
2004                // cell also contains a shortcut, then create a folder with the two shortcuts.
2005                if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
2006                        dropTargetLayout, mTargetCell, false, d.dragView, null)) {
2007                    return;
2008                }
2009
2010                if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, d, false)) {
2011                    return;
2012                }
2013
2014                // Aside from the special case where we're dropping a shortcut onto a shortcut,
2015                // we need to find the nearest cell location that is vacant
2016                mTargetCell = findNearestVacantArea((int) mDragViewVisualCenter[0],
2017                        (int) mDragViewVisualCenter[1], mDragInfo.spanX, mDragInfo.spanY, cell,
2018                        dropTargetLayout, mTargetCell);
2019
2020                if (mCurrentPage != screen && !hasMovedIntoHotseat) {
2021                    snapScreen = screen;
2022                    snapToPage(screen);
2023                }
2024
2025                if (mTargetCell[0] >= 0 && mTargetCell[1] >= 0) {
2026                    if (hasMovedLayouts) {
2027                        // Reparent the view
2028                        getParentCellLayoutForView(cell).removeView(cell);
2029                        addInScreen(cell, container, screen, mTargetCell[0], mTargetCell[1],
2030                                mDragInfo.spanX, mDragInfo.spanY);
2031                    }
2032
2033                    // update the item's position after drop
2034                    final ItemInfo info = (ItemInfo) cell.getTag();
2035                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2036                    dropTargetLayout.onMove(cell, mTargetCell[0], mTargetCell[1]);
2037                    lp.cellX = mTargetCell[0];
2038                    lp.cellY = mTargetCell[1];
2039                    cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screen,
2040                            mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
2041
2042                    if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
2043                            cell instanceof LauncherAppWidgetHostView) {
2044                        final CellLayout cellLayout = dropTargetLayout;
2045                        // We post this call so that the widget has a chance to be placed
2046                        // in its final location
2047
2048                        final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
2049                        AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
2050                        if (pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
2051                            final Runnable resizeRunnable = new Runnable() {
2052                                public void run() {
2053                                    DragLayer dragLayer = mLauncher.getDragLayer();
2054                                    dragLayer.addResizeFrame(info, hostView, cellLayout);
2055                                }
2056                            };
2057                            post(new Runnable() {
2058                                public void run() {
2059                                    if (!isPageMoving()) {
2060                                        resizeRunnable.run();
2061                                    } else {
2062                                        mDelayedResizeRunnable = resizeRunnable;
2063                                    }
2064                                }
2065                            });
2066                        }
2067                    }
2068
2069                    LauncherModel.moveItemInDatabase(mLauncher, info, container, screen, lp.cellX,
2070                            lp.cellY);
2071                }
2072            }
2073
2074            final CellLayout parent = (CellLayout) cell.getParent().getParent();
2075
2076            // Prepare it to be animated into its new position
2077            // This must be called after the view has been re-parented
2078            final Runnable disableHardwareLayersRunnable = new Runnable() {
2079                @Override
2080                public void run() {
2081                    mAnimatingViewIntoPlace = false;
2082                    updateChildrenLayersEnabled();
2083                }
2084            };
2085            mAnimatingViewIntoPlace = true;
2086            if (d.dragView.hasDrawn()) {
2087                int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
2088                setFinalScrollForPageChange(snapScreen);
2089                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
2090                        disableHardwareLayersRunnable);
2091                resetFinalScrollForPageChange(snapScreen);
2092            } else {
2093                cell.setVisibility(VISIBLE);
2094            }
2095            parent.onDropChild(cell);
2096        }
2097    }
2098
2099    public void setFinalScrollForPageChange(int screen) {
2100        if (screen >= 0) {
2101            mSavedScrollX = getScrollX();
2102            CellLayout cl = (CellLayout) getChildAt(screen);
2103            mSavedTranslationX = cl.getTranslationX();
2104            mSavedRotationY = cl.getRotationY();
2105            final int newX = getChildOffset(screen) - getRelativeChildOffset(screen);
2106            setScrollX(newX);
2107            cl.setTranslationX(0f);
2108            cl.setRotationY(0f);
2109        }
2110    }
2111
2112    public void resetFinalScrollForPageChange(int screen) {
2113        if (screen >= 0) {
2114            CellLayout cl = (CellLayout) getChildAt(screen);
2115            setScrollX(mSavedScrollX);
2116            cl.setTranslationX(mSavedTranslationX);
2117            cl.setRotationY(mSavedRotationY);
2118        }
2119    }
2120
2121    public void getViewLocationRelativeToSelf(View v, int[] location) {
2122        getLocationInWindow(location);
2123        int x = location[0];
2124        int y = location[1];
2125
2126        v.getLocationInWindow(location);
2127        int vX = location[0];
2128        int vY = location[1];
2129
2130        location[0] = vX - x;
2131        location[1] = vY - y;
2132    }
2133
2134    public void onDragEnter(DragObject d) {
2135        if (mDragTargetLayout != null) {
2136            mDragTargetLayout.setIsDragOverlapping(false);
2137            mDragTargetLayout.onDragExit();
2138        }
2139        mDragTargetLayout = getCurrentDropLayout();
2140        mDragTargetLayout.setIsDragOverlapping(true);
2141        mDragTargetLayout.onDragEnter();
2142
2143        // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we
2144        // don't need to show the outlines
2145        if (LauncherApplication.isScreenLarge()) {
2146            showOutlines();
2147        }
2148    }
2149
2150    private void doDragExit(DragObject d) {
2151        // Clean up folders
2152        cleanupFolderCreation(d);
2153
2154        // Reset the scroll area and previous drag target
2155        onResetScrollArea();
2156
2157        if (mDragTargetLayout != null) {
2158            mDragTargetLayout.setIsDragOverlapping(false);
2159            mDragTargetLayout.onDragExit();
2160        }
2161        mLastDragOverView = null;
2162
2163        if (!mIsPageMoving) {
2164            hideOutlines();
2165        }
2166    }
2167
2168    public void onDragExit(DragObject d) {
2169        doDragExit(d);
2170    }
2171
2172    public DropTarget getDropTargetDelegate(DragObject d) {
2173        return null;
2174    }
2175
2176    /**
2177     * Tests to see if the drop will be accepted by Launcher, and if so, includes additional data
2178     * in the returned structure related to the widgets that match the drop (or a null list if it is
2179     * a shortcut drop).  If the drop is not accepted then a null structure is returned.
2180     */
2181    private Pair<Integer, List<WidgetMimeTypeHandlerData>> validateDrag(DragEvent event) {
2182        final LauncherModel model = mLauncher.getModel();
2183        final ClipDescription desc = event.getClipDescription();
2184        final int mimeTypeCount = desc.getMimeTypeCount();
2185        for (int i = 0; i < mimeTypeCount; ++i) {
2186            final String mimeType = desc.getMimeType(i);
2187            if (mimeType.equals(InstallShortcutReceiver.SHORTCUT_MIMETYPE)) {
2188                return new Pair<Integer, List<WidgetMimeTypeHandlerData>>(i, null);
2189            } else {
2190                final List<WidgetMimeTypeHandlerData> widgets =
2191                    model.resolveWidgetsForMimeType(mContext, mimeType);
2192                if (widgets.size() > 0) {
2193                    return new Pair<Integer, List<WidgetMimeTypeHandlerData>>(i, widgets);
2194                }
2195            }
2196        }
2197        return null;
2198    }
2199
2200    /**
2201     * Global drag and drop handler
2202     */
2203    @Override
2204    public boolean onDragEvent(DragEvent event) {
2205        final ClipDescription desc = event.getClipDescription();
2206        final CellLayout layout = (CellLayout) getChildAt(mCurrentPage);
2207        final int[] pos = new int[2];
2208        layout.getLocationOnScreen(pos);
2209        // We need to offset the drag coordinates to layout coordinate space
2210        final int x = (int) event.getX() - pos[0];
2211        final int y = (int) event.getY() - pos[1];
2212
2213        switch (event.getAction()) {
2214        case DragEvent.ACTION_DRAG_STARTED: {
2215            // Validate this drag
2216            Pair<Integer, List<WidgetMimeTypeHandlerData>> test = validateDrag(event);
2217            if (test != null) {
2218                boolean isShortcut = (test.second == null);
2219                if (isShortcut) {
2220                    // Check if we have enough space on this screen to add a new shortcut
2221                    if (!layout.findCellForSpan(pos, 1, 1)) {
2222                        mLauncher.showOutOfSpaceMessage();
2223                        return false;
2224                    }
2225                }
2226            } else {
2227                // Show error message if we couldn't accept any of the items
2228                Toast.makeText(mContext, mContext.getString(R.string.external_drop_widget_error),
2229                        Toast.LENGTH_SHORT).show();
2230                return false;
2231            }
2232
2233            // Create the drag outline
2234            // We need to add extra padding to the bitmap to make room for the glow effect
2235            final Canvas canvas = new Canvas();
2236            final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;
2237            mDragOutline = createExternalDragOutline(canvas, bitmapPadding);
2238
2239            // Show the current page outlines to indicate that we can accept this drop
2240            showOutlines();
2241            layout.setIsDragOccuring(true);
2242            layout.onDragEnter();
2243            layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1);
2244
2245            return true;
2246        }
2247        case DragEvent.ACTION_DRAG_LOCATION:
2248            // Visualize the drop location
2249            layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1);
2250            return true;
2251        case DragEvent.ACTION_DROP: {
2252            // Try and add any shortcuts
2253            final LauncherModel model = mLauncher.getModel();
2254            final ClipData data = event.getClipData();
2255
2256            // We assume that the mime types are ordered in descending importance of
2257            // representation. So we enumerate the list of mime types and alert the
2258            // user if any widgets can handle the drop.  Only the most preferred
2259            // representation will be handled.
2260            pos[0] = x;
2261            pos[1] = y;
2262            Pair<Integer, List<WidgetMimeTypeHandlerData>> test = validateDrag(event);
2263            if (test != null) {
2264                final int index = test.first;
2265                final List<WidgetMimeTypeHandlerData> widgets = test.second;
2266                final boolean isShortcut = (widgets == null);
2267                final String mimeType = desc.getMimeType(index);
2268                if (isShortcut) {
2269                    final Intent intent = data.getItemAt(index).getIntent();
2270                    Object info = model.infoFromShortcutIntent(mContext, intent, data.getIcon());
2271                    onDropExternal(new int[] { x, y }, info, layout, false);
2272                } else {
2273                    if (widgets.size() == 1) {
2274                        // If there is only one item, then go ahead and add and configure
2275                        // that widget
2276                        final AppWidgetProviderInfo widgetInfo = widgets.get(0).widgetInfo;
2277                        final PendingAddWidgetInfo createInfo =
2278                                new PendingAddWidgetInfo(widgetInfo, mimeType, data, "11");
2279                        mLauncher.addAppWidgetFromDrop(createInfo,
2280                            LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentPage, null, pos);
2281                    } else {
2282                        // Show the widget picker dialog if there is more than one widget
2283                        // that can handle this data type
2284                        final InstallWidgetReceiver.WidgetListAdapter adapter =
2285                            new InstallWidgetReceiver.WidgetListAdapter(mLauncher, mimeType,
2286                                    data, widgets, layout, mCurrentPage, pos);
2287                        final AlertDialog.Builder builder =
2288                            new AlertDialog.Builder(mContext);
2289                        builder.setAdapter(adapter, adapter);
2290                        builder.setCancelable(true);
2291                        builder.setTitle(mContext.getString(
2292                                R.string.external_drop_widget_pick_title));
2293                        builder.setIcon(R.drawable.ic_no_applications);
2294                        builder.show();
2295                    }
2296                }
2297            }
2298            return true;
2299        }
2300        case DragEvent.ACTION_DRAG_ENDED:
2301            // Hide the page outlines after the drop
2302            layout.setIsDragOccuring(false);
2303            layout.onDragExit();
2304            hideOutlines();
2305            return true;
2306        }
2307        return super.onDragEvent(event);
2308    }
2309
2310    /*
2311    *
2312    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
2313    * coordinate space. The argument xy is modified with the return result.
2314    *
2315    */
2316   void mapPointFromSelfToChild(View v, float[] xy) {
2317       mapPointFromSelfToChild(v, xy, null);
2318   }
2319
2320   /*
2321    *
2322    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
2323    * coordinate space. The argument xy is modified with the return result.
2324    *
2325    * if cachedInverseMatrix is not null, this method will just use that matrix instead of
2326    * computing it itself; we use this to avoid redundant matrix inversions in
2327    * findMatchingPageForDragOver
2328    *
2329    */
2330   void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
2331       if (cachedInverseMatrix == null) {
2332           v.getMatrix().invert(mTempInverseMatrix);
2333           cachedInverseMatrix = mTempInverseMatrix;
2334       }
2335       xy[0] = xy[0] + mScrollX - v.getLeft();
2336       xy[1] = xy[1] + mScrollY - v.getTop();
2337       cachedInverseMatrix.mapPoints(xy);
2338   }
2339
2340   /*
2341    * Maps a point from the Workspace's coordinate system to another sibling view's. (Workspace
2342    * covers the full screen)
2343    */
2344   void mapPointFromSelfToSibling(View v, float[] xy) {
2345       xy[0] = xy[0] - v.getLeft();
2346       xy[1] = xy[1] - v.getTop();
2347   }
2348
2349   /*
2350    *
2351    * Convert the 2D coordinate xy from this CellLayout's coordinate space to
2352    * the parent View's coordinate space. The argument xy is modified with the return result.
2353    *
2354    */
2355   void mapPointFromChildToSelf(View v, float[] xy) {
2356       v.getMatrix().mapPoints(xy);
2357       xy[0] -= (mScrollX - v.getLeft());
2358       xy[1] -= (mScrollY - v.getTop());
2359   }
2360
2361   static private float squaredDistance(float[] point1, float[] point2) {
2362        float distanceX = point1[0] - point2[0];
2363        float distanceY = point2[1] - point2[1];
2364        return distanceX * distanceX + distanceY * distanceY;
2365   }
2366
2367    /*
2368     *
2369     * Returns true if the passed CellLayout cl overlaps with dragView
2370     *
2371     */
2372    boolean overlaps(CellLayout cl, DragView dragView,
2373            int dragViewX, int dragViewY, Matrix cachedInverseMatrix) {
2374        // Transform the coordinates of the item being dragged to the CellLayout's coordinates
2375        final float[] draggedItemTopLeft = mTempDragCoordinates;
2376        draggedItemTopLeft[0] = dragViewX;
2377        draggedItemTopLeft[1] = dragViewY;
2378        final float[] draggedItemBottomRight = mTempDragBottomRightCoordinates;
2379        draggedItemBottomRight[0] = draggedItemTopLeft[0] + dragView.getDragRegionWidth();
2380        draggedItemBottomRight[1] = draggedItemTopLeft[1] + dragView.getDragRegionHeight();
2381
2382        // Transform the dragged item's top left coordinates
2383        // to the CellLayout's local coordinates
2384        mapPointFromSelfToChild(cl, draggedItemTopLeft, cachedInverseMatrix);
2385        float overlapRegionLeft = Math.max(0f, draggedItemTopLeft[0]);
2386        float overlapRegionTop = Math.max(0f, draggedItemTopLeft[1]);
2387
2388        if (overlapRegionLeft <= cl.getWidth() && overlapRegionTop >= 0) {
2389            // Transform the dragged item's bottom right coordinates
2390            // to the CellLayout's local coordinates
2391            mapPointFromSelfToChild(cl, draggedItemBottomRight, cachedInverseMatrix);
2392            float overlapRegionRight = Math.min(cl.getWidth(), draggedItemBottomRight[0]);
2393            float overlapRegionBottom = Math.min(cl.getHeight(), draggedItemBottomRight[1]);
2394
2395            if (overlapRegionRight >= 0 && overlapRegionBottom <= cl.getHeight()) {
2396                float overlap = (overlapRegionRight - overlapRegionLeft) *
2397                         (overlapRegionBottom - overlapRegionTop);
2398                if (overlap > 0) {
2399                    return true;
2400                }
2401             }
2402        }
2403        return false;
2404    }
2405
2406    /*
2407     *
2408     * This method returns the CellLayout that is currently being dragged to. In order to drag
2409     * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
2410     * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
2411     *
2412     * Return null if no CellLayout is currently being dragged over
2413     *
2414     */
2415    private CellLayout findMatchingPageForDragOver(
2416            DragView dragView, float originX, float originY, boolean exact) {
2417        // We loop through all the screens (ie CellLayouts) and see which ones overlap
2418        // with the item being dragged and then choose the one that's closest to the touch point
2419        final int screenCount = getChildCount();
2420        CellLayout bestMatchingScreen = null;
2421        float smallestDistSoFar = Float.MAX_VALUE;
2422
2423        for (int i = 0; i < screenCount; i++) {
2424            CellLayout cl = (CellLayout) getChildAt(i);
2425
2426            final float[] touchXy = {originX, originY};
2427            // Transform the touch coordinates to the CellLayout's local coordinates
2428            // If the touch point is within the bounds of the cell layout, we can return immediately
2429            cl.getMatrix().invert(mTempInverseMatrix);
2430            mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
2431
2432            if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
2433                    touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
2434                return cl;
2435            }
2436
2437            if (!exact && overlaps(cl, dragView, (int) originX, (int) originY, mTempInverseMatrix)) {
2438                // Get the center of the cell layout in screen coordinates
2439                final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
2440                cellLayoutCenter[0] = cl.getWidth()/2;
2441                cellLayoutCenter[1] = cl.getHeight()/2;
2442                mapPointFromChildToSelf(cl, cellLayoutCenter);
2443
2444                touchXy[0] = originX;
2445                touchXy[1] = originY;
2446
2447                // Calculate the distance between the center of the CellLayout
2448                // and the touch point
2449                float dist = squaredDistance(touchXy, cellLayoutCenter);
2450
2451                if (dist < smallestDistSoFar) {
2452                    smallestDistSoFar = dist;
2453                    bestMatchingScreen = cl;
2454                }
2455            }
2456        }
2457        return bestMatchingScreen;
2458    }
2459
2460    // This is used to compute the visual center of the dragView. This point is then
2461    // used to visualize drop locations and determine where to drop an item. The idea is that
2462    // the visual center represents the user's interpretation of where the item is, and hence
2463    // is the appropriate point to use when determining drop location.
2464    private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
2465            DragView dragView, float[] recycle) {
2466        float res[];
2467        if (recycle == null) {
2468            res = new float[2];
2469        } else {
2470            res = recycle;
2471        }
2472
2473        // First off, the drag view has been shifted in a way that is not represented in the
2474        // x and y values or the x/yOffsets. Here we account for that shift.
2475        x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX);
2476        y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
2477
2478        // These represent the visual top and left of drag view if a dragRect was provided.
2479        // If a dragRect was not provided, then they correspond to the actual view left and
2480        // top, as the dragRect is in that case taken to be the entire dragView.
2481        // R.dimen.dragViewOffsetY.
2482        int left = x - xOffset;
2483        int top = y - yOffset;
2484
2485        // In order to find the visual center, we shift by half the dragRect
2486        res[0] = left + dragView.getDragRegion().width() / 2;
2487        res[1] = top + dragView.getDragRegion().height() / 2;
2488
2489        return res;
2490    }
2491
2492    private boolean isDragWidget(DragObject d) {
2493        return (d.dragInfo instanceof LauncherAppWidgetInfo ||
2494                d.dragInfo instanceof PendingAddWidgetInfo);
2495    }
2496    private boolean isExternalDragWidget(DragObject d) {
2497        return d.dragSource != this && isDragWidget(d);
2498    }
2499
2500    public void onDragOver(DragObject d) {
2501        // Skip drag over events while we are dragging over side pages
2502        if (mInScrollArea) return;
2503        if (mIsSwitchingState) return;
2504
2505        Rect r = new Rect();
2506        CellLayout layout = null;
2507        ItemInfo item = (ItemInfo) d.dragInfo;
2508
2509        // Ensure that we have proper spans for the item that we are dropping
2510        if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
2511        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
2512            d.dragView, mDragViewVisualCenter);
2513
2514        // Identify whether we have dragged over a side page
2515        if (isSmall()) {
2516            if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
2517                mLauncher.getHotseat().getHitRect(r);
2518                if (r.contains(d.x, d.y)) {
2519                    layout = mLauncher.getHotseat().getLayout();
2520                }
2521            }
2522            if (layout == null) {
2523                layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, true);
2524            }
2525            if (layout != mDragTargetLayout) {
2526                // Cancel all intermediate folder states
2527                cleanupFolderCreation(d);
2528
2529                if (mDragTargetLayout != null) {
2530                    mDragTargetLayout.setIsDragOverlapping(false);
2531                    mDragTargetLayout.onDragExit();
2532                }
2533                mDragTargetLayout = layout;
2534                if (mDragTargetLayout != null) {
2535                    mDragTargetLayout.setIsDragOverlapping(true);
2536                    mDragTargetLayout.onDragEnter();
2537                } else {
2538                    mLastDragOverView = null;
2539                }
2540
2541                boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
2542                if (isInSpringLoadedMode) {
2543                    if (mLauncher.isHotseatLayout(layout)) {
2544                        mSpringLoadedDragController.cancel();
2545                    } else {
2546                        mSpringLoadedDragController.setAlarm(mDragTargetLayout);
2547                    }
2548                }
2549            }
2550        } else {
2551            // Test to see if we are over the hotseat otherwise just use the current page
2552            if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
2553                mLauncher.getHotseat().getHitRect(r);
2554                if (r.contains(d.x, d.y)) {
2555                    layout = mLauncher.getHotseat().getLayout();
2556                }
2557            }
2558            if (layout == null) {
2559                layout = getCurrentDropLayout();
2560            }
2561            if (layout != mDragTargetLayout) {
2562                if (mDragTargetLayout != null) {
2563                    mDragTargetLayout.setIsDragOverlapping(false);
2564                    mDragTargetLayout.onDragExit();
2565                }
2566                mDragTargetLayout = layout;
2567                mDragTargetLayout.setIsDragOverlapping(true);
2568                mDragTargetLayout.onDragEnter();
2569            }
2570        }
2571
2572        // Handle the drag over
2573        if (mDragTargetLayout != null) {
2574            final View child = (mDragInfo == null) ? null : mDragInfo.cell;
2575
2576            // We want the point to be mapped to the dragTarget.
2577            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
2578                mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter);
2579            } else {
2580                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
2581            }
2582            ItemInfo info = (ItemInfo) d.dragInfo;
2583
2584            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2585                    (int) mDragViewVisualCenter[1], 1, 1, mDragTargetLayout, mTargetCell);
2586            final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0],
2587                    mTargetCell[1]);
2588
2589            boolean userFolderPending = willCreateUserFolder(info, mDragTargetLayout,
2590                    mTargetCell, false);
2591            boolean isOverFolder = dragOverView instanceof FolderIcon;
2592            if (dragOverView != mLastDragOverView) {
2593                cancelFolderCreation();
2594                if (mLastDragOverView != null && mLastDragOverView instanceof FolderIcon) {
2595                    ((FolderIcon) mLastDragOverView).onDragExit(d.dragInfo);
2596                }
2597            }
2598
2599            if (userFolderPending && dragOverView != mLastDragOverView) {
2600                mFolderCreationAlarm.setOnAlarmListener(new
2601                        FolderCreationAlarmListener(mDragTargetLayout, mTargetCell[0], mTargetCell[1]));
2602                mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
2603            }
2604
2605            if (dragOverView != mLastDragOverView && isOverFolder) {
2606                ((FolderIcon) dragOverView).onDragEnter(d.dragInfo);
2607                if (mDragTargetLayout != null) {
2608                    mDragTargetLayout.clearDragOutlines();
2609                }
2610            }
2611            mLastDragOverView = dragOverView;
2612
2613            if (!mCreateUserFolderOnDrop && !isOverFolder) {
2614                mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
2615                        (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
2616                        item.spanX, item.spanY);
2617            }
2618        }
2619    }
2620
2621    private void cleanupFolderCreation(DragObject d) {
2622        if (mDragFolderRingAnimator != null && mCreateUserFolderOnDrop) {
2623            mDragFolderRingAnimator.animateToNaturalState();
2624        }
2625        if (mLastDragOverView != null && mLastDragOverView instanceof FolderIcon) {
2626            if (d != null) {
2627                ((FolderIcon) mLastDragOverView).onDragExit(d.dragInfo);
2628            }
2629        }
2630        mFolderCreationAlarm.cancelAlarm();
2631    }
2632
2633    private void cancelFolderCreation() {
2634        if (mDragFolderRingAnimator != null && mCreateUserFolderOnDrop) {
2635            mDragFolderRingAnimator.animateToNaturalState();
2636        }
2637        mCreateUserFolderOnDrop = false;
2638        mFolderCreationAlarm.cancelAlarm();
2639    }
2640
2641    class FolderCreationAlarmListener implements OnAlarmListener {
2642        CellLayout layout;
2643        int cellX;
2644        int cellY;
2645
2646        public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
2647            this.layout = layout;
2648            this.cellX = cellX;
2649            this.cellY = cellY;
2650        }
2651
2652        public void onAlarm(Alarm alarm) {
2653            if (mDragFolderRingAnimator == null) {
2654                mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
2655            }
2656            mDragFolderRingAnimator.setCell(cellX, cellY);
2657            mDragFolderRingAnimator.setCellLayout(layout);
2658            mDragFolderRingAnimator.animateToAcceptState();
2659            layout.showFolderAccept(mDragFolderRingAnimator);
2660            layout.clearDragOutlines();
2661            mCreateUserFolderOnDrop = true;
2662        }
2663    }
2664
2665    @Override
2666    public void getHitRect(Rect outRect) {
2667        // We want the workspace to have the whole area of the display (it will find the correct
2668        // cell layout to drop to in the existing drag/drop logic.
2669        final Display d = mLauncher.getWindowManager().getDefaultDisplay();
2670        outRect.set(0, 0, d.getWidth(), d.getHeight());
2671    }
2672
2673    /**
2674     * Add the item specified by dragInfo to the given layout.
2675     * @return true if successful
2676     */
2677    public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
2678        if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
2679            onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
2680            return true;
2681        }
2682        mLauncher.showOutOfSpaceMessage();
2683        return false;
2684    }
2685
2686    private void onDropExternal(int[] touchXY, Object dragInfo,
2687            CellLayout cellLayout, boolean insertAtFirst) {
2688        onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
2689    }
2690
2691    /**
2692     * Drop an item that didn't originate on one of the workspace screens.
2693     * It may have come from Launcher (e.g. from all apps or customize), or it may have
2694     * come from another app altogether.
2695     *
2696     * NOTE: This can also be called when we are outside of a drag event, when we want
2697     * to add an item to one of the workspace screens.
2698     */
2699    private void onDropExternal(final int[] touchXY, final Object dragInfo,
2700            final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
2701        final Runnable exitSpringLoadedRunnable = new Runnable() {
2702            @Override
2703            public void run() {
2704                mLauncher.exitSpringLoadedDragModeDelayed(true, false);
2705            }
2706        };
2707
2708        ItemInfo info = (ItemInfo) dragInfo;
2709        int spanX = info.spanX;
2710        int spanY = info.spanY;
2711        if (mDragInfo != null) {
2712            spanX = mDragInfo.spanX;
2713            spanY = mDragInfo.spanY;
2714        }
2715
2716        final long container = mLauncher.isHotseatLayout(cellLayout) ?
2717                LauncherSettings.Favorites.CONTAINER_HOTSEAT :
2718                    LauncherSettings.Favorites.CONTAINER_DESKTOP;
2719        final int screen = indexOfChild(cellLayout);
2720        if (!mLauncher.isHotseatLayout(cellLayout) && screen != mCurrentPage
2721                && mState != State.SPRING_LOADED) {
2722            snapToPage(screen);
2723        }
2724
2725        if (info instanceof PendingAddItemInfo) {
2726            final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
2727
2728            boolean findNearestVacantCell = true;
2729            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
2730                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
2731                        cellLayout, mTargetCell);
2732                if (willCreateUserFolder((ItemInfo) d.dragInfo, mDragTargetLayout, mTargetCell,
2733                        true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
2734                                mDragTargetLayout, mTargetCell)) {
2735                    findNearestVacantCell = false;
2736                }
2737            }
2738            if (findNearestVacantCell) {
2739                    mTargetCell = findNearestVacantArea(touchXY[0], touchXY[1], spanX, spanY, null,
2740                        cellLayout, mTargetCell);
2741            }
2742
2743            Runnable onAnimationCompleteRunnable = new Runnable() {
2744                @Override
2745                public void run() {
2746                    // When dragging and dropping from customization tray, we deal with creating
2747                    // widgets/shortcuts/folders in a slightly different way
2748                    switch (pendingInfo.itemType) {
2749                    case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2750                        mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
2751                                container, screen, mTargetCell, null);
2752                        break;
2753                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
2754                        mLauncher.processShortcutFromDrop(pendingInfo.componentName,
2755                                container, screen, mTargetCell, null);
2756                        break;
2757                    default:
2758                        throw new IllegalStateException("Unknown item type: " +
2759                                pendingInfo.itemType);
2760                    }
2761                    cellLayout.onDragExit();
2762                }
2763            };
2764
2765            // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
2766            // location and size on the home screen.
2767            int loc[] = new int[2];
2768            cellLayout.cellToPoint(mTargetCell[0], mTargetCell[1], loc);
2769
2770            RectF r = new RectF();
2771            cellLayout.cellToRect(mTargetCell[0], mTargetCell[1], spanX, spanY, r);
2772            setFinalTransitionTransform(cellLayout);
2773            float cellLayoutScale =
2774                    mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(cellLayout, loc);
2775            resetTransitionTransform(cellLayout);
2776
2777            float dragViewScale =  r.width() / d.dragView.getMeasuredWidth();
2778            // The animation will scale the dragView about its center, so we need to center about
2779            // the final location.
2780            loc[0] -= (d.dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
2781            loc[1] -= (d.dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
2782
2783            mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, loc,
2784                    dragViewScale * cellLayoutScale, onAnimationCompleteRunnable);
2785        } else {
2786            // This is for other drag/drop cases, like dragging from All Apps
2787            View view = null;
2788
2789            switch (info.itemType) {
2790            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
2791            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
2792                if (info.container == NO_ID && info instanceof ApplicationInfo) {
2793                    // Came from all apps -- make a copy
2794                    info = new ShortcutInfo((ApplicationInfo) info, "12");
2795                }
2796                view = mLauncher.createShortcut(R.layout.application, cellLayout,
2797                        (ShortcutInfo) info);
2798                break;
2799            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
2800                view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
2801                        (FolderInfo) info, mIconCache);
2802                break;
2803            default:
2804                throw new IllegalStateException("Unknown item type: " + info.itemType);
2805            }
2806
2807            // First we find the cell nearest to point at which the item is
2808            // dropped, without any consideration to whether there is an item there.
2809            if (touchXY != null) {
2810                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
2811                        cellLayout, mTargetCell);
2812                d.postAnimationRunnable = exitSpringLoadedRunnable;
2813                if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, true,
2814                        d.dragView, d.postAnimationRunnable)) {
2815                    return;
2816                }
2817                if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, d, true)) {
2818                    return;
2819                }
2820            }
2821
2822            if (touchXY != null) {
2823                // when dragging and dropping, just find the closest free spot
2824                mTargetCell = findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, null,
2825                        cellLayout, mTargetCell);
2826            } else {
2827                cellLayout.findCellForSpan(mTargetCell, 1, 1);
2828            }
2829            addInScreen(view, container, screen, mTargetCell[0], mTargetCell[1], info.spanX,
2830                    info.spanY, insertAtFirst);
2831            cellLayout.onDropChild(view);
2832            cellLayout.animateDrop();
2833            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
2834            cellLayout.getChildrenLayout().measureChild(view);
2835
2836            LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen,
2837                    lp.cellX, lp.cellY);
2838
2839            if (d.dragView != null) {
2840                // We wrap the animation call in the temporary set and reset of the current
2841                // cellLayout to its final transform -- this means we animate the drag view to
2842                // the correct final location.
2843                setFinalTransitionTransform(cellLayout);
2844                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
2845                        exitSpringLoadedRunnable);
2846                resetTransitionTransform(cellLayout);
2847            }
2848        }
2849    }
2850
2851    public void setFinalTransitionTransform(CellLayout layout) {
2852        if (isSwitchingState()) {
2853            int index = indexOfChild(layout);
2854            mCurrentScaleX = layout.getScaleX();
2855            mCurrentScaleY = layout.getScaleY();
2856            mCurrentTranslationX = layout.getTranslationX();
2857            mCurrentTranslationY = layout.getTranslationY();
2858            mCurrentRotationY = layout.getRotationY();
2859            layout.setScaleX(mNewScaleXs[index]);
2860            layout.setScaleY(mNewScaleYs[index]);
2861            layout.setTranslationX(mNewTranslationXs[index]);
2862            layout.setTranslationY(mNewTranslationYs[index]);
2863            layout.setRotationY(mNewRotationYs[index]);
2864        }
2865    }
2866    public void resetTransitionTransform(CellLayout layout) {
2867        if (isSwitchingState()) {
2868            mCurrentScaleX = layout.getScaleX();
2869            mCurrentScaleY = layout.getScaleY();
2870            mCurrentTranslationX = layout.getTranslationX();
2871            mCurrentTranslationY = layout.getTranslationY();
2872            mCurrentRotationY = layout.getRotationY();
2873            layout.setScaleX(mCurrentScaleX);
2874            layout.setScaleY(mCurrentScaleY);
2875            layout.setTranslationX(mCurrentTranslationX);
2876            layout.setTranslationY(mCurrentTranslationY);
2877            layout.setRotationY(mCurrentRotationY);
2878        }
2879    }
2880
2881    /**
2882     * Return the current {@link CellLayout}, correctly picking the destination
2883     * screen while a scroll is in progress.
2884     */
2885    public CellLayout getCurrentDropLayout() {
2886        return (CellLayout) getChildAt(mNextPage == INVALID_PAGE ? mCurrentPage : mNextPage);
2887    }
2888
2889    /**
2890     * Return the current CellInfo describing our current drag; this method exists
2891     * so that Launcher can sync this object with the correct info when the activity is created/
2892     * destroyed
2893     *
2894     */
2895    public CellLayout.CellInfo getDragInfo() {
2896        return mDragInfo;
2897    }
2898
2899    /**
2900     * Calculate the nearest cell where the given object would be dropped.
2901     *
2902     * pixelX and pixelY should be in the coordinate system of layout
2903     */
2904    private int[] findNearestVacantArea(int pixelX, int pixelY,
2905            int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
2906        return layout.findNearestVacantArea(
2907                pixelX, pixelY, spanX, spanY, ignoreView, recycle);
2908    }
2909
2910    /**
2911     * Calculate the nearest cell where the given object would be dropped.
2912     *
2913     * pixelX and pixelY should be in the coordinate system of layout
2914     */
2915    private int[] findNearestArea(int pixelX, int pixelY,
2916            int spanX, int spanY, CellLayout layout, int[] recycle) {
2917        return layout.findNearestArea(
2918                pixelX, pixelY, spanX, spanY, recycle);
2919    }
2920
2921    void setup(Launcher launcher, DragController dragController) {
2922        mLauncher = launcher;
2923        mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
2924        mDragController = dragController;
2925
2926        // hardware layers on children are enabled on startup, but should be disabled until
2927        // needed
2928        updateChildrenLayersEnabled();
2929        setWallpaperDimension();
2930    }
2931
2932    /**
2933     * Called at the end of a drag which originated on the workspace.
2934     */
2935    public void onDropCompleted(View target, DragObject d, boolean success) {
2936        if (success) {
2937            if (target != this) {
2938                if (mDragInfo != null) {
2939                    getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2940                    if (mDragInfo.cell instanceof DropTarget) {
2941                        mDragController.removeDropTarget((DropTarget) mDragInfo.cell);
2942                    }
2943                }
2944            }
2945        } else if (mDragInfo != null) {
2946            // NOTE: When 'success' is true, onDragExit is called by the DragController before
2947            // calling onDropCompleted(). We call it ourselves here, but maybe this should be
2948            // moved into DragController.cancelDrag().
2949            doDragExit(null);
2950            CellLayout cellLayout;
2951            if (mLauncher.isHotseatLayout(target)) {
2952                cellLayout = mLauncher.getHotseat().getLayout();
2953            } else {
2954                cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
2955            }
2956            cellLayout.onDropChild(mDragInfo.cell);
2957        }
2958        mDragOutline = null;
2959        mDragInfo = null;
2960    }
2961
2962    public boolean isDropEnabled() {
2963        return true;
2964    }
2965
2966    @Override
2967    protected void onRestoreInstanceState(Parcelable state) {
2968        super.onRestoreInstanceState(state);
2969        Launcher.setScreen(mCurrentPage);
2970    }
2971
2972    @Override
2973    public void scrollLeft() {
2974        if (!isSmall() && !mIsSwitchingState) {
2975            super.scrollLeft();
2976        }
2977        Folder openFolder = getOpenFolder();
2978        if (openFolder != null) {
2979            openFolder.completeDragExit();
2980        }
2981    }
2982
2983    @Override
2984    public void scrollRight() {
2985        if (!isSmall() && !mIsSwitchingState) {
2986            super.scrollRight();
2987        }
2988        Folder openFolder = getOpenFolder();
2989        if (openFolder != null) {
2990            openFolder.completeDragExit();
2991        }
2992    }
2993
2994    @Override
2995    public void onEnterScrollArea(int x, int y, int direction) {
2996        // Ignore the scroll area if we are dragging over the hot seat
2997        if (mLauncher.getHotseat() != null) {
2998            Rect r = new Rect();
2999            mLauncher.getHotseat().getHitRect(r);
3000            if (r.contains(x, y)) {
3001                return;
3002            }
3003        }
3004
3005        if (!isSmall() && !mIsSwitchingState) {
3006            mInScrollArea = true;
3007
3008            final int page = mCurrentPage + (direction == DragController.SCROLL_LEFT ? -1 : 1);
3009            final CellLayout layout = (CellLayout) getChildAt(page);
3010            cancelFolderCreation();
3011
3012            if (layout != null) {
3013                // Exit the current layout and mark the overlapping layout
3014                if (mDragTargetLayout != null) {
3015                    mDragTargetLayout.setIsDragOverlapping(false);
3016                    mDragTargetLayout.onDragExit();
3017                }
3018                mDragTargetLayout = layout;
3019                mDragTargetLayout.setIsDragOverlapping(true);
3020
3021                // Workspace is responsible for drawing the edge glow on adjacent pages,
3022                // so we need to redraw the workspace when this may have changed.
3023                invalidate();
3024            }
3025        }
3026    }
3027
3028    @Override
3029    public void onExitScrollArea() {
3030        if (mInScrollArea) {
3031            if (mDragTargetLayout != null) {
3032                // Unmark the overlapping layout and re-enter the current layout
3033                mDragTargetLayout.setIsDragOverlapping(false);
3034                mDragTargetLayout = getCurrentDropLayout();
3035                mDragTargetLayout.onDragEnter();
3036
3037                // Workspace is responsible for drawing the edge glow on adjacent pages,
3038                // so we need to redraw the workspace when this may have changed.
3039                invalidate();
3040            }
3041            mInScrollArea = false;
3042        }
3043    }
3044
3045    private void onResetScrollArea() {
3046        if (mDragTargetLayout != null) {
3047            // Unmark the overlapping layout
3048            mDragTargetLayout.setIsDragOverlapping(false);
3049
3050            // Workspace is responsible for drawing the edge glow on adjacent pages,
3051            // so we need to redraw the workspace when this may have changed.
3052            invalidate();
3053        }
3054        mInScrollArea = false;
3055    }
3056
3057    /**
3058     * Returns a specific CellLayout
3059     */
3060    CellLayout getParentCellLayoutForView(View v) {
3061        ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
3062        for (CellLayout layout : layouts) {
3063            if (layout.getChildrenLayout().indexOfChild(v) > -1) {
3064                return layout;
3065            }
3066        }
3067        return null;
3068    }
3069
3070    /**
3071     * Returns a list of all the CellLayouts in the workspace.
3072     */
3073    ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
3074        ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
3075        int screenCount = getChildCount();
3076        for (int screen = 0; screen < screenCount; screen++) {
3077            layouts.add(((CellLayout) getChildAt(screen)));
3078        }
3079        if (mLauncher.getHotseat() != null) {
3080            layouts.add(mLauncher.getHotseat().getLayout());
3081        }
3082        return layouts;
3083    }
3084
3085    /**
3086     * We should only use this to search for specific children.  Do not use this method to modify
3087     * CellLayoutChildren directly.
3088     */
3089    ArrayList<CellLayoutChildren> getWorkspaceAndHotseatCellLayoutChildren() {
3090        ArrayList<CellLayoutChildren> childrenLayouts = new ArrayList<CellLayoutChildren>();
3091        int screenCount = getChildCount();
3092        for (int screen = 0; screen < screenCount; screen++) {
3093            childrenLayouts.add(((CellLayout) getChildAt(screen)).getChildrenLayout());
3094        }
3095        if (mLauncher.getHotseat() != null) {
3096            childrenLayouts.add(mLauncher.getHotseat().getLayout().getChildrenLayout());
3097        }
3098        return childrenLayouts;
3099    }
3100
3101    public Folder getFolderForTag(Object tag) {
3102        ArrayList<CellLayoutChildren> childrenLayouts = getWorkspaceAndHotseatCellLayoutChildren();
3103        for (CellLayoutChildren layout: childrenLayouts) {
3104            int count = layout.getChildCount();
3105            for (int i = 0; i < count; i++) {
3106                View child = layout.getChildAt(i);
3107                if (child instanceof Folder) {
3108                    Folder f = (Folder) child;
3109                    if (f.getInfo() == tag && f.getInfo().opened) {
3110                        return f;
3111                    }
3112                }
3113            }
3114        }
3115        return null;
3116    }
3117
3118    public View getViewForTag(Object tag) {
3119        ArrayList<CellLayoutChildren> childrenLayouts = getWorkspaceAndHotseatCellLayoutChildren();
3120        for (CellLayoutChildren layout: childrenLayouts) {
3121            int count = layout.getChildCount();
3122            for (int i = 0; i < count; i++) {
3123                View child = layout.getChildAt(i);
3124                if (child.getTag() == tag) {
3125                    return child;
3126                }
3127            }
3128        }
3129        return null;
3130    }
3131
3132    void clearDropTargets() {
3133        ArrayList<CellLayoutChildren> childrenLayouts = getWorkspaceAndHotseatCellLayoutChildren();
3134        for (CellLayoutChildren layout: childrenLayouts) {
3135            int childCount = layout.getChildCount();
3136            for (int j = 0; j < childCount; j++) {
3137                View v = layout.getChildAt(j);
3138                if (v instanceof DropTarget) {
3139                    mDragController.removeDropTarget((DropTarget) v);
3140                }
3141            }
3142        }
3143    }
3144
3145    void removeItems(final ArrayList<ApplicationInfo> apps) {
3146        final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext());
3147
3148        final HashSet<String> packageNames = new HashSet<String>();
3149        final int appCount = apps.size();
3150        for (int i = 0; i < appCount; i++) {
3151            packageNames.add(apps.get(i).componentName.getPackageName());
3152        }
3153
3154        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
3155        for (final CellLayout layoutParent: cellLayouts) {
3156            final ViewGroup layout = layoutParent.getChildrenLayout();
3157
3158            // Avoid ANRs by treating each screen separately
3159            post(new Runnable() {
3160                public void run() {
3161                    final ArrayList<View> childrenToRemove = new ArrayList<View>();
3162                    childrenToRemove.clear();
3163
3164                    int childCount = layout.getChildCount();
3165                    for (int j = 0; j < childCount; j++) {
3166                        final View view = layout.getChildAt(j);
3167                        Object tag = view.getTag();
3168
3169                        if (tag instanceof ShortcutInfo) {
3170                            final ShortcutInfo info = (ShortcutInfo) tag;
3171                            final Intent intent = info.intent;
3172                            final ComponentName name = intent.getComponent();
3173
3174                            if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
3175                                for (String packageName: packageNames) {
3176                                    if (packageName.equals(name.getPackageName())) {
3177                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
3178                                        childrenToRemove.add(view);
3179                                    }
3180                                }
3181                            }
3182                        } else if (tag instanceof FolderInfo) {
3183                            final FolderInfo info = (FolderInfo) tag;
3184                            final ArrayList<ShortcutInfo> contents = info.contents;
3185                            final int contentsCount = contents.size();
3186                            final ArrayList<ShortcutInfo> appsToRemoveFromFolder =
3187                                    new ArrayList<ShortcutInfo>();
3188
3189                            for (int k = 0; k < contentsCount; k++) {
3190                                final ShortcutInfo appInfo = contents.get(k);
3191                                final Intent intent = appInfo.intent;
3192                                final ComponentName name = intent.getComponent();
3193
3194                                if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
3195                                    for (String packageName: packageNames) {
3196                                        if (packageName.equals(name.getPackageName())) {
3197                                            appsToRemoveFromFolder.add(appInfo);
3198                                        }
3199                                    }
3200                                }
3201                            }
3202                            for (ShortcutInfo item: appsToRemoveFromFolder) {
3203                                info.remove(item);
3204                                LauncherModel.deleteItemFromDatabase(mLauncher, item);
3205                            }
3206                        } else if (tag instanceof LauncherAppWidgetInfo) {
3207                            final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag;
3208                            final AppWidgetProviderInfo provider =
3209                                    widgets.getAppWidgetInfo(info.appWidgetId);
3210                            if (provider != null) {
3211                                for (String packageName: packageNames) {
3212                                    if (packageName.equals(provider.provider.getPackageName())) {
3213                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
3214                                        childrenToRemove.add(view);
3215                                    }
3216                                }
3217                            }
3218                        }
3219                    }
3220
3221                    childCount = childrenToRemove.size();
3222                    for (int j = 0; j < childCount; j++) {
3223                        View child = childrenToRemove.get(j);
3224                        // Note: We can not remove the view directly from CellLayoutChildren as this
3225                        // does not re-mark the spaces as unoccupied.
3226                        layoutParent.removeViewInLayout(child);
3227                        if (child instanceof DropTarget) {
3228                            mDragController.removeDropTarget((DropTarget)child);
3229                        }
3230                    }
3231
3232                    if (childCount > 0) {
3233                        layout.requestLayout();
3234                        layout.invalidate();
3235                    }
3236                }
3237            });
3238        }
3239    }
3240
3241    void updateShortcuts(ArrayList<ApplicationInfo> apps) {
3242        ArrayList<CellLayoutChildren> childrenLayouts = getWorkspaceAndHotseatCellLayoutChildren();
3243        for (CellLayoutChildren layout: childrenLayouts) {
3244            int childCount = layout.getChildCount();
3245            for (int j = 0; j < childCount; j++) {
3246                final View view = layout.getChildAt(j);
3247                Object tag = view.getTag();
3248                if (tag instanceof ShortcutInfo) {
3249                    ShortcutInfo info = (ShortcutInfo)tag;
3250                    // We need to check for ACTION_MAIN otherwise getComponent() might
3251                    // return null for some shortcuts (for instance, for shortcuts to
3252                    // web pages.)
3253                    final Intent intent = info.intent;
3254                    final ComponentName name = intent.getComponent();
3255                    if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
3256                            Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
3257                        final int appCount = apps.size();
3258                        for (int k = 0; k < appCount; k++) {
3259                            ApplicationInfo app = apps.get(k);
3260                            if (app.componentName.equals(name)) {
3261                                info.setIcon(mIconCache.getIcon(info.intent));
3262                                ((TextView)view).setCompoundDrawablesWithIntrinsicBounds(null,
3263                                        new FastBitmapDrawable(info.getIcon(mIconCache)),
3264                                        null, null);
3265                                }
3266                        }
3267                    }
3268                }
3269            }
3270        }
3271    }
3272
3273    void moveToDefaultScreen(boolean animate) {
3274        if (!isSmall()) {
3275            if (animate) {
3276                snapToPage(mDefaultPage);
3277            } else {
3278                setCurrentPage(mDefaultPage);
3279            }
3280        }
3281        getChildAt(mDefaultPage).requestFocus();
3282    }
3283
3284    @Override
3285    public void syncPages() {
3286    }
3287
3288    @Override
3289    public void syncPageItems(int page, boolean immediate) {
3290    }
3291
3292    @Override
3293    protected String getCurrentPageDescription() {
3294        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
3295        return String.format(mContext.getString(R.string.workspace_scroll_format),
3296                page + 1, getChildCount());
3297    }
3298
3299    public void getLocationInDragLayer(int[] loc) {
3300        mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
3301    }
3302
3303    /**
3304     * Return true because we want the scrolling indicator to stretch to fit the space.
3305     */
3306    protected boolean hasElasticScrollIndicator() {
3307        return true;
3308    }
3309
3310    void showDockDivider(boolean immediately) {
3311        final ViewGroup parent = (ViewGroup) getParent();
3312        final View qsbDivider = (ImageView) (parent.findViewById(R.id.qsb_divider));
3313        final View dockDivider = (ImageView) (parent.findViewById(R.id.dock_divider));
3314        if (qsbDivider != null && dockDivider != null) {
3315            qsbDivider.setVisibility(View.VISIBLE);
3316            dockDivider.setVisibility(View.VISIBLE);
3317            if (mDividerAnimator != null) {
3318                mDividerAnimator.cancel();
3319                mDividerAnimator = null;
3320            }
3321            if (immediately) {
3322                qsbDivider.setAlpha(1f);
3323                dockDivider.setAlpha(1f);
3324            } else {
3325                mDividerAnimator = new AnimatorSet();
3326                mDividerAnimator.playTogether(ObjectAnimator.ofFloat(qsbDivider, "alpha", 1f),
3327                        ObjectAnimator.ofFloat(dockDivider, "alpha", 1f));
3328                mDividerAnimator.setDuration(sScrollIndicatorFadeInDuration);
3329                mDividerAnimator.start();
3330            }
3331        }
3332    }
3333
3334    void hideDockDivider(boolean immediately) {
3335        final ViewGroup parent = (ViewGroup) getParent();
3336        final View qsbDivider = (ImageView) (parent.findViewById(R.id.qsb_divider));
3337        final View dockDivider = (ImageView) (parent.findViewById(R.id.dock_divider));
3338        if (qsbDivider != null && dockDivider != null) {
3339            if (mDividerAnimator != null) {
3340                mDividerAnimator.cancel();
3341                mDividerAnimator = null;
3342            }
3343            if (immediately) {
3344                qsbDivider.setVisibility(View.GONE);
3345                dockDivider.setVisibility(View.GONE);
3346                qsbDivider.setAlpha(0f);
3347                dockDivider.setAlpha(0f);
3348            } else {
3349                mDividerAnimator = new AnimatorSet();
3350                mDividerAnimator.playTogether(ObjectAnimator.ofFloat(qsbDivider, "alpha", 0f),
3351                        ObjectAnimator.ofFloat(dockDivider, "alpha", 0f));
3352                mDividerAnimator.addListener(new AnimatorListenerAdapter() {
3353                    private boolean cancelled = false;
3354                    @Override
3355                    public void onAnimationCancel(android.animation.Animator animation) {
3356                        cancelled = true;
3357                    }
3358                    @Override
3359                    public void onAnimationEnd(android.animation.Animator animation) {
3360                        if (!cancelled) {
3361                            qsbDivider.setVisibility(View.GONE);
3362                            dockDivider.setVisibility(View.GONE);
3363                        }
3364                    }
3365                });
3366                mDividerAnimator.setDuration(sScrollIndicatorFadeOutDuration);
3367                mDividerAnimator.start();
3368            }
3369        }
3370    }
3371}
3372