Workspace.java revision 6400b84d5594d7b9244e3b118f107033eb8b87a0
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            } else {
2239                // create a dummy animation so we don't need to do null checks later
2240                pageIndicatorAlpha = ValueAnimator.ofFloat(0, 0);
2241            }
2242            Animator hotseatAlpha = new LauncherViewPropertyAnimator(hotseat)
2243                .alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
2244            Animator searchBarAlpha = new LauncherViewPropertyAnimator(searchBar)
2245                .alpha(finalSearchBarAlpha).withLayer();
2246            Animator overviewPanelAlpha = new LauncherViewPropertyAnimator(overviewPanel)
2247                .alpha(finalOverviewPanelAlpha).withLayer();
2248
2249            pageIndicatorAlpha.addListener(new AlphaUpdateListener(pageIndicator));
2250            hotseatAlpha.addListener(new AlphaUpdateListener(hotseat));
2251            searchBarAlpha.addListener(new AlphaUpdateListener(searchBar));
2252            overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel));
2253
2254            if (workspaceToOverview) {
2255                pageIndicatorAlpha.setInterpolator(new DecelerateInterpolator(2));
2256                hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
2257                overviewPanelAlpha.setInterpolator(null);
2258            } else if (overviewToWorkspace) {
2259                pageIndicatorAlpha.setInterpolator(null);
2260                hotseatAlpha.setInterpolator(null);
2261                overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
2262            }
2263            searchBarAlpha.setInterpolator(null);
2264
2265            anim.play(overviewPanelAlpha);
2266            anim.play(hotseatAlpha);
2267            anim.play(searchBarAlpha);
2268            anim.play(pageIndicatorAlpha);
2269            anim.setStartDelay(delay);
2270        } else {
2271            overviewPanel.setAlpha(finalOverviewPanelAlpha);
2272            AlphaUpdateListener.updateVisibility(overviewPanel);
2273            hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha);
2274            AlphaUpdateListener.updateVisibility(hotseat);
2275            if (pageIndicator != null) {
2276                pageIndicator.setAlpha(finalHotseatAndPageIndicatorAlpha);
2277                AlphaUpdateListener.updateVisibility(pageIndicator);
2278            }
2279            searchBar.setAlpha(finalSearchBarAlpha);
2280            AlphaUpdateListener.updateVisibility(searchBar);
2281            updateCustomContentVisibility();
2282            setScaleX(mNewScale);
2283            setScaleY(mNewScale);
2284            setTranslationY(finalWorkspaceTranslationY);
2285        }
2286        mLauncher.updateVoiceButtonProxyVisible(false);
2287
2288        if (stateIsSpringLoaded) {
2289            // Right now we're covered by Apps Customize
2290            // Show the background gradient immediately, so the gradient will
2291            // be showing once AppsCustomize disappears
2292            animateBackgroundGradient(getResources().getInteger(
2293                    R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, false);
2294        } else if (stateIsOverview) {
2295            animateBackgroundGradient(getResources().getInteger(
2296                    R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, true);
2297        } else {
2298            // Fade the background gradient away
2299            animateBackgroundGradient(0f, animated);
2300        }
2301        return anim;
2302    }
2303
2304    static class AlphaUpdateListener implements AnimatorUpdateListener, AnimatorListener {
2305        View view;
2306        public AlphaUpdateListener(View v) {
2307            view = v;
2308        }
2309
2310        @Override
2311        public void onAnimationUpdate(ValueAnimator arg0) {
2312            updateVisibility(view);
2313        }
2314
2315        public static void updateVisibility(View view) {
2316            // We want to avoid the extra layout pass by setting the views to GONE unless
2317            // accessibility is on, in which case not setting them to GONE causes a glitch.
2318            int invisibleState = sAccessibilityEnabled ? GONE : INVISIBLE;
2319            if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != invisibleState) {
2320                view.setVisibility(invisibleState);
2321            } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
2322                    && view.getVisibility() != VISIBLE) {
2323                view.setVisibility(VISIBLE);
2324            }
2325        }
2326
2327        @Override
2328        public void onAnimationCancel(Animator arg0) {
2329        }
2330
2331        @Override
2332        public void onAnimationEnd(Animator arg0) {
2333            updateVisibility(view);
2334        }
2335
2336        @Override
2337        public void onAnimationRepeat(Animator arg0) {
2338        }
2339
2340        @Override
2341        public void onAnimationStart(Animator arg0) {
2342            // We want the views to be visible for animation, so fade-in/out is visible
2343            view.setVisibility(VISIBLE);
2344        }
2345    }
2346
2347    @Override
2348    public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
2349        onTransitionPrepare();
2350    }
2351
2352    @Override
2353    public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
2354    }
2355
2356    @Override
2357    public void onLauncherTransitionStep(Launcher l, float t) {
2358        mTransitionProgress = t;
2359    }
2360
2361    @Override
2362    public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
2363        onTransitionEnd();
2364    }
2365
2366    private void onTransitionPrepare() {
2367        mIsSwitchingState = true;
2368
2369        // Invalidate here to ensure that the pages are rendered during the state change transition.
2370        invalidate();
2371
2372        updateChildrenLayersEnabled(false);
2373        hideCustomContentIfNecessary();
2374    }
2375
2376    void updateCustomContentVisibility() {
2377        int visibility = mState == Workspace.State.NORMAL ? VISIBLE : INVISIBLE;
2378        if (hasCustomContent()) {
2379            mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(visibility);
2380        }
2381    }
2382
2383    void showCustomContentIfNecessary() {
2384        boolean show  = mState == Workspace.State.NORMAL;
2385        if (show && hasCustomContent()) {
2386            mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(VISIBLE);
2387        }
2388    }
2389
2390    void hideCustomContentIfNecessary() {
2391        boolean hide  = mState != Workspace.State.NORMAL;
2392        if (hide && hasCustomContent()) {
2393            disableLayoutTransitions();
2394            mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE);
2395            enableLayoutTransitions();
2396        }
2397    }
2398
2399    private void onTransitionEnd() {
2400        mIsSwitchingState = false;
2401        updateChildrenLayersEnabled(false);
2402        // The code in getChangeStateAnimation to determine initialAlpha and finalAlpha will ensure
2403        // ensure that only the current page is visible during (and subsequently, after) the
2404        // transition animation.  If fade adjacent pages is disabled, then re-enable the page
2405        // visibility after the transition animation.
2406        if (!mWorkspaceFadeInAdjacentScreens) {
2407            for (int i = 0; i < getChildCount(); i++) {
2408                final CellLayout cl = (CellLayout) getChildAt(i);
2409                cl.setShortcutAndWidgetAlpha(1f);
2410            }
2411        } else {
2412            for (int i = 0; i < numCustomPages(); i++) {
2413                final CellLayout cl = (CellLayout) getChildAt(i);
2414                cl.setShortcutAndWidgetAlpha(1f);
2415            }
2416        }
2417        showCustomContentIfNecessary();
2418    }
2419
2420    @Override
2421    public View getContent() {
2422        return this;
2423    }
2424
2425    /**
2426     * Draw the View v into the given Canvas.
2427     *
2428     * @param v the view to draw
2429     * @param destCanvas the canvas to draw on
2430     * @param padding the horizontal and vertical padding to use when drawing
2431     */
2432    private void drawDragView(View v, Canvas destCanvas, int padding, boolean pruneToDrawable) {
2433        final Rect clipRect = mTempRect;
2434        v.getDrawingRect(clipRect);
2435
2436        boolean textVisible = false;
2437
2438        destCanvas.save();
2439        if (v instanceof TextView && pruneToDrawable) {
2440            Drawable d = ((TextView) v).getCompoundDrawables()[1];
2441            clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding);
2442            destCanvas.translate(padding / 2, padding / 2);
2443            d.draw(destCanvas);
2444        } else {
2445            if (v instanceof FolderIcon) {
2446                // For FolderIcons the text can bleed into the icon area, and so we need to
2447                // hide the text completely (which can't be achieved by clipping).
2448                if (((FolderIcon) v).getTextVisible()) {
2449                    ((FolderIcon) v).setTextVisible(false);
2450                    textVisible = true;
2451                }
2452            } else if (v instanceof BubbleTextView) {
2453                final BubbleTextView tv = (BubbleTextView) v;
2454                clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V +
2455                        tv.getLayout().getLineTop(0);
2456            } else if (v instanceof TextView) {
2457                final TextView tv = (TextView) v;
2458                clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() +
2459                        tv.getLayout().getLineTop(0);
2460            }
2461            destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
2462            destCanvas.clipRect(clipRect, Op.REPLACE);
2463            v.draw(destCanvas);
2464
2465            // Restore text visibility of FolderIcon if necessary
2466            if (textVisible) {
2467                ((FolderIcon) v).setTextVisible(true);
2468            }
2469        }
2470        destCanvas.restore();
2471    }
2472
2473    /**
2474     * Returns a new bitmap to show when the given View is being dragged around.
2475     * Responsibility for the bitmap is transferred to the caller.
2476     */
2477    public Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
2478        Bitmap b;
2479
2480        if (v instanceof TextView) {
2481            Drawable d = ((TextView) v).getCompoundDrawables()[1];
2482            b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding,
2483                    d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888);
2484        } else {
2485            b = Bitmap.createBitmap(
2486                    v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
2487        }
2488
2489        canvas.setBitmap(b);
2490        drawDragView(v, canvas, padding, true);
2491        canvas.setBitmap(null);
2492
2493        return b;
2494    }
2495
2496    /**
2497     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2498     * Responsibility for the bitmap is transferred to the caller.
2499     */
2500    private Bitmap createDragOutline(View v, Canvas canvas, int padding) {
2501        final int outlineColor = getResources().getColor(R.color.outline_color);
2502        final Bitmap b = Bitmap.createBitmap(
2503                v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
2504
2505        canvas.setBitmap(b);
2506        drawDragView(v, canvas, padding, true);
2507        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
2508        canvas.setBitmap(null);
2509        return b;
2510    }
2511
2512    /**
2513     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2514     * Responsibility for the bitmap is transferred to the caller.
2515     */
2516    private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding, int w, int h,
2517            boolean clipAlpha) {
2518        final int outlineColor = getResources().getColor(R.color.outline_color);
2519        final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
2520        canvas.setBitmap(b);
2521
2522        Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
2523        float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(),
2524                (h - padding) / (float) orig.getHeight());
2525        int scaledWidth = (int) (scaleFactor * orig.getWidth());
2526        int scaledHeight = (int) (scaleFactor * orig.getHeight());
2527        Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
2528
2529        // center the image
2530        dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
2531
2532        canvas.drawBitmap(orig, src, dst, null);
2533        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor,
2534                clipAlpha);
2535        canvas.setBitmap(null);
2536
2537        return b;
2538    }
2539
2540    void startDrag(CellLayout.CellInfo cellInfo) {
2541        View child = cellInfo.cell;
2542
2543        // Make sure the drag was started by a long press as opposed to a long click.
2544        if (!child.isInTouchMode()) {
2545            return;
2546        }
2547
2548        mDragInfo = cellInfo;
2549        child.setVisibility(INVISIBLE);
2550        CellLayout layout = (CellLayout) child.getParent().getParent();
2551        layout.prepareChildForDrag(child);
2552
2553        child.clearFocus();
2554        child.setPressed(false);
2555
2556        final Canvas canvas = new Canvas();
2557
2558        // The outline is used to visualize where the item will land if dropped
2559        mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING);
2560        beginDragShared(child, this);
2561    }
2562
2563    public void beginDragShared(View child, DragSource source) {
2564        // The drag bitmap follows the touch point around on the screen
2565        final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING);
2566
2567        final int bmpWidth = b.getWidth();
2568        final int bmpHeight = b.getHeight();
2569
2570        float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
2571        int dragLayerX =
2572                Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
2573        int dragLayerY =
2574                Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2
2575                        - DRAG_BITMAP_PADDING / 2);
2576
2577        LauncherAppState app = LauncherAppState.getInstance();
2578        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2579        Point dragVisualizeOffset = null;
2580        Rect dragRect = null;
2581        if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
2582            int iconSize = grid.iconSizePx;
2583            int top = child.getPaddingTop();
2584            int left = (bmpWidth - iconSize) / 2;
2585            int right = left + iconSize;
2586            int bottom = top + iconSize;
2587            dragLayerY += top;
2588            // Note: The drag region is used to calculate drag layer offsets, but the
2589            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2590            dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2, DRAG_BITMAP_PADDING / 2);
2591            dragRect = new Rect(left, top, right, bottom);
2592        } else if (child instanceof FolderIcon) {
2593            int previewSize = grid.folderIconSizePx;
2594            dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
2595        }
2596
2597        // Clear the pressed state if necessary
2598        if (child instanceof BubbleTextView) {
2599            BubbleTextView icon = (BubbleTextView) child;
2600            icon.clearPressedOrFocusedBackground();
2601        }
2602
2603        if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
2604            String msg = "Drag started with a view that has no tag set. This "
2605                    + "will cause a crash (issue 11627249) down the line. "
2606                    + "View: " + child + "  tag: " + child.getTag();
2607            throw new IllegalStateException(msg);
2608        }
2609
2610        DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
2611                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
2612        dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
2613
2614        if (child.getParent() instanceof ShortcutAndWidgetContainer) {
2615            mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
2616        }
2617
2618        b.recycle();
2619    }
2620
2621    void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, long screenId,
2622            int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
2623        View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
2624
2625        final int[] cellXY = new int[2];
2626        target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
2627        addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
2628
2629        LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, cellXY[0],
2630                cellXY[1]);
2631    }
2632
2633    public boolean transitionStateShouldAllowDrop() {
2634        return ((!isSwitchingState() || mTransitionProgress > 0.5f) && mState != State.SMALL);
2635    }
2636
2637    /**
2638     * {@inheritDoc}
2639     */
2640    public boolean acceptDrop(DragObject d) {
2641        // If it's an external drop (e.g. from All Apps), check if it should be accepted
2642        CellLayout dropTargetLayout = mDropToLayout;
2643        if (d.dragSource != this) {
2644            // Don't accept the drop if we're not over a screen at time of drop
2645            if (dropTargetLayout == null) {
2646                return false;
2647            }
2648            if (!transitionStateShouldAllowDrop()) return false;
2649
2650            mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
2651                    d.dragView, mDragViewVisualCenter);
2652
2653            // We want the point to be mapped to the dragTarget.
2654            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2655                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2656            } else {
2657                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2658            }
2659
2660            int spanX = 1;
2661            int spanY = 1;
2662            if (mDragInfo != null) {
2663                final CellLayout.CellInfo dragCellInfo = mDragInfo;
2664                spanX = dragCellInfo.spanX;
2665                spanY = dragCellInfo.spanY;
2666            } else {
2667                final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
2668                spanX = dragInfo.spanX;
2669                spanY = dragInfo.spanY;
2670            }
2671
2672            int minSpanX = spanX;
2673            int minSpanY = spanY;
2674            if (d.dragInfo instanceof PendingAddWidgetInfo) {
2675                minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
2676                minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
2677            }
2678
2679            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2680                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
2681                    mTargetCell);
2682            float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2683                    mDragViewVisualCenter[1], mTargetCell);
2684            if (willCreateUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
2685                    mTargetCell, distance, true)) {
2686                return true;
2687            }
2688            if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
2689                    mTargetCell, distance)) {
2690                return true;
2691            }
2692
2693            int[] resultSpan = new int[2];
2694            mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
2695                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2696                    null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
2697            boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2698
2699            // Don't accept the drop if there's no room for the item
2700            if (!foundCell) {
2701                // Don't show the message if we are dropping on the AllApps button and the hotseat
2702                // is full
2703                boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2704                if (mTargetCell != null && isHotseat) {
2705                    Hotseat hotseat = mLauncher.getHotseat();
2706                    if (hotseat.isAllAppsButtonRank(
2707                            hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
2708                        return false;
2709                    }
2710                }
2711
2712                mLauncher.showOutOfSpaceMessage(isHotseat);
2713                return false;
2714            }
2715        }
2716
2717        long screenId = getIdForScreen(dropTargetLayout);
2718        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
2719            commitExtraEmptyScreen();
2720        }
2721
2722        return true;
2723    }
2724
2725    boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float
2726            distance, boolean considerTimeout) {
2727        if (distance > mMaxDistanceForFolderCreation) return false;
2728        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2729
2730        if (dropOverView != null) {
2731            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2732            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2733                return false;
2734            }
2735        }
2736
2737        boolean hasntMoved = false;
2738        if (mDragInfo != null) {
2739            hasntMoved = dropOverView == mDragInfo.cell;
2740        }
2741
2742        if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
2743            return false;
2744        }
2745
2746        boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
2747        boolean willBecomeShortcut =
2748                (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
2749                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
2750
2751        return (aboveShortcut && willBecomeShortcut);
2752    }
2753
2754    boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
2755            float distance) {
2756        if (distance > mMaxDistanceForFolderCreation) return false;
2757        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2758
2759        if (dropOverView != null) {
2760            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2761            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2762                return false;
2763            }
2764        }
2765
2766        if (dropOverView instanceof FolderIcon) {
2767            FolderIcon fi = (FolderIcon) dropOverView;
2768            if (fi.acceptDrop(dragInfo)) {
2769                return true;
2770            }
2771        }
2772        return false;
2773    }
2774
2775    boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
2776            int[] targetCell, float distance, boolean external, DragView dragView,
2777            Runnable postAnimationRunnable) {
2778        if (distance > mMaxDistanceForFolderCreation) return false;
2779        View v = target.getChildAt(targetCell[0], targetCell[1]);
2780
2781        boolean hasntMoved = false;
2782        if (mDragInfo != null) {
2783            CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
2784            hasntMoved = (mDragInfo.cellX == targetCell[0] &&
2785                    mDragInfo.cellY == targetCell[1]) && (cellParent == target);
2786        }
2787
2788        if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
2789        mCreateUserFolderOnDrop = false;
2790        final long screenId = (targetCell == null) ? mDragInfo.screenId : getIdForScreen(target);
2791
2792        boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
2793        boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
2794
2795        if (aboveShortcut && willBecomeShortcut) {
2796            ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
2797            ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
2798            // if the drag started here, we need to remove it from the workspace
2799            if (!external) {
2800                getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2801            }
2802
2803            Rect folderLocation = new Rect();
2804            float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
2805            target.removeView(v);
2806
2807            FolderIcon fi =
2808                mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
2809            destInfo.cellX = -1;
2810            destInfo.cellY = -1;
2811            sourceInfo.cellX = -1;
2812            sourceInfo.cellY = -1;
2813
2814            // If the dragView is null, we can't animate
2815            boolean animate = dragView != null;
2816            if (animate) {
2817                fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
2818                        postAnimationRunnable);
2819            } else {
2820                fi.addItem(destInfo);
2821                fi.addItem(sourceInfo);
2822            }
2823            return true;
2824        }
2825        return false;
2826    }
2827
2828    boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
2829            float distance, DragObject d, boolean external) {
2830        if (distance > mMaxDistanceForFolderCreation) return false;
2831
2832        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2833        if (!mAddToExistingFolderOnDrop) return false;
2834        mAddToExistingFolderOnDrop = false;
2835
2836        if (dropOverView instanceof FolderIcon) {
2837            FolderIcon fi = (FolderIcon) dropOverView;
2838            if (fi.acceptDrop(d.dragInfo)) {
2839                fi.onDrop(d);
2840
2841                // if the drag started here, we need to remove it from the workspace
2842                if (!external) {
2843                    getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2844                }
2845                return true;
2846            }
2847        }
2848        return false;
2849    }
2850
2851    public void onDrop(final DragObject d) {
2852        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
2853                mDragViewVisualCenter);
2854
2855        CellLayout dropTargetLayout = mDropToLayout;
2856
2857        // We want the point to be mapped to the dragTarget.
2858        if (dropTargetLayout != null) {
2859            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2860                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2861            } else {
2862                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2863            }
2864        }
2865
2866        int snapScreen = -1;
2867        boolean resizeOnDrop = false;
2868        if (d.dragSource != this) {
2869            final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
2870                    (int) mDragViewVisualCenter[1] };
2871            onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
2872        } else if (mDragInfo != null) {
2873            final View cell = mDragInfo.cell;
2874
2875            Runnable resizeRunnable = null;
2876            if (dropTargetLayout != null && !d.cancelled) {
2877                // Move internally
2878                boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
2879                boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2880                long container = hasMovedIntoHotseat ?
2881                        LauncherSettings.Favorites.CONTAINER_HOTSEAT :
2882                        LauncherSettings.Favorites.CONTAINER_DESKTOP;
2883                long screenId = (mTargetCell[0] < 0) ?
2884                        mDragInfo.screenId : getIdForScreen(dropTargetLayout);
2885                int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
2886                int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
2887                // First we find the cell nearest to point at which the item is
2888                // dropped, without any consideration to whether there is an item there.
2889
2890                mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
2891                        mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
2892                float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2893                        mDragViewVisualCenter[1], mTargetCell);
2894
2895                // If the item being dropped is a shortcut and the nearest drop
2896                // cell also contains a shortcut, then create a folder with the two shortcuts.
2897                if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
2898                        dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
2899                    removeExtraEmptyScreen(true, null, 0, true);
2900                    return;
2901                }
2902
2903                if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
2904                        distance, d, false)) {
2905                    removeExtraEmptyScreen(true, null, 0, true);
2906                    return;
2907                }
2908
2909                // Aside from the special case where we're dropping a shortcut onto a shortcut,
2910                // we need to find the nearest cell location that is vacant
2911                ItemInfo item = (ItemInfo) d.dragInfo;
2912                int minSpanX = item.spanX;
2913                int minSpanY = item.spanY;
2914                if (item.minSpanX > 0 && item.minSpanY > 0) {
2915                    minSpanX = item.minSpanX;
2916                    minSpanY = item.minSpanY;
2917                }
2918
2919                int[] resultSpan = new int[2];
2920                mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
2921                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
2922                        mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
2923
2924                boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2925
2926                // if the widget resizes on drop
2927                if (foundCell && (cell instanceof AppWidgetHostView) &&
2928                        (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
2929                    resizeOnDrop = true;
2930                    item.spanX = resultSpan[0];
2931                    item.spanY = resultSpan[1];
2932                    AppWidgetHostView awhv = (AppWidgetHostView) cell;
2933                    AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
2934                            resultSpan[1]);
2935                }
2936
2937                if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
2938                    snapScreen = getPageIndexForScreenId(screenId);
2939                    snapToPage(snapScreen);
2940                }
2941
2942                if (foundCell) {
2943                    final ItemInfo info = (ItemInfo) cell.getTag();
2944                    if (hasMovedLayouts) {
2945                        // Reparent the view
2946                        getParentCellLayoutForView(cell).removeView(cell);
2947                        addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
2948                                info.spanX, info.spanY);
2949                    }
2950
2951                    // update the item's position after drop
2952                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2953                    lp.cellX = lp.tmpCellX = mTargetCell[0];
2954                    lp.cellY = lp.tmpCellY = mTargetCell[1];
2955                    lp.cellHSpan = item.spanX;
2956                    lp.cellVSpan = item.spanY;
2957                    lp.isLockedToGrid = true;
2958
2959                    if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
2960                            cell instanceof LauncherAppWidgetHostView) {
2961                        final CellLayout cellLayout = dropTargetLayout;
2962                        // We post this call so that the widget has a chance to be placed
2963                        // in its final location
2964
2965                        final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
2966                        AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
2967                        if (pinfo != null &&
2968                                pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
2969                            final Runnable addResizeFrame = new Runnable() {
2970                                public void run() {
2971                                    DragLayer dragLayer = mLauncher.getDragLayer();
2972                                    dragLayer.addResizeFrame(info, hostView, cellLayout);
2973                                }
2974                            };
2975                            resizeRunnable = (new Runnable() {
2976                                public void run() {
2977                                    if (!isPageMoving()) {
2978                                        addResizeFrame.run();
2979                                    } else {
2980                                        mDelayedResizeRunnable = addResizeFrame;
2981                                    }
2982                                }
2983                            });
2984                        }
2985                    }
2986
2987                    LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX,
2988                            lp.cellY, item.spanX, item.spanY);
2989                } else {
2990                    // If we can't find a drop location, we return the item to its original position
2991                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2992                    mTargetCell[0] = lp.cellX;
2993                    mTargetCell[1] = lp.cellY;
2994                    CellLayout layout = (CellLayout) cell.getParent().getParent();
2995                    layout.markCellsAsOccupiedForView(cell);
2996                }
2997            }
2998
2999            final CellLayout parent = (CellLayout) cell.getParent().getParent();
3000            final Runnable finalResizeRunnable = resizeRunnable;
3001            // Prepare it to be animated into its new position
3002            // This must be called after the view has been re-parented
3003            final Runnable onCompleteRunnable = new Runnable() {
3004                @Override
3005                public void run() {
3006                    mAnimatingViewIntoPlace = false;
3007                    updateChildrenLayersEnabled(false);
3008                    if (finalResizeRunnable != null) {
3009                        finalResizeRunnable.run();
3010                    }
3011                    removeExtraEmptyScreen(true, null, 0, true);
3012                }
3013            };
3014            mAnimatingViewIntoPlace = true;
3015            if (d.dragView.hasDrawn()) {
3016                final ItemInfo info = (ItemInfo) cell.getTag();
3017                if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
3018                    int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
3019                            ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3020                    animateWidgetDrop(info, parent, d.dragView,
3021                            onCompleteRunnable, animationType, cell, false);
3022                } else {
3023                    int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
3024                    mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
3025                            onCompleteRunnable, this);
3026                }
3027            } else {
3028                d.deferDragViewCleanupPostAnimation = false;
3029                cell.setVisibility(VISIBLE);
3030            }
3031            parent.onDropChild(cell);
3032        }
3033    }
3034
3035    public void setFinalScrollForPageChange(int pageIndex) {
3036        CellLayout cl = (CellLayout) getChildAt(pageIndex);
3037        if (cl != null) {
3038            mSavedScrollX = getScrollX();
3039            mSavedTranslationX = cl.getTranslationX();
3040            mSavedRotationY = cl.getRotationY();
3041            final int newX = getScrollForPage(pageIndex);
3042            setScrollX(newX);
3043            cl.setTranslationX(0f);
3044            cl.setRotationY(0f);
3045        }
3046    }
3047
3048    public void resetFinalScrollForPageChange(int pageIndex) {
3049        if (pageIndex >= 0) {
3050            CellLayout cl = (CellLayout) getChildAt(pageIndex);
3051            setScrollX(mSavedScrollX);
3052            cl.setTranslationX(mSavedTranslationX);
3053            cl.setRotationY(mSavedRotationY);
3054        }
3055    }
3056
3057    public void getViewLocationRelativeToSelf(View v, int[] location) {
3058        getLocationInWindow(location);
3059        int x = location[0];
3060        int y = location[1];
3061
3062        v.getLocationInWindow(location);
3063        int vX = location[0];
3064        int vY = location[1];
3065
3066        location[0] = vX - x;
3067        location[1] = vY - y;
3068    }
3069
3070    public void onDragEnter(DragObject d) {
3071        mDragEnforcer.onDragEnter();
3072        mCreateUserFolderOnDrop = false;
3073        mAddToExistingFolderOnDrop = false;
3074
3075        mDropToLayout = null;
3076        CellLayout layout = getCurrentDropLayout();
3077        setCurrentDropLayout(layout);
3078        setCurrentDragOverlappingLayout(layout);
3079
3080        // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we
3081        // don't need to show the outlines
3082        if (LauncherAppState.getInstance().isScreenLarge()) {
3083            showOutlines();
3084        }
3085    }
3086
3087    /** Return a rect that has the cellWidth/cellHeight (left, top), and
3088     * widthGap/heightGap (right, bottom) */
3089    static Rect getCellLayoutMetrics(Launcher launcher, int orientation) {
3090        LauncherAppState app = LauncherAppState.getInstance();
3091        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
3092
3093        Resources res = launcher.getResources();
3094        Display display = launcher.getWindowManager().getDefaultDisplay();
3095        Point smallestSize = new Point();
3096        Point largestSize = new Point();
3097        display.getCurrentSizeRange(smallestSize, largestSize);
3098        int countX = (int) grid.numColumns;
3099        int countY = (int) grid.numRows;
3100        int constrainedLongEdge = largestSize.y;
3101        int constrainedShortEdge = smallestSize.y;
3102        if (orientation == CellLayout.LANDSCAPE) {
3103            if (mLandscapeCellLayoutMetrics == null) {
3104                Rect padding = grid.getWorkspacePadding(CellLayout.LANDSCAPE);
3105                int width = constrainedLongEdge - padding.left - padding.right;
3106                int height = constrainedShortEdge - padding.top - padding.bottom;
3107                mLandscapeCellLayoutMetrics = new Rect();
3108                mLandscapeCellLayoutMetrics.set(
3109                        grid.calculateCellWidth(width, countX),
3110                        grid.calculateCellHeight(height, countY), 0, 0);
3111            }
3112            return mLandscapeCellLayoutMetrics;
3113        } else if (orientation == CellLayout.PORTRAIT) {
3114            if (mPortraitCellLayoutMetrics == null) {
3115                Rect padding = grid.getWorkspacePadding(CellLayout.PORTRAIT);
3116                int width = constrainedShortEdge - padding.left - padding.right;
3117                int height = constrainedLongEdge - padding.top - padding.bottom;
3118                mPortraitCellLayoutMetrics = new Rect();
3119                mPortraitCellLayoutMetrics.set(
3120                        grid.calculateCellWidth(width, countX),
3121                        grid.calculateCellHeight(height, countY), 0, 0);
3122            }
3123            return mPortraitCellLayoutMetrics;
3124        }
3125        return null;
3126    }
3127
3128    public void onDragExit(DragObject d) {
3129        mDragEnforcer.onDragExit();
3130
3131        // Here we store the final page that will be dropped to, if the workspace in fact
3132        // receives the drop
3133        if (mInScrollArea) {
3134            if (isPageMoving()) {
3135                // If the user drops while the page is scrolling, we should use that page as the
3136                // destination instead of the page that is being hovered over.
3137                mDropToLayout = (CellLayout) getPageAt(getNextPage());
3138            } else {
3139                mDropToLayout = mDragOverlappingLayout;
3140            }
3141        } else {
3142            mDropToLayout = mDragTargetLayout;
3143        }
3144
3145        if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
3146            mCreateUserFolderOnDrop = true;
3147        } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
3148            mAddToExistingFolderOnDrop = true;
3149        }
3150
3151        // Reset the scroll area and previous drag target
3152        onResetScrollArea();
3153        setCurrentDropLayout(null);
3154        setCurrentDragOverlappingLayout(null);
3155
3156        mSpringLoadedDragController.cancel();
3157
3158        if (!mIsPageMoving) {
3159            hideOutlines();
3160        }
3161    }
3162
3163    void setCurrentDropLayout(CellLayout layout) {
3164        if (mDragTargetLayout != null) {
3165            mDragTargetLayout.revertTempState();
3166            mDragTargetLayout.onDragExit();
3167        }
3168        mDragTargetLayout = layout;
3169        if (mDragTargetLayout != null) {
3170            mDragTargetLayout.onDragEnter();
3171        }
3172        cleanupReorder(true);
3173        cleanupFolderCreation();
3174        setCurrentDropOverCell(-1, -1);
3175    }
3176
3177    void setCurrentDragOverlappingLayout(CellLayout layout) {
3178        if (mDragOverlappingLayout != null) {
3179            mDragOverlappingLayout.setIsDragOverlapping(false);
3180        }
3181        mDragOverlappingLayout = layout;
3182        if (mDragOverlappingLayout != null) {
3183            mDragOverlappingLayout.setIsDragOverlapping(true);
3184        }
3185        invalidate();
3186    }
3187
3188    void setCurrentDropOverCell(int x, int y) {
3189        if (x != mDragOverX || y != mDragOverY) {
3190            mDragOverX = x;
3191            mDragOverY = y;
3192            setDragMode(DRAG_MODE_NONE);
3193        }
3194    }
3195
3196    void setDragMode(int dragMode) {
3197        if (dragMode != mDragMode) {
3198            if (dragMode == DRAG_MODE_NONE) {
3199                cleanupAddToFolder();
3200                // We don't want to cancel the re-order alarm every time the target cell changes
3201                // as this feels to slow / unresponsive.
3202                cleanupReorder(false);
3203                cleanupFolderCreation();
3204            } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
3205                cleanupReorder(true);
3206                cleanupFolderCreation();
3207            } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
3208                cleanupAddToFolder();
3209                cleanupReorder(true);
3210            } else if (dragMode == DRAG_MODE_REORDER) {
3211                cleanupAddToFolder();
3212                cleanupFolderCreation();
3213            }
3214            mDragMode = dragMode;
3215        }
3216    }
3217
3218    private void cleanupFolderCreation() {
3219        if (mDragFolderRingAnimator != null) {
3220            mDragFolderRingAnimator.animateToNaturalState();
3221            mDragFolderRingAnimator = null;
3222        }
3223        mFolderCreationAlarm.setOnAlarmListener(null);
3224        mFolderCreationAlarm.cancelAlarm();
3225    }
3226
3227    private void cleanupAddToFolder() {
3228        if (mDragOverFolderIcon != null) {
3229            mDragOverFolderIcon.onDragExit(null);
3230            mDragOverFolderIcon = null;
3231        }
3232    }
3233
3234    private void cleanupReorder(boolean cancelAlarm) {
3235        // Any pending reorders are canceled
3236        if (cancelAlarm) {
3237            mReorderAlarm.cancelAlarm();
3238        }
3239        mLastReorderX = -1;
3240        mLastReorderY = -1;
3241    }
3242
3243   /*
3244    *
3245    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
3246    * coordinate space. The argument xy is modified with the return result.
3247    *
3248    * if cachedInverseMatrix is not null, this method will just use that matrix instead of
3249    * computing it itself; we use this to avoid redundant matrix inversions in
3250    * findMatchingPageForDragOver
3251    *
3252    */
3253   void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
3254       xy[0] = xy[0] - v.getLeft();
3255       xy[1] = xy[1] - v.getTop();
3256   }
3257
3258   boolean isPointInSelfOverHotseat(int x, int y, Rect r) {
3259       if (r == null) {
3260           r = new Rect();
3261       }
3262       mTempPt[0] = x;
3263       mTempPt[1] = y;
3264       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
3265
3266       LauncherAppState app = LauncherAppState.getInstance();
3267       DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
3268       r = grid.getHotseatRect();
3269       if (r.contains(mTempPt[0], mTempPt[1])) {
3270           return true;
3271       }
3272       return false;
3273   }
3274
3275   void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
3276       mTempPt[0] = (int) xy[0];
3277       mTempPt[1] = (int) xy[1];
3278       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
3279       mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempPt);
3280
3281       xy[0] = mTempPt[0];
3282       xy[1] = mTempPt[1];
3283   }
3284
3285   /*
3286    *
3287    * Convert the 2D coordinate xy from this CellLayout's coordinate space to
3288    * the parent View's coordinate space. The argument xy is modified with the return result.
3289    *
3290    */
3291   void mapPointFromChildToSelf(View v, float[] xy) {
3292       xy[0] += v.getLeft();
3293       xy[1] += v.getTop();
3294   }
3295
3296   static private float squaredDistance(float[] point1, float[] point2) {
3297        float distanceX = point1[0] - point2[0];
3298        float distanceY = point2[1] - point2[1];
3299        return distanceX * distanceX + distanceY * distanceY;
3300   }
3301
3302    /*
3303     *
3304     * This method returns the CellLayout that is currently being dragged to. In order to drag
3305     * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
3306     * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
3307     *
3308     * Return null if no CellLayout is currently being dragged over
3309     *
3310     */
3311    private CellLayout findMatchingPageForDragOver(
3312            DragView dragView, float originX, float originY, boolean exact) {
3313        // We loop through all the screens (ie CellLayouts) and see which ones overlap
3314        // with the item being dragged and then choose the one that's closest to the touch point
3315        final int screenCount = getChildCount();
3316        CellLayout bestMatchingScreen = null;
3317        float smallestDistSoFar = Float.MAX_VALUE;
3318
3319        for (int i = 0; i < screenCount; i++) {
3320            // The custom content screen is not a valid drag over option
3321            if (mScreenOrder.get(i) == CUSTOM_CONTENT_SCREEN_ID) {
3322                continue;
3323            }
3324
3325            CellLayout cl = (CellLayout) getChildAt(i);
3326
3327            final float[] touchXy = {originX, originY};
3328            // Transform the touch coordinates to the CellLayout's local coordinates
3329            // If the touch point is within the bounds of the cell layout, we can return immediately
3330            cl.getMatrix().invert(mTempInverseMatrix);
3331            mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
3332
3333            if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
3334                    touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
3335                return cl;
3336            }
3337
3338            if (!exact) {
3339                // Get the center of the cell layout in screen coordinates
3340                final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
3341                cellLayoutCenter[0] = cl.getWidth()/2;
3342                cellLayoutCenter[1] = cl.getHeight()/2;
3343                mapPointFromChildToSelf(cl, cellLayoutCenter);
3344
3345                touchXy[0] = originX;
3346                touchXy[1] = originY;
3347
3348                // Calculate the distance between the center of the CellLayout
3349                // and the touch point
3350                float dist = squaredDistance(touchXy, cellLayoutCenter);
3351
3352                if (dist < smallestDistSoFar) {
3353                    smallestDistSoFar = dist;
3354                    bestMatchingScreen = cl;
3355                }
3356            }
3357        }
3358        return bestMatchingScreen;
3359    }
3360
3361    // This is used to compute the visual center of the dragView. This point is then
3362    // used to visualize drop locations and determine where to drop an item. The idea is that
3363    // the visual center represents the user's interpretation of where the item is, and hence
3364    // is the appropriate point to use when determining drop location.
3365    private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
3366            DragView dragView, float[] recycle) {
3367        float res[];
3368        if (recycle == null) {
3369            res = new float[2];
3370        } else {
3371            res = recycle;
3372        }
3373
3374        // First off, the drag view has been shifted in a way that is not represented in the
3375        // x and y values or the x/yOffsets. Here we account for that shift.
3376        x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX);
3377        y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
3378
3379        // These represent the visual top and left of drag view if a dragRect was provided.
3380        // If a dragRect was not provided, then they correspond to the actual view left and
3381        // top, as the dragRect is in that case taken to be the entire dragView.
3382        // R.dimen.dragViewOffsetY.
3383        int left = x - xOffset;
3384        int top = y - yOffset;
3385
3386        // In order to find the visual center, we shift by half the dragRect
3387        res[0] = left + dragView.getDragRegion().width() / 2;
3388        res[1] = top + dragView.getDragRegion().height() / 2;
3389
3390        return res;
3391    }
3392
3393    private boolean isDragWidget(DragObject d) {
3394        return (d.dragInfo instanceof LauncherAppWidgetInfo ||
3395                d.dragInfo instanceof PendingAddWidgetInfo);
3396    }
3397    private boolean isExternalDragWidget(DragObject d) {
3398        return d.dragSource != this && isDragWidget(d);
3399    }
3400
3401    public void onDragOver(DragObject d) {
3402        // Skip drag over events while we are dragging over side pages
3403        if (mInScrollArea || mIsSwitchingState || mState == State.SMALL) return;
3404
3405        Rect r = new Rect();
3406        CellLayout layout = null;
3407        ItemInfo item = (ItemInfo) d.dragInfo;
3408
3409        // Ensure that we have proper spans for the item that we are dropping
3410        if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
3411        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
3412            d.dragView, mDragViewVisualCenter);
3413
3414        final View child = (mDragInfo == null) ? null : mDragInfo.cell;
3415        // Identify whether we have dragged over a side page
3416        if (isSmall()) {
3417            if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
3418                if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3419                    layout = mLauncher.getHotseat().getLayout();
3420                }
3421            }
3422            if (layout == null) {
3423                layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false);
3424            }
3425            if (layout != mDragTargetLayout) {
3426                setCurrentDropLayout(layout);
3427                setCurrentDragOverlappingLayout(layout);
3428
3429                boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
3430                if (isInSpringLoadedMode) {
3431                    if (mLauncher.isHotseatLayout(layout)) {
3432                        mSpringLoadedDragController.cancel();
3433                    } else {
3434                        mSpringLoadedDragController.setAlarm(mDragTargetLayout);
3435                    }
3436                }
3437            }
3438        } else {
3439            // Test to see if we are over the hotseat otherwise just use the current page
3440            if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
3441                if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3442                    layout = mLauncher.getHotseat().getLayout();
3443                }
3444            }
3445            if (layout == null) {
3446                layout = getCurrentDropLayout();
3447            }
3448            if (layout != mDragTargetLayout) {
3449                setCurrentDropLayout(layout);
3450                setCurrentDragOverlappingLayout(layout);
3451            }
3452        }
3453
3454        // Handle the drag over
3455        if (mDragTargetLayout != null) {
3456            // We want the point to be mapped to the dragTarget.
3457            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
3458                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
3459            } else {
3460                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
3461            }
3462
3463            ItemInfo info = (ItemInfo) d.dragInfo;
3464
3465            int minSpanX = item.spanX;
3466            int minSpanY = item.spanY;
3467            if (item.minSpanX > 0 && item.minSpanY > 0) {
3468                minSpanX = item.minSpanX;
3469                minSpanY = item.minSpanY;
3470            }
3471
3472            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3473                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY,
3474                    mDragTargetLayout, mTargetCell);
3475            int reorderX = mTargetCell[0];
3476            int reorderY = mTargetCell[1];
3477
3478            setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
3479
3480            float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
3481                    mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
3482
3483            final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0],
3484                    mTargetCell[1]);
3485
3486            manageFolderFeedback(info, mDragTargetLayout, mTargetCell,
3487                    targetCellDistance, dragOverView);
3488
3489            boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
3490                    mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
3491                    item.spanY, child, mTargetCell);
3492
3493            if (!nearestDropOccupied) {
3494                mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3495                        (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
3496                        mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
3497                        d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
3498            } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
3499                    && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
3500                    mLastReorderY != reorderY)) {
3501
3502                // Otherwise, if we aren't adding to or creating a folder and there's no pending
3503                // reorder, then we schedule a reorder
3504                ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
3505                        minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child);
3506                mReorderAlarm.setOnAlarmListener(listener);
3507                mReorderAlarm.setAlarm(REORDER_TIMEOUT);
3508            }
3509
3510            if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
3511                    !nearestDropOccupied) {
3512                if (mDragTargetLayout != null) {
3513                    mDragTargetLayout.revertTempState();
3514                }
3515            }
3516        }
3517    }
3518
3519    private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
3520            int[] targetCell, float distance, View dragOverView) {
3521        boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
3522                false);
3523
3524        if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
3525                !mFolderCreationAlarm.alarmPending()) {
3526            mFolderCreationAlarm.setOnAlarmListener(new
3527                    FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
3528            mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
3529            return;
3530        }
3531
3532        boolean willAddToFolder =
3533                willAddToExistingUserFolder(info, targetLayout, targetCell, distance);
3534
3535        if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
3536            mDragOverFolderIcon = ((FolderIcon) dragOverView);
3537            mDragOverFolderIcon.onDragEnter(info);
3538            if (targetLayout != null) {
3539                targetLayout.clearDragOutlines();
3540            }
3541            setDragMode(DRAG_MODE_ADD_TO_FOLDER);
3542            return;
3543        }
3544
3545        if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
3546            setDragMode(DRAG_MODE_NONE);
3547        }
3548        if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
3549            setDragMode(DRAG_MODE_NONE);
3550        }
3551
3552        return;
3553    }
3554
3555    class FolderCreationAlarmListener implements OnAlarmListener {
3556        CellLayout layout;
3557        int cellX;
3558        int cellY;
3559
3560        public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
3561            this.layout = layout;
3562            this.cellX = cellX;
3563            this.cellY = cellY;
3564        }
3565
3566        public void onAlarm(Alarm alarm) {
3567            if (mDragFolderRingAnimator != null) {
3568                // This shouldn't happen ever, but just in case, make sure we clean up the mess.
3569                mDragFolderRingAnimator.animateToNaturalState();
3570            }
3571            mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
3572            mDragFolderRingAnimator.setCell(cellX, cellY);
3573            mDragFolderRingAnimator.setCellLayout(layout);
3574            mDragFolderRingAnimator.animateToAcceptState();
3575            layout.showFolderAccept(mDragFolderRingAnimator);
3576            layout.clearDragOutlines();
3577            setDragMode(DRAG_MODE_CREATE_FOLDER);
3578        }
3579    }
3580
3581    class ReorderAlarmListener implements OnAlarmListener {
3582        float[] dragViewCenter;
3583        int minSpanX, minSpanY, spanX, spanY;
3584        DragView dragView;
3585        View child;
3586
3587        public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
3588                int spanY, DragView dragView, View child) {
3589            this.dragViewCenter = dragViewCenter;
3590            this.minSpanX = minSpanX;
3591            this.minSpanY = minSpanY;
3592            this.spanX = spanX;
3593            this.spanY = spanY;
3594            this.child = child;
3595            this.dragView = dragView;
3596        }
3597
3598        public void onAlarm(Alarm alarm) {
3599            int[] resultSpan = new int[2];
3600            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3601                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout,
3602                    mTargetCell);
3603            mLastReorderX = mTargetCell[0];
3604            mLastReorderY = mTargetCell[1];
3605
3606            mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
3607                (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
3608                child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
3609
3610            if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
3611                mDragTargetLayout.revertTempState();
3612            } else {
3613                setDragMode(DRAG_MODE_REORDER);
3614            }
3615
3616            boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
3617            mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3618                (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
3619                mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize,
3620                dragView.getDragVisualizeOffset(), dragView.getDragRegion());
3621        }
3622    }
3623
3624    @Override
3625    public void getHitRectRelativeToDragLayer(Rect outRect) {
3626        // We want the workspace to have the whole area of the display (it will find the correct
3627        // cell layout to drop to in the existing drag/drop logic.
3628        mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect);
3629    }
3630
3631    /**
3632     * Add the item specified by dragInfo to the given layout.
3633     * @return true if successful
3634     */
3635    public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
3636        if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
3637            onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
3638            return true;
3639        }
3640        mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
3641        return false;
3642    }
3643
3644    private void onDropExternal(int[] touchXY, Object dragInfo,
3645            CellLayout cellLayout, boolean insertAtFirst) {
3646        onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
3647    }
3648
3649    /**
3650     * Drop an item that didn't originate on one of the workspace screens.
3651     * It may have come from Launcher (e.g. from all apps or customize), or it may have
3652     * come from another app altogether.
3653     *
3654     * NOTE: This can also be called when we are outside of a drag event, when we want
3655     * to add an item to one of the workspace screens.
3656     */
3657    private void onDropExternal(final int[] touchXY, final Object dragInfo,
3658            final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
3659        final Runnable exitSpringLoadedRunnable = new Runnable() {
3660            @Override
3661            public void run() {
3662                removeExtraEmptyScreen(false, new Runnable() {
3663                    @Override
3664                    public void run() {
3665                        mLauncher.exitSpringLoadedDragModeDelayed(true,
3666                                Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
3667                    }
3668                });
3669            }
3670        };
3671
3672        ItemInfo info = (ItemInfo) dragInfo;
3673        int spanX = info.spanX;
3674        int spanY = info.spanY;
3675        if (mDragInfo != null) {
3676            spanX = mDragInfo.spanX;
3677            spanY = mDragInfo.spanY;
3678        }
3679
3680        final long container = mLauncher.isHotseatLayout(cellLayout) ?
3681                LauncherSettings.Favorites.CONTAINER_HOTSEAT :
3682                    LauncherSettings.Favorites.CONTAINER_DESKTOP;
3683        final long screenId = getIdForScreen(cellLayout);
3684        if (!mLauncher.isHotseatLayout(cellLayout)
3685                && screenId != getScreenIdForPageIndex(mCurrentPage)
3686                && mState != State.SPRING_LOADED) {
3687            snapToScreenId(screenId, null);
3688        }
3689
3690        if (info instanceof PendingAddItemInfo) {
3691            final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
3692
3693            boolean findNearestVacantCell = true;
3694            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
3695                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3696                        cellLayout, mTargetCell);
3697                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3698                        mDragViewVisualCenter[1], mTargetCell);
3699                if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell,
3700                        distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
3701                                cellLayout, mTargetCell, distance)) {
3702                    findNearestVacantCell = false;
3703                }
3704            }
3705
3706            final ItemInfo item = (ItemInfo) d.dragInfo;
3707            boolean updateWidgetSize = false;
3708            if (findNearestVacantCell) {
3709                int minSpanX = item.spanX;
3710                int minSpanY = item.spanY;
3711                if (item.minSpanX > 0 && item.minSpanY > 0) {
3712                    minSpanX = item.minSpanX;
3713                    minSpanY = item.minSpanY;
3714                }
3715                int[] resultSpan = new int[2];
3716                mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
3717                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
3718                        null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
3719
3720                if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) {
3721                    updateWidgetSize = true;
3722                }
3723                item.spanX = resultSpan[0];
3724                item.spanY = resultSpan[1];
3725            }
3726
3727            Runnable onAnimationCompleteRunnable = new Runnable() {
3728                @Override
3729                public void run() {
3730                    // When dragging and dropping from customization tray, we deal with creating
3731                    // widgets/shortcuts/folders in a slightly different way
3732                    switch (pendingInfo.itemType) {
3733                    case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
3734                        int span[] = new int[2];
3735                        span[0] = item.spanX;
3736                        span[1] = item.spanY;
3737                        mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
3738                                container, screenId, mTargetCell, span, null);
3739                        break;
3740                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3741                        mLauncher.processShortcutFromDrop(pendingInfo.componentName,
3742                                container, screenId, mTargetCell, null);
3743                        break;
3744                    default:
3745                        throw new IllegalStateException("Unknown item type: " +
3746                                pendingInfo.itemType);
3747                    }
3748                }
3749            };
3750            View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
3751                    ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
3752
3753            if (finalView instanceof AppWidgetHostView && updateWidgetSize) {
3754                AppWidgetHostView awhv = (AppWidgetHostView) finalView;
3755                AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, item.spanX,
3756                        item.spanY);
3757            }
3758
3759            int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3760            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
3761                    ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
3762                animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
3763            }
3764            animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
3765                    animationStyle, finalView, true);
3766        } else {
3767            // This is for other drag/drop cases, like dragging from All Apps
3768            View view = null;
3769
3770            switch (info.itemType) {
3771            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3772            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3773                if (info.container == NO_ID && info instanceof AppInfo) {
3774                    // Came from all apps -- make a copy
3775                    info = new ShortcutInfo((AppInfo) info);
3776                }
3777                view = mLauncher.createShortcut(R.layout.application, cellLayout,
3778                        (ShortcutInfo) info);
3779                break;
3780            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3781                view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
3782                        (FolderInfo) info, mIconCache);
3783                break;
3784            default:
3785                throw new IllegalStateException("Unknown item type: " + info.itemType);
3786            }
3787
3788            // First we find the cell nearest to point at which the item is
3789            // dropped, without any consideration to whether there is an item there.
3790            if (touchXY != null) {
3791                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3792                        cellLayout, mTargetCell);
3793                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3794                        mDragViewVisualCenter[1], mTargetCell);
3795                d.postAnimationRunnable = exitSpringLoadedRunnable;
3796                if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
3797                        true, d.dragView, d.postAnimationRunnable)) {
3798                    return;
3799                }
3800                if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
3801                        true)) {
3802                    return;
3803                }
3804            }
3805
3806            if (touchXY != null) {
3807                // when dragging and dropping, just find the closest free spot
3808                mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
3809                        (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
3810                        null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
3811            } else {
3812                cellLayout.findCellForSpan(mTargetCell, 1, 1);
3813            }
3814            addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX,
3815                    info.spanY, insertAtFirst);
3816            cellLayout.onDropChild(view);
3817            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
3818            cellLayout.getShortcutsAndWidgets().measureChild(view);
3819
3820            LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
3821                    lp.cellX, lp.cellY);
3822
3823            if (d.dragView != null) {
3824                // We wrap the animation call in the temporary set and reset of the current
3825                // cellLayout to its final transform -- this means we animate the drag view to
3826                // the correct final location.
3827                setFinalTransitionTransform(cellLayout);
3828                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
3829                        exitSpringLoadedRunnable, this);
3830                resetTransitionTransform(cellLayout);
3831            }
3832        }
3833    }
3834
3835    public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
3836        int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
3837                widgetInfo.spanY, widgetInfo, false);
3838        int visibility = layout.getVisibility();
3839        layout.setVisibility(VISIBLE);
3840
3841        int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
3842        int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
3843        Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
3844                Bitmap.Config.ARGB_8888);
3845        Canvas c = new Canvas(b);
3846
3847        layout.measure(width, height);
3848        layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
3849        layout.draw(c);
3850        c.setBitmap(null);
3851        layout.setVisibility(visibility);
3852        return b;
3853    }
3854
3855    private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
3856            DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell,
3857            boolean external, boolean scale) {
3858        // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
3859        // location and size on the home screen.
3860        int spanX = info.spanX;
3861        int spanY = info.spanY;
3862
3863        Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
3864        loc[0] = r.left;
3865        loc[1] = r.top;
3866
3867        setFinalTransitionTransform(layout);
3868        float cellLayoutScale =
3869                mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
3870        resetTransitionTransform(layout);
3871
3872        float dragViewScaleX;
3873        float dragViewScaleY;
3874        if (scale) {
3875            dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
3876            dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
3877        } else {
3878            dragViewScaleX = 1f;
3879            dragViewScaleY = 1f;
3880        }
3881
3882        // The animation will scale the dragView about its center, so we need to center about
3883        // the final location.
3884        loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
3885        loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
3886
3887        scaleXY[0] = dragViewScaleX * cellLayoutScale;
3888        scaleXY[1] = dragViewScaleY * cellLayoutScale;
3889    }
3890
3891    public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
3892            final Runnable onCompleteRunnable, int animationType, final View finalView,
3893            boolean external) {
3894        Rect from = new Rect();
3895        mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
3896
3897        int[] finalPos = new int[2];
3898        float scaleXY[] = new float[2];
3899        boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
3900        getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
3901                external, scalePreview);
3902
3903        Resources res = mLauncher.getResources();
3904        final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
3905
3906        // In the case where we've prebound the widget, we remove it from the DragLayer
3907        if (finalView instanceof AppWidgetHostView && external) {
3908            Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView");
3909            mLauncher.getDragLayer().removeView(finalView);
3910        }
3911        if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
3912            Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
3913            dragView.setCrossFadeBitmap(crossFadeBitmap);
3914            dragView.crossFade((int) (duration * 0.8f));
3915        } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) {
3916            scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0],  scaleXY[1]);
3917        }
3918
3919        DragLayer dragLayer = mLauncher.getDragLayer();
3920        if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
3921            mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
3922                    DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
3923        } else {
3924            int endStyle;
3925            if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
3926                endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
3927            } else {
3928                endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
3929            }
3930
3931            Runnable onComplete = new Runnable() {
3932                @Override
3933                public void run() {
3934                    if (finalView != null) {
3935                        finalView.setVisibility(VISIBLE);
3936                    }
3937                    if (onCompleteRunnable != null) {
3938                        onCompleteRunnable.run();
3939                    }
3940                }
3941            };
3942            dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
3943                    finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
3944                    duration, this);
3945        }
3946    }
3947
3948    public void setFinalTransitionTransform(CellLayout layout) {
3949        if (isSwitchingState()) {
3950            mCurrentScale = getScaleX();
3951            setScaleX(mNewScale);
3952            setScaleY(mNewScale);
3953        }
3954    }
3955    public void resetTransitionTransform(CellLayout layout) {
3956        if (isSwitchingState()) {
3957            setScaleX(mCurrentScale);
3958            setScaleY(mCurrentScale);
3959        }
3960    }
3961
3962    /**
3963     * Return the current {@link CellLayout}, correctly picking the destination
3964     * screen while a scroll is in progress.
3965     */
3966    public CellLayout getCurrentDropLayout() {
3967        return (CellLayout) getChildAt(getNextPage());
3968    }
3969
3970    /**
3971     * Return the current CellInfo describing our current drag; this method exists
3972     * so that Launcher can sync this object with the correct info when the activity is created/
3973     * destroyed
3974     *
3975     */
3976    public CellLayout.CellInfo getDragInfo() {
3977        return mDragInfo;
3978    }
3979
3980    public int getCurrentPageOffsetFromCustomContent() {
3981        return getNextPage() - numCustomPages();
3982    }
3983
3984    /**
3985     * Calculate the nearest cell where the given object would be dropped.
3986     *
3987     * pixelX and pixelY should be in the coordinate system of layout
3988     */
3989    private int[] findNearestArea(int pixelX, int pixelY,
3990            int spanX, int spanY, CellLayout layout, int[] recycle) {
3991        return layout.findNearestArea(
3992                pixelX, pixelY, spanX, spanY, recycle);
3993    }
3994
3995    void setup(DragController dragController) {
3996        mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
3997        mDragController = dragController;
3998
3999        // hardware layers on children are enabled on startup, but should be disabled until
4000        // needed
4001        updateChildrenLayersEnabled(false);
4002        setWallpaperDimension();
4003    }
4004
4005    /**
4006     * Called at the end of a drag which originated on the workspace.
4007     */
4008    public void onDropCompleted(final View target, final DragObject d,
4009            final boolean isFlingToDelete, final boolean success) {
4010        if (mDeferDropAfterUninstall) {
4011            mDeferredAction = new Runnable() {
4012                public void run() {
4013                    onDropCompleted(target, d, isFlingToDelete, success);
4014                    mDeferredAction = null;
4015                }
4016            };
4017            return;
4018        }
4019
4020        boolean beingCalledAfterUninstall = mDeferredAction != null;
4021
4022        if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) {
4023            if (target != this && mDragInfo != null) {
4024                CellLayout parentCell = getParentCellLayoutForView(mDragInfo.cell);
4025                if (parentCell != null) {
4026                    parentCell.removeView(mDragInfo.cell);
4027                }
4028                if (mDragInfo.cell instanceof DropTarget) {
4029                    mDragController.removeDropTarget((DropTarget) mDragInfo.cell);
4030                }
4031                // If we move the item to anything not on the Workspace, check if any empty
4032                // screens need to be removed. If we dropped back on the workspace, this will
4033                // be done post drop animation.
4034                removeExtraEmptyScreen(true, null, 0, true);
4035            }
4036        } else if (mDragInfo != null) {
4037            CellLayout cellLayout;
4038            if (mLauncher.isHotseatLayout(target)) {
4039                cellLayout = mLauncher.getHotseat().getLayout();
4040            } else {
4041                cellLayout = getScreenWithId(mDragInfo.screenId);
4042            }
4043            cellLayout.onDropChild(mDragInfo.cell);
4044        }
4045        if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful))
4046                && mDragInfo.cell != null) {
4047            mDragInfo.cell.setVisibility(VISIBLE);
4048        }
4049        mDragOutline = null;
4050        mDragInfo = null;
4051    }
4052
4053    public void deferCompleteDropAfterUninstallActivity() {
4054        mDeferDropAfterUninstall = true;
4055    }
4056
4057    /// maybe move this into a smaller part
4058    public void onUninstallActivityReturned(boolean success) {
4059        mDeferDropAfterUninstall = false;
4060        mUninstallSuccessful = success;
4061        if (mDeferredAction != null) {
4062            mDeferredAction.run();
4063        }
4064    }
4065
4066    void updateItemLocationsInDatabase(CellLayout cl) {
4067        int count = cl.getShortcutsAndWidgets().getChildCount();
4068
4069        long screenId = getIdForScreen(cl);
4070        int container = Favorites.CONTAINER_DESKTOP;
4071
4072        if (mLauncher.isHotseatLayout(cl)) {
4073            screenId = -1;
4074            container = Favorites.CONTAINER_HOTSEAT;
4075        }
4076
4077        for (int i = 0; i < count; i++) {
4078            View v = cl.getShortcutsAndWidgets().getChildAt(i);
4079            ItemInfo info = (ItemInfo) v.getTag();
4080            // Null check required as the AllApps button doesn't have an item info
4081            if (info != null && info.requiresDbUpdate) {
4082                info.requiresDbUpdate = false;
4083                LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, info.cellX,
4084                        info.cellY, info.spanX, info.spanY);
4085            }
4086        }
4087    }
4088
4089    ArrayList<ComponentName> getUniqueComponents(boolean stripDuplicates, ArrayList<ComponentName> duplicates) {
4090        ArrayList<ComponentName> uniqueIntents = new ArrayList<ComponentName>();
4091        getUniqueIntents((CellLayout) mLauncher.getHotseat().getLayout(), uniqueIntents, duplicates, false);
4092        int count = getChildCount();
4093        for (int i = 0; i < count; i++) {
4094            CellLayout cl = (CellLayout) getChildAt(i);
4095            getUniqueIntents(cl, uniqueIntents, duplicates, false);
4096        }
4097        return uniqueIntents;
4098    }
4099
4100    void getUniqueIntents(CellLayout cl, ArrayList<ComponentName> uniqueIntents,
4101            ArrayList<ComponentName> duplicates, boolean stripDuplicates) {
4102        int count = cl.getShortcutsAndWidgets().getChildCount();
4103
4104        ArrayList<View> children = new ArrayList<View>();
4105        for (int i = 0; i < count; i++) {
4106            View v = cl.getShortcutsAndWidgets().getChildAt(i);
4107            children.add(v);
4108        }
4109
4110        for (int i = 0; i < count; i++) {
4111            View v = children.get(i);
4112            ItemInfo info = (ItemInfo) v.getTag();
4113            // Null check required as the AllApps button doesn't have an item info
4114            if (info instanceof ShortcutInfo) {
4115                ShortcutInfo si = (ShortcutInfo) info;
4116                ComponentName cn = si.intent.getComponent();
4117
4118                Uri dataUri = si.intent.getData();
4119                // If dataUri is not null / empty or if this component isn't one that would
4120                // have previously showed up in the AllApps list, then this is a widget-type
4121                // shortcut, so ignore it.
4122                if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
4123                    continue;
4124                }
4125
4126                if (!uniqueIntents.contains(cn)) {
4127                    uniqueIntents.add(cn);
4128                } else {
4129                    if (stripDuplicates) {
4130                        cl.removeViewInLayout(v);
4131                        LauncherModel.deleteItemFromDatabase(mLauncher, si);
4132                    }
4133                    if (duplicates != null) {
4134                        duplicates.add(cn);
4135                    }
4136                }
4137            }
4138            if (v instanceof FolderIcon) {
4139                FolderIcon fi = (FolderIcon) v;
4140                ArrayList<View> items = fi.getFolder().getItemsInReadingOrder();
4141                for (int j = 0; j < items.size(); j++) {
4142                    if (items.get(j).getTag() instanceof ShortcutInfo) {
4143                        ShortcutInfo si = (ShortcutInfo) items.get(j).getTag();
4144                        ComponentName cn = si.intent.getComponent();
4145
4146                        Uri dataUri = si.intent.getData();
4147                        // If dataUri is not null / empty or if this component isn't one that would
4148                        // have previously showed up in the AllApps list, then this is a widget-type
4149                        // shortcut, so ignore it.
4150                        if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
4151                            continue;
4152                        }
4153
4154                        if (!uniqueIntents.contains(cn)) {
4155                            uniqueIntents.add(cn);
4156                        }  else {
4157                            if (stripDuplicates) {
4158                                fi.getFolderInfo().remove(si);
4159                                LauncherModel.deleteItemFromDatabase(mLauncher, si);
4160                            }
4161                            if (duplicates != null) {
4162                                duplicates.add(cn);
4163                            }
4164                        }
4165                    }
4166                }
4167            }
4168        }
4169    }
4170
4171    void saveWorkspaceToDb() {
4172        saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout());
4173        int count = getChildCount();
4174        for (int i = 0; i < count; i++) {
4175            CellLayout cl = (CellLayout) getChildAt(i);
4176            saveWorkspaceScreenToDb(cl);
4177        }
4178    }
4179
4180    void saveWorkspaceScreenToDb(CellLayout cl) {
4181        int count = cl.getShortcutsAndWidgets().getChildCount();
4182
4183        long screenId = getIdForScreen(cl);
4184        int container = Favorites.CONTAINER_DESKTOP;
4185
4186        Hotseat hotseat = mLauncher.getHotseat();
4187        if (mLauncher.isHotseatLayout(cl)) {
4188            screenId = -1;
4189            container = Favorites.CONTAINER_HOTSEAT;
4190        }
4191
4192        for (int i = 0; i < count; i++) {
4193            View v = cl.getShortcutsAndWidgets().getChildAt(i);
4194            ItemInfo info = (ItemInfo) v.getTag();
4195            // Null check required as the AllApps button doesn't have an item info
4196            if (info != null) {
4197                int cellX = info.cellX;
4198                int cellY = info.cellY;
4199                if (container == Favorites.CONTAINER_HOTSEAT) {
4200                    cellX = hotseat.getCellXFromOrder((int) info.screenId);
4201                    cellY = hotseat.getCellYFromOrder((int) info.screenId);
4202                }
4203                LauncherModel.addItemToDatabase(mLauncher, info, container, screenId, cellX,
4204                        cellY, false);
4205            }
4206            if (v instanceof FolderIcon) {
4207                FolderIcon fi = (FolderIcon) v;
4208                fi.getFolder().addItemLocationsInDatabase();
4209            }
4210        }
4211    }
4212
4213    @Override
4214    public float getIntrinsicIconScaleFactor() {
4215        return 1f;
4216    }
4217
4218    @Override
4219    public boolean supportsFlingToDelete() {
4220        return true;
4221    }
4222
4223    @Override
4224    public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
4225        // Do nothing
4226    }
4227
4228    @Override
4229    public void onFlingToDeleteCompleted() {
4230        // Do nothing
4231    }
4232
4233    public boolean isDropEnabled() {
4234        return true;
4235    }
4236
4237    @Override
4238    protected void onRestoreInstanceState(Parcelable state) {
4239        super.onRestoreInstanceState(state);
4240        Launcher.setScreen(mCurrentPage);
4241    }
4242
4243    @Override
4244    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
4245        // We don't dispatch restoreInstanceState to our children using this code path.
4246        // Some pages will be restored immediately as their items are bound immediately, and
4247        // others we will need to wait until after their items are bound.
4248        mSavedStates = container;
4249    }
4250
4251    public void restoreInstanceStateForChild(int child) {
4252        if (mSavedStates != null) {
4253            mRestoredPages.add(child);
4254            CellLayout cl = (CellLayout) getChildAt(child);
4255            cl.restoreInstanceState(mSavedStates);
4256        }
4257    }
4258
4259    public void restoreInstanceStateForRemainingPages() {
4260        int count = getChildCount();
4261        for (int i = 0; i < count; i++) {
4262            if (!mRestoredPages.contains(i)) {
4263                restoreInstanceStateForChild(i);
4264            }
4265        }
4266        mRestoredPages.clear();
4267        mSavedStates = null;
4268    }
4269
4270    @Override
4271    public void scrollLeft() {
4272        if (!isSmall() && !mIsSwitchingState) {
4273            super.scrollLeft();
4274        }
4275        Folder openFolder = getOpenFolder();
4276        if (openFolder != null) {
4277            openFolder.completeDragExit();
4278        }
4279    }
4280
4281    @Override
4282    public void scrollRight() {
4283        if (!isSmall() && !mIsSwitchingState) {
4284            super.scrollRight();
4285        }
4286        Folder openFolder = getOpenFolder();
4287        if (openFolder != null) {
4288            openFolder.completeDragExit();
4289        }
4290    }
4291
4292    @Override
4293    public boolean onEnterScrollArea(int x, int y, int direction) {
4294        // Ignore the scroll area if we are dragging over the hot seat
4295        boolean isPortrait = !LauncherAppState.isScreenLandscape(getContext());
4296        if (mLauncher.getHotseat() != null && isPortrait) {
4297            Rect r = new Rect();
4298            mLauncher.getHotseat().getHitRect(r);
4299            if (r.contains(x, y)) {
4300                return false;
4301            }
4302        }
4303
4304        boolean result = false;
4305        if (!isSmall() && !mIsSwitchingState && getOpenFolder() == null) {
4306            mInScrollArea = true;
4307
4308            final int page = getNextPage() +
4309                       (direction == DragController.SCROLL_LEFT ? -1 : 1);
4310            // We always want to exit the current layout to ensure parity of enter / exit
4311            setCurrentDropLayout(null);
4312
4313            if (0 <= page && page < getChildCount()) {
4314                // Ensure that we are not dragging over to the custom content screen
4315                if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) {
4316                    return false;
4317                }
4318
4319                CellLayout layout = (CellLayout) getChildAt(page);
4320                setCurrentDragOverlappingLayout(layout);
4321
4322                // Workspace is responsible for drawing the edge glow on adjacent pages,
4323                // so we need to redraw the workspace when this may have changed.
4324                invalidate();
4325                result = true;
4326            }
4327        }
4328        return result;
4329    }
4330
4331    @Override
4332    public boolean onExitScrollArea() {
4333        boolean result = false;
4334        if (mInScrollArea) {
4335            invalidate();
4336            CellLayout layout = getCurrentDropLayout();
4337            setCurrentDropLayout(layout);
4338            setCurrentDragOverlappingLayout(layout);
4339
4340            result = true;
4341            mInScrollArea = false;
4342        }
4343        return result;
4344    }
4345
4346    private void onResetScrollArea() {
4347        setCurrentDragOverlappingLayout(null);
4348        mInScrollArea = false;
4349    }
4350
4351    /**
4352     * Returns a specific CellLayout
4353     */
4354    CellLayout getParentCellLayoutForView(View v) {
4355        ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
4356        for (CellLayout layout : layouts) {
4357            if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
4358                return layout;
4359            }
4360        }
4361        return null;
4362    }
4363
4364    /**
4365     * Returns a list of all the CellLayouts in the workspace.
4366     */
4367    ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
4368        ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
4369        int screenCount = getChildCount();
4370        for (int screen = 0; screen < screenCount; screen++) {
4371            layouts.add(((CellLayout) getChildAt(screen)));
4372        }
4373        if (mLauncher.getHotseat() != null) {
4374            layouts.add(mLauncher.getHotseat().getLayout());
4375        }
4376        return layouts;
4377    }
4378
4379    /**
4380     * We should only use this to search for specific children.  Do not use this method to modify
4381     * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
4382     * the hotseat and workspace pages
4383     */
4384    ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
4385        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
4386                new ArrayList<ShortcutAndWidgetContainer>();
4387        int screenCount = getChildCount();
4388        for (int screen = 0; screen < screenCount; screen++) {
4389            childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
4390        }
4391        if (mLauncher.getHotseat() != null) {
4392            childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
4393        }
4394        return childrenLayouts;
4395    }
4396
4397    public Folder getFolderForTag(Object tag) {
4398        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
4399                getAllShortcutAndWidgetContainers();
4400        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
4401            int count = layout.getChildCount();
4402            for (int i = 0; i < count; i++) {
4403                View child = layout.getChildAt(i);
4404                if (child instanceof Folder) {
4405                    Folder f = (Folder) child;
4406                    if (f.getInfo() == tag && f.getInfo().opened) {
4407                        return f;
4408                    }
4409                }
4410            }
4411        }
4412        return null;
4413    }
4414
4415    public View getViewForTag(Object tag) {
4416        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
4417                getAllShortcutAndWidgetContainers();
4418        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
4419            int count = layout.getChildCount();
4420            for (int i = 0; i < count; i++) {
4421                View child = layout.getChildAt(i);
4422                if (child.getTag() == tag) {
4423                    return child;
4424                }
4425            }
4426        }
4427        return null;
4428    }
4429
4430    void clearDropTargets() {
4431        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
4432                getAllShortcutAndWidgetContainers();
4433        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
4434            int childCount = layout.getChildCount();
4435            for (int j = 0; j < childCount; j++) {
4436                View v = layout.getChildAt(j);
4437                if (v instanceof DropTarget) {
4438                    mDragController.removeDropTarget((DropTarget) v);
4439                }
4440            }
4441        }
4442    }
4443
4444    // Removes ALL items that match a given package name, this is usually called when a package
4445    // has been removed and we want to remove all components (widgets, shortcuts, apps) that
4446    // belong to that package.
4447    void removeItemsByPackageName(final ArrayList<String> packages) {
4448        final HashSet<String> packageNames = new HashSet<String>();
4449        packageNames.addAll(packages);
4450
4451        // Filter out all the ItemInfos that this is going to affect
4452        final HashSet<ItemInfo> infos = new HashSet<ItemInfo>();
4453        final HashSet<ComponentName> cns = new HashSet<ComponentName>();
4454        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4455        for (CellLayout layoutParent : cellLayouts) {
4456            ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4457            int childCount = layout.getChildCount();
4458            for (int i = 0; i < childCount; ++i) {
4459                View view = layout.getChildAt(i);
4460                infos.add((ItemInfo) view.getTag());
4461            }
4462        }
4463        LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4464            @Override
4465            public boolean filterItem(ItemInfo parent, ItemInfo info,
4466                                      ComponentName cn) {
4467                if (packageNames.contains(cn.getPackageName())) {
4468                    cns.add(cn);
4469                    return true;
4470                }
4471                return false;
4472            }
4473        };
4474        LauncherModel.filterItemInfos(infos, filter);
4475
4476        // Remove the affected components
4477        removeItemsByComponentName(cns);
4478    }
4479
4480    // Removes items that match the application info specified, when applications are removed
4481    // as a part of an update, this is called to ensure that other widgets and application
4482    // shortcuts are not removed.
4483    void removeItemsByApplicationInfo(final ArrayList<AppInfo> appInfos) {
4484        // Just create a hash table of all the specific components that this will affect
4485        HashSet<ComponentName> cns = new HashSet<ComponentName>();
4486        for (AppInfo info : appInfos) {
4487            cns.add(info.componentName);
4488        }
4489
4490        // Remove all the things
4491        removeItemsByComponentName(cns);
4492    }
4493
4494    void removeItemsByComponentName(final HashSet<ComponentName> componentNames) {
4495        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4496        for (final CellLayout layoutParent: cellLayouts) {
4497            final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4498
4499            final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
4500            for (int j = 0; j < layout.getChildCount(); j++) {
4501                final View view = layout.getChildAt(j);
4502                children.put((ItemInfo) view.getTag(), view);
4503            }
4504
4505            final ArrayList<View> childrenToRemove = new ArrayList<View>();
4506            final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove =
4507                    new HashMap<FolderInfo, ArrayList<ShortcutInfo>>();
4508            LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4509                @Override
4510                public boolean filterItem(ItemInfo parent, ItemInfo info,
4511                                          ComponentName cn) {
4512                    if (parent instanceof FolderInfo) {
4513                        if (componentNames.contains(cn)) {
4514                            FolderInfo folder = (FolderInfo) parent;
4515                            ArrayList<ShortcutInfo> appsToRemove;
4516                            if (folderAppsToRemove.containsKey(folder)) {
4517                                appsToRemove = folderAppsToRemove.get(folder);
4518                            } else {
4519                                appsToRemove = new ArrayList<ShortcutInfo>();
4520                                folderAppsToRemove.put(folder, appsToRemove);
4521                            }
4522                            appsToRemove.add((ShortcutInfo) info);
4523                            return true;
4524                        }
4525                    } else {
4526                        if (componentNames.contains(cn)) {
4527                            childrenToRemove.add(children.get(info));
4528                            return true;
4529                        }
4530                    }
4531                    return false;
4532                }
4533            };
4534            LauncherModel.filterItemInfos(children.keySet(), filter);
4535
4536            // Remove all the apps from their folders
4537            for (FolderInfo folder : folderAppsToRemove.keySet()) {
4538                ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
4539                for (ShortcutInfo info : appsToRemove) {
4540                    folder.remove(info);
4541                }
4542            }
4543
4544            // Remove all the other children
4545            for (View child : childrenToRemove) {
4546                // Note: We can not remove the view directly from CellLayoutChildren as this
4547                // does not re-mark the spaces as unoccupied.
4548                layoutParent.removeViewInLayout(child);
4549                if (child instanceof DropTarget) {
4550                    mDragController.removeDropTarget((DropTarget) child);
4551                }
4552            }
4553
4554            if (childrenToRemove.size() > 0) {
4555                layout.requestLayout();
4556                layout.invalidate();
4557            }
4558        }
4559
4560        // Strip all the empty screens
4561        stripEmptyScreens();
4562    }
4563
4564    private void updateShortcut(HashMap<ComponentName, AppInfo> appsMap, ItemInfo info,
4565                                View child) {
4566        ComponentName cn = info.getIntent().getComponent();
4567        if (cn != null) {
4568            AppInfo appInfo = appsMap.get(info.getIntent().getComponent());
4569            if ((appInfo != null) && LauncherModel.isShortcutInfoUpdateable(info)) {
4570                ShortcutInfo shortcutInfo = (ShortcutInfo) info;
4571                BubbleTextView shortcut = (BubbleTextView) child;
4572                shortcutInfo.updateIcon(mIconCache);
4573                shortcutInfo.title = appInfo.title.toString();
4574                shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache);
4575            }
4576        }
4577    }
4578
4579    void updateShortcuts(ArrayList<AppInfo> apps) {
4580        // Create a map of the apps to test against
4581        final HashMap<ComponentName, AppInfo> appsMap = new HashMap<ComponentName, AppInfo>();
4582        for (AppInfo ai : apps) {
4583            appsMap.put(ai.componentName, ai);
4584        }
4585
4586        ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers();
4587        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
4588            // Update all the children shortcuts
4589            final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
4590            for (int j = 0; j < layout.getChildCount(); j++) {
4591                View v = layout.getChildAt(j);
4592                ItemInfo info = (ItemInfo) v.getTag();
4593                if (info instanceof FolderInfo && v instanceof FolderIcon) {
4594                    FolderIcon folder = (FolderIcon) v;
4595                    ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder();
4596                    for (View fv : folderChildren) {
4597                        info = (ItemInfo) fv.getTag();
4598                        updateShortcut(appsMap, info, fv);
4599                    }
4600                    folder.invalidate();
4601                } else if (info instanceof ShortcutInfo) {
4602                    updateShortcut(appsMap, info, v);
4603                }
4604            }
4605        }
4606    }
4607
4608    private void moveToScreen(int page, boolean animate) {
4609        if (!isSmall()) {
4610            if (animate) {
4611                snapToPage(page);
4612            } else {
4613                setCurrentPage(page);
4614            }
4615        }
4616        View child = getChildAt(page);
4617        if (child != null) {
4618            child.requestFocus();
4619        }
4620    }
4621
4622    void moveToDefaultScreen(boolean animate) {
4623        moveToScreen(mDefaultPage, animate);
4624    }
4625
4626    void moveToCustomContentScreen(boolean animate) {
4627        if (hasCustomContent()) {
4628            int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
4629            if (animate) {
4630                snapToPage(ccIndex);
4631            } else {
4632                setCurrentPage(ccIndex);
4633            }
4634            View child = getChildAt(ccIndex);
4635            if (child != null) {
4636                child.requestFocus();
4637            }
4638         }
4639        exitWidgetResizeMode();
4640    }
4641
4642    @Override
4643    protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) {
4644        long screenId = getScreenIdForPageIndex(pageIndex);
4645        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
4646            int count = mScreenOrder.size() - numCustomPages();
4647            if (count > 1) {
4648                return new PageIndicator.PageMarkerResources(R.drawable.ic_pageindicator_current,
4649                        R.drawable.ic_pageindicator_add);
4650            }
4651        }
4652
4653        return super.getPageIndicatorMarker(pageIndex);
4654    }
4655
4656    @Override
4657    public void syncPages() {
4658    }
4659
4660    @Override
4661    public void syncPageItems(int page, boolean immediate) {
4662    }
4663
4664    protected String getPageIndicatorDescription() {
4665        String settings = getResources().getString(R.string.settings_button_text);
4666        return getCurrentPageDescription() + ", " + settings;
4667    }
4668
4669    protected String getCurrentPageDescription() {
4670        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
4671        int delta = numCustomPages();
4672        if (hasCustomContent() && getNextPage() == 0) {
4673            return mCustomContentDescription;
4674        }
4675        return String.format(getContext().getString(R.string.workspace_scroll_format),
4676                page + 1 - delta, getChildCount() - delta);
4677    }
4678
4679    public void getLocationInDragLayer(int[] loc) {
4680        mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
4681    }
4682}
4683