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