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