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