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