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