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