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