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