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