Workspace.java revision 482a5b6ed389ef943990277e461444626c34ebf2
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.launcher3;
18
19import android.animation.Animator;
20import android.animation.AnimatorSet;
21import android.animation.LayoutTransition;
22import android.animation.ObjectAnimator;
23import android.animation.TimeInterpolator;
24import android.animation.ValueAnimator;
25import android.animation.ValueAnimator.AnimatorUpdateListener;
26import android.app.WallpaperManager;
27import android.appwidget.AppWidgetHostView;
28import android.appwidget.AppWidgetProviderInfo;
29import android.content.ComponentName;
30import android.content.Context;
31import android.content.Intent;
32import android.content.SharedPreferences;
33import android.content.res.Resources;
34import android.content.res.TypedArray;
35import android.graphics.Bitmap;
36import android.graphics.Canvas;
37import android.graphics.Color;
38import android.graphics.Matrix;
39import android.graphics.Paint;
40import android.graphics.Point;
41import android.graphics.PointF;
42import android.graphics.Rect;
43import android.graphics.Region.Op;
44import android.graphics.drawable.Drawable;
45import android.os.IBinder;
46import android.os.Parcelable;
47import android.util.AttributeSet;
48import android.util.Log;
49import android.util.SparseArray;
50import android.view.Display;
51import android.view.MotionEvent;
52import android.view.View;
53import android.view.ViewGroup;
54import android.view.animation.DecelerateInterpolator;
55import android.widget.ImageView;
56import android.widget.TextView;
57
58import com.android.launcher3.FolderIcon.FolderRingAnimator;
59import com.android.launcher3.Launcher.CustomContentCallbacks;
60import com.android.launcher3.LauncherSettings.Favorites;
61
62import java.net.URISyntaxException;
63import java.util.ArrayList;
64import java.util.HashMap;
65import java.util.HashSet;
66import java.util.Iterator;
67import java.util.Set;
68
69/**
70 * The workspace is a wide area with a wallpaper and a finite number of pages.
71 * Each page contains a number of icons, folders or widgets the user can
72 * interact with. A workspace is meant to be used with a fixed width only.
73 */
74public class Workspace extends SmoothPagedView
75        implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
76        DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener {
77    private static final String TAG = "Launcher.Workspace";
78
79    // Y rotation to apply to the workspace screens
80    private static final float WORKSPACE_OVERSCROLL_ROTATION = 24f;
81
82    private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
83    private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
84    private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
85
86    private static final int BACKGROUND_FADE_OUT_DURATION = 350;
87    private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
88    private static final int FLING_THRESHOLD_VELOCITY = 500;
89
90    // These animators are used to fade the children's outlines
91    private ObjectAnimator mChildrenOutlineFadeInAnimation;
92    private ObjectAnimator mChildrenOutlineFadeOutAnimation;
93    private float mChildrenOutlineAlpha = 0;
94
95    // These properties refer to the background protection gradient used for AllApps and Customize
96    private ValueAnimator mBackgroundFadeInAnimation;
97    private ValueAnimator mBackgroundFadeOutAnimation;
98    private Drawable mBackground;
99    boolean mDrawBackground = true;
100    private float mBackgroundAlpha = 0;
101
102    private LayoutTransition mLayoutTransition;
103    private final WallpaperManager mWallpaperManager;
104    private IBinder mWindowToken;
105    private static final float WALLPAPER_SCREENS_SPAN = 2f;
106
107    private int mDefaultPage;
108
109    // The screen id used for the empty screen always present to the right.
110    private final static long EXTRA_EMPTY_SCREEN_ID = -201;
111    private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
112
113    private HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>();
114    private ArrayList<Long> mScreenOrder = new ArrayList<Long>();
115
116    /**
117     * CellInfo for the cell that is currently being dragged
118     */
119    private CellLayout.CellInfo mDragInfo;
120
121    /**
122     * Target drop area calculated during last acceptDrop call.
123     */
124    private int[] mTargetCell = new int[2];
125    private int mDragOverX = -1;
126    private int mDragOverY = -1;
127
128    static Rect mLandscapeCellLayoutMetrics = null;
129    static Rect mPortraitCellLayoutMetrics = null;
130
131    CustomContentCallbacks mCustomContentCallbacks;
132    boolean mCustomContentShowing;
133
134    /**
135     * The CellLayout that is currently being dragged over
136     */
137    private CellLayout mDragTargetLayout = null;
138    /**
139     * The CellLayout that we will show as glowing
140     */
141    private CellLayout mDragOverlappingLayout = null;
142
143    /**
144     * The CellLayout which will be dropped to
145     */
146    private CellLayout mDropToLayout = null;
147
148    private Launcher mLauncher;
149    private IconCache mIconCache;
150    private DragController mDragController;
151
152    // These are temporary variables to prevent having to allocate a new object just to
153    // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
154    private int[] mTempCell = new int[2];
155    private int[] mTempPt = new int[2];
156    private int[] mTempEstimate = new int[2];
157    private float[] mDragViewVisualCenter = new float[2];
158    private float[] mTempCellLayoutCenterCoordinates = new float[2];
159    private Matrix mTempInverseMatrix = new Matrix();
160
161    private SpringLoadedDragController mSpringLoadedDragController;
162    private float mSpringLoadedShrinkFactor;
163
164    private static final int DEFAULT_CELL_COUNT_X = 4;
165    private static final int DEFAULT_CELL_COUNT_Y = 4;
166
167    // State variable that indicates whether the pages are small (ie when you're
168    // in all apps or customize mode)
169
170    enum State { NORMAL, SPRING_LOADED, SMALL };
171    private State mState = State.NORMAL;
172    private boolean mIsSwitchingState = false;
173
174    boolean mAnimatingViewIntoPlace = false;
175    boolean mIsDragOccuring = false;
176    boolean mChildrenLayersEnabled = true;
177
178    private boolean mStripScreensOnPageStopMoving = false;
179
180    /** Is the user is dragging an item near the edge of a page? */
181    private boolean mInScrollArea = false;
182
183    private HolographicOutlineHelper mOutlineHelper;
184    private Bitmap mDragOutline = null;
185    private final Rect mTempRect = new Rect();
186    private final int[] mTempXY = new int[2];
187    private int[] mTempVisiblePagesRange = new int[2];
188    private float mOverscrollFade = 0;
189    private boolean mOverscrollTransformsSet;
190    public static final int DRAG_BITMAP_PADDING = 2;
191    private boolean mWorkspaceFadeInAdjacentScreens;
192
193    enum WallpaperVerticalOffset { TOP, MIDDLE, BOTTOM };
194    int mWallpaperWidth;
195    int mWallpaperHeight;
196    WallpaperOffsetInterpolator mWallpaperOffset;
197    boolean mUpdateWallpaperOffsetImmediately = false;
198    private Runnable mDelayedResizeRunnable;
199    private Runnable mDelayedSnapToPageRunnable;
200    private Point mDisplaySize = new Point();
201    private boolean mIsStaticWallpaper;
202    private int mWallpaperTravelWidth;
203    private int mCameraDistance;
204
205    // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
206    private static final int FOLDER_CREATION_TIMEOUT = 0;
207    private static final int REORDER_TIMEOUT = 250;
208    private final Alarm mFolderCreationAlarm = new Alarm();
209    private final Alarm mReorderAlarm = new Alarm();
210    private FolderRingAnimator mDragFolderRingAnimator = null;
211    private FolderIcon mDragOverFolderIcon = null;
212    private boolean mCreateUserFolderOnDrop = false;
213    private boolean mAddToExistingFolderOnDrop = false;
214    private DropTarget.DragEnforcer mDragEnforcer;
215    private float mMaxDistanceForFolderCreation;
216
217    // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
218    private float mXDown;
219    private float mYDown;
220    final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
221    final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
222    final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
223
224    // Relating to the animation of items being dropped externally
225    public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0;
226    public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1;
227    public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2;
228    public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3;
229    public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4;
230
231    // Related to dragging, folder creation and reordering
232    private static final int DRAG_MODE_NONE = 0;
233    private static final int DRAG_MODE_CREATE_FOLDER = 1;
234    private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
235    private static final int DRAG_MODE_REORDER = 3;
236    private int mDragMode = DRAG_MODE_NONE;
237    private int mLastReorderX = -1;
238    private int mLastReorderY = -1;
239
240    private SparseArray<Parcelable> mSavedStates;
241    private final ArrayList<Integer> mRestoredPages = new ArrayList<Integer>();
242
243    // These variables are used for storing the initial and final values during workspace animations
244    private int mSavedScrollX;
245    private float mSavedRotationY;
246    private float mSavedTranslationX;
247
248    private float mCurrentScale;
249    private float mNewScale;
250    private float[] mOldBackgroundAlphas;
251    private float[] mOldAlphas;
252    private float[] mNewBackgroundAlphas;
253    private float[] mNewAlphas;
254    private int mLastChildCount = -1;
255    private float mTransitionProgress;
256
257    private Runnable mDeferredAction;
258    private boolean mDeferDropAfterUninstall;
259    private boolean mUninstallSuccessful;
260
261    private final Runnable mBindPages = new Runnable() {
262        @Override
263        public void run() {
264            mLauncher.getModel().bindRemainingSynchronousPages();
265        }
266    };
267
268    /**
269     * Used to inflate the Workspace from XML.
270     *
271     * @param context The application's context.
272     * @param attrs The attributes set containing the Workspace's customization values.
273     */
274    public Workspace(Context context, AttributeSet attrs) {
275        this(context, attrs, 0);
276    }
277
278    /**
279     * Used to inflate the Workspace from XML.
280     *
281     * @param context The application's context.
282     * @param attrs The attributes set containing the Workspace's customization values.
283     * @param defStyle Unused.
284     */
285    public Workspace(Context context, AttributeSet attrs, int defStyle) {
286        super(context, attrs, defStyle);
287        mContentIsRefreshable = false;
288
289        mOutlineHelper = HolographicOutlineHelper.obtain(context);
290
291        mDragEnforcer = new DropTarget.DragEnforcer(context);
292        // With workspace, data is available straight from the get-go
293        setDataIsReady();
294
295        mLauncher = (Launcher) context;
296        final Resources res = getResources();
297        mWorkspaceFadeInAdjacentScreens = res.getBoolean(R.bool.config_workspaceFadeAdjacentScreens);
298        mFadeInAdjacentScreens = false;
299        mWallpaperManager = WallpaperManager.getInstance(context);
300
301        int cellCountX = DEFAULT_CELL_COUNT_X;
302        int cellCountY = DEFAULT_CELL_COUNT_Y;
303
304        TypedArray a = context.obtainStyledAttributes(attrs,
305                R.styleable.Workspace, defStyle, 0);
306
307        if (LauncherAppState.getInstance().isScreenLarge()) {
308            // Determine number of rows/columns dynamically
309            // TODO: This code currently fails on tablets with an aspect ratio < 1.3.
310            // Around that ratio we should make cells the same size in portrait and
311            // landscape
312            TypedArray actionBarSizeTypedArray =
313                context.obtainStyledAttributes(new int[] { android.R.attr.actionBarSize });
314            final float actionBarHeight = actionBarSizeTypedArray.getDimension(0, 0f);
315
316            Point minDims = new Point();
317            Point maxDims = new Point();
318            mLauncher.getWindowManager().getDefaultDisplay().getCurrentSizeRange(minDims, maxDims);
319
320            cellCountX = 1;
321            while (CellLayout.widthInPortrait(res, cellCountX + 1) <= minDims.x) {
322                cellCountX++;
323            }
324
325            cellCountY = 1;
326            while (actionBarHeight + CellLayout.heightInLandscape(res, cellCountY + 1)
327                <= minDims.y) {
328                cellCountY++;
329            }
330        }
331
332        mSpringLoadedShrinkFactor =
333            res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
334        mCameraDistance = res.getInteger(R.integer.config_cameraDistance);
335
336        // if the value is manually specified, use that instead
337        cellCountX = a.getInt(R.styleable.Workspace_cellCountX, cellCountX);
338        cellCountY = a.getInt(R.styleable.Workspace_cellCountY, cellCountY);
339        mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
340
341        a.recycle();
342
343        setOnHierarchyChangeListener(this);
344
345        LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY);
346        setHapticFeedbackEnabled(false);
347
348        initWorkspace();
349
350        // Disable multitouch across the workspace/all apps/customize tray
351        setMotionEventSplittingEnabled(true);
352
353        // Unless otherwise specified this view is important for accessibility.
354        if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
355            setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
356        }
357    }
358
359    // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
360    // dimension if unsuccessful
361    public int[] estimateItemSize(int hSpan, int vSpan,
362            ItemInfo itemInfo, boolean springLoaded) {
363        int[] size = new int[2];
364        if (getChildCount() > 0) {
365            CellLayout cl = (CellLayout) mLauncher.getWorkspace().getChildAt(0);
366            Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan);
367            size[0] = r.width();
368            size[1] = r.height();
369            if (springLoaded) {
370                size[0] *= mSpringLoadedShrinkFactor;
371                size[1] *= mSpringLoadedShrinkFactor;
372            }
373            return size;
374        } else {
375            size[0] = Integer.MAX_VALUE;
376            size[1] = Integer.MAX_VALUE;
377            return size;
378        }
379    }
380
381    public Rect estimateItemPosition(CellLayout cl, ItemInfo pendingInfo,
382            int hCell, int vCell, int hSpan, int vSpan) {
383        Rect r = new Rect();
384        cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
385        return r;
386    }
387
388    public void onDragStart(DragSource source, Object info, int dragAction) {
389        mIsDragOccuring = true;
390        updateChildrenLayersEnabled(false);
391        mLauncher.lockScreenOrientation();
392        setChildrenBackgroundAlphaMultipliers(1f);
393        // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging
394        InstallShortcutReceiver.enableInstallQueue();
395        UninstallShortcutReceiver.enableUninstallQueue();
396    }
397
398    public void onDragEnd() {
399        mIsDragOccuring = false;
400        updateChildrenLayersEnabled(false);
401        mLauncher.unlockScreenOrientation(false);
402
403        // Re-enable any Un/InstallShortcutReceiver and now process any queued items
404        InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
405        UninstallShortcutReceiver.disableAndFlushUninstallQueue(getContext());
406    }
407
408    /**
409     * Initializes various states for this workspace.
410     */
411    protected void initWorkspace() {
412        Context context = getContext();
413        mCurrentPage = mDefaultPage;
414        Launcher.setScreen(mCurrentPage);
415        LauncherAppState app = LauncherAppState.getInstance();
416        mIconCache = app.getIconCache();
417        setWillNotDraw(false);
418        setClipChildren(false);
419        setClipToPadding(false);
420        setChildrenDrawnWithCacheEnabled(true);
421        setMinScale(0.5f);
422        setupLayoutTransition();
423
424        final Resources res = getResources();
425        try {
426            mBackground = res.getDrawable(R.drawable.apps_customize_bg);
427        } catch (Resources.NotFoundException e) {
428            // In this case, we will skip drawing background protection
429        }
430
431        mWallpaperOffset = new WallpaperOffsetInterpolator();
432        Display display = mLauncher.getWindowManager().getDefaultDisplay();
433        display.getSize(mDisplaySize);
434        mWallpaperTravelWidth = (int) (mDisplaySize.x *
435                wallpaperTravelToScreenWidthRatio(mDisplaySize.x, mDisplaySize.y));
436
437        mMaxDistanceForFolderCreation = (0.55f * res.getDimensionPixelSize(R.dimen.app_icon_size));
438        mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
439    }
440
441    private void setupLayoutTransition() {
442        // We want to show layout transitions when pages are deleted, to close the gap.
443        mLayoutTransition = new LayoutTransition();
444        mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
445        mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
446        mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
447        mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
448        setLayoutTransition(mLayoutTransition);
449    }
450
451    @Override
452    protected int getScrollMode() {
453        return SmoothPagedView.X_LARGE_MODE;
454    }
455
456    @Override
457    public void onChildViewAdded(View parent, View child) {
458        if (!(child instanceof CellLayout)) {
459            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
460        }
461        CellLayout cl = ((CellLayout) child);
462        cl.setOnInterceptTouchListener(this);
463        cl.setClickable(true);
464        cl.setContentDescription(getContext().getString(
465                R.string.workspace_description_format, getChildCount()));
466
467        super.onChildViewAdded(parent, child);
468    }
469
470    protected boolean shouldDrawChild(View child) {
471        final CellLayout cl = (CellLayout) child;
472        return super.shouldDrawChild(child) &&
473            (cl.getShortcutsAndWidgets().getAlpha() > 0 ||
474             cl.getBackgroundAlpha() > 0);
475    }
476
477    /**
478     * @return The open folder on the current screen, or null if there is none
479     */
480    Folder getOpenFolder() {
481        DragLayer dragLayer = mLauncher.getDragLayer();
482        int count = dragLayer.getChildCount();
483        for (int i = 0; i < count; i++) {
484            View child = dragLayer.getChildAt(i);
485            if (child instanceof Folder) {
486                Folder folder = (Folder) child;
487                if (folder.getInfo().opened)
488                    return folder;
489            }
490        }
491        return null;
492    }
493
494    boolean isTouchActive() {
495        return mTouchState != TOUCH_STATE_REST;
496    }
497
498    public long insertNewWorkspaceScreen(long screenId) {
499        return insertNewWorkspaceScreen(screenId, getChildCount(), true);
500    }
501
502    public long insertNewWorkspaceScreenBeforeEmptyScreen(long screenId, boolean updateDb) {
503        // Find the index to insert this view into.  If the empty screen exists, then
504        // insert it before that.
505        int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
506        if (insertIndex < 0) {
507            insertIndex = mScreenOrder.size();
508        }
509        return insertNewWorkspaceScreen(screenId, insertIndex, updateDb);
510    }
511
512    public long insertNewWorkspaceScreen(long screenId, boolean updateDb) {
513        return insertNewWorkspaceScreen(screenId, getChildCount(), updateDb);
514    }
515
516    public long insertNewWorkspaceScreen(long screenId, int insertIndex, boolean updateDb) {
517        CellLayout newScreen = (CellLayout)
518                mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
519
520        newScreen.setOnLongClickListener(mLongClickListener);
521        mWorkspaceScreens.put(screenId, newScreen);
522        mScreenOrder.add(insertIndex, screenId);
523        addView(newScreen, insertIndex);
524        if (updateDb) {
525            // On bind we don't need to update the screens in the database.
526            mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
527        }
528        return screenId;
529    }
530
531    public void createCustomContentPage() {
532        CellLayout customScreen = (CellLayout)
533                mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
534
535        Rect p = new Rect();
536        AppWidgetHostView.getDefaultPaddingForWidget(mLauncher, mLauncher.getComponentName(), p);
537
538        mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen);
539        mScreenOrder.add(0, CUSTOM_CONTENT_SCREEN_ID);
540
541        addFullScreenPage(customScreen);
542
543        // Ensure that the current page and default page are maintained.
544        mDefaultPage++;
545        setCurrentPage(getCurrentPage() + 1);
546    }
547
548    public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks) {
549        if (getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID) < 0) {
550            throw new RuntimeException("Expected custom content screen to exist");
551        }
552
553        // Add the custom content to the full screen custom page
554        CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
555        int spanX = customScreen.getCountX();
556        int spanY = customScreen.getCountY();
557        CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, spanX, spanY);
558        lp.canReorder  = false;
559        lp.isFullscreen = true;
560        customScreen.addViewToCellLayout(customContent, 0, 0, lp, true);
561
562        mCustomContentCallbacks = callbacks;
563    }
564
565    public long commitExtraEmptyScreen() {
566        CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
567        mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
568        mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
569
570        long newId = LauncherAppState.getInstance().getLauncherProvider().generateNewScreenId();
571        mWorkspaceScreens.put(newId, cl);
572        mScreenOrder.add(newId);
573
574        addExtraEmptyScreen();
575
576        // Update the model for the new screen
577        mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
578
579        return newId;
580    }
581
582    public void addExtraEmptyScreen() {
583        insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID, false);
584    }
585
586    public CellLayout getScreenWithId(long screenId) {
587        CellLayout layout = mWorkspaceScreens.get(screenId);
588        return layout;
589    }
590
591    public long getIdForScreen(CellLayout layout) {
592        Iterator<Long> iter = mWorkspaceScreens.keySet().iterator();
593        while (iter.hasNext()) {
594            long id = iter.next();
595            if (mWorkspaceScreens.get(id) == layout) {
596                return id;
597            }
598        }
599        return -1;
600    }
601
602    public int getPageIndexForScreenId(long screenId) {
603        return indexOfChild(mWorkspaceScreens.get(screenId));
604    }
605
606    public long getScreenIdForPageIndex(int index) {
607        return mScreenOrder.get(index);
608    }
609
610    ArrayList<Long> getScreenOrder() {
611        return mScreenOrder;
612    }
613
614    public void stripEmptyScreens() {
615        if (isPageMoving()) {
616            mStripScreensOnPageStopMoving = true;
617            return;
618        }
619
620        int currentPage = getNextPage();
621        ArrayList<Long> removeScreens = new ArrayList<Long>();
622        for (Long id: mWorkspaceScreens.keySet()) {
623            CellLayout cl = mWorkspaceScreens.get(id);
624            if (id >= 0 && cl.getShortcutsAndWidgets().getChildCount() == 0) {
625                removeScreens.add(id);
626            }
627        }
628
629        int pageShift = 0;
630        for (Long id: removeScreens) {
631            CellLayout cl = mWorkspaceScreens.get(id);
632            mWorkspaceScreens.remove(id);
633            mScreenOrder.remove(id);
634            if (indexOfChild(cl) < currentPage) {
635                pageShift++;
636            }
637            removeView(cl);
638        }
639
640        if (!removeScreens.isEmpty()) {
641            // Update the model if we have changed any screens
642            mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
643        }
644
645        if (pageShift >= 0) {
646            setCurrentPage(currentPage - pageShift);
647        }
648    }
649
650    // See implementation for parameter definition.
651    void addInScreen(View child, long container, long screenId,
652            int x, int y, int spanX, int spanY) {
653        addInScreen(child, container, screenId, x, y, spanX, spanY, false, false);
654    }
655
656    // At bind time, we use the rank (screenId) to compute x and y for hotseat items.
657    // See implementation for parameter definition.
658    void addInScreenFromBind(View child, long container, long screenId, int x, int y,
659            int spanX, int spanY) {
660        addInScreen(child, container, screenId, x, y, spanX, spanY, false, true);
661    }
662
663    // See implementation for parameter definition.
664    void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
665            boolean insert) {
666        addInScreen(child, container, screenId, x, y, spanX, spanY, insert, false);
667    }
668
669    /**
670     * Adds the specified child in the specified screen. The position and dimension of
671     * the child are defined by x, y, spanX and spanY.
672     *
673     * @param child The child to add in one of the workspace's screens.
674     * @param screenId The screen in which to add the child.
675     * @param x The X position of the child in the screen's grid.
676     * @param y The Y position of the child in the screen's grid.
677     * @param spanX The number of cells spanned horizontally by the child.
678     * @param spanY The number of cells spanned vertically by the child.
679     * @param insert When true, the child is inserted at the beginning of the children list.
680     * @param computeXYFromRank When true, we use the rank (stored in screenId) to compute
681     *                          the x and y position in which to place hotseat items. Otherwise
682     *                          we use the x and y position to compute the rank.
683     */
684    void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
685            boolean insert, boolean computeXYFromRank) {
686        if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
687            if (getScreenWithId(screenId) == null) {
688                Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
689                return;
690            }
691        }
692        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
693            // This should never happen
694            throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
695        }
696
697        final CellLayout layout;
698        if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
699            layout = mLauncher.getHotseat().getLayout();
700            child.setOnKeyListener(null);
701
702            // Hide folder title in the hotseat
703            if (child instanceof FolderIcon) {
704                ((FolderIcon) child).setTextVisible(false);
705            }
706
707            if (computeXYFromRank) {
708                x = mLauncher.getHotseat().getCellXFromOrder((int) screenId);
709                y = mLauncher.getHotseat().getCellYFromOrder((int) screenId);
710            } else {
711                screenId = mLauncher.getHotseat().getOrderInHotseat(x, y);
712            }
713        } else {
714            // Show folder title if not in the hotseat
715            if (child instanceof FolderIcon) {
716                ((FolderIcon) child).setTextVisible(true);
717            }
718            layout = getScreenWithId(screenId);
719            child.setOnKeyListener(new IconKeyEventListener());
720        }
721
722        ViewGroup.LayoutParams genericLp = child.getLayoutParams();
723        CellLayout.LayoutParams lp;
724        if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
725            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
726        } else {
727            lp = (CellLayout.LayoutParams) genericLp;
728            lp.cellX = x;
729            lp.cellY = y;
730            lp.cellHSpan = spanX;
731            lp.cellVSpan = spanY;
732        }
733
734        if (spanX < 0 && spanY < 0) {
735            lp.isLockedToGrid = false;
736        }
737
738        // Get the canonical child id to uniquely represent this view in this screen
739        int childId = LauncherModel.getCellLayoutChildId(container, screenId, x, y, spanX, spanY);
740        boolean markCellsAsOccupied = !(child instanceof Folder);
741        if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
742            // TODO: This branch occurs when the workspace is adding views
743            // outside of the defined grid
744            // maybe we should be deleting these items from the LauncherModel?
745            Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
746        }
747
748        if (!(child instanceof Folder)) {
749            child.setHapticFeedbackEnabled(false);
750            child.setOnLongClickListener(mLongClickListener);
751        }
752        if (child instanceof DropTarget) {
753            mDragController.addDropTarget((DropTarget) child);
754        }
755    }
756
757    /**
758     * Called directly from a CellLayout (not by the framework), after we've been added as a
759     * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
760     * that it should intercept touch events, which is not something that is normally supported.
761     */
762    @Override
763    public boolean onTouch(View v, MotionEvent event) {
764        return (isSmall() || !isFinishedSwitchingState());
765    }
766
767    public boolean isSwitchingState() {
768        return mIsSwitchingState;
769    }
770
771    /** This differs from isSwitchingState in that we take into account how far the transition
772     *  has completed. */
773    public boolean isFinishedSwitchingState() {
774        return !mIsSwitchingState || (mTransitionProgress > 0.5f);
775    }
776
777    protected void onWindowVisibilityChanged (int visibility) {
778        mLauncher.onWindowVisibilityChanged(visibility);
779        if (mCustomContentShowing && mCustomContentCallbacks != null) {
780            if (visibility == View.VISIBLE) {
781                mCustomContentCallbacks.onShow();
782            } else if (visibility == View.GONE) {
783                mCustomContentCallbacks.onHide();
784            }
785        }
786    }
787
788    @Override
789    public boolean dispatchUnhandledMove(View focused, int direction) {
790        if (isSmall() || !isFinishedSwitchingState()) {
791            // when the home screens are shrunken, shouldn't allow side-scrolling
792            return false;
793        }
794        return super.dispatchUnhandledMove(focused, direction);
795    }
796
797    @Override
798    public boolean onInterceptTouchEvent(MotionEvent ev) {
799        switch (ev.getAction() & MotionEvent.ACTION_MASK) {
800        case MotionEvent.ACTION_DOWN:
801            mXDown = ev.getX();
802            mYDown = ev.getY();
803            break;
804        case MotionEvent.ACTION_POINTER_UP:
805        case MotionEvent.ACTION_UP:
806            if (mTouchState == TOUCH_STATE_REST) {
807                final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
808                if (!currentPage.lastDownOnOccupiedCell()) {
809                    onWallpaperTap(ev);
810                }
811            }
812        }
813
814        if (mLauncher != null && mLauncher.onTouch(this, ev)) {
815            return true;
816        }
817
818        return super.onInterceptTouchEvent(ev);
819    }
820
821    protected void reinflateWidgetsIfNecessary() {
822        final int clCount = getChildCount();
823        for (int i = 0; i < clCount; i++) {
824            CellLayout cl = (CellLayout) getChildAt(i);
825            ShortcutAndWidgetContainer swc = cl.getShortcutsAndWidgets();
826            final int itemCount = swc.getChildCount();
827            for (int j = 0; j < itemCount; j++) {
828                View v = swc.getChildAt(j);
829
830                if (v.getTag() instanceof LauncherAppWidgetInfo) {
831                    LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
832                    LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) info.hostView;
833                    if (lahv != null && lahv.orientationChangedSincedInflation()) {
834                        mLauncher.removeAppWidget(info);
835                        // Remove the current widget which is inflated with the wrong orientation
836                        cl.removeView(lahv);
837                        mLauncher.bindAppWidget(info);
838                    }
839                }
840            }
841        }
842    }
843
844    @Override
845    protected void determineScrollingStart(MotionEvent ev) {
846        if (isSmall()) return;
847        if (!isFinishedSwitchingState()) return;
848
849        float deltaX = Math.abs(ev.getX() - mXDown);
850        float deltaY = Math.abs(ev.getY() - mYDown);
851
852        if (Float.compare(deltaX, 0f) == 0) return;
853
854        float slope = deltaY / deltaX;
855        float theta = (float) Math.atan(slope);
856
857        if (deltaX > mTouchSlop || deltaY > mTouchSlop) {
858            cancelCurrentPageLongPress();
859        }
860
861        if (theta > MAX_SWIPE_ANGLE) {
862            // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
863            return;
864        } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
865            // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
866            // increase the touch slop to make it harder to begin scrolling the workspace. This
867            // results in vertically scrolling widgets to more easily. The higher the angle, the
868            // more we increase touch slop.
869            theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
870            float extraRatio = (float)
871                    Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
872            super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
873        } else {
874            // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
875            super.determineScrollingStart(ev);
876        }
877    }
878
879    protected void onPageBeginMoving() {
880        super.onPageBeginMoving();
881
882        if (isHardwareAccelerated()) {
883            updateChildrenLayersEnabled(false);
884        } else {
885            if (mNextPage != INVALID_PAGE) {
886                // we're snapping to a particular screen
887                enableChildrenCache(mCurrentPage, mNextPage);
888            } else {
889                // this is when user is actively dragging a particular screen, they might
890                // swipe it either left or right (but we won't advance by more than one screen)
891                enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
892            }
893        }
894
895        // Only show page outlines as we pan if we are on large screen
896        if (LauncherAppState.getInstance().isScreenLarge()) {
897            showOutlines();
898            mIsStaticWallpaper = mWallpaperManager.getWallpaperInfo() == null;
899        }
900
901        // If we are not fading in adjacent screens, we still need to restore the alpha in case the
902        // user scrolls while we are transitioning (should not affect dispatchDraw optimizations)
903        if (!mWorkspaceFadeInAdjacentScreens) {
904            for (int i = 0; i < getChildCount(); ++i) {
905                ((CellLayout) getPageAt(i)).setShortcutAndWidgetAlpha(1f);
906            }
907        }
908    }
909
910    protected void onPageEndMoving() {
911        super.onPageEndMoving();
912
913        if (isHardwareAccelerated()) {
914            updateChildrenLayersEnabled(false);
915        } else {
916            clearChildrenCache();
917        }
918
919
920        if (mDragController.isDragging()) {
921            if (isSmall()) {
922                // If we are in springloaded mode, then force an event to check if the current touch
923                // is under a new page (to scroll to)
924                mDragController.forceTouchMove();
925            }
926        } else {
927            // If we are not mid-dragging, hide the page outlines if we are on a large screen
928            if (LauncherAppState.getInstance().isScreenLarge()) {
929                hideOutlines();
930            }
931        }
932
933        if (mDelayedResizeRunnable != null) {
934            mDelayedResizeRunnable.run();
935            mDelayedResizeRunnable = null;
936        }
937
938        if (mDelayedSnapToPageRunnable != null) {
939            mDelayedSnapToPageRunnable.run();
940            mDelayedSnapToPageRunnable = null;
941        }
942        if (mStripScreensOnPageStopMoving) {
943            stripEmptyScreens();
944            mStripScreensOnPageStopMoving = false;
945        }
946
947        if (hasCustomContent() && getNextPage() == 0 && !mCustomContentShowing) {
948            mCustomContentShowing = true;
949            if (mCustomContentCallbacks != null) {
950                mCustomContentCallbacks.onShow();
951            }
952        } else if (hasCustomContent() && getNextPage() != 0 && mCustomContentShowing) {
953            mCustomContentShowing = false;
954            if (mCustomContentCallbacks != null) {
955                mCustomContentCallbacks.onHide();
956                mLauncher.resetQSBScroll();
957            }
958        }
959    }
960
961    @Override
962    protected void notifyPageSwitchListener() {
963        super.notifyPageSwitchListener();
964        Launcher.setScreen(mCurrentPage);
965    };
966
967    // As a ratio of screen height, the total distance we want the parallax effect to span
968    // horizontally
969    private float wallpaperTravelToScreenWidthRatio(int width, int height) {
970        float aspectRatio = width / (float) height;
971
972        // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
973        // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
974        // We will use these two data points to extrapolate how much the wallpaper parallax effect
975        // to span (ie travel) at any aspect ratio:
976
977        final float ASPECT_RATIO_LANDSCAPE = 16/10f;
978        final float ASPECT_RATIO_PORTRAIT = 10/16f;
979        final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
980        final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;
981
982        // To find out the desired width at different aspect ratios, we use the following two
983        // formulas, where the coefficient on x is the aspect ratio (width/height):
984        //   (16/10)x + y = 1.5
985        //   (10/16)x + y = 1.2
986        // We solve for x and y and end up with a final formula:
987        final float x =
988            (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
989            (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
990        final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
991        return x * aspectRatio + y;
992    }
993
994    protected void setWallpaperDimension() {
995        Point minDims = new Point();
996        Point maxDims = new Point();
997        mLauncher.getWindowManager().getDefaultDisplay().getCurrentSizeRange(minDims, maxDims);
998
999        final int maxDim = Math.max(maxDims.x, maxDims.y);
1000        final int minDim = Math.min(minDims.x, minDims.y);
1001
1002        // We need to ensure that there is enough extra space in the wallpaper for the intended
1003        // parallax effects
1004        if (LauncherAppState.getInstance().isScreenLarge()) {
1005            mWallpaperWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));
1006            mWallpaperHeight = maxDim;
1007        } else {
1008            mWallpaperWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
1009            mWallpaperHeight = maxDim;
1010        }
1011        new Thread("setWallpaperDimension") {
1012            public void run() {
1013                mWallpaperManager.suggestDesiredDimensions(mWallpaperWidth, mWallpaperHeight);
1014            }
1015        }.start();
1016    }
1017
1018    private void syncWallpaperOffsetWithScroll() {
1019        final boolean enableWallpaperEffects = isHardwareAccelerated();
1020        if (enableWallpaperEffects) {
1021            // TODO: figure out what to do about parallax, for now disable it
1022            //mWallpaperOffset.setFinalX(wallpaperOffsetForCurrentScroll());
1023        }
1024    }
1025
1026    public void updateWallpaperOffsetImmediately() {
1027        mUpdateWallpaperOffsetImmediately = true;
1028    }
1029
1030    private void updateWallpaperOffsets() {
1031        boolean updateNow = false;
1032        boolean keepUpdating = true;
1033        if (mUpdateWallpaperOffsetImmediately) {
1034            updateNow = true;
1035            keepUpdating = false;
1036            mWallpaperOffset.jumpToFinal();
1037            mUpdateWallpaperOffsetImmediately = false;
1038        } else {
1039            updateNow = keepUpdating = mWallpaperOffset.computeScrollOffset();
1040        }
1041        if (updateNow) {
1042            if (mWindowToken != null) {
1043                mWallpaperManager.setWallpaperOffsets(mWindowToken,
1044                        mWallpaperOffset.getCurrX(), mWallpaperOffset.getCurrY());
1045            }
1046        }
1047        if (keepUpdating) {
1048            invalidate();
1049        }
1050    }
1051
1052    protected void snapToPage(int whichPage, Runnable r) {
1053        if (mDelayedSnapToPageRunnable != null) {
1054            mDelayedSnapToPageRunnable.run();
1055        }
1056        mDelayedSnapToPageRunnable = r;
1057        snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION);
1058    }
1059
1060    protected void snapToScreenId(long screenId, Runnable r) {
1061        snapToPage(getPageIndexForScreenId(screenId), r);
1062    }
1063
1064    class WallpaperOffsetInterpolator {
1065        float mFinalHorizontalWallpaperOffset = 0.0f;
1066        float mFinalVerticalWallpaperOffset = 0.5f;
1067        float mHorizontalWallpaperOffset = 0.0f;
1068        float mVerticalWallpaperOffset = 0.5f;
1069        long mLastWallpaperOffsetUpdateTime;
1070        boolean mIsMovingFast;
1071        boolean mOverrideHorizontalCatchupConstant;
1072        float mHorizontalCatchupConstant = 0.35f;
1073        float mVerticalCatchupConstant = 0.35f;
1074
1075        public WallpaperOffsetInterpolator() {
1076        }
1077
1078        public void setOverrideHorizontalCatchupConstant(boolean override) {
1079            mOverrideHorizontalCatchupConstant = override;
1080        }
1081
1082        public void setHorizontalCatchupConstant(float f) {
1083            mHorizontalCatchupConstant = f;
1084        }
1085
1086        public void setVerticalCatchupConstant(float f) {
1087            mVerticalCatchupConstant = f;
1088        }
1089
1090        public boolean computeScrollOffset() {
1091            if (Float.compare(mHorizontalWallpaperOffset, mFinalHorizontalWallpaperOffset) == 0 &&
1092                    Float.compare(mVerticalWallpaperOffset, mFinalVerticalWallpaperOffset) == 0) {
1093                mIsMovingFast = false;
1094                return false;
1095            }
1096            boolean isLandscape = mDisplaySize.x > mDisplaySize.y;
1097
1098            long currentTime = System.currentTimeMillis();
1099            long timeSinceLastUpdate = currentTime - mLastWallpaperOffsetUpdateTime;
1100            timeSinceLastUpdate = Math.min((long) (1000/30f), timeSinceLastUpdate);
1101            timeSinceLastUpdate = Math.max(1L, timeSinceLastUpdate);
1102
1103            float xdiff = Math.abs(mFinalHorizontalWallpaperOffset - mHorizontalWallpaperOffset);
1104            if (!mIsMovingFast && xdiff > 0.07) {
1105                mIsMovingFast = true;
1106            }
1107
1108            float fractionToCatchUpIn1MsHorizontal;
1109            if (mOverrideHorizontalCatchupConstant) {
1110                fractionToCatchUpIn1MsHorizontal = mHorizontalCatchupConstant;
1111            } else if (mIsMovingFast) {
1112                fractionToCatchUpIn1MsHorizontal = isLandscape ? 0.5f : 0.75f;
1113            } else {
1114                // slow
1115                fractionToCatchUpIn1MsHorizontal = isLandscape ? 0.27f : 0.5f;
1116            }
1117            float fractionToCatchUpIn1MsVertical = mVerticalCatchupConstant;
1118
1119            fractionToCatchUpIn1MsHorizontal /= 33f;
1120            fractionToCatchUpIn1MsVertical /= 33f;
1121
1122            final float UPDATE_THRESHOLD = 0.00001f;
1123            float hOffsetDelta = mFinalHorizontalWallpaperOffset - mHorizontalWallpaperOffset;
1124            float vOffsetDelta = mFinalVerticalWallpaperOffset - mVerticalWallpaperOffset;
1125            boolean jumpToFinalValue = Math.abs(hOffsetDelta) < UPDATE_THRESHOLD &&
1126                Math.abs(vOffsetDelta) < UPDATE_THRESHOLD;
1127
1128            // Don't have any lag between workspace and wallpaper on non-large devices
1129            if (!LauncherAppState.getInstance().isScreenLarge() || jumpToFinalValue) {
1130                mHorizontalWallpaperOffset = mFinalHorizontalWallpaperOffset;
1131                mVerticalWallpaperOffset = mFinalVerticalWallpaperOffset;
1132            } else {
1133                float percentToCatchUpVertical =
1134                    Math.min(1.0f, timeSinceLastUpdate * fractionToCatchUpIn1MsVertical);
1135                float percentToCatchUpHorizontal =
1136                    Math.min(1.0f, timeSinceLastUpdate * fractionToCatchUpIn1MsHorizontal);
1137                mHorizontalWallpaperOffset += percentToCatchUpHorizontal * hOffsetDelta;
1138                mVerticalWallpaperOffset += percentToCatchUpVertical * vOffsetDelta;
1139            }
1140
1141            mLastWallpaperOffsetUpdateTime = System.currentTimeMillis();
1142            return true;
1143        }
1144
1145        public float getCurrX() {
1146            return mHorizontalWallpaperOffset;
1147        }
1148
1149        public float getFinalX() {
1150            return mFinalHorizontalWallpaperOffset;
1151        }
1152
1153        public float getCurrY() {
1154            return mVerticalWallpaperOffset;
1155        }
1156
1157        public float getFinalY() {
1158            return mFinalVerticalWallpaperOffset;
1159        }
1160
1161        public void setFinalX(float x) {
1162            mFinalHorizontalWallpaperOffset = Math.max(0f, Math.min(x, 1.0f));
1163        }
1164
1165        public void setFinalY(float y) {
1166            mFinalVerticalWallpaperOffset = Math.max(0f, Math.min(y, 1.0f));
1167        }
1168
1169        public void jumpToFinal() {
1170            mHorizontalWallpaperOffset = mFinalHorizontalWallpaperOffset;
1171            mVerticalWallpaperOffset = mFinalVerticalWallpaperOffset;
1172        }
1173    }
1174
1175    @Override
1176    public void computeScroll() {
1177        super.computeScroll();
1178        syncWallpaperOffsetWithScroll();
1179    }
1180
1181    void showOutlines() {
1182        if (!isSmall() && !mIsSwitchingState) {
1183            if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
1184            if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
1185            mChildrenOutlineFadeInAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 1.0f);
1186            mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
1187            mChildrenOutlineFadeInAnimation.start();
1188        }
1189    }
1190
1191    void hideOutlines() {
1192        if (!isSmall() && !mIsSwitchingState) {
1193            if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
1194            if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
1195            mChildrenOutlineFadeOutAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 0.0f);
1196            mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
1197            mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
1198            mChildrenOutlineFadeOutAnimation.start();
1199        }
1200    }
1201
1202    public void showOutlinesTemporarily() {
1203        if (!mIsPageMoving && !isTouchActive()) {
1204            snapToPage(mCurrentPage);
1205        }
1206    }
1207
1208    public void setChildrenOutlineAlpha(float alpha) {
1209        mChildrenOutlineAlpha = alpha;
1210        for (int i = 0; i < getChildCount(); i++) {
1211            CellLayout cl = (CellLayout) getChildAt(i);
1212            cl.setBackgroundAlpha(alpha);
1213        }
1214    }
1215
1216    public float getChildrenOutlineAlpha() {
1217        return mChildrenOutlineAlpha;
1218    }
1219
1220    void disableBackground() {
1221        mDrawBackground = false;
1222    }
1223    void enableBackground() {
1224        mDrawBackground = true;
1225    }
1226
1227    private void animateBackgroundGradient(float finalAlpha, boolean animated) {
1228        if (mBackground == null) return;
1229        if (mBackgroundFadeInAnimation != null) {
1230            mBackgroundFadeInAnimation.cancel();
1231            mBackgroundFadeInAnimation = null;
1232        }
1233        if (mBackgroundFadeOutAnimation != null) {
1234            mBackgroundFadeOutAnimation.cancel();
1235            mBackgroundFadeOutAnimation = null;
1236        }
1237        float startAlpha = getBackgroundAlpha();
1238        if (finalAlpha != startAlpha) {
1239            if (animated) {
1240                mBackgroundFadeOutAnimation =
1241                        LauncherAnimUtils.ofFloat(this, startAlpha, finalAlpha);
1242                mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() {
1243                    public void onAnimationUpdate(ValueAnimator animation) {
1244                        setBackgroundAlpha(((Float) animation.getAnimatedValue()).floatValue());
1245                    }
1246                });
1247                mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
1248                mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
1249                mBackgroundFadeOutAnimation.start();
1250            } else {
1251                setBackgroundAlpha(finalAlpha);
1252            }
1253        }
1254    }
1255
1256    public void setBackgroundAlpha(float alpha) {
1257        if (alpha != mBackgroundAlpha) {
1258            mBackgroundAlpha = alpha;
1259            invalidate();
1260        }
1261    }
1262
1263    public float getBackgroundAlpha() {
1264        return mBackgroundAlpha;
1265    }
1266
1267    float backgroundAlphaInterpolator(float r) {
1268        float pivotA = 0.1f;
1269        float pivotB = 0.4f;
1270        if (r < pivotA) {
1271            return 0;
1272        } else if (r > pivotB) {
1273            return 1.0f;
1274        } else {
1275            return (r - pivotA)/(pivotB - pivotA);
1276        }
1277    }
1278
1279    private void updatePageAlphaValues(int screenCenter) {
1280        boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
1281        if (mWorkspaceFadeInAdjacentScreens &&
1282                mState == State.NORMAL &&
1283                !mIsSwitchingState &&
1284                !isInOverscroll) {
1285            for (int i = 0; i < getChildCount(); i++) {
1286                CellLayout child = (CellLayout) getChildAt(i);
1287                if (child != null) {
1288                    float scrollProgress = getScrollProgress(screenCenter, child, i);
1289                    float alpha = 1 - Math.abs(scrollProgress);
1290                    child.getShortcutsAndWidgets().setAlpha(alpha);
1291                    if (!mIsDragOccuring) {
1292                        child.setBackgroundAlphaMultiplier(
1293                                backgroundAlphaInterpolator(Math.abs(scrollProgress)));
1294                    } else {
1295                        child.setBackgroundAlphaMultiplier(1f);
1296                    }
1297                }
1298            }
1299        }
1300    }
1301
1302    private void setChildrenBackgroundAlphaMultipliers(float a) {
1303        for (int i = 0; i < getChildCount(); i++) {
1304            CellLayout child = (CellLayout) getChildAt(i);
1305            child.setBackgroundAlphaMultiplier(a);
1306        }
1307    }
1308
1309    public boolean hasCustomContent() {
1310        return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID);
1311    }
1312
1313    public boolean isOnOrMovingToCustomContent() {
1314        return hasCustomContent() && getNextPage() == 0;
1315    }
1316
1317    private void updateStateForCustomContent(int screenCenter) {
1318        if (hasCustomContent()) {
1319            int index = mScreenOrder.indexOf(CUSTOM_CONTENT_SCREEN_ID);
1320
1321            int scrollDelta = getScrollForPage(index + 1) - getScrollX();
1322
1323            float progress = (1.0f * scrollDelta) /
1324                    (getScrollForPage(index + 1) - getScrollForPage(index));
1325            progress = Math.max(0, progress);
1326
1327            setBackgroundAlpha(progress * 0.8f);
1328            float height = getViewportHeight();
1329            if (getPageIndicator() != null) {
1330                height -= getPageIndicator().getTop();
1331            } else if (mLauncher.getHotseat() != null) {
1332                height -= mLauncher.getHotseat().getTop();
1333            }
1334            float transY = progress * height;
1335
1336            if (mLauncher.getHotseat() != null) {
1337                mLauncher.getHotseat().setTranslationY(transY);
1338                mLauncher.getHotseat().setAlpha(1 - progress);
1339            }
1340            if (getPageIndicator() != null) {
1341                getPageIndicator().setAlpha(1 - progress);
1342            }
1343        }
1344    }
1345
1346    @Override
1347    protected void screenScrolled(int screenCenter) {
1348        final boolean isRtl = isLayoutRtl();
1349        super.screenScrolled(screenCenter);
1350
1351        updatePageAlphaValues(screenCenter);
1352        updateStateForCustomContent(screenCenter);
1353        enableHwLayersOnVisiblePages();
1354
1355        if ((mOverScrollX < 0 && !hasCustomContent()) || mOverScrollX > mMaxScrollX) {
1356            int index = 0;
1357            float pivotX = 0f;
1358            final float leftBiasedPivot = 0.25f;
1359            final float rightBiasedPivot = 0.75f;
1360            final int lowerIndex = 0;
1361            final int upperIndex = getChildCount() - 1;
1362            if (isRtl) {
1363                index = mOverScrollX < 0 ? upperIndex : lowerIndex;
1364                pivotX = (index == 0 ? leftBiasedPivot : rightBiasedPivot);
1365            } else {
1366                index = mOverScrollX < 0 ? lowerIndex : upperIndex;
1367                pivotX = (index == 0 ? rightBiasedPivot : leftBiasedPivot);
1368            }
1369
1370            CellLayout cl = (CellLayout) getChildAt(index);
1371            float scrollProgress = getScrollProgress(screenCenter, cl, index);
1372            final boolean isLeftPage = (isRtl ? index > 0 : index == 0);
1373            cl.setOverScrollAmount(Math.abs(scrollProgress), isLeftPage);
1374            float rotation = -WORKSPACE_OVERSCROLL_ROTATION * scrollProgress;
1375            cl.setRotationY(rotation);
1376            setFadeForOverScroll(Math.abs(scrollProgress));
1377            if (!mOverscrollTransformsSet) {
1378                mOverscrollTransformsSet = true;
1379                cl.setCameraDistance(mDensity * mCameraDistance);
1380                cl.setPivotX(cl.getMeasuredWidth() * pivotX);
1381                cl.setPivotY(cl.getMeasuredHeight() * 0.5f);
1382                cl.setOverscrollTransformsDirty(true);
1383            }
1384        } else {
1385            if (mOverscrollFade != 0) {
1386                setFadeForOverScroll(0);
1387            }
1388            if (mOverscrollTransformsSet) {
1389                mOverscrollTransformsSet = false;
1390                ((CellLayout) getChildAt(0)).resetOverscrollTransforms();
1391                ((CellLayout) getChildAt(getChildCount() - 1)).resetOverscrollTransforms();
1392            }
1393        }
1394    }
1395
1396    @Override
1397    protected void overScroll(float amount) {
1398        acceleratedOverScroll(amount);
1399    }
1400
1401    protected void onAttachedToWindow() {
1402        super.onAttachedToWindow();
1403        mWindowToken = getWindowToken();
1404        computeScroll();
1405        mDragController.setWindowToken(mWindowToken);
1406    }
1407
1408    protected void onDetachedFromWindow() {
1409        mWindowToken = null;
1410    }
1411
1412    @Override
1413    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1414        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
1415            mUpdateWallpaperOffsetImmediately = true;
1416        }
1417        super.onLayout(changed, left, top, right, bottom);
1418    }
1419
1420    @Override
1421    protected void onDraw(Canvas canvas) {
1422        updateWallpaperOffsets();
1423
1424        // Draw the background gradient if necessary
1425        if (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground) {
1426            int alpha = (int) (mBackgroundAlpha * 255);
1427            mBackground.setAlpha(alpha);
1428            mBackground.setBounds(getScrollX(), 0, getScrollX() + getMeasuredWidth(),
1429                    getMeasuredHeight());
1430            mBackground.draw(canvas);
1431        }
1432
1433        super.onDraw(canvas);
1434
1435        // Call back to LauncherModel to finish binding after the first draw
1436        post(mBindPages);
1437    }
1438
1439    boolean isDrawingBackgroundGradient() {
1440        return (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground);
1441    }
1442
1443    @Override
1444    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1445        if (!mLauncher.isAllAppsVisible()) {
1446            final Folder openFolder = getOpenFolder();
1447            if (openFolder != null) {
1448                return openFolder.requestFocus(direction, previouslyFocusedRect);
1449            } else {
1450                return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
1451            }
1452        }
1453        return false;
1454    }
1455
1456    @Override
1457    public int getDescendantFocusability() {
1458        if (isSmall()) {
1459            return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
1460        }
1461        return super.getDescendantFocusability();
1462    }
1463
1464    @Override
1465    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1466        if (!mLauncher.isAllAppsVisible()) {
1467            final Folder openFolder = getOpenFolder();
1468            if (openFolder != null) {
1469                openFolder.addFocusables(views, direction);
1470            } else {
1471                super.addFocusables(views, direction, focusableMode);
1472            }
1473        }
1474    }
1475
1476    public boolean isSmall() {
1477        return mState == State.SMALL || mState == State.SPRING_LOADED;
1478    }
1479
1480    void enableChildrenCache(int fromPage, int toPage) {
1481        if (fromPage > toPage) {
1482            final int temp = fromPage;
1483            fromPage = toPage;
1484            toPage = temp;
1485        }
1486
1487        final int screenCount = getChildCount();
1488
1489        fromPage = Math.max(fromPage, 0);
1490        toPage = Math.min(toPage, screenCount - 1);
1491
1492        for (int i = fromPage; i <= toPage; i++) {
1493            final CellLayout layout = (CellLayout) getChildAt(i);
1494            layout.setChildrenDrawnWithCacheEnabled(true);
1495            layout.setChildrenDrawingCacheEnabled(true);
1496        }
1497    }
1498
1499    void clearChildrenCache() {
1500        final int screenCount = getChildCount();
1501        for (int i = 0; i < screenCount; i++) {
1502            final CellLayout layout = (CellLayout) getChildAt(i);
1503            layout.setChildrenDrawnWithCacheEnabled(false);
1504            // In software mode, we don't want the items to continue to be drawn into bitmaps
1505            if (!isHardwareAccelerated()) {
1506                layout.setChildrenDrawingCacheEnabled(false);
1507            }
1508        }
1509    }
1510
1511
1512    private void updateChildrenLayersEnabled(boolean force) {
1513        boolean small = mState == State.SMALL || mIsSwitchingState;
1514        boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving();
1515
1516        if (enableChildrenLayers != mChildrenLayersEnabled) {
1517            mChildrenLayersEnabled = enableChildrenLayers;
1518            if (mChildrenLayersEnabled) {
1519                enableHwLayersOnVisiblePages();
1520            } else {
1521                for (int i = 0; i < getPageCount(); i++) {
1522                    final CellLayout cl = (CellLayout) getChildAt(i);
1523                    cl.disableHardwareLayers();
1524                }
1525            }
1526        }
1527    }
1528
1529    private void enableHwLayersOnVisiblePages() {
1530        if (mChildrenLayersEnabled) {
1531            final int screenCount = getChildCount();
1532            getVisiblePages(mTempVisiblePagesRange);
1533            int leftScreen = mTempVisiblePagesRange[0];
1534            int rightScreen = mTempVisiblePagesRange[1];
1535            if (leftScreen == rightScreen) {
1536                // make sure we're caching at least two pages always
1537                if (rightScreen < screenCount - 1) {
1538                    rightScreen++;
1539                } else if (leftScreen > 0) {
1540                    leftScreen--;
1541                }
1542            }
1543            for (int i = 0; i < screenCount; i++) {
1544                final CellLayout layout = (CellLayout) getPageAt(i);
1545                if (!(leftScreen <= i && i <= rightScreen && shouldDrawChild(layout))) {
1546                    layout.disableHardwareLayers();
1547                }
1548            }
1549            for (int i = 0; i < screenCount; i++) {
1550                final CellLayout layout = (CellLayout) getPageAt(i);
1551                if (leftScreen <= i && i <= rightScreen && shouldDrawChild(layout)) {
1552                    layout.enableHardwareLayers();
1553                }
1554            }
1555        }
1556    }
1557
1558    public void buildPageHardwareLayers() {
1559        // force layers to be enabled just for the call to buildLayer
1560        updateChildrenLayersEnabled(true);
1561        if (getWindowToken() != null) {
1562            final int childCount = getChildCount();
1563            for (int i = 0; i < childCount; i++) {
1564                CellLayout cl = (CellLayout) getChildAt(i);
1565                cl.buildHardwareLayer();
1566            }
1567        }
1568        updateChildrenLayersEnabled(false);
1569    }
1570
1571    protected void onWallpaperTap(MotionEvent ev) {
1572        final int[] position = mTempCell;
1573        getLocationOnScreen(position);
1574
1575        int pointerIndex = ev.getActionIndex();
1576        position[0] += (int) ev.getX(pointerIndex);
1577        position[1] += (int) ev.getY(pointerIndex);
1578
1579        mWallpaperManager.sendWallpaperCommand(getWindowToken(),
1580                ev.getAction() == MotionEvent.ACTION_UP
1581                        ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
1582                position[0], position[1], 0, null);
1583    }
1584
1585    /*
1586     * This interpolator emulates the rate at which the perceived scale of an object changes
1587     * as its distance from a camera increases. When this interpolator is applied to a scale
1588     * animation on a view, it evokes the sense that the object is shrinking due to moving away
1589     * from the camera.
1590     */
1591    static class ZInterpolator implements TimeInterpolator {
1592        private float focalLength;
1593
1594        public ZInterpolator(float foc) {
1595            focalLength = foc;
1596        }
1597
1598        public float getInterpolation(float input) {
1599            return (1.0f - focalLength / (focalLength + input)) /
1600                (1.0f - focalLength / (focalLength + 1.0f));
1601        }
1602    }
1603
1604    /*
1605     * The exact reverse of ZInterpolator.
1606     */
1607    static class InverseZInterpolator implements TimeInterpolator {
1608        private ZInterpolator zInterpolator;
1609        public InverseZInterpolator(float foc) {
1610            zInterpolator = new ZInterpolator(foc);
1611        }
1612        public float getInterpolation(float input) {
1613            return 1 - zInterpolator.getInterpolation(1 - input);
1614        }
1615    }
1616
1617    /*
1618     * ZInterpolator compounded with an ease-out.
1619     */
1620    static class ZoomOutInterpolator implements TimeInterpolator {
1621        private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75f);
1622        private final ZInterpolator zInterpolator = new ZInterpolator(0.13f);
1623
1624        public float getInterpolation(float input) {
1625            return decelerate.getInterpolation(zInterpolator.getInterpolation(input));
1626        }
1627    }
1628
1629    /*
1630     * InvereZInterpolator compounded with an ease-out.
1631     */
1632    static class ZoomInInterpolator implements TimeInterpolator {
1633        private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
1634        private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
1635
1636        public float getInterpolation(float input) {
1637            return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
1638        }
1639    }
1640
1641    private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
1642
1643    /*
1644    *
1645    * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we
1646    * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
1647    *
1648    * These methods mark the appropriate pages as accepting drops (which alters their visual
1649    * appearance).
1650    *
1651    */
1652    public void onDragStartedWithItem(View v) {
1653        final Canvas canvas = new Canvas();
1654
1655        // The outline is used to visualize where the item will land if dropped
1656        mDragOutline = createDragOutline(v, canvas, DRAG_BITMAP_PADDING);
1657    }
1658
1659    public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
1660        final Canvas canvas = new Canvas();
1661
1662        int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
1663
1664        // The outline is used to visualize where the item will land if dropped
1665        mDragOutline = createDragOutline(b, canvas, DRAG_BITMAP_PADDING, size[0],
1666                size[1], clipAlpha);
1667    }
1668
1669    public void exitWidgetResizeMode() {
1670        DragLayer dragLayer = mLauncher.getDragLayer();
1671        dragLayer.clearAllResizeFrames();
1672    }
1673
1674    private void initAnimationArrays() {
1675        final int childCount = getChildCount();
1676        if (mLastChildCount == childCount) return;
1677
1678        mOldBackgroundAlphas = new float[childCount];
1679        mOldAlphas = new float[childCount];
1680        mNewBackgroundAlphas = new float[childCount];
1681        mNewAlphas = new float[childCount];
1682    }
1683
1684    Animator getChangeStateAnimation(final State state, boolean animated) {
1685        return getChangeStateAnimation(state, animated, 0);
1686    }
1687
1688    void getReorderablePages(int[] range) {
1689        int count = mScreenOrder.size();
1690
1691        int start = -1;
1692        int end = -1;
1693        //
1694        for (int i = 0; i < count; i++) {
1695            if (start < 0 && mScreenOrder.get(i) >= 0) {
1696                start = i;
1697            }
1698            if (start >=0 && mScreenOrder.get(i) >= 0) {
1699                end = i;
1700            }
1701        }
1702        range[0] = start;
1703        range[1] = end;
1704     }
1705
1706    protected void onStartReordering() {
1707        super.onStartReordering();
1708        int count = getChildCount();
1709        for (int i = 0; i < count; i++) {
1710            ((CellLayout) getChildAt(i)).setUseActiveGlowBackground(true);
1711        }
1712        showOutlines();
1713
1714        // Reordering handles its own animations, disable the automatic ones.
1715        setLayoutTransition(null);
1716    }
1717
1718    protected void onEndReordering() {
1719        super.onEndReordering();
1720        int count = getChildCount();
1721        for (int i = 0; i < count; i++) {
1722            ((CellLayout) getChildAt(i)).setUseActiveGlowBackground(false);
1723        }
1724        hideOutlines();
1725
1726        mScreenOrder.clear();
1727        for (int i = 0; i < count; i++) {
1728            CellLayout cl = ((CellLayout) getChildAt(i));
1729            mScreenOrder.add(getIdForScreen(cl));
1730        }
1731        mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
1732
1733        // Re-enable auto layout transitions for page deletion.
1734        setLayoutTransition(mLayoutTransition);
1735    }
1736
1737    Animator getChangeStateAnimation(final State state, boolean animated, int delay) {
1738        if (mState == state) {
1739            return null;
1740        }
1741
1742        // Initialize animation arrays for the first time if necessary
1743        initAnimationArrays();
1744
1745        AnimatorSet anim = animated ? LauncherAnimUtils.createAnimatorSet() : null;
1746
1747        // Stop any scrolling, move to the current page right away
1748        setCurrentPage(getNextPage());
1749
1750        final State oldState = mState;
1751        final boolean oldStateIsNormal = (oldState == State.NORMAL);
1752        final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED);
1753        final boolean oldStateIsSmall = (oldState == State.SMALL);
1754        mState = state;
1755        final boolean stateIsNormal = (state == State.NORMAL);
1756        final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED);
1757        final boolean stateIsSmall = (state == State.SMALL);
1758        float finalBackgroundAlpha = stateIsSpringLoaded ? 1.0f : 0f;
1759        boolean zoomIn = true;
1760        mNewScale = 1.0f;
1761
1762        if (state != State.NORMAL) {
1763            mNewScale = mSpringLoadedShrinkFactor - (stateIsSmall ? 0.1f : 0);
1764            if (oldStateIsNormal && stateIsSmall) {
1765                zoomIn = false;
1766                updateChildrenLayersEnabled(false);
1767            } else {
1768                finalBackgroundAlpha = 1.0f;
1769            }
1770        }
1771        final int duration = zoomIn ?
1772                getResources().getInteger(R.integer.config_workspaceUnshrinkTime) :
1773                getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
1774        for (int i = 0; i < getChildCount(); i++) {
1775            final CellLayout cl = (CellLayout) getChildAt(i);
1776            float finalAlpha = (!mWorkspaceFadeInAdjacentScreens || stateIsSpringLoaded ||
1777                    (i == mCurrentPage)) ? 1f : 0f;
1778            float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
1779            float initialAlpha = currentAlpha;
1780
1781            // Determine the pages alpha during the state transition
1782            if ((oldStateIsSmall && stateIsNormal) ||
1783                (oldStateIsNormal && stateIsSmall)) {
1784                // To/from workspace - only show the current page unless the transition is not
1785                //                     animated and the animation end callback below doesn't run;
1786                //                     or, if we're in spring-loaded mode
1787                if (i == mCurrentPage || !animated || oldStateIsSpringLoaded) {
1788                    finalAlpha = 1f;
1789                } else {
1790                    initialAlpha = 0f;
1791                    finalAlpha = 0f;
1792                }
1793            }
1794
1795            mOldAlphas[i] = initialAlpha;
1796            mNewAlphas[i] = finalAlpha;
1797            if (animated) {
1798                mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
1799                mNewBackgroundAlphas[i] = finalBackgroundAlpha;
1800            } else {
1801                setScaleX(mNewScale);
1802                setScaleY(mNewScale);
1803                cl.setBackgroundAlpha(finalBackgroundAlpha);
1804                cl.setShortcutAndWidgetAlpha(finalAlpha);
1805            }
1806        }
1807
1808        if (animated) {
1809            LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this);
1810            scale.scaleX(mNewScale)
1811                .scaleY(mNewScale)
1812                .setInterpolator(mZoomInInterpolator);
1813            anim.play(scale);
1814            for (int index = 0; index < getChildCount(); index++) {
1815                final int i = index;
1816                final CellLayout cl = (CellLayout) getChildAt(i);
1817                float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
1818                if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) {
1819                    cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
1820                    cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
1821                } else {
1822
1823                    if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
1824                        LauncherViewPropertyAnimator alphaAnim =
1825                            new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
1826                        alphaAnim.alpha(mNewAlphas[i])
1827                            .setDuration(duration)
1828                            .setInterpolator(mZoomInInterpolator);
1829                        anim.play(alphaAnim);
1830                    }
1831                    if (mOldBackgroundAlphas[i] != 0 ||
1832                        mNewBackgroundAlphas[i] != 0) {
1833                        ValueAnimator bgAnim =
1834                                LauncherAnimUtils.ofFloat(cl, 0f, 1f).setDuration(duration);
1835                        bgAnim.setInterpolator(mZoomInInterpolator);
1836                        bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
1837                                public void onAnimationUpdate(float a, float b) {
1838                                    cl.setBackgroundAlpha(
1839                                            a * mOldBackgroundAlphas[i] +
1840                                            b * mNewBackgroundAlphas[i]);
1841                                }
1842                            });
1843                        anim.play(bgAnim);
1844                    }
1845                }
1846            }
1847            anim.setStartDelay(delay);
1848        }
1849
1850        if (stateIsSpringLoaded) {
1851            // Right now we're covered by Apps Customize
1852            // Show the background gradient immediately, so the gradient will
1853            // be showing once AppsCustomize disappears
1854            animateBackgroundGradient(getResources().getInteger(
1855                    R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, false);
1856        } else {
1857            // Fade the background gradient away
1858            animateBackgroundGradient(0f, true);
1859        }
1860        return anim;
1861    }
1862
1863    @Override
1864    public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
1865        mIsSwitchingState = true;
1866        updateChildrenLayersEnabled(false);
1867    }
1868
1869    @Override
1870    public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
1871    }
1872
1873    @Override
1874    public void onLauncherTransitionStep(Launcher l, float t) {
1875        mTransitionProgress = t;
1876    }
1877
1878    @Override
1879    public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
1880        mIsSwitchingState = false;
1881        mWallpaperOffset.setOverrideHorizontalCatchupConstant(false);
1882        updateChildrenLayersEnabled(false);
1883        // The code in getChangeStateAnimation to determine initialAlpha and finalAlpha will ensure
1884        // ensure that only the current page is visible during (and subsequently, after) the
1885        // transition animation.  If fade adjacent pages is disabled, then re-enable the page
1886        // visibility after the transition animation.
1887        if (!mWorkspaceFadeInAdjacentScreens) {
1888            for (int i = 0; i < getChildCount(); i++) {
1889                final CellLayout cl = (CellLayout) getChildAt(i);
1890                cl.setShortcutAndWidgetAlpha(1f);
1891            }
1892        }
1893    }
1894
1895    @Override
1896    public View getContent() {
1897        return this;
1898    }
1899
1900    /**
1901     * Draw the View v into the given Canvas.
1902     *
1903     * @param v the view to draw
1904     * @param destCanvas the canvas to draw on
1905     * @param padding the horizontal and vertical padding to use when drawing
1906     */
1907    private void drawDragView(View v, Canvas destCanvas, int padding, boolean pruneToDrawable) {
1908        final Rect clipRect = mTempRect;
1909        v.getDrawingRect(clipRect);
1910
1911        boolean textVisible = false;
1912
1913        destCanvas.save();
1914        if (v instanceof TextView && pruneToDrawable) {
1915            Drawable d = ((TextView) v).getCompoundDrawables()[1];
1916            clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding);
1917            destCanvas.translate(padding / 2, padding / 2);
1918            d.draw(destCanvas);
1919        } else {
1920            if (v instanceof FolderIcon) {
1921                // For FolderIcons the text can bleed into the icon area, and so we need to
1922                // hide the text completely (which can't be achieved by clipping).
1923                if (((FolderIcon) v).getTextVisible()) {
1924                    ((FolderIcon) v).setTextVisible(false);
1925                    textVisible = true;
1926                }
1927            } else if (v instanceof BubbleTextView) {
1928                final BubbleTextView tv = (BubbleTextView) v;
1929                clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V +
1930                        tv.getLayout().getLineTop(0);
1931            } else if (v instanceof TextView) {
1932                final TextView tv = (TextView) v;
1933                clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() +
1934                        tv.getLayout().getLineTop(0);
1935            }
1936            destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
1937            destCanvas.clipRect(clipRect, Op.REPLACE);
1938            v.draw(destCanvas);
1939
1940            // Restore text visibility of FolderIcon if necessary
1941            if (textVisible) {
1942                ((FolderIcon) v).setTextVisible(true);
1943            }
1944        }
1945        destCanvas.restore();
1946    }
1947
1948    /**
1949     * Returns a new bitmap to show when the given View is being dragged around.
1950     * Responsibility for the bitmap is transferred to the caller.
1951     */
1952    public Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
1953        Bitmap b;
1954
1955        if (v instanceof TextView) {
1956            Drawable d = ((TextView) v).getCompoundDrawables()[1];
1957            b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding,
1958                    d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888);
1959        } else {
1960            b = Bitmap.createBitmap(
1961                    v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
1962        }
1963
1964        canvas.setBitmap(b);
1965        drawDragView(v, canvas, padding, true);
1966        canvas.setBitmap(null);
1967
1968        return b;
1969    }
1970
1971    /**
1972     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
1973     * Responsibility for the bitmap is transferred to the caller.
1974     */
1975    private Bitmap createDragOutline(View v, Canvas canvas, int padding) {
1976        final int outlineColor = getResources().getColor(android.R.color.holo_blue_light);
1977        final Bitmap b = Bitmap.createBitmap(
1978                v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
1979
1980        canvas.setBitmap(b);
1981        drawDragView(v, canvas, padding, true);
1982        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
1983        canvas.setBitmap(null);
1984        return b;
1985    }
1986
1987    /**
1988     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
1989     * Responsibility for the bitmap is transferred to the caller.
1990     */
1991    private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding, int w, int h,
1992            boolean clipAlpha) {
1993        final int outlineColor = getResources().getColor(android.R.color.holo_blue_light);
1994        final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
1995        canvas.setBitmap(b);
1996
1997        Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
1998        float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(),
1999                (h - padding) / (float) orig.getHeight());
2000        int scaledWidth = (int) (scaleFactor * orig.getWidth());
2001        int scaledHeight = (int) (scaleFactor * orig.getHeight());
2002        Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
2003
2004        // center the image
2005        dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
2006
2007        canvas.drawBitmap(orig, src, dst, null);
2008        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor,
2009                clipAlpha);
2010        canvas.setBitmap(null);
2011
2012        return b;
2013    }
2014
2015    void startDrag(CellLayout.CellInfo cellInfo) {
2016        View child = cellInfo.cell;
2017
2018        // Make sure the drag was started by a long press as opposed to a long click.
2019        if (!child.isInTouchMode()) {
2020            return;
2021        }
2022
2023        mDragInfo = cellInfo;
2024        child.setVisibility(INVISIBLE);
2025        CellLayout layout = (CellLayout) child.getParent().getParent();
2026        layout.prepareChildForDrag(child);
2027
2028        child.clearFocus();
2029        child.setPressed(false);
2030
2031        final Canvas canvas = new Canvas();
2032
2033        // The outline is used to visualize where the item will land if dropped
2034        mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING);
2035        beginDragShared(child, this);
2036    }
2037
2038    public void beginDragShared(View child, DragSource source) {
2039        Resources r = getResources();
2040
2041        // The drag bitmap follows the touch point around on the screen
2042        final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING);
2043
2044        final int bmpWidth = b.getWidth();
2045        final int bmpHeight = b.getHeight();
2046
2047        float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
2048        int dragLayerX =
2049                Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
2050        int dragLayerY =
2051                Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2
2052                        - DRAG_BITMAP_PADDING / 2);
2053
2054        Point dragVisualizeOffset = null;
2055        Rect dragRect = null;
2056        if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
2057            int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size);
2058            int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top);
2059            int top = child.getPaddingTop();
2060            int left = (bmpWidth - iconSize) / 2;
2061            int right = left + iconSize;
2062            int bottom = top + iconSize;
2063            dragLayerY += top;
2064            // Note: The drag region is used to calculate drag layer offsets, but the
2065            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2066            dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2,
2067                    iconPaddingTop - DRAG_BITMAP_PADDING / 2);
2068            dragRect = new Rect(left, top, right, bottom);
2069        } else if (child instanceof FolderIcon) {
2070            int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size);
2071            dragRect = new Rect(0, 0, child.getWidth(), previewSize);
2072        }
2073
2074        // Clear the pressed state if necessary
2075        if (child instanceof BubbleTextView) {
2076            BubbleTextView icon = (BubbleTextView) child;
2077            icon.clearPressedOrFocusedBackground();
2078        }
2079
2080        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
2081                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
2082        b.recycle();
2083    }
2084
2085    void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, long screenId,
2086            int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
2087        View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
2088
2089        final int[] cellXY = new int[2];
2090        target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
2091        addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
2092
2093        LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, cellXY[0],
2094                cellXY[1]);
2095    }
2096
2097    public boolean transitionStateShouldAllowDrop() {
2098        return ((!isSwitchingState() || mTransitionProgress > 0.5f) && mState != State.SMALL);
2099    }
2100
2101    /**
2102     * {@inheritDoc}
2103     */
2104    public boolean acceptDrop(DragObject d) {
2105        // If it's an external drop (e.g. from All Apps), check if it should be accepted
2106        CellLayout dropTargetLayout = mDropToLayout;
2107        if (d.dragSource != this) {
2108            // Don't accept the drop if we're not over a screen at time of drop
2109            if (dropTargetLayout == null) {
2110                return false;
2111            }
2112            if (!transitionStateShouldAllowDrop()) return false;
2113
2114            mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
2115                    d.dragView, mDragViewVisualCenter);
2116
2117            // We want the point to be mapped to the dragTarget.
2118            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2119                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2120            } else {
2121                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2122            }
2123
2124            int spanX = 1;
2125            int spanY = 1;
2126            if (mDragInfo != null) {
2127                final CellLayout.CellInfo dragCellInfo = mDragInfo;
2128                spanX = dragCellInfo.spanX;
2129                spanY = dragCellInfo.spanY;
2130            } else {
2131                final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
2132                spanX = dragInfo.spanX;
2133                spanY = dragInfo.spanY;
2134            }
2135
2136            int minSpanX = spanX;
2137            int minSpanY = spanY;
2138            if (d.dragInfo instanceof PendingAddWidgetInfo) {
2139                minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
2140                minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
2141            }
2142
2143            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2144                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
2145                    mTargetCell);
2146            float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2147                    mDragViewVisualCenter[1], mTargetCell);
2148            if (willCreateUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
2149                    mTargetCell, distance, true)) {
2150                return true;
2151            }
2152            if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
2153                    mTargetCell, distance)) {
2154                return true;
2155            }
2156
2157            int[] resultSpan = new int[2];
2158            mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
2159                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2160                    null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
2161            boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2162
2163            // Don't accept the drop if there's no room for the item
2164            if (!foundCell) {
2165                // Don't show the message if we are dropping on the AllApps button and the hotseat
2166                // is full
2167                boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2168                if (mTargetCell != null && isHotseat) {
2169                    Hotseat hotseat = mLauncher.getHotseat();
2170                    if (hotseat.isAllAppsButtonRank(
2171                            hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
2172                        return false;
2173                    }
2174                }
2175
2176                mLauncher.showOutOfSpaceMessage(isHotseat);
2177                return false;
2178            }
2179        }
2180
2181        long screenId = getIdForScreen(dropTargetLayout);
2182        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
2183            commitExtraEmptyScreen();
2184        }
2185
2186        return true;
2187    }
2188
2189    boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float
2190            distance, boolean considerTimeout) {
2191        if (distance > mMaxDistanceForFolderCreation) return false;
2192        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2193
2194        if (dropOverView != null) {
2195            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2196            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2197                return false;
2198            }
2199        }
2200
2201        boolean hasntMoved = false;
2202        if (mDragInfo != null) {
2203            hasntMoved = dropOverView == mDragInfo.cell;
2204        }
2205
2206        if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
2207            return false;
2208        }
2209
2210        boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
2211        boolean willBecomeShortcut =
2212                (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
2213                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
2214
2215        return (aboveShortcut && willBecomeShortcut);
2216    }
2217
2218    boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
2219            float distance) {
2220        if (distance > mMaxDistanceForFolderCreation) return false;
2221        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2222
2223        if (dropOverView != null) {
2224            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2225            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2226                return false;
2227            }
2228        }
2229
2230        if (dropOverView instanceof FolderIcon) {
2231            FolderIcon fi = (FolderIcon) dropOverView;
2232            if (fi.acceptDrop(dragInfo)) {
2233                return true;
2234            }
2235        }
2236        return false;
2237    }
2238
2239    boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
2240            int[] targetCell, float distance, boolean external, DragView dragView,
2241            Runnable postAnimationRunnable) {
2242        if (distance > mMaxDistanceForFolderCreation) return false;
2243        View v = target.getChildAt(targetCell[0], targetCell[1]);
2244
2245        boolean hasntMoved = false;
2246        if (mDragInfo != null) {
2247            CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
2248            hasntMoved = (mDragInfo.cellX == targetCell[0] &&
2249                    mDragInfo.cellY == targetCell[1]) && (cellParent == target);
2250        }
2251
2252        if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
2253        mCreateUserFolderOnDrop = false;
2254        final long screenId = (targetCell == null) ? mDragInfo.screenId : getIdForScreen(target);
2255
2256        boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
2257        boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
2258
2259        if (aboveShortcut && willBecomeShortcut) {
2260            ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
2261            ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
2262            // if the drag started here, we need to remove it from the workspace
2263            if (!external) {
2264                getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2265            }
2266
2267            Rect folderLocation = new Rect();
2268            float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
2269            target.removeView(v);
2270
2271            FolderIcon fi =
2272                mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
2273            destInfo.cellX = -1;
2274            destInfo.cellY = -1;
2275            sourceInfo.cellX = -1;
2276            sourceInfo.cellY = -1;
2277
2278            // If the dragView is null, we can't animate
2279            boolean animate = dragView != null;
2280            if (animate) {
2281                fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
2282                        postAnimationRunnable);
2283            } else {
2284                fi.addItem(destInfo);
2285                fi.addItem(sourceInfo);
2286            }
2287            return true;
2288        }
2289        return false;
2290    }
2291
2292    boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
2293            float distance, DragObject d, boolean external) {
2294        if (distance > mMaxDistanceForFolderCreation) return false;
2295
2296        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2297        if (!mAddToExistingFolderOnDrop) return false;
2298        mAddToExistingFolderOnDrop = false;
2299
2300        if (dropOverView instanceof FolderIcon) {
2301            FolderIcon fi = (FolderIcon) dropOverView;
2302            if (fi.acceptDrop(d.dragInfo)) {
2303                fi.onDrop(d);
2304
2305                // if the drag started here, we need to remove it from the workspace
2306                if (!external) {
2307                    getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2308                }
2309                return true;
2310            }
2311        }
2312        return false;
2313    }
2314
2315    public void onDrop(final DragObject d) {
2316        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
2317                mDragViewVisualCenter);
2318
2319        CellLayout dropTargetLayout = mDropToLayout;
2320
2321        // We want the point to be mapped to the dragTarget.
2322        if (dropTargetLayout != null) {
2323            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2324                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2325            } else {
2326                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2327            }
2328        }
2329
2330        int snapScreen = -1;
2331        boolean resizeOnDrop = false;
2332        if (d.dragSource != this) {
2333            final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
2334                    (int) mDragViewVisualCenter[1] };
2335            onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
2336        } else if (mDragInfo != null) {
2337            final View cell = mDragInfo.cell;
2338
2339            Runnable resizeRunnable = null;
2340            if (dropTargetLayout != null && !d.cancelled) {
2341                // Move internally
2342                boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
2343                boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2344                long container = hasMovedIntoHotseat ?
2345                        LauncherSettings.Favorites.CONTAINER_HOTSEAT :
2346                        LauncherSettings.Favorites.CONTAINER_DESKTOP;
2347                long screenId = (mTargetCell[0] < 0) ?
2348                        mDragInfo.screenId : getIdForScreen(dropTargetLayout);
2349                int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
2350                int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
2351                // First we find the cell nearest to point at which the item is
2352                // dropped, without any consideration to whether there is an item there.
2353
2354                mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
2355                        mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
2356                float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2357                        mDragViewVisualCenter[1], mTargetCell);
2358
2359                // If the item being dropped is a shortcut and the nearest drop
2360                // cell also contains a shortcut, then create a folder with the two shortcuts.
2361                if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
2362                        dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
2363                    stripEmptyScreens();
2364                    return;
2365                }
2366
2367                if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
2368                        distance, d, false)) {
2369                    stripEmptyScreens();
2370                    return;
2371                }
2372
2373                // Aside from the special case where we're dropping a shortcut onto a shortcut,
2374                // we need to find the nearest cell location that is vacant
2375                ItemInfo item = (ItemInfo) d.dragInfo;
2376                int minSpanX = item.spanX;
2377                int minSpanY = item.spanY;
2378                if (item.minSpanX > 0 && item.minSpanY > 0) {
2379                    minSpanX = item.minSpanX;
2380                    minSpanY = item.minSpanY;
2381                }
2382
2383                int[] resultSpan = new int[2];
2384                mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
2385                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
2386                        mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
2387
2388                boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2389
2390                // if the widget resizes on drop
2391                if (foundCell && (cell instanceof AppWidgetHostView) &&
2392                        (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
2393                    resizeOnDrop = true;
2394                    item.spanX = resultSpan[0];
2395                    item.spanY = resultSpan[1];
2396                    AppWidgetHostView awhv = (AppWidgetHostView) cell;
2397                    AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
2398                            resultSpan[1]);
2399                }
2400
2401                if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
2402                    snapScreen = getPageIndexForScreenId(screenId);
2403                    snapToPage(snapScreen);
2404                }
2405
2406                if (foundCell) {
2407                    final ItemInfo info = (ItemInfo) cell.getTag();
2408                    if (hasMovedLayouts) {
2409                        // Reparent the view
2410                        getParentCellLayoutForView(cell).removeView(cell);
2411                        addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
2412                                info.spanX, info.spanY);
2413                    }
2414
2415                    // update the item's position after drop
2416                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2417                    lp.cellX = lp.tmpCellX = mTargetCell[0];
2418                    lp.cellY = lp.tmpCellY = mTargetCell[1];
2419                    lp.cellHSpan = item.spanX;
2420                    lp.cellVSpan = item.spanY;
2421                    lp.isLockedToGrid = true;
2422                    cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screenId,
2423                            mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
2424
2425                    if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
2426                            cell instanceof LauncherAppWidgetHostView) {
2427                        final CellLayout cellLayout = dropTargetLayout;
2428                        // We post this call so that the widget has a chance to be placed
2429                        // in its final location
2430
2431                        final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
2432                        AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
2433                        if (pinfo != null &&
2434                                pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
2435                            final Runnable addResizeFrame = new Runnable() {
2436                                public void run() {
2437                                    DragLayer dragLayer = mLauncher.getDragLayer();
2438                                    dragLayer.addResizeFrame(info, hostView, cellLayout);
2439                                }
2440                            };
2441                            resizeRunnable = (new Runnable() {
2442                                public void run() {
2443                                    if (!isPageMoving()) {
2444                                        addResizeFrame.run();
2445                                    } else {
2446                                        mDelayedResizeRunnable = addResizeFrame;
2447                                    }
2448                                }
2449                            });
2450                        }
2451                    }
2452
2453                    LauncherModel.moveItemInDatabase(mLauncher, info, container, screenId, lp.cellX,
2454                            lp.cellY);
2455                } else {
2456                    // If we can't find a drop location, we return the item to its original position
2457                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2458                    mTargetCell[0] = lp.cellX;
2459                    mTargetCell[1] = lp.cellY;
2460                    CellLayout layout = (CellLayout) cell.getParent().getParent();
2461                    layout.markCellsAsOccupiedForView(cell);
2462                }
2463            }
2464
2465            final CellLayout parent = (CellLayout) cell.getParent().getParent();
2466            final Runnable finalResizeRunnable = resizeRunnable;
2467            // Prepare it to be animated into its new position
2468            // This must be called after the view has been re-parented
2469            final Runnable onCompleteRunnable = new Runnable() {
2470                @Override
2471                public void run() {
2472                    mAnimatingViewIntoPlace = false;
2473                    updateChildrenLayersEnabled(false);
2474                    if (finalResizeRunnable != null) {
2475                        finalResizeRunnable.run();
2476                    }
2477                    stripEmptyScreens();
2478                }
2479            };
2480            mAnimatingViewIntoPlace = true;
2481            if (d.dragView.hasDrawn()) {
2482                final ItemInfo info = (ItemInfo) cell.getTag();
2483                if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
2484                    int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
2485                            ANIMATE_INTO_POSITION_AND_DISAPPEAR;
2486                    animateWidgetDrop(info, parent, d.dragView,
2487                            onCompleteRunnable, animationType, cell, false);
2488                } else {
2489                    int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
2490                    mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
2491                            onCompleteRunnable, this);
2492                }
2493            } else {
2494                d.deferDragViewCleanupPostAnimation = false;
2495                cell.setVisibility(VISIBLE);
2496            }
2497            parent.onDropChild(cell);
2498        }
2499    }
2500
2501    public void setFinalScrollForPageChange(int pageIndex) {
2502        CellLayout cl = (CellLayout) getChildAt(pageIndex);
2503        if (cl != null) {
2504            mSavedScrollX = getScrollX();
2505            mSavedTranslationX = cl.getTranslationX();
2506            mSavedRotationY = cl.getRotationY();
2507            final int newX = getScrollForPage(pageIndex);
2508            setScrollX(newX);
2509            cl.setTranslationX(0f);
2510            cl.setRotationY(0f);
2511        }
2512    }
2513
2514    public void resetFinalScrollForPageChange(int pageIndex) {
2515        if (pageIndex >= 0) {
2516            CellLayout cl = (CellLayout) getChildAt(pageIndex);
2517            setScrollX(mSavedScrollX);
2518            cl.setTranslationX(mSavedTranslationX);
2519            cl.setRotationY(mSavedRotationY);
2520        }
2521    }
2522
2523    public void getViewLocationRelativeToSelf(View v, int[] location) {
2524        getLocationInWindow(location);
2525        int x = location[0];
2526        int y = location[1];
2527
2528        v.getLocationInWindow(location);
2529        int vX = location[0];
2530        int vY = location[1];
2531
2532        location[0] = vX - x;
2533        location[1] = vY - y;
2534    }
2535
2536    public void onDragEnter(DragObject d) {
2537        mDragEnforcer.onDragEnter();
2538        mCreateUserFolderOnDrop = false;
2539        mAddToExistingFolderOnDrop = false;
2540
2541        mDropToLayout = null;
2542        CellLayout layout = getCurrentDropLayout();
2543        setCurrentDropLayout(layout);
2544        setCurrentDragOverlappingLayout(layout);
2545
2546        // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we
2547        // don't need to show the outlines
2548        if (LauncherAppState.getInstance().isScreenLarge()) {
2549            showOutlines();
2550        }
2551    }
2552
2553    static Rect getCellLayoutMetrics(Launcher launcher, int orientation) {
2554        Resources res = launcher.getResources();
2555        Display display = launcher.getWindowManager().getDefaultDisplay();
2556        Point smallestSize = new Point();
2557        Point largestSize = new Point();
2558        display.getCurrentSizeRange(smallestSize, largestSize);
2559        if (orientation == CellLayout.LANDSCAPE) {
2560            if (mLandscapeCellLayoutMetrics == null) {
2561                int paddingLeft = res.getDimensionPixelSize(R.dimen.workspace_left_padding_land);
2562                int paddingRight = res.getDimensionPixelSize(R.dimen.workspace_right_padding_land);
2563                int paddingTop = res.getDimensionPixelSize(R.dimen.workspace_top_padding_land);
2564                int paddingBottom = res.getDimensionPixelSize(R.dimen.workspace_bottom_padding_land);
2565                int width = largestSize.x - paddingLeft - paddingRight;
2566                int height = smallestSize.y - paddingTop - paddingBottom;
2567                mLandscapeCellLayoutMetrics = new Rect();
2568                CellLayout.getMetrics(mLandscapeCellLayoutMetrics, res,
2569                        width, height, LauncherModel.getCellCountX(), LauncherModel.getCellCountY(),
2570                        orientation);
2571            }
2572            return mLandscapeCellLayoutMetrics;
2573        } else if (orientation == CellLayout.PORTRAIT) {
2574            if (mPortraitCellLayoutMetrics == null) {
2575                int paddingLeft = res.getDimensionPixelSize(R.dimen.workspace_left_padding_land);
2576                int paddingRight = res.getDimensionPixelSize(R.dimen.workspace_right_padding_land);
2577                int paddingTop = res.getDimensionPixelSize(R.dimen.workspace_top_padding_land);
2578                int paddingBottom = res.getDimensionPixelSize(R.dimen.workspace_bottom_padding_land);
2579                int width = smallestSize.x - paddingLeft - paddingRight;
2580                int height = largestSize.y - paddingTop - paddingBottom;
2581                mPortraitCellLayoutMetrics = new Rect();
2582                CellLayout.getMetrics(mPortraitCellLayoutMetrics, res,
2583                        width, height, LauncherModel.getCellCountX(), LauncherModel.getCellCountY(),
2584                        orientation);
2585            }
2586            return mPortraitCellLayoutMetrics;
2587        }
2588        return null;
2589    }
2590
2591    public void onDragExit(DragObject d) {
2592        mDragEnforcer.onDragExit();
2593
2594        // Here we store the final page that will be dropped to, if the workspace in fact
2595        // receives the drop
2596        if (mInScrollArea) {
2597            if (isPageMoving()) {
2598                // If the user drops while the page is scrolling, we should use that page as the
2599                // destination instead of the page that is being hovered over.
2600                mDropToLayout = (CellLayout) getPageAt(getNextPage());
2601            } else {
2602                mDropToLayout = mDragOverlappingLayout;
2603            }
2604        } else {
2605            mDropToLayout = mDragTargetLayout;
2606        }
2607
2608        if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
2609            mCreateUserFolderOnDrop = true;
2610        } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
2611            mAddToExistingFolderOnDrop = true;
2612        }
2613
2614        // Reset the scroll area and previous drag target
2615        onResetScrollArea();
2616        setCurrentDropLayout(null);
2617        setCurrentDragOverlappingLayout(null);
2618
2619        mSpringLoadedDragController.cancel();
2620
2621        if (!mIsPageMoving) {
2622            hideOutlines();
2623        }
2624    }
2625
2626    void setCurrentDropLayout(CellLayout layout) {
2627        if (mDragTargetLayout != null) {
2628            mDragTargetLayout.revertTempState();
2629            mDragTargetLayout.onDragExit();
2630        }
2631        mDragTargetLayout = layout;
2632        if (mDragTargetLayout != null) {
2633            mDragTargetLayout.onDragEnter();
2634        }
2635        cleanupReorder(true);
2636        cleanupFolderCreation();
2637        setCurrentDropOverCell(-1, -1);
2638    }
2639
2640    void setCurrentDragOverlappingLayout(CellLayout layout) {
2641        if (mDragOverlappingLayout != null) {
2642            mDragOverlappingLayout.setIsDragOverlapping(false);
2643        }
2644        mDragOverlappingLayout = layout;
2645        if (mDragOverlappingLayout != null) {
2646            mDragOverlappingLayout.setIsDragOverlapping(true);
2647        }
2648        invalidate();
2649    }
2650
2651    void setCurrentDropOverCell(int x, int y) {
2652        if (x != mDragOverX || y != mDragOverY) {
2653            mDragOverX = x;
2654            mDragOverY = y;
2655            setDragMode(DRAG_MODE_NONE);
2656        }
2657    }
2658
2659    void setDragMode(int dragMode) {
2660        if (dragMode != mDragMode) {
2661            if (dragMode == DRAG_MODE_NONE) {
2662                cleanupAddToFolder();
2663                // We don't want to cancel the re-order alarm every time the target cell changes
2664                // as this feels to slow / unresponsive.
2665                cleanupReorder(false);
2666                cleanupFolderCreation();
2667            } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
2668                cleanupReorder(true);
2669                cleanupFolderCreation();
2670            } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
2671                cleanupAddToFolder();
2672                cleanupReorder(true);
2673            } else if (dragMode == DRAG_MODE_REORDER) {
2674                cleanupAddToFolder();
2675                cleanupFolderCreation();
2676            }
2677            mDragMode = dragMode;
2678        }
2679    }
2680
2681    private void cleanupFolderCreation() {
2682        if (mDragFolderRingAnimator != null) {
2683            mDragFolderRingAnimator.animateToNaturalState();
2684        }
2685        mFolderCreationAlarm.cancelAlarm();
2686    }
2687
2688    private void cleanupAddToFolder() {
2689        if (mDragOverFolderIcon != null) {
2690            mDragOverFolderIcon.onDragExit(null);
2691            mDragOverFolderIcon = null;
2692        }
2693    }
2694
2695    private void cleanupReorder(boolean cancelAlarm) {
2696        // Any pending reorders are canceled
2697        if (cancelAlarm) {
2698            mReorderAlarm.cancelAlarm();
2699        }
2700        mLastReorderX = -1;
2701        mLastReorderY = -1;
2702    }
2703
2704   /*
2705    *
2706    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
2707    * coordinate space. The argument xy is modified with the return result.
2708    *
2709    * if cachedInverseMatrix is not null, this method will just use that matrix instead of
2710    * computing it itself; we use this to avoid redundant matrix inversions in
2711    * findMatchingPageForDragOver
2712    *
2713    */
2714   void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
2715       xy[0] = xy[0] - v.getLeft();
2716       xy[1] = xy[1] - v.getTop();
2717   }
2718
2719   boolean isPointInSelfOverHotseat(int x, int y, Rect r) {
2720       if (r == null) {
2721           r = new Rect();
2722       }
2723       mTempPt[0] = x;
2724       mTempPt[1] = y;
2725       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
2726       mLauncher.getHotseat().getHitRect(r);
2727       if (r.contains(mTempPt[0], mTempPt[1])) {
2728           return true;
2729       }
2730       return false;
2731   }
2732
2733   void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
2734       mTempPt[0] = (int) xy[0];
2735       mTempPt[1] = (int) xy[1];
2736       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
2737       mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempPt);
2738
2739       xy[0] = mTempPt[0];
2740       xy[1] = mTempPt[1];
2741   }
2742
2743   /*
2744    *
2745    * Convert the 2D coordinate xy from this CellLayout's coordinate space to
2746    * the parent View's coordinate space. The argument xy is modified with the return result.
2747    *
2748    */
2749   void mapPointFromChildToSelf(View v, float[] xy) {
2750       xy[0] += v.getLeft();
2751       xy[1] += v.getTop();
2752   }
2753
2754   static private float squaredDistance(float[] point1, float[] point2) {
2755        float distanceX = point1[0] - point2[0];
2756        float distanceY = point2[1] - point2[1];
2757        return distanceX * distanceX + distanceY * distanceY;
2758   }
2759
2760    /*
2761     *
2762     * This method returns the CellLayout that is currently being dragged to. In order to drag
2763     * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
2764     * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
2765     *
2766     * Return null if no CellLayout is currently being dragged over
2767     *
2768     */
2769    private CellLayout findMatchingPageForDragOver(
2770            DragView dragView, float originX, float originY, boolean exact) {
2771        // We loop through all the screens (ie CellLayouts) and see which ones overlap
2772        // with the item being dragged and then choose the one that's closest to the touch point
2773        final int screenCount = getChildCount();
2774        CellLayout bestMatchingScreen = null;
2775        float smallestDistSoFar = Float.MAX_VALUE;
2776
2777        for (int i = 0; i < screenCount; i++) {
2778            CellLayout cl = (CellLayout) getChildAt(i);
2779
2780            final float[] touchXy = {originX, originY};
2781            // Transform the touch coordinates to the CellLayout's local coordinates
2782            // If the touch point is within the bounds of the cell layout, we can return immediately
2783            cl.getMatrix().invert(mTempInverseMatrix);
2784            mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
2785
2786            if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
2787                    touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
2788                return cl;
2789            }
2790
2791            if (!exact) {
2792                // Get the center of the cell layout in screen coordinates
2793                final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
2794                cellLayoutCenter[0] = cl.getWidth()/2;
2795                cellLayoutCenter[1] = cl.getHeight()/2;
2796                mapPointFromChildToSelf(cl, cellLayoutCenter);
2797
2798                touchXy[0] = originX;
2799                touchXy[1] = originY;
2800
2801                // Calculate the distance between the center of the CellLayout
2802                // and the touch point
2803                float dist = squaredDistance(touchXy, cellLayoutCenter);
2804
2805                if (dist < smallestDistSoFar) {
2806                    smallestDistSoFar = dist;
2807                    bestMatchingScreen = cl;
2808                }
2809            }
2810        }
2811        return bestMatchingScreen;
2812    }
2813
2814    // This is used to compute the visual center of the dragView. This point is then
2815    // used to visualize drop locations and determine where to drop an item. The idea is that
2816    // the visual center represents the user's interpretation of where the item is, and hence
2817    // is the appropriate point to use when determining drop location.
2818    private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
2819            DragView dragView, float[] recycle) {
2820        float res[];
2821        if (recycle == null) {
2822            res = new float[2];
2823        } else {
2824            res = recycle;
2825        }
2826
2827        // First off, the drag view has been shifted in a way that is not represented in the
2828        // x and y values or the x/yOffsets. Here we account for that shift.
2829        x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX);
2830        y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
2831
2832        // These represent the visual top and left of drag view if a dragRect was provided.
2833        // If a dragRect was not provided, then they correspond to the actual view left and
2834        // top, as the dragRect is in that case taken to be the entire dragView.
2835        // R.dimen.dragViewOffsetY.
2836        int left = x - xOffset;
2837        int top = y - yOffset;
2838
2839        // In order to find the visual center, we shift by half the dragRect
2840        res[0] = left + dragView.getDragRegion().width() / 2;
2841        res[1] = top + dragView.getDragRegion().height() / 2;
2842
2843        return res;
2844    }
2845
2846    private boolean isDragWidget(DragObject d) {
2847        return (d.dragInfo instanceof LauncherAppWidgetInfo ||
2848                d.dragInfo instanceof PendingAddWidgetInfo);
2849    }
2850    private boolean isExternalDragWidget(DragObject d) {
2851        return d.dragSource != this && isDragWidget(d);
2852    }
2853
2854    public void onDragOver(DragObject d) {
2855        // Skip drag over events while we are dragging over side pages
2856        if (mInScrollArea || mIsSwitchingState || mState == State.SMALL) return;
2857
2858        Rect r = new Rect();
2859        CellLayout layout = null;
2860        ItemInfo item = (ItemInfo) d.dragInfo;
2861
2862        // Ensure that we have proper spans for the item that we are dropping
2863        if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
2864        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
2865            d.dragView, mDragViewVisualCenter);
2866
2867        final View child = (mDragInfo == null) ? null : mDragInfo.cell;
2868        // Identify whether we have dragged over a side page
2869        if (isSmall()) {
2870            if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
2871                if (isPointInSelfOverHotseat(d.x, d.y, r)) {
2872                    layout = mLauncher.getHotseat().getLayout();
2873                }
2874            }
2875            if (layout == null) {
2876                layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false);
2877            }
2878            if (layout != mDragTargetLayout) {
2879                setCurrentDropLayout(layout);
2880                setCurrentDragOverlappingLayout(layout);
2881
2882                boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
2883                if (isInSpringLoadedMode) {
2884                    if (mLauncher.isHotseatLayout(layout)) {
2885                        mSpringLoadedDragController.cancel();
2886                    } else {
2887                        mSpringLoadedDragController.setAlarm(mDragTargetLayout);
2888                    }
2889                }
2890            }
2891        } else {
2892            // Test to see if we are over the hotseat otherwise just use the current page
2893            if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
2894                if (isPointInSelfOverHotseat(d.x, d.y, r)) {
2895                    layout = mLauncher.getHotseat().getLayout();
2896                }
2897            }
2898            if (layout == null) {
2899                layout = getCurrentDropLayout();
2900            }
2901            if (layout != mDragTargetLayout) {
2902                setCurrentDropLayout(layout);
2903                setCurrentDragOverlappingLayout(layout);
2904            }
2905        }
2906
2907        // Handle the drag over
2908        if (mDragTargetLayout != null) {
2909            // We want the point to be mapped to the dragTarget.
2910            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
2911                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2912            } else {
2913                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
2914            }
2915
2916            ItemInfo info = (ItemInfo) d.dragInfo;
2917
2918            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2919                    (int) mDragViewVisualCenter[1], item.spanX, item.spanY,
2920                    mDragTargetLayout, mTargetCell);
2921
2922            setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
2923
2924            float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
2925                    mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
2926
2927            final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0],
2928                    mTargetCell[1]);
2929
2930            manageFolderFeedback(info, mDragTargetLayout, mTargetCell,
2931                    targetCellDistance, dragOverView);
2932
2933            int minSpanX = item.spanX;
2934            int minSpanY = item.spanY;
2935            if (item.minSpanX > 0 && item.minSpanY > 0) {
2936                minSpanX = item.minSpanX;
2937                minSpanY = item.minSpanY;
2938            }
2939
2940            boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
2941                    mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
2942                    item.spanY, child, mTargetCell);
2943
2944            if (!nearestDropOccupied) {
2945                mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
2946                        (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
2947                        mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
2948                        d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
2949            } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
2950                    && !mReorderAlarm.alarmPending() && (mLastReorderX != mTargetCell[0] ||
2951                    mLastReorderY != mTargetCell[1])) {
2952
2953                // Otherwise, if we aren't adding to or creating a folder and there's no pending
2954                // reorder, then we schedule a reorder
2955                ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
2956                        minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child);
2957                mReorderAlarm.setOnAlarmListener(listener);
2958                mReorderAlarm.setAlarm(REORDER_TIMEOUT);
2959            }
2960
2961            if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
2962                    !nearestDropOccupied) {
2963                if (mDragTargetLayout != null) {
2964                    mDragTargetLayout.revertTempState();
2965                }
2966            }
2967        }
2968    }
2969
2970    private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
2971            int[] targetCell, float distance, View dragOverView) {
2972        boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
2973                false);
2974
2975        if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
2976                !mFolderCreationAlarm.alarmPending()) {
2977            mFolderCreationAlarm.setOnAlarmListener(new
2978                    FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
2979            mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
2980            return;
2981        }
2982
2983        boolean willAddToFolder =
2984                willAddToExistingUserFolder(info, targetLayout, targetCell, distance);
2985
2986        if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
2987            mDragOverFolderIcon = ((FolderIcon) dragOverView);
2988            mDragOverFolderIcon.onDragEnter(info);
2989            if (targetLayout != null) {
2990                targetLayout.clearDragOutlines();
2991            }
2992            setDragMode(DRAG_MODE_ADD_TO_FOLDER);
2993            return;
2994        }
2995
2996        if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
2997            setDragMode(DRAG_MODE_NONE);
2998        }
2999        if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
3000            setDragMode(DRAG_MODE_NONE);
3001        }
3002
3003        return;
3004    }
3005
3006    class FolderCreationAlarmListener implements OnAlarmListener {
3007        CellLayout layout;
3008        int cellX;
3009        int cellY;
3010
3011        public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
3012            this.layout = layout;
3013            this.cellX = cellX;
3014            this.cellY = cellY;
3015        }
3016
3017        public void onAlarm(Alarm alarm) {
3018            if (mDragFolderRingAnimator == null) {
3019                mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
3020            }
3021            mDragFolderRingAnimator.setCell(cellX, cellY);
3022            mDragFolderRingAnimator.setCellLayout(layout);
3023            mDragFolderRingAnimator.animateToAcceptState();
3024            layout.showFolderAccept(mDragFolderRingAnimator);
3025            layout.clearDragOutlines();
3026            setDragMode(DRAG_MODE_CREATE_FOLDER);
3027        }
3028    }
3029
3030    class ReorderAlarmListener implements OnAlarmListener {
3031        float[] dragViewCenter;
3032        int minSpanX, minSpanY, spanX, spanY;
3033        DragView dragView;
3034        View child;
3035
3036        public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
3037                int spanY, DragView dragView, View child) {
3038            this.dragViewCenter = dragViewCenter;
3039            this.minSpanX = minSpanX;
3040            this.minSpanY = minSpanY;
3041            this.spanX = spanX;
3042            this.spanY = spanY;
3043            this.child = child;
3044            this.dragView = dragView;
3045        }
3046
3047        public void onAlarm(Alarm alarm) {
3048            int[] resultSpan = new int[2];
3049            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3050                    (int) mDragViewVisualCenter[1], spanX, spanY, mDragTargetLayout, mTargetCell);
3051            mLastReorderX = mTargetCell[0];
3052            mLastReorderY = mTargetCell[1];
3053
3054            mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
3055                (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
3056                child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
3057
3058            if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
3059                mDragTargetLayout.revertTempState();
3060            } else {
3061                setDragMode(DRAG_MODE_REORDER);
3062            }
3063
3064            boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
3065            mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3066                (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
3067                mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize,
3068                dragView.getDragVisualizeOffset(), dragView.getDragRegion());
3069        }
3070    }
3071
3072    @Override
3073    public void getHitRectRelativeToDragLayer(Rect outRect) {
3074        // We want the workspace to have the whole area of the display (it will find the correct
3075        // cell layout to drop to in the existing drag/drop logic.
3076        mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect);
3077    }
3078
3079    /**
3080     * Add the item specified by dragInfo to the given layout.
3081     * @return true if successful
3082     */
3083    public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
3084        if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
3085            onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
3086            return true;
3087        }
3088        mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
3089        return false;
3090    }
3091
3092    private void onDropExternal(int[] touchXY, Object dragInfo,
3093            CellLayout cellLayout, boolean insertAtFirst) {
3094        onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
3095    }
3096
3097    /**
3098     * Drop an item that didn't originate on one of the workspace screens.
3099     * It may have come from Launcher (e.g. from all apps or customize), or it may have
3100     * come from another app altogether.
3101     *
3102     * NOTE: This can also be called when we are outside of a drag event, when we want
3103     * to add an item to one of the workspace screens.
3104     */
3105    private void onDropExternal(final int[] touchXY, final Object dragInfo,
3106            final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
3107        final Runnable exitSpringLoadedRunnable = new Runnable() {
3108            @Override
3109            public void run() {
3110                mLauncher.exitSpringLoadedDragModeDelayed(true, false, null);
3111            }
3112        };
3113
3114        ItemInfo info = (ItemInfo) dragInfo;
3115        int spanX = info.spanX;
3116        int spanY = info.spanY;
3117        if (mDragInfo != null) {
3118            spanX = mDragInfo.spanX;
3119            spanY = mDragInfo.spanY;
3120        }
3121
3122        final long container = mLauncher.isHotseatLayout(cellLayout) ?
3123                LauncherSettings.Favorites.CONTAINER_HOTSEAT :
3124                    LauncherSettings.Favorites.CONTAINER_DESKTOP;
3125        final long screenId = getIdForScreen(cellLayout);
3126        if (!mLauncher.isHotseatLayout(cellLayout)
3127                && screenId != getScreenIdForPageIndex(mCurrentPage)
3128                && mState != State.SPRING_LOADED) {
3129            snapToScreenId(screenId, null);
3130        }
3131
3132        if (info instanceof PendingAddItemInfo) {
3133            final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
3134
3135            boolean findNearestVacantCell = true;
3136            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
3137                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3138                        cellLayout, mTargetCell);
3139                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3140                        mDragViewVisualCenter[1], mTargetCell);
3141                if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell,
3142                        distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
3143                                cellLayout, mTargetCell, distance)) {
3144                    findNearestVacantCell = false;
3145                }
3146            }
3147
3148            final ItemInfo item = (ItemInfo) d.dragInfo;
3149            boolean updateWidgetSize = false;
3150            if (findNearestVacantCell) {
3151                int minSpanX = item.spanX;
3152                int minSpanY = item.spanY;
3153                if (item.minSpanX > 0 && item.minSpanY > 0) {
3154                    minSpanX = item.minSpanX;
3155                    minSpanY = item.minSpanY;
3156                }
3157                int[] resultSpan = new int[2];
3158                mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
3159                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
3160                        null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
3161
3162                if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) {
3163                    updateWidgetSize = true;
3164                }
3165                item.spanX = resultSpan[0];
3166                item.spanY = resultSpan[1];
3167            }
3168
3169            Runnable onAnimationCompleteRunnable = new Runnable() {
3170                @Override
3171                public void run() {
3172                    // When dragging and dropping from customization tray, we deal with creating
3173                    // widgets/shortcuts/folders in a slightly different way
3174                    switch (pendingInfo.itemType) {
3175                    case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
3176                        int span[] = new int[2];
3177                        span[0] = item.spanX;
3178                        span[1] = item.spanY;
3179                        mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
3180                                container, screenId, mTargetCell, span, null);
3181                        break;
3182                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3183                        mLauncher.processShortcutFromDrop(pendingInfo.componentName,
3184                                container, screenId, mTargetCell, null);
3185                        break;
3186                    default:
3187                        throw new IllegalStateException("Unknown item type: " +
3188                                pendingInfo.itemType);
3189                    }
3190                }
3191            };
3192            View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
3193                    ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
3194
3195            if (finalView instanceof AppWidgetHostView && updateWidgetSize) {
3196                AppWidgetHostView awhv = (AppWidgetHostView) finalView;
3197                AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, item.spanX,
3198                        item.spanY);
3199            }
3200
3201            int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3202            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
3203                    ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
3204                animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
3205            }
3206            animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
3207                    animationStyle, finalView, true);
3208        } else {
3209            // This is for other drag/drop cases, like dragging from All Apps
3210            View view = null;
3211
3212            switch (info.itemType) {
3213            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3214            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3215                if (info.container == NO_ID && info instanceof ApplicationInfo) {
3216                    // Came from all apps -- make a copy
3217                    info = new ShortcutInfo((ApplicationInfo) info);
3218                }
3219                view = mLauncher.createShortcut(R.layout.application, cellLayout,
3220                        (ShortcutInfo) info);
3221                break;
3222            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3223                view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
3224                        (FolderInfo) info, mIconCache);
3225                break;
3226            default:
3227                throw new IllegalStateException("Unknown item type: " + info.itemType);
3228            }
3229
3230            // First we find the cell nearest to point at which the item is
3231            // dropped, without any consideration to whether there is an item there.
3232            if (touchXY != null) {
3233                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3234                        cellLayout, mTargetCell);
3235                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3236                        mDragViewVisualCenter[1], mTargetCell);
3237                d.postAnimationRunnable = exitSpringLoadedRunnable;
3238                if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
3239                        true, d.dragView, d.postAnimationRunnable)) {
3240                    return;
3241                }
3242                if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
3243                        true)) {
3244                    return;
3245                }
3246            }
3247
3248            if (touchXY != null) {
3249                // when dragging and dropping, just find the closest free spot
3250                mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
3251                        (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
3252                        null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
3253            } else {
3254                cellLayout.findCellForSpan(mTargetCell, 1, 1);
3255            }
3256            addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX,
3257                    info.spanY, insertAtFirst);
3258            cellLayout.onDropChild(view);
3259            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
3260            cellLayout.getShortcutsAndWidgets().measureChild(view);
3261
3262            LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
3263                    lp.cellX, lp.cellY);
3264
3265            if (d.dragView != null) {
3266                // We wrap the animation call in the temporary set and reset of the current
3267                // cellLayout to its final transform -- this means we animate the drag view to
3268                // the correct final location.
3269                setFinalTransitionTransform(cellLayout);
3270                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
3271                        exitSpringLoadedRunnable);
3272                resetTransitionTransform(cellLayout);
3273            }
3274        }
3275    }
3276
3277    public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
3278        int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
3279                widgetInfo.spanY, widgetInfo, false);
3280        int visibility = layout.getVisibility();
3281        layout.setVisibility(VISIBLE);
3282
3283        int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
3284        int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
3285        Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
3286                Bitmap.Config.ARGB_8888);
3287        Canvas c = new Canvas(b);
3288
3289        layout.measure(width, height);
3290        layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
3291        layout.draw(c);
3292        c.setBitmap(null);
3293        layout.setVisibility(visibility);
3294        return b;
3295    }
3296
3297    private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
3298            DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell,
3299            boolean external, boolean scale) {
3300        // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
3301        // location and size on the home screen.
3302        int spanX = info.spanX;
3303        int spanY = info.spanY;
3304
3305        Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
3306        loc[0] = r.left;
3307        loc[1] = r.top;
3308
3309        setFinalTransitionTransform(layout);
3310        float cellLayoutScale =
3311                mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
3312        resetTransitionTransform(layout);
3313
3314        float dragViewScaleX;
3315        float dragViewScaleY;
3316        if (scale) {
3317            dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
3318            dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
3319        } else {
3320            dragViewScaleX = 1f;
3321            dragViewScaleY = 1f;
3322        }
3323
3324        // The animation will scale the dragView about its center, so we need to center about
3325        // the final location.
3326        loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
3327        loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
3328
3329        scaleXY[0] = dragViewScaleX * cellLayoutScale;
3330        scaleXY[1] = dragViewScaleY * cellLayoutScale;
3331    }
3332
3333    public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
3334            final Runnable onCompleteRunnable, int animationType, final View finalView,
3335            boolean external) {
3336        Rect from = new Rect();
3337        mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
3338
3339        int[] finalPos = new int[2];
3340        float scaleXY[] = new float[2];
3341        boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
3342        getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
3343                external, scalePreview);
3344
3345        Resources res = mLauncher.getResources();
3346        int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
3347
3348        // In the case where we've prebound the widget, we remove it from the DragLayer
3349        if (finalView instanceof AppWidgetHostView && external) {
3350            Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView");
3351            mLauncher.getDragLayer().removeView(finalView);
3352        }
3353        if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
3354            Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
3355            dragView.setCrossFadeBitmap(crossFadeBitmap);
3356            dragView.crossFade((int) (duration * 0.8f));
3357        } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) {
3358            scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0],  scaleXY[1]);
3359        }
3360
3361        DragLayer dragLayer = mLauncher.getDragLayer();
3362        if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
3363            mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
3364                    DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
3365        } else {
3366            int endStyle;
3367            if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
3368                endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
3369            } else {
3370                endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
3371            }
3372
3373            Runnable onComplete = new Runnable() {
3374                @Override
3375                public void run() {
3376                    if (finalView != null) {
3377                        finalView.setVisibility(VISIBLE);
3378                    }
3379                    if (onCompleteRunnable != null) {
3380                        onCompleteRunnable.run();
3381                    }
3382                }
3383            };
3384            dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
3385                    finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
3386                    duration, this);
3387        }
3388    }
3389
3390    public void setFinalTransitionTransform(CellLayout layout) {
3391        if (isSwitchingState()) {
3392            mCurrentScale = getScaleX();
3393            setScaleX(mNewScale);
3394            setScaleY(mNewScale);
3395        }
3396    }
3397    public void resetTransitionTransform(CellLayout layout) {
3398        if (isSwitchingState()) {
3399            setScaleX(mCurrentScale);
3400            setScaleY(mCurrentScale);
3401        }
3402    }
3403
3404    /**
3405     * Return the current {@link CellLayout}, correctly picking the destination
3406     * screen while a scroll is in progress.
3407     */
3408    public CellLayout getCurrentDropLayout() {
3409        return (CellLayout) getChildAt(getNextPage());
3410    }
3411
3412    /**
3413     * Return the current CellInfo describing our current drag; this method exists
3414     * so that Launcher can sync this object with the correct info when the activity is created/
3415     * destroyed
3416     *
3417     */
3418    public CellLayout.CellInfo getDragInfo() {
3419        return mDragInfo;
3420    }
3421
3422    /**
3423     * Calculate the nearest cell where the given object would be dropped.
3424     *
3425     * pixelX and pixelY should be in the coordinate system of layout
3426     */
3427    private int[] findNearestArea(int pixelX, int pixelY,
3428            int spanX, int spanY, CellLayout layout, int[] recycle) {
3429        return layout.findNearestArea(
3430                pixelX, pixelY, spanX, spanY, recycle);
3431    }
3432
3433    void setup(DragController dragController) {
3434        mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
3435        mDragController = dragController;
3436
3437        // hardware layers on children are enabled on startup, but should be disabled until
3438        // needed
3439        updateChildrenLayersEnabled(false);
3440        setWallpaperDimension();
3441    }
3442
3443    /**
3444     * Called at the end of a drag which originated on the workspace.
3445     */
3446    public void onDropCompleted(final View target, final DragObject d,
3447            final boolean isFlingToDelete, final boolean success) {
3448        if (mDeferDropAfterUninstall) {
3449            mDeferredAction = new Runnable() {
3450                    public void run() {
3451                        onDropCompleted(target, d, isFlingToDelete, success);
3452                        mDeferredAction = null;
3453                    }
3454                };
3455            return;
3456        }
3457
3458        boolean beingCalledAfterUninstall = mDeferredAction != null;
3459
3460        if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) {
3461            if (target != this && mDragInfo != null) {
3462                getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
3463                if (mDragInfo.cell instanceof DropTarget) {
3464                    mDragController.removeDropTarget((DropTarget) mDragInfo.cell);
3465                }
3466                // If we move the item to anything not on the Workspace, check if any empty
3467                // screens need to be removed. If we dropped back on the workspace, this will
3468                // be done post drop animation.
3469                stripEmptyScreens();
3470            }
3471        } else if (mDragInfo != null) {
3472            CellLayout cellLayout;
3473            if (mLauncher.isHotseatLayout(target)) {
3474                cellLayout = mLauncher.getHotseat().getLayout();
3475            } else {
3476                cellLayout = getScreenWithId(mDragInfo.screenId);
3477            }
3478            cellLayout.onDropChild(mDragInfo.cell);
3479        }
3480        if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful))
3481                && mDragInfo.cell != null) {
3482            mDragInfo.cell.setVisibility(VISIBLE);
3483        }
3484        mDragOutline = null;
3485        mDragInfo = null;
3486    }
3487
3488    public void deferCompleteDropAfterUninstallActivity() {
3489        mDeferDropAfterUninstall = true;
3490    }
3491
3492    /// maybe move this into a smaller part
3493    public void onUninstallActivityReturned(boolean success) {
3494        mDeferDropAfterUninstall = false;
3495        mUninstallSuccessful = success;
3496        if (mDeferredAction != null) {
3497            mDeferredAction.run();
3498        }
3499    }
3500
3501    void updateItemLocationsInDatabase(CellLayout cl) {
3502        int count = cl.getShortcutsAndWidgets().getChildCount();
3503
3504        long screenId = getIdForScreen(cl);
3505        int container = Favorites.CONTAINER_DESKTOP;
3506
3507        if (mLauncher.isHotseatLayout(cl)) {
3508            screenId = -1;
3509            container = Favorites.CONTAINER_HOTSEAT;
3510        }
3511
3512        for (int i = 0; i < count; i++) {
3513            View v = cl.getShortcutsAndWidgets().getChildAt(i);
3514            ItemInfo info = (ItemInfo) v.getTag();
3515            // Null check required as the AllApps button doesn't have an item info
3516            if (info != null && info.requiresDbUpdate) {
3517                info.requiresDbUpdate = false;
3518                LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, info.cellX,
3519                        info.cellY, info.spanX, info.spanY);
3520            }
3521        }
3522    }
3523
3524    ArrayList<ComponentName> stripDuplicateApps() {
3525        ArrayList<ComponentName> uniqueIntents = new ArrayList<ComponentName>();
3526        stripDuplicateApps((CellLayout) mLauncher.getHotseat().getLayout(), uniqueIntents);
3527        int count = getChildCount();
3528        for (int i = 0; i < count; i++) {
3529            CellLayout cl = (CellLayout) getChildAt(i);
3530            stripDuplicateApps(cl, uniqueIntents);
3531        }
3532        return uniqueIntents;
3533    }
3534
3535    void stripDuplicateApps(CellLayout cl, ArrayList<ComponentName> uniqueIntents) {
3536        int count = cl.getShortcutsAndWidgets().getChildCount();
3537
3538        ArrayList<View> children = new ArrayList<View>();
3539        for (int i = 0; i < count; i++) {
3540            View v = cl.getShortcutsAndWidgets().getChildAt(i);
3541            children.add(v);
3542        }
3543
3544        for (int i = 0; i < count; i++) {
3545            View v = children.get(i);
3546            ItemInfo info = (ItemInfo) v.getTag();
3547            // Null check required as the AllApps button doesn't have an item info
3548            if (info instanceof ShortcutInfo) {
3549                ShortcutInfo si = (ShortcutInfo) info;
3550                ComponentName cn = si.intent.getComponent();
3551
3552                if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
3553                    continue;
3554                }
3555
3556                if (!uniqueIntents.contains(cn)) {
3557                    uniqueIntents.add(cn);
3558                } else {
3559                    cl.removeViewInLayout(v);
3560                    LauncherModel.deleteItemFromDatabase(mLauncher, si);
3561                }
3562            }
3563            if (v instanceof FolderIcon) {
3564                FolderIcon fi = (FolderIcon) v;
3565                ArrayList<View> items = fi.getFolder().getItemsInReadingOrder();
3566                for (int j = 0; j < items.size(); j++) {
3567                    if (items.get(j).getTag() instanceof ShortcutInfo) {
3568                        ShortcutInfo si = (ShortcutInfo) items.get(j).getTag();
3569                        ComponentName cn = si.intent.getComponent();
3570
3571                        if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
3572                            continue;
3573                        }
3574                        if (!uniqueIntents.contains(cn)) {
3575                            uniqueIntents.add(cn);
3576                        } else {
3577                            fi.getFolderInfo().remove(si);
3578                            LauncherModel.deleteItemFromDatabase(mLauncher, si);
3579                        }
3580                    }
3581                }
3582            }
3583        }
3584    }
3585
3586    void saveWorkspaceToDb() {
3587        saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout());
3588        int count = getChildCount();
3589        for (int i = 0; i < count; i++) {
3590            CellLayout cl = (CellLayout) getChildAt(i);
3591            saveWorkspaceScreenToDb(cl);
3592        }
3593    }
3594
3595    void saveWorkspaceScreenToDb(CellLayout cl) {
3596        int count = cl.getShortcutsAndWidgets().getChildCount();
3597
3598        long screenId = getIdForScreen(cl);
3599        int container = Favorites.CONTAINER_DESKTOP;
3600
3601        Hotseat hotseat = mLauncher.getHotseat();
3602        if (mLauncher.isHotseatLayout(cl)) {
3603            screenId = -1;
3604            container = Favorites.CONTAINER_HOTSEAT;
3605        }
3606
3607        for (int i = 0; i < count; i++) {
3608            View v = cl.getShortcutsAndWidgets().getChildAt(i);
3609            ItemInfo info = (ItemInfo) v.getTag();
3610            // Null check required as the AllApps button doesn't have an item info
3611            if (info != null) {
3612                int cellX = info.cellX;
3613                int cellY = info.cellY;
3614                if (container == Favorites.CONTAINER_HOTSEAT) {
3615                    cellX = hotseat.getCellXFromOrder((int) info.screenId);
3616                    cellY = hotseat.getCellYFromOrder((int) info.screenId);
3617                }
3618                LauncherModel.addItemToDatabase(mLauncher, info, container, screenId, cellX,
3619                        cellY, false);
3620            }
3621            if (v instanceof FolderIcon) {
3622                FolderIcon fi = (FolderIcon) v;
3623                fi.getFolder().addItemLocationsInDatabase();
3624            }
3625        }
3626    }
3627
3628    @Override
3629    public boolean supportsFlingToDelete() {
3630        return true;
3631    }
3632
3633    @Override
3634    public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
3635        // Do nothing
3636    }
3637
3638    @Override
3639    public void onFlingToDeleteCompleted() {
3640        // Do nothing
3641    }
3642
3643    public boolean isDropEnabled() {
3644        return true;
3645    }
3646
3647    @Override
3648    protected void onRestoreInstanceState(Parcelable state) {
3649        super.onRestoreInstanceState(state);
3650        Launcher.setScreen(mCurrentPage);
3651    }
3652
3653    @Override
3654    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
3655        // We don't dispatch restoreInstanceState to our children using this code path.
3656        // Some pages will be restored immediately as their items are bound immediately, and
3657        // others we will need to wait until after their items are bound.
3658        mSavedStates = container;
3659    }
3660
3661    public void restoreInstanceStateForChild(int child) {
3662        if (mSavedStates != null) {
3663            mRestoredPages.add(child);
3664            CellLayout cl = (CellLayout) getChildAt(child);
3665            cl.restoreInstanceState(mSavedStates);
3666        }
3667    }
3668
3669    public void restoreInstanceStateForRemainingPages() {
3670        int count = getChildCount();
3671        for (int i = 0; i < count; i++) {
3672            if (!mRestoredPages.contains(i)) {
3673                restoreInstanceStateForChild(i);
3674            }
3675        }
3676        mRestoredPages.clear();
3677    }
3678
3679    @Override
3680    public void scrollLeft() {
3681        if (!isSmall() && !mIsSwitchingState) {
3682            super.scrollLeft();
3683        }
3684        Folder openFolder = getOpenFolder();
3685        if (openFolder != null) {
3686            openFolder.completeDragExit();
3687        }
3688    }
3689
3690    @Override
3691    public void scrollRight() {
3692        if (!isSmall() && !mIsSwitchingState) {
3693            super.scrollRight();
3694        }
3695        Folder openFolder = getOpenFolder();
3696        if (openFolder != null) {
3697            openFolder.completeDragExit();
3698        }
3699    }
3700
3701    @Override
3702    public boolean onEnterScrollArea(int x, int y, int direction) {
3703        // Ignore the scroll area if we are dragging over the hot seat
3704        boolean isPortrait = !LauncherAppState.isScreenLandscape(getContext());
3705        if (mLauncher.getHotseat() != null && isPortrait) {
3706            Rect r = new Rect();
3707            mLauncher.getHotseat().getHitRect(r);
3708            if (r.contains(x, y)) {
3709                return false;
3710            }
3711        }
3712
3713        boolean result = false;
3714        if (!isSmall() && !mIsSwitchingState) {
3715            mInScrollArea = true;
3716
3717            final int page = getNextPage() +
3718                       (direction == DragController.SCROLL_LEFT ? -1 : 1);
3719
3720            // We always want to exit the current layout to ensure parity of enter / exit
3721            setCurrentDropLayout(null);
3722
3723            if (0 <= page && page < getChildCount()) {
3724                // Ensure that we are not dragging over to the custom content screen
3725                if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) {
3726                    return false;
3727                }
3728
3729                CellLayout layout = (CellLayout) getChildAt(page);
3730                setCurrentDragOverlappingLayout(layout);
3731
3732                // Workspace is responsible for drawing the edge glow on adjacent pages,
3733                // so we need to redraw the workspace when this may have changed.
3734                invalidate();
3735                result = true;
3736            }
3737        }
3738        return result;
3739    }
3740
3741    @Override
3742    public boolean onExitScrollArea() {
3743        boolean result = false;
3744        if (mInScrollArea) {
3745            invalidate();
3746            CellLayout layout = getCurrentDropLayout();
3747            setCurrentDropLayout(layout);
3748            setCurrentDragOverlappingLayout(layout);
3749
3750            result = true;
3751            mInScrollArea = false;
3752        }
3753        return result;
3754    }
3755
3756    private void onResetScrollArea() {
3757        setCurrentDragOverlappingLayout(null);
3758        mInScrollArea = false;
3759    }
3760
3761    /**
3762     * Returns a specific CellLayout
3763     */
3764    CellLayout getParentCellLayoutForView(View v) {
3765        ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
3766        for (CellLayout layout : layouts) {
3767            if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
3768                return layout;
3769            }
3770        }
3771        return null;
3772    }
3773
3774    /**
3775     * Returns a list of all the CellLayouts in the workspace.
3776     */
3777    ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
3778        ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
3779        int screenCount = getChildCount();
3780        for (int screen = 0; screen < screenCount; screen++) {
3781            layouts.add(((CellLayout) getChildAt(screen)));
3782        }
3783        if (mLauncher.getHotseat() != null) {
3784            layouts.add(mLauncher.getHotseat().getLayout());
3785        }
3786        return layouts;
3787    }
3788
3789    /**
3790     * We should only use this to search for specific children.  Do not use this method to modify
3791     * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
3792     * the hotseat and workspace pages
3793     */
3794    ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
3795        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3796                new ArrayList<ShortcutAndWidgetContainer>();
3797        int screenCount = getChildCount();
3798        for (int screen = 0; screen < screenCount; screen++) {
3799            childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
3800        }
3801        if (mLauncher.getHotseat() != null) {
3802            childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
3803        }
3804        return childrenLayouts;
3805    }
3806
3807    public Folder getFolderForTag(Object tag) {
3808        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3809                getAllShortcutAndWidgetContainers();
3810        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
3811            int count = layout.getChildCount();
3812            for (int i = 0; i < count; i++) {
3813                View child = layout.getChildAt(i);
3814                if (child instanceof Folder) {
3815                    Folder f = (Folder) child;
3816                    if (f.getInfo() == tag && f.getInfo().opened) {
3817                        return f;
3818                    }
3819                }
3820            }
3821        }
3822        return null;
3823    }
3824
3825    public View getViewForTag(Object tag) {
3826        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3827                getAllShortcutAndWidgetContainers();
3828        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
3829            int count = layout.getChildCount();
3830            for (int i = 0; i < count; i++) {
3831                View child = layout.getChildAt(i);
3832                if (child.getTag() == tag) {
3833                    return child;
3834                }
3835            }
3836        }
3837        return null;
3838    }
3839
3840    void clearDropTargets() {
3841        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3842                getAllShortcutAndWidgetContainers();
3843        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
3844            int childCount = layout.getChildCount();
3845            for (int j = 0; j < childCount; j++) {
3846                View v = layout.getChildAt(j);
3847                if (v instanceof DropTarget) {
3848                    mDragController.removeDropTarget((DropTarget) v);
3849                }
3850            }
3851        }
3852    }
3853
3854    // Removes ALL items that match a given package name, this is usually called when a package
3855    // has been removed and we want to remove all components (widgets, shortcuts, apps) that
3856    // belong to that package.
3857    void removeItemsByPackageName(final ArrayList<String> packages) {
3858        final HashSet<String> packageNames = new HashSet<String>();
3859        packageNames.addAll(packages);
3860
3861        // Filter out all the ItemInfos that this is going to affect
3862        final HashSet<ItemInfo> infos = new HashSet<ItemInfo>();
3863        final HashSet<ComponentName> cns = new HashSet<ComponentName>();
3864        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
3865        for (CellLayout layoutParent : cellLayouts) {
3866            ViewGroup layout = layoutParent.getShortcutsAndWidgets();
3867            int childCount = layout.getChildCount();
3868            for (int i = 0; i < childCount; ++i) {
3869                View view = layout.getChildAt(i);
3870                infos.add((ItemInfo) view.getTag());
3871            }
3872        }
3873        LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
3874            @Override
3875            public boolean filterItem(ItemInfo parent, ItemInfo info,
3876                                      ComponentName cn) {
3877                if (packageNames.contains(cn.getPackageName())) {
3878                    cns.add(cn);
3879                    return true;
3880                }
3881                return false;
3882            }
3883        };
3884        LauncherModel.filterItemInfos(infos, filter);
3885
3886        // Remove the affected components
3887        removeItemsByComponentName(cns);
3888    }
3889
3890    // Removes items that match the application info specified, when applications are removed
3891    // as a part of an update, this is called to ensure that other widgets and application
3892    // shortcuts are not removed.
3893    void removeItemsByApplicationInfo(final ArrayList<ApplicationInfo> appInfos) {
3894        // Just create a hash table of all the specific components that this will affect
3895        HashSet<ComponentName> cns = new HashSet<ComponentName>();
3896        for (ApplicationInfo info : appInfos) {
3897            cns.add(info.componentName);
3898        }
3899
3900        // Remove all the things
3901        removeItemsByComponentName(cns);
3902    }
3903
3904    void removeItemsByComponentName(final HashSet<ComponentName> componentNames) {
3905        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
3906        for (final CellLayout layoutParent: cellLayouts) {
3907            final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
3908
3909            final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
3910            for (int j = 0; j < layout.getChildCount(); j++) {
3911                final View view = layout.getChildAt(j);
3912                children.put((ItemInfo) view.getTag(), view);
3913            }
3914
3915            final ArrayList<View> childrenToRemove = new ArrayList<View>();
3916            final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove =
3917                    new HashMap<FolderInfo, ArrayList<ShortcutInfo>>();
3918            LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
3919                @Override
3920                public boolean filterItem(ItemInfo parent, ItemInfo info,
3921                                          ComponentName cn) {
3922                    if (parent instanceof FolderInfo) {
3923                        if (componentNames.contains(cn)) {
3924                            FolderInfo folder = (FolderInfo) parent;
3925                            ArrayList<ShortcutInfo> appsToRemove;
3926                            if (folderAppsToRemove.containsKey(folder)) {
3927                                appsToRemove = folderAppsToRemove.get(folder);
3928                            } else {
3929                                appsToRemove = new ArrayList<ShortcutInfo>();
3930                                folderAppsToRemove.put(folder, appsToRemove);
3931                            }
3932                            appsToRemove.add((ShortcutInfo) info);
3933                            return true;
3934                        }
3935                    } else {
3936                        if (componentNames.contains(cn)) {
3937                            childrenToRemove.add(children.get(info));
3938                            return true;
3939                        }
3940                    }
3941                    return false;
3942                }
3943            };
3944            LauncherModel.filterItemInfos(children.keySet(), filter);
3945
3946            // Remove all the apps from their folders
3947            for (FolderInfo folder : folderAppsToRemove.keySet()) {
3948                ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
3949                for (ShortcutInfo info : appsToRemove) {
3950                    folder.remove(info);
3951                }
3952            }
3953
3954            // Remove all the other children
3955            for (View child : childrenToRemove) {
3956                // Note: We can not remove the view directly from CellLayoutChildren as this
3957                // does not re-mark the spaces as unoccupied.
3958                layoutParent.removeViewInLayout(child);
3959                if (child instanceof DropTarget) {
3960                    mDragController.removeDropTarget((DropTarget) child);
3961                }
3962            }
3963
3964            if (childrenToRemove.size() > 0) {
3965                layout.requestLayout();
3966                layout.invalidate();
3967            }
3968        }
3969
3970        // Strip all the empty screens
3971        stripEmptyScreens();
3972
3973        // Clean up new-apps animation list
3974        final Context context = getContext();
3975        post(new Runnable() {
3976            @Override
3977            public void run() {
3978                String spKey = LauncherAppState.getSharedPreferencesKey();
3979                SharedPreferences sp = context.getSharedPreferences(spKey,
3980                        Context.MODE_PRIVATE);
3981                Set<String> newApps = sp.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY,
3982                        null);
3983
3984                // Remove all queued items that match the same package
3985                if (newApps != null) {
3986                    synchronized (newApps) {
3987                        Iterator<String> iter = newApps.iterator();
3988                        while (iter.hasNext()) {
3989                            try {
3990                                Intent intent = Intent.parseUri(iter.next(), 0);
3991                                if (componentNames.contains(intent.getComponent())) {
3992                                    iter.remove();
3993                                }
3994                            } catch (URISyntaxException e) {}
3995                        }
3996                    }
3997                }
3998            }
3999        });
4000    }
4001
4002    void updateShortcuts(ArrayList<ApplicationInfo> apps) {
4003        ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers();
4004        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
4005            int childCount = layout.getChildCount();
4006            for (int j = 0; j < childCount; j++) {
4007                final View view = layout.getChildAt(j);
4008                Object tag = view.getTag();
4009
4010                if (LauncherModel.isShortcutInfoUpdateable((ItemInfo) tag)) {
4011                    ShortcutInfo info = (ShortcutInfo) tag;
4012
4013                    final Intent intent = info.intent;
4014                    final ComponentName name = intent.getComponent();
4015                    final int appCount = apps.size();
4016                    for (int k = 0; k < appCount; k++) {
4017                        ApplicationInfo app = apps.get(k);
4018                        if (app.componentName.equals(name)) {
4019                            BubbleTextView shortcut = (BubbleTextView) view;
4020                            info.updateIcon(mIconCache);
4021                            info.title = app.title.toString();
4022                            shortcut.applyFromShortcutInfo(info, mIconCache);
4023                        }
4024                    }
4025                }
4026            }
4027        }
4028    }
4029
4030    void moveToDefaultScreen(boolean animate) {
4031        if (!isSmall()) {
4032            if (animate) {
4033                snapToPage(mDefaultPage);
4034            } else {
4035                setCurrentPage(mDefaultPage);
4036            }
4037        }
4038        View child = getChildAt(mDefaultPage);
4039        if (child != null) {
4040            child.requestFocus();
4041        }
4042    }
4043
4044    @Override
4045    protected int getPageIndicatorMarker(int pageIndex) {
4046        if (getScreenIdForPageIndex(pageIndex) == CUSTOM_CONTENT_SCREEN_ID) {
4047            return R.layout.now_page_indicator_marker;
4048        }
4049        return super.getPageIndicatorMarker(pageIndex);
4050    }
4051
4052    @Override
4053    public void syncPages() {
4054    }
4055
4056    @Override
4057    public void syncPageItems(int page, boolean immediate) {
4058    }
4059
4060    protected String getCurrentPageDescription() {
4061        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
4062        return String.format(getContext().getString(R.string.workspace_scroll_format),
4063                page + 1, getChildCount());
4064    }
4065
4066    public void getLocationInDragLayer(int[] loc) {
4067        mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
4068    }
4069
4070    void setFadeForOverScroll(float fade) {
4071        mOverscrollFade = fade;
4072        float reducedFade = 0.5f + 0.5f * (1 - fade);
4073        final ViewGroup parent = (ViewGroup) getParent();
4074        final ImageView qsbDivider = (ImageView) (parent.findViewById(R.id.qsb_divider));
4075
4076        if (qsbDivider != null) qsbDivider.setAlpha(reducedFade);
4077    }
4078}
4079