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 static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
20import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
21import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
22import static com.android.launcher3.LauncherState.ALL_APPS;
23import static com.android.launcher3.LauncherState.NORMAL;
24import static com.android.launcher3.LauncherState.SPRING_LOADED;
25import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_OVERLAY;
26
27import android.animation.Animator;
28import android.animation.AnimatorListenerAdapter;
29import android.animation.LayoutTransition;
30import android.animation.ObjectAnimator;
31import android.animation.PropertyValuesHolder;
32import android.animation.ValueAnimator;
33import android.animation.ValueAnimator.AnimatorUpdateListener;
34import android.annotation.SuppressLint;
35import android.app.WallpaperManager;
36import android.appwidget.AppWidgetHostView;
37import android.appwidget.AppWidgetProviderInfo;
38import android.content.Context;
39import android.content.res.Resources;
40import android.graphics.Bitmap;
41import android.graphics.Canvas;
42import android.graphics.Point;
43import android.graphics.Rect;
44import android.graphics.drawable.Drawable;
45import android.os.Handler;
46import android.os.IBinder;
47import android.os.Parcelable;
48import android.os.UserHandle;
49import android.util.AttributeSet;
50import android.util.Log;
51import android.util.SparseArray;
52import android.view.LayoutInflater;
53import android.view.MotionEvent;
54import android.view.View;
55import android.view.ViewGroup;
56import android.view.ViewTreeObserver;
57import android.widget.Toast;
58
59import com.android.launcher3.Launcher.LauncherOverlay;
60import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
61import com.android.launcher3.LauncherStateManager.AnimationConfig;
62import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
63import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
64import com.android.launcher3.anim.AnimatorSetBuilder;
65import com.android.launcher3.anim.Interpolators;
66import com.android.launcher3.badge.FolderBadgeInfo;
67import com.android.launcher3.compat.AppWidgetManagerCompat;
68import com.android.launcher3.config.FeatureFlags;
69import com.android.launcher3.dragndrop.DragController;
70import com.android.launcher3.dragndrop.DragLayer;
71import com.android.launcher3.dragndrop.DragOptions;
72import com.android.launcher3.dragndrop.DragView;
73import com.android.launcher3.dragndrop.SpringLoadedDragController;
74import com.android.launcher3.folder.Folder;
75import com.android.launcher3.folder.FolderIcon;
76import com.android.launcher3.folder.PreviewBackground;
77import com.android.launcher3.graphics.DragPreviewProvider;
78import com.android.launcher3.graphics.PreloadIconDrawable;
79import com.android.launcher3.pageindicators.WorkspacePageIndicator;
80import com.android.launcher3.popup.PopupContainerWithArrow;
81import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
82import com.android.launcher3.touch.ItemLongClickListener;
83import com.android.launcher3.touch.WorkspaceTouchListener;
84import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
85import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
86import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
87import com.android.launcher3.util.ItemInfoMatcher;
88import com.android.launcher3.util.LongArrayMap;
89import com.android.launcher3.util.PackageUserKey;
90import com.android.launcher3.util.Thunk;
91import com.android.launcher3.util.WallpaperOffsetInterpolator;
92import com.android.launcher3.widget.LauncherAppWidgetHostView;
93import com.android.launcher3.widget.PendingAddShortcutInfo;
94import com.android.launcher3.widget.PendingAddWidgetInfo;
95import com.android.launcher3.widget.PendingAppWidgetHostView;
96
97import java.util.ArrayList;
98import java.util.HashSet;
99import java.util.Set;
100
101/**
102 * The workspace is a wide area with a wallpaper and a finite number of pages.
103 * Each page contains a number of icons, folders or widgets the user can
104 * interact with. A workspace is meant to be used with a fixed width only.
105 */
106public class Workspace extends PagedView<WorkspacePageIndicator>
107        implements DropTarget, DragSource, View.OnTouchListener,
108        DragController.DragListener, Insettable, LauncherStateManager.StateHandler {
109    private static final String TAG = "Launcher.Workspace";
110
111    /** The value that {@link #mTransitionProgress} must be greater than for
112     * {@link #transitionStateShouldAllowDrop()} to return true. */
113    private static final float ALLOW_DROP_TRANSITION_PROGRESS = 0.25f;
114
115    /** The value that {@link #mTransitionProgress} must be greater than for
116     * {@link #isFinishedSwitchingState()} ()} to return true. */
117    private static final float FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS = 0.5f;
118
119    private static final boolean ENFORCE_DRAG_EVENT_ORDER = false;
120
121    private static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
122    private static final int FADE_EMPTY_SCREEN_DURATION = 150;
123
124    private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
125
126    private static final int DEFAULT_PAGE = 0;
127
128    private static final boolean MAP_NO_RECURSE = false;
129    private static final boolean MAP_RECURSE = true;
130
131    // The screen id used for the empty screen always present to the right.
132    public static final long EXTRA_EMPTY_SCREEN_ID = -201;
133    // The is the first screen. It is always present, even if its empty.
134    public static final long FIRST_SCREEN_ID = 0;
135
136    private LayoutTransition mLayoutTransition;
137    @Thunk final WallpaperManager mWallpaperManager;
138
139    private ShortcutAndWidgetContainer mDragSourceInternal;
140
141    @Thunk final LongArrayMap<CellLayout> mWorkspaceScreens = new LongArrayMap<>();
142    @Thunk final ArrayList<Long> mScreenOrder = new ArrayList<>();
143
144    @Thunk Runnable mRemoveEmptyScreenRunnable;
145    @Thunk boolean mDeferRemoveExtraEmptyScreen = false;
146
147    /**
148     * CellInfo for the cell that is currently being dragged
149     */
150    private CellLayout.CellInfo mDragInfo;
151
152    /**
153     * Target drop area calculated during last acceptDrop call.
154     */
155    @Thunk int[] mTargetCell = new int[2];
156    private int mDragOverX = -1;
157    private int mDragOverY = -1;
158
159    /**
160     * The CellLayout that is currently being dragged over
161     */
162    @Thunk CellLayout mDragTargetLayout = null;
163    /**
164     * The CellLayout that we will show as highlighted
165     */
166    private CellLayout mDragOverlappingLayout = null;
167
168    /**
169     * The CellLayout which will be dropped to
170     */
171    private CellLayout mDropToLayout = null;
172
173    @Thunk final Launcher mLauncher;
174    @Thunk DragController mDragController;
175
176    private final int[] mTempXY = new int[2];
177    @Thunk float[] mDragViewVisualCenter = new float[2];
178    private final float[] mTempTouchCoordinates = new float[2];
179
180    private SpringLoadedDragController mSpringLoadedDragController;
181
182    private boolean mIsSwitchingState = false;
183
184    boolean mChildrenLayersEnabled = true;
185
186    private boolean mStripScreensOnPageStopMoving = false;
187
188    private DragPreviewProvider mOutlineProvider = null;
189    private boolean mWorkspaceFadeInAdjacentScreens;
190
191    final WallpaperOffsetInterpolator mWallpaperOffset;
192    private boolean mUnlockWallpaperFromDefaultPageOnLayout;
193
194    // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
195    private static final int FOLDER_CREATION_TIMEOUT = 0;
196    public static final int REORDER_TIMEOUT = 650;
197    private final Alarm mFolderCreationAlarm = new Alarm();
198    private final Alarm mReorderAlarm = new Alarm();
199    private PreviewBackground mFolderCreateBg;
200    private FolderIcon mDragOverFolderIcon = null;
201    private boolean mCreateUserFolderOnDrop = false;
202    private boolean mAddToExistingFolderOnDrop = false;
203    private float mMaxDistanceForFolderCreation;
204
205    // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
206    private float mXDown;
207    private float mYDown;
208    final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
209    final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
210    final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
211
212    // Relating to the animation of items being dropped externally
213    public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0;
214    public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1;
215    public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2;
216    public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3;
217    public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4;
218
219    // Related to dragging, folder creation and reordering
220    private static final int DRAG_MODE_NONE = 0;
221    private static final int DRAG_MODE_CREATE_FOLDER = 1;
222    private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
223    private static final int DRAG_MODE_REORDER = 3;
224    private int mDragMode = DRAG_MODE_NONE;
225    @Thunk int mLastReorderX = -1;
226    @Thunk int mLastReorderY = -1;
227
228    private SparseArray<Parcelable> mSavedStates;
229    private final ArrayList<Integer> mRestoredPages = new ArrayList<>();
230
231    private float mCurrentScale;
232    private float mTransitionProgress;
233
234    // State related to Launcher Overlay
235    LauncherOverlay mLauncherOverlay;
236    boolean mScrollInteractionBegan;
237    boolean mStartedSendingScrollEvents;
238    float mLastOverlayScroll = 0;
239    boolean mOverlayShown = false;
240    private Runnable mOnOverlayHiddenCallback;
241
242    private boolean mForceDrawAdjacentPages = false;
243
244    // Total over scrollX in the overlay direction.
245    private float mOverlayTranslation;
246
247    // Handles workspace state transitions
248    private final WorkspaceStateTransitionAnimation mStateTransitionAnimation;
249
250    /**
251     * Used to inflate the Workspace from XML.
252     *
253     * @param context The application's context.
254     * @param attrs The attributes set containing the Workspace's customization values.
255     */
256    public Workspace(Context context, AttributeSet attrs) {
257        this(context, attrs, 0);
258    }
259
260    /**
261     * Used to inflate the Workspace from XML.
262     *
263     * @param context The application's context.
264     * @param attrs The attributes set containing the Workspace's customization values.
265     * @param defStyle Unused.
266     */
267    public Workspace(Context context, AttributeSet attrs, int defStyle) {
268        super(context, attrs, defStyle);
269
270        mLauncher = Launcher.getLauncher(context);
271        mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this);
272        mWallpaperManager = WallpaperManager.getInstance(context);
273
274        mWallpaperOffset = new WallpaperOffsetInterpolator(this);
275
276        setHapticFeedbackEnabled(false);
277        initWorkspace();
278
279        // Disable multitouch across the workspace/all apps/customize tray
280        setMotionEventSplittingEnabled(true);
281        setOnTouchListener(new WorkspaceTouchListener(mLauncher, this));
282    }
283
284    @Override
285    public void setInsets(Rect insets) {
286        mInsets.set(insets);
287
288        DeviceProfile grid = mLauncher.getDeviceProfile();
289        mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
290        mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
291
292        Rect padding = grid.workspacePadding;
293        setPadding(padding.left, padding.top, padding.right, padding.bottom);
294
295        if (grid.shouldFadeAdjacentWorkspaceScreens()) {
296            // In landscape mode the page spacing is set to the default.
297            setPageSpacing(grid.defaultPageSpacingPx);
298        } else {
299            // In portrait, we want the pages spaced such that there is no
300            // overhang of the previous / next page into the current page viewport.
301            // We assume symmetrical padding in portrait mode.
302            setPageSpacing(Math.max(grid.defaultPageSpacingPx, padding.left + 1));
303        }
304
305        int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx;
306        int paddingBottom = grid.cellLayoutBottomPaddingPx;
307        for (int i = mWorkspaceScreens.size() - 1; i >= 0; i--) {
308            mWorkspaceScreens.valueAt(i)
309                    .setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
310        }
311    }
312
313    /**
314     * Estimates the size of an item using spans: hSpan, vSpan.
315     *
316     * @return MAX_VALUE for each dimension if unsuccessful.
317     */
318    public int[] estimateItemSize(ItemInfo itemInfo) {
319        int[] size = new int[2];
320        if (getChildCount() > 0) {
321            // Use the first page to estimate the child position
322            CellLayout cl = (CellLayout) getChildAt(0);
323            boolean isWidget = itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
324
325            Rect r = estimateItemPosition(cl, 0, 0, itemInfo.spanX, itemInfo.spanY);
326
327            float scale = 1;
328            if (isWidget) {
329                DeviceProfile profile = mLauncher.getDeviceProfile();
330                scale = Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
331            }
332            size[0] = r.width();
333            size[1] = r.height();
334
335            if (isWidget) {
336                size[0] /= scale;
337                size[1] /= scale;
338            }
339            return size;
340        } else {
341            size[0] = Integer.MAX_VALUE;
342            size[1] = Integer.MAX_VALUE;
343            return size;
344        }
345    }
346
347    public float getWallpaperOffsetForCenterPage() {
348        int pageScroll = getScrollForPage(getPageNearestToCenterOfScreen());
349        return mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
350    }
351
352    public Rect estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan) {
353        Rect r = new Rect();
354        cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
355        return r;
356    }
357
358    @Override
359    public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
360        if (ENFORCE_DRAG_EVENT_ORDER) {
361            enforceDragParity("onDragStart", 0, 0);
362        }
363
364        if (mDragInfo != null && mDragInfo.cell != null) {
365            CellLayout layout = (CellLayout) mDragInfo.cell.getParent().getParent();
366            layout.markCellsAsUnoccupiedForView(mDragInfo.cell);
367        }
368
369        if (mOutlineProvider != null) {
370            if (dragObject.dragView != null) {
371                Bitmap preview = dragObject.dragView.getPreviewBitmap();
372
373                // The outline is used to visualize where the item will land if dropped
374                mOutlineProvider.generateDragOutline(preview);
375            }
376        }
377
378        updateChildrenLayersEnabled();
379
380        // Do not add a new page if it is a accessible drag which was not started by the workspace.
381        // We do not support accessibility drag from other sources and instead provide a direct
382        // action for move/add to homescreen.
383        // When a accessible drag is started by the folder, we only allow rearranging withing the
384        // folder.
385        boolean addNewPage = !(options.isAccessibleDrag && dragObject.dragSource != this);
386
387        if (addNewPage) {
388            mDeferRemoveExtraEmptyScreen = false;
389            addExtraEmptyScreenOnDrag();
390
391            if (dragObject.dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
392                    && dragObject.dragSource != this) {
393                // When dragging a widget from different source, move to a page which has
394                // enough space to place this widget (after rearranging/resizing). We special case
395                // widgets as they cannot be placed inside a folder.
396                // Start at the current page and search right (on LTR) until finding a page with
397                // enough space. Since an empty screen is the furthest right, a page must be found.
398                int currentPage = getPageNearestToCenterOfScreen();
399                for (int pageIndex = currentPage; pageIndex < getPageCount(); pageIndex++) {
400                    CellLayout page = (CellLayout) getPageAt(pageIndex);
401                    if (page.hasReorderSolution(dragObject.dragInfo)) {
402                        setCurrentPage(pageIndex);
403                        break;
404                    }
405                }
406            }
407        }
408
409        // Always enter the spring loaded mode
410        mLauncher.getStateManager().goToState(SPRING_LOADED);
411    }
412
413    public void deferRemoveExtraEmptyScreen() {
414        mDeferRemoveExtraEmptyScreen = true;
415    }
416
417    @Override
418    public void onDragEnd() {
419        if (ENFORCE_DRAG_EVENT_ORDER) {
420            enforceDragParity("onDragEnd", 0, 0);
421        }
422
423        if (!mDeferRemoveExtraEmptyScreen) {
424            removeExtraEmptyScreen(true, mDragSourceInternal != null);
425        }
426
427        updateChildrenLayersEnabled();
428        mDragInfo = null;
429        mOutlineProvider = null;
430        mDragSourceInternal = null;
431    }
432
433    /**
434     * Initializes various states for this workspace.
435     */
436    protected void initWorkspace() {
437        mCurrentPage = DEFAULT_PAGE;
438        setClipToPadding(false);
439
440        setupLayoutTransition();
441
442        // Set the wallpaper dimensions when Launcher starts up
443        setWallpaperDimension();
444    }
445
446    private void setupLayoutTransition() {
447        // We want to show layout transitions when pages are deleted, to close the gap.
448        mLayoutTransition = new LayoutTransition();
449        mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
450        mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
451        mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
452        mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
453        setLayoutTransition(mLayoutTransition);
454    }
455
456    void enableLayoutTransitions() {
457        setLayoutTransition(mLayoutTransition);
458    }
459    void disableLayoutTransitions() {
460        setLayoutTransition(null);
461    }
462
463    @Override
464    public void onViewAdded(View child) {
465        if (!(child instanceof CellLayout)) {
466            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
467        }
468        CellLayout cl = ((CellLayout) child);
469        cl.setOnInterceptTouchListener(this);
470        cl.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
471        super.onViewAdded(child);
472    }
473
474    public boolean isTouchActive() {
475        return mTouchState != TOUCH_STATE_REST;
476    }
477
478    /**
479     * Initializes and binds the first page
480     * @param qsb an existing qsb to recycle or null.
481     */
482    public void bindAndInitFirstWorkspaceScreen(View qsb) {
483        if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
484            return;
485        }
486        // Add the first page
487        CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, 0);
488        // Always add a QSB on the first screen.
489        if (qsb == null) {
490            // In transposed layout, we add the QSB in the Grid. As workspace does not touch the
491            // edges, we do not need a full width QSB.
492            qsb = LayoutInflater.from(getContext())
493                    .inflate(R.layout.search_container_workspace,firstPage, false);
494        }
495
496        CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, firstPage.getCountX(), 1);
497        lp.canReorder = false;
498        if (!firstPage.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true)) {
499            Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
500        }
501    }
502
503    public void removeAllWorkspaceScreens() {
504        // Disable all layout transitions before removing all pages to ensure that we don't get the
505        // transition animations competing with us changing the scroll when we add pages
506        disableLayoutTransitions();
507
508        // Recycle the QSB widget
509        View qsb = findViewById(R.id.search_container_workspace);
510        if (qsb != null) {
511            ((ViewGroup) qsb.getParent()).removeView(qsb);
512        }
513
514        // Remove the pages and clear the screen models
515        removeFolderListeners();
516        removeAllViews();
517        mScreenOrder.clear();
518        mWorkspaceScreens.clear();
519
520        // Ensure that the first page is always present
521        bindAndInitFirstWorkspaceScreen(qsb);
522
523        // Re-enable the layout transitions
524        enableLayoutTransitions();
525    }
526
527    public void insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
528        // Find the index to insert this view into.  If the empty screen exists, then
529        // insert it before that.
530        int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
531        if (insertIndex < 0) {
532            insertIndex = mScreenOrder.size();
533        }
534        insertNewWorkspaceScreen(screenId, insertIndex);
535    }
536
537    public void insertNewWorkspaceScreen(long screenId) {
538        insertNewWorkspaceScreen(screenId, getChildCount());
539    }
540
541    public CellLayout insertNewWorkspaceScreen(long screenId, int insertIndex) {
542        if (mWorkspaceScreens.containsKey(screenId)) {
543            throw new RuntimeException("Screen id " + screenId + " already exists!");
544        }
545
546        // Inflate the cell layout, but do not add it automatically so that we can get the newly
547        // created CellLayout.
548        CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
549                        R.layout.workspace_screen, this, false /* attachToRoot */);
550        int paddingLeftRight = mLauncher.getDeviceProfile().cellLayoutPaddingLeftRightPx;
551        int paddingBottom = mLauncher.getDeviceProfile().cellLayoutBottomPaddingPx;
552        newScreen.setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
553
554        mWorkspaceScreens.put(screenId, newScreen);
555        mScreenOrder.add(insertIndex, screenId);
556        addView(newScreen, insertIndex);
557        mStateTransitionAnimation.applyChildState(
558                mLauncher.getStateManager().getState(), newScreen, insertIndex);
559
560        if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
561            newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
562        }
563
564        return newScreen;
565    }
566
567    public void addExtraEmptyScreenOnDrag() {
568        boolean lastChildOnScreen = false;
569        boolean childOnFinalScreen = false;
570
571        // Cancel any pending removal of empty screen
572        mRemoveEmptyScreenRunnable = null;
573
574        if (mDragSourceInternal != null) {
575            if (mDragSourceInternal.getChildCount() == 1) {
576                lastChildOnScreen = true;
577            }
578            CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
579            if (indexOfChild(cl) == getChildCount() - 1) {
580                childOnFinalScreen = true;
581            }
582        }
583
584        // If this is the last item on the final screen
585        if (lastChildOnScreen && childOnFinalScreen) {
586            return;
587        }
588        if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
589            insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
590        }
591    }
592
593    public boolean addExtraEmptyScreen() {
594        if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
595            insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
596            return true;
597        }
598        return false;
599    }
600
601    private void convertFinalScreenToEmptyScreenIfNecessary() {
602        if (mLauncher.isWorkspaceLoading()) {
603            // Invalid and dangerous operation if workspace is loading
604            return;
605        }
606
607        if (hasExtraEmptyScreen() || mScreenOrder.size() == 0) return;
608        long finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1);
609
610        CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId);
611
612        // If the final screen is empty, convert it to the extra empty screen
613        if (finalScreen.getShortcutsAndWidgets().getChildCount() == 0 &&
614                !finalScreen.isDropPending()) {
615            mWorkspaceScreens.remove(finalScreenId);
616            mScreenOrder.remove(finalScreenId);
617
618            // if this is the last screen, convert it to the empty screen
619            mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen);
620            mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
621
622            // Update the model if we have changed any screens
623            LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
624        }
625    }
626
627    public void removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens) {
628        removeExtraEmptyScreenDelayed(animate, null, 0, stripEmptyScreens);
629    }
630
631    public void removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete,
632            final int delay, final boolean stripEmptyScreens) {
633        if (mLauncher.isWorkspaceLoading()) {
634            // Don't strip empty screens if the workspace is still loading
635            return;
636        }
637
638        if (delay > 0) {
639            postDelayed(new Runnable() {
640                @Override
641                public void run() {
642                    removeExtraEmptyScreenDelayed(animate, onComplete, 0, stripEmptyScreens);
643                }
644            }, delay);
645            return;
646        }
647
648        convertFinalScreenToEmptyScreenIfNecessary();
649        if (hasExtraEmptyScreen()) {
650            int emptyIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
651            if (getNextPage() == emptyIndex) {
652                snapToPage(getNextPage() - 1, SNAP_OFF_EMPTY_SCREEN_DURATION);
653                fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION,
654                        onComplete, stripEmptyScreens);
655            } else {
656                snapToPage(getNextPage(), 0);
657                fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION,
658                        onComplete, stripEmptyScreens);
659            }
660            return;
661        } else if (stripEmptyScreens) {
662            // If we're not going to strip the empty screens after removing
663            // the extra empty screen, do it right away.
664            stripEmptyScreens();
665        }
666
667        if (onComplete != null) {
668            onComplete.run();
669        }
670    }
671
672    private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete,
673            final boolean stripEmptyScreens) {
674        // XXX: Do we need to update LM workspace screens below?
675        PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
676        PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", 0f);
677
678        final CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
679
680        mRemoveEmptyScreenRunnable = new Runnable() {
681            @Override
682            public void run() {
683                if (hasExtraEmptyScreen()) {
684                    mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
685                    mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
686                    removeView(cl);
687                    if (stripEmptyScreens) {
688                        stripEmptyScreens();
689                    }
690                    // Update the page indicator to reflect the removed page.
691                    showPageIndicatorAtCurrentScroll();
692                }
693            }
694        };
695
696        ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(cl, alpha, bgAlpha);
697        oa.setDuration(duration);
698        oa.setStartDelay(delay);
699        oa.addListener(new AnimatorListenerAdapter() {
700            @Override
701            public void onAnimationEnd(Animator animation) {
702                if (mRemoveEmptyScreenRunnable != null) {
703                    mRemoveEmptyScreenRunnable.run();
704                }
705                if (onComplete != null) {
706                    onComplete.run();
707                }
708            }
709        });
710        oa.start();
711    }
712
713    public boolean hasExtraEmptyScreen() {
714        return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && getChildCount() > 1;
715    }
716
717    public long commitExtraEmptyScreen() {
718        if (mLauncher.isWorkspaceLoading()) {
719            // Invalid and dangerous operation if workspace is loading
720            return -1;
721        }
722
723        CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
724        mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
725        mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
726
727        long newId = LauncherSettings.Settings.call(getContext().getContentResolver(),
728                LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
729                .getLong(LauncherSettings.Settings.EXTRA_VALUE);
730        mWorkspaceScreens.put(newId, cl);
731        mScreenOrder.add(newId);
732
733        // Update the model for the new screen
734        LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
735
736        return newId;
737    }
738
739    public CellLayout getScreenWithId(long screenId) {
740        return mWorkspaceScreens.get(screenId);
741    }
742
743    public long getIdForScreen(CellLayout layout) {
744        int index = mWorkspaceScreens.indexOfValue(layout);
745        if (index != -1) {
746            return mWorkspaceScreens.keyAt(index);
747        }
748        return -1;
749    }
750
751    public int getPageIndexForScreenId(long screenId) {
752        return indexOfChild(mWorkspaceScreens.get(screenId));
753    }
754
755    public long getScreenIdForPageIndex(int index) {
756        if (0 <= index && index < mScreenOrder.size()) {
757            return mScreenOrder.get(index);
758        }
759        return -1;
760    }
761
762    public ArrayList<Long> getScreenOrder() {
763        return mScreenOrder;
764    }
765
766    public void stripEmptyScreens() {
767        if (mLauncher.isWorkspaceLoading()) {
768            // Don't strip empty screens if the workspace is still loading.
769            // This is dangerous and can result in data loss.
770            return;
771        }
772
773        if (isPageInTransition()) {
774            mStripScreensOnPageStopMoving = true;
775            return;
776        }
777
778        int currentPage = getNextPage();
779        ArrayList<Long> removeScreens = new ArrayList<>();
780        int total = mWorkspaceScreens.size();
781        for (int i = 0; i < total; i++) {
782            long id = mWorkspaceScreens.keyAt(i);
783            CellLayout cl = mWorkspaceScreens.valueAt(i);
784            // FIRST_SCREEN_ID can never be removed.
785            if ((!FeatureFlags.QSB_ON_FIRST_SCREEN || id > FIRST_SCREEN_ID)
786                    && cl.getShortcutsAndWidgets().getChildCount() == 0) {
787                removeScreens.add(id);
788            }
789        }
790
791        boolean isInAccessibleDrag = mLauncher.getAccessibilityDelegate().isInAccessibleDrag();
792
793        // We enforce at least one page to add new items to. In the case that we remove the last
794        // such screen, we convert the last screen to the empty screen
795        int minScreens = 1;
796
797        int pageShift = 0;
798        for (Long id: removeScreens) {
799            CellLayout cl = mWorkspaceScreens.get(id);
800            mWorkspaceScreens.remove(id);
801            mScreenOrder.remove(id);
802
803            if (getChildCount() > minScreens) {
804                if (indexOfChild(cl) < currentPage) {
805                    pageShift++;
806                }
807
808                if (isInAccessibleDrag) {
809                    cl.enableAccessibleDrag(false, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
810                }
811
812                removeView(cl);
813            } else {
814                // if this is the last screen, convert it to the empty screen
815                mRemoveEmptyScreenRunnable = null;
816                mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
817                mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
818            }
819        }
820
821        if (!removeScreens.isEmpty()) {
822            // Update the model if we have changed any screens
823            LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
824        }
825
826        if (pageShift >= 0) {
827            setCurrentPage(currentPage - pageShift);
828        }
829    }
830
831    /**
832     * At bind time, we use the rank (screenId) to compute x and y for hotseat items.
833     * See {@link #addInScreen}.
834     */
835    public void addInScreenFromBind(View child, ItemInfo info) {
836        int x = info.cellX;
837        int y = info.cellY;
838        if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
839            int screenId = (int) info.screenId;
840            x = mLauncher.getHotseat().getCellXFromOrder(screenId);
841            y = mLauncher.getHotseat().getCellYFromOrder(screenId);
842        }
843        addInScreen(child, info.container, info.screenId, x, y, info.spanX, info.spanY);
844    }
845
846    /**
847     * Adds the specified child in the specified screen based on the {@param info}
848     * See {@link #addInScreen(View, long, long, int, int, int, int)}.
849     */
850    public void addInScreen(View child, ItemInfo info) {
851        addInScreen(child, info.container, info.screenId, info.cellX, info.cellY,
852                info.spanX, info.spanY);
853    }
854
855    /**
856     * Adds the specified child in the specified screen. The position and dimension of
857     * the child are defined by x, y, spanX and spanY.
858     *
859     * @param child The child to add in one of the workspace's screens.
860     * @param screenId The screen in which to add the child.
861     * @param x The X position of the child in the screen's grid.
862     * @param y The Y position of the child in the screen's grid.
863     * @param spanX The number of cells spanned horizontally by the child.
864     * @param spanY The number of cells spanned vertically by the child.
865     */
866    private void addInScreen(View child, long container, long screenId, int x, int y,
867            int spanX, int spanY) {
868        if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
869            if (getScreenWithId(screenId) == null) {
870                Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
871                // DEBUGGING - Print out the stack trace to see where we are adding from
872                new Throwable().printStackTrace();
873                return;
874            }
875        }
876        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
877            // This should never happen
878            throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
879        }
880
881        final CellLayout layout;
882        if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
883            layout = mLauncher.getHotseat().getLayout();
884
885            // Hide folder title in the hotseat
886            if (child instanceof FolderIcon) {
887                ((FolderIcon) child).setTextVisible(false);
888            }
889        } else {
890            // Show folder title if not in the hotseat
891            if (child instanceof FolderIcon) {
892                ((FolderIcon) child).setTextVisible(true);
893            }
894            layout = getScreenWithId(screenId);
895        }
896
897        ViewGroup.LayoutParams genericLp = child.getLayoutParams();
898        CellLayout.LayoutParams lp;
899        if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
900            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
901        } else {
902            lp = (CellLayout.LayoutParams) genericLp;
903            lp.cellX = x;
904            lp.cellY = y;
905            lp.cellHSpan = spanX;
906            lp.cellVSpan = spanY;
907        }
908
909        if (spanX < 0 && spanY < 0) {
910            lp.isLockedToGrid = false;
911        }
912
913        // Get the canonical child id to uniquely represent this view in this screen
914        ItemInfo info = (ItemInfo) child.getTag();
915        int childId = mLauncher.getViewIdForItem(info);
916
917        boolean markCellsAsOccupied = !(child instanceof Folder);
918        if (!layout.addViewToCellLayout(child, -1, childId, lp, markCellsAsOccupied)) {
919            // TODO: This branch occurs when the workspace is adding views
920            // outside of the defined grid
921            // maybe we should be deleting these items from the LauncherModel?
922            Log.e(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
923        }
924
925        child.setHapticFeedbackEnabled(false);
926        child.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
927        if (child instanceof DropTarget) {
928            mDragController.addDropTarget((DropTarget) child);
929        }
930    }
931
932    /**
933     * Called directly from a CellLayout (not by the framework), after we've been added as a
934     * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
935     * that it should intercept touch events, which is not something that is normally supported.
936     */
937    @SuppressLint("ClickableViewAccessibility")
938    @Override
939    public boolean onTouch(View v, MotionEvent event) {
940        return shouldConsumeTouch(v);
941    }
942
943    private boolean shouldConsumeTouch(View v) {
944        return !workspaceIconsCanBeDragged()
945                || (!workspaceInModalState() && indexOfChild(v) != mCurrentPage);
946    }
947
948    public boolean isSwitchingState() {
949        return mIsSwitchingState;
950    }
951
952    /** This differs from isSwitchingState in that we take into account how far the transition
953     *  has completed. */
954    public boolean isFinishedSwitchingState() {
955        return !mIsSwitchingState
956                || (mTransitionProgress > FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS);
957    }
958
959    @Override
960    public boolean dispatchUnhandledMove(View focused, int direction) {
961        if (workspaceInModalState() || !isFinishedSwitchingState()) {
962            // when the home screens are shrunken, shouldn't allow side-scrolling
963            return false;
964        }
965        return super.dispatchUnhandledMove(focused, direction);
966    }
967
968    @Override
969    public boolean onInterceptTouchEvent(MotionEvent ev) {
970        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
971            mXDown = ev.getX();
972            mYDown = ev.getY();
973        }
974        return super.onInterceptTouchEvent(ev);
975    }
976
977    @Override
978    protected void determineScrollingStart(MotionEvent ev) {
979        if (!isFinishedSwitchingState()) return;
980
981        float deltaX = ev.getX() - mXDown;
982        float absDeltaX = Math.abs(deltaX);
983        float absDeltaY = Math.abs(ev.getY() - mYDown);
984
985        if (Float.compare(absDeltaX, 0f) == 0) return;
986
987        float slope = absDeltaY / absDeltaX;
988        float theta = (float) Math.atan(slope);
989
990        if (absDeltaX > mTouchSlop || absDeltaY > mTouchSlop) {
991            cancelCurrentPageLongPress();
992        }
993
994        if (theta > MAX_SWIPE_ANGLE) {
995            // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
996            return;
997        } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
998            // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
999            // increase the touch slop to make it harder to begin scrolling the workspace. This
1000            // results in vertically scrolling widgets to more easily. The higher the angle, the
1001            // more we increase touch slop.
1002            theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
1003            float extraRatio = (float)
1004                    Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
1005            super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
1006        } else {
1007            // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
1008            super.determineScrollingStart(ev);
1009        }
1010    }
1011
1012    protected void onPageBeginTransition() {
1013        super.onPageBeginTransition();
1014        updateChildrenLayersEnabled();
1015    }
1016
1017    protected void onPageEndTransition() {
1018        super.onPageEndTransition();
1019        updateChildrenLayersEnabled();
1020
1021        if (mDragController.isDragging()) {
1022            if (workspaceInModalState()) {
1023                // If we are in springloaded mode, then force an event to check if the current touch
1024                // is under a new page (to scroll to)
1025                mDragController.forceTouchMove();
1026            }
1027        }
1028
1029        if (mStripScreensOnPageStopMoving) {
1030            stripEmptyScreens();
1031            mStripScreensOnPageStopMoving = false;
1032        }
1033    }
1034
1035    protected void onScrollInteractionBegin() {
1036        super.onScrollInteractionEnd();
1037        mScrollInteractionBegan = true;
1038    }
1039
1040    protected void onScrollInteractionEnd() {
1041        super.onScrollInteractionEnd();
1042        mScrollInteractionBegan = false;
1043        if (mStartedSendingScrollEvents) {
1044            mStartedSendingScrollEvents = false;
1045            mLauncherOverlay.onScrollInteractionEnd();
1046        }
1047    }
1048
1049    public void setLauncherOverlay(LauncherOverlay overlay) {
1050        mLauncherOverlay = overlay;
1051        // A new overlay has been set. Reset event tracking
1052        mStartedSendingScrollEvents = false;
1053        onOverlayScrollChanged(0);
1054    }
1055
1056
1057    private boolean isScrollingOverlay() {
1058        return mLauncherOverlay != null &&
1059                ((mIsRtl && getUnboundedScrollX() > mMaxScrollX) || (!mIsRtl && getUnboundedScrollX() < 0));
1060    }
1061
1062    @Override
1063    protected void snapToDestination() {
1064        // If we're overscrolling the overlay, we make sure to immediately reset the PagedView
1065        // to it's baseline position instead of letting the overscroll settle. The overlay handles
1066        // it's own settling, and every gesture to the overlay should be self-contained and start
1067        // from 0, so we zero it out here.
1068        if (isScrollingOverlay()) {
1069            // We reset mWasInOverscroll so that PagedView doesn't zero out the overscroll
1070            // interaction when we call snapToPageImmediately.
1071            mWasInOverscroll = false;
1072            snapToPageImmediately(0);
1073        } else {
1074            super.snapToDestination();
1075        }
1076    }
1077
1078    @Override
1079    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
1080        super.onScrollChanged(l, t, oldl, oldt);
1081
1082        // Update the page indicator progress.
1083        boolean isTransitioning = mIsSwitchingState
1084                || (getLayoutTransition() != null && getLayoutTransition().isRunning());
1085        if (!isTransitioning) {
1086            showPageIndicatorAtCurrentScroll();
1087        }
1088
1089        updatePageAlphaValues();
1090        enableHwLayersOnVisiblePages();
1091    }
1092
1093    public void showPageIndicatorAtCurrentScroll() {
1094        if (mPageIndicator != null) {
1095            mPageIndicator.setScroll(getScrollX(), computeMaxScrollX());
1096        }
1097    }
1098
1099    @Override
1100    protected void overScroll(float amount) {
1101        boolean shouldScrollOverlay = mLauncherOverlay != null &&
1102                ((amount <= 0 && !mIsRtl) || (amount >= 0 && mIsRtl));
1103
1104        boolean shouldZeroOverlay = mLauncherOverlay != null && mLastOverlayScroll != 0 &&
1105                ((amount >= 0 && !mIsRtl) || (amount <= 0 && mIsRtl));
1106
1107        if (shouldScrollOverlay) {
1108            if (!mStartedSendingScrollEvents && mScrollInteractionBegan) {
1109                mStartedSendingScrollEvents = true;
1110                mLauncherOverlay.onScrollInteractionBegin();
1111            }
1112
1113            mLastOverlayScroll = Math.abs(amount / getMeasuredWidth());
1114            mLauncherOverlay.onScrollChange(mLastOverlayScroll, mIsRtl);
1115        } else {
1116            dampedOverScroll(amount);
1117        }
1118
1119        if (shouldZeroOverlay) {
1120            mLauncherOverlay.onScrollChange(0, mIsRtl);
1121        }
1122    }
1123
1124    @Override
1125    protected boolean shouldFlingForVelocity(int velocityX) {
1126        // When the overlay is moving, the fling or settle transition is controlled by the overlay.
1127        return Float.compare(Math.abs(mOverlayTranslation), 0) == 0 &&
1128                super.shouldFlingForVelocity(velocityX);
1129    }
1130
1131    /**
1132     * The overlay scroll is being controlled locally, just update our overlay effect
1133     */
1134    public void onOverlayScrollChanged(float scroll) {
1135        if (Float.compare(scroll, 1f) == 0) {
1136            if (!mOverlayShown) {
1137                mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
1138                        Action.Direction.LEFT, ContainerType.WORKSPACE, 0);
1139            }
1140            mOverlayShown = true;
1141            // Not announcing the overlay page for accessibility since it announces itself.
1142        } else if (Float.compare(scroll, 0f) == 0) {
1143            if (mOverlayShown) {
1144                mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
1145                        Action.Direction.RIGHT, ContainerType.WORKSPACE, -1);
1146            } else if (Float.compare(mOverlayTranslation, 0f) != 0) {
1147                // When arriving to 0 overscroll from non-zero overscroll, announce page for
1148                // accessibility since default announcements were disabled while in overscroll
1149                // state.
1150                // Not doing this if mOverlayShown because in that case the accessibility service
1151                // will announce the launcher window description upon regaining focus after
1152                // switching from the overlay screen.
1153                announcePageForAccessibility();
1154            }
1155            mOverlayShown = false;
1156            tryRunOverlayCallback();
1157        }
1158
1159        float offset = 0f;
1160
1161        scroll = Math.max(scroll - offset, 0);
1162        scroll = Math.min(1, scroll / (1 - offset));
1163
1164        float alpha = 1 - Interpolators.DEACCEL_3.getInterpolation(scroll);
1165        float transX = mLauncher.getDragLayer().getMeasuredWidth() * scroll;
1166
1167        if (mIsRtl) {
1168            transX = -transX;
1169        }
1170        mOverlayTranslation = transX;
1171
1172        // TODO(adamcohen): figure out a final effect here. We may need to recommend
1173        // different effects based on device performance. On at least one relatively high-end
1174        // device I've tried, translating the launcher causes things to get quite laggy.
1175        mLauncher.getDragLayer().setTranslationX(transX);
1176        mLauncher.getDragLayer().getAlphaProperty(ALPHA_INDEX_OVERLAY).setValue(alpha);
1177    }
1178
1179    /**
1180     * @return false if the callback is still pending
1181     */
1182    private boolean tryRunOverlayCallback() {
1183        if (mOnOverlayHiddenCallback == null) {
1184            // Return true as no callback is pending. This is used by OnWindowFocusChangeListener
1185            // to remove itself if multiple focus handles were added.
1186            return true;
1187        }
1188        if (mOverlayShown || !hasWindowFocus()) {
1189            return false;
1190        }
1191
1192        mOnOverlayHiddenCallback.run();
1193        mOnOverlayHiddenCallback = null;
1194        return true;
1195    }
1196
1197    /**
1198     * Runs the given callback when the minus one overlay is hidden. Specifically, it is run
1199     * when launcher's window has focus and the overlay is no longer being shown. If a callback
1200     * is already present, the new callback will chain off it so both are run.
1201     *
1202     * @return Whether the callback was deferred.
1203     */
1204    public boolean runOnOverlayHidden(Runnable callback) {
1205        if (mOnOverlayHiddenCallback == null) {
1206            mOnOverlayHiddenCallback = callback;
1207        } else {
1208            // Chain the new callback onto the previous callback(s).
1209            Runnable oldCallback = mOnOverlayHiddenCallback;
1210            mOnOverlayHiddenCallback = () -> {
1211                oldCallback.run();
1212                callback.run();
1213            };
1214        }
1215        if (!tryRunOverlayCallback()) {
1216            ViewTreeObserver observer = getViewTreeObserver();
1217            if (observer != null && observer.isAlive()) {
1218                observer.addOnWindowFocusChangeListener(
1219                        new ViewTreeObserver.OnWindowFocusChangeListener() {
1220                            @Override
1221                            public void onWindowFocusChanged(boolean hasFocus) {
1222                                if (tryRunOverlayCallback() && observer.isAlive()) {
1223                                    observer.removeOnWindowFocusChangeListener(this);
1224                                }
1225                            }});
1226            }
1227            return true;
1228        }
1229        return false;
1230    }
1231
1232    @Override
1233    protected void notifyPageSwitchListener(int prevPage) {
1234        super.notifyPageSwitchListener(prevPage);
1235        if (prevPage != mCurrentPage) {
1236            int swipeDirection = (prevPage < mCurrentPage) ? Action.Direction.RIGHT : Action.Direction.LEFT;
1237            mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
1238                    swipeDirection, ContainerType.WORKSPACE, prevPage);
1239        }
1240    }
1241
1242    protected void setWallpaperDimension() {
1243        Utilities.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1244            @Override
1245            public void run() {
1246                final Point size = LauncherAppState.getIDP(getContext()).defaultWallpaperSize;
1247                if (size.x != mWallpaperManager.getDesiredMinimumWidth()
1248                        || size.y != mWallpaperManager.getDesiredMinimumHeight()) {
1249                    mWallpaperManager.suggestDesiredDimensions(size.x, size.y);
1250                }
1251            }
1252        });
1253    }
1254
1255    public void lockWallpaperToDefaultPage() {
1256        mWallpaperOffset.setLockToDefaultPage(true);
1257    }
1258
1259    public void unlockWallpaperFromDefaultPageOnNextLayout() {
1260        if (mWallpaperOffset.isLockedToDefaultPage()) {
1261            mUnlockWallpaperFromDefaultPageOnLayout = true;
1262            requestLayout();
1263        }
1264    }
1265
1266    @Override
1267    public void computeScroll() {
1268        super.computeScroll();
1269        mWallpaperOffset.syncWithScroll();
1270    }
1271
1272    public void computeScrollWithoutInvalidation() {
1273        computeScrollHelper(false);
1274    }
1275
1276    @Override
1277    protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
1278        if (!isSwitchingState()) {
1279            super.determineScrollingStart(ev, touchSlopScale);
1280        }
1281    }
1282
1283    @Override
1284    public void announceForAccessibility(CharSequence text) {
1285        // Don't announce if apps is on top of us.
1286        if (!mLauncher.isInState(ALL_APPS)) {
1287            super.announceForAccessibility(text);
1288        }
1289    }
1290
1291    public void showOutlinesTemporarily() {
1292        if (!mIsPageInTransition && !isTouchActive()) {
1293            snapToPage(mCurrentPage);
1294        }
1295    }
1296
1297    private void updatePageAlphaValues() {
1298        // We need to check the isDragging case because updatePageAlphaValues is called between
1299        // goToState(SPRING_LOADED) and onStartStateTransition.
1300        if (!workspaceInModalState() && !mIsSwitchingState && !mDragController.isDragging()) {
1301            int screenCenter = getScrollX() + getMeasuredWidth() / 2;
1302            for (int i = 0; i < getChildCount(); i++) {
1303                CellLayout child = (CellLayout) getChildAt(i);
1304                if (child != null) {
1305                    float scrollProgress = getScrollProgress(screenCenter, child, i);
1306                    float alpha = 1 - Math.abs(scrollProgress);
1307                    if (mWorkspaceFadeInAdjacentScreens) {
1308                        child.getShortcutsAndWidgets().setAlpha(alpha);
1309                    } else {
1310                        // Pages that are off-screen aren't important for accessibility.
1311                        child.getShortcutsAndWidgets().setImportantForAccessibility(
1312                                alpha > 0 ? IMPORTANT_FOR_ACCESSIBILITY_AUTO
1313                                        : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
1314                    }
1315                }
1316            }
1317        }
1318    }
1319
1320    protected void onAttachedToWindow() {
1321        super.onAttachedToWindow();
1322        IBinder windowToken = getWindowToken();
1323        mWallpaperOffset.setWindowToken(windowToken);
1324        computeScroll();
1325        mDragController.setWindowToken(windowToken);
1326    }
1327
1328    protected void onDetachedFromWindow() {
1329        super.onDetachedFromWindow();
1330        mWallpaperOffset.setWindowToken(null);
1331    }
1332
1333    @Override
1334    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1335        if (mUnlockWallpaperFromDefaultPageOnLayout) {
1336            mWallpaperOffset.setLockToDefaultPage(false);
1337            mUnlockWallpaperFromDefaultPageOnLayout = false;
1338        }
1339        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
1340            mWallpaperOffset.syncWithScroll();
1341            mWallpaperOffset.jumpToFinal();
1342        }
1343        super.onLayout(changed, left, top, right, bottom);
1344        updatePageAlphaValues();
1345    }
1346
1347    @Override
1348    public int getDescendantFocusability() {
1349        if (workspaceInModalState()) {
1350            return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
1351        }
1352        return super.getDescendantFocusability();
1353    }
1354
1355    private boolean workspaceInModalState() {
1356        return !mLauncher.isInState(NORMAL);
1357    }
1358
1359    /** Returns whether a drag should be allowed to be started from the current workspace state. */
1360    public boolean workspaceIconsCanBeDragged() {
1361        return mLauncher.getStateManager().getState().workspaceIconsCanBeDragged;
1362    }
1363
1364    private void updateChildrenLayersEnabled() {
1365        boolean enableChildrenLayers = mIsSwitchingState || isPageInTransition();
1366
1367        if (enableChildrenLayers != mChildrenLayersEnabled) {
1368            mChildrenLayersEnabled = enableChildrenLayers;
1369            if (mChildrenLayersEnabled) {
1370                enableHwLayersOnVisiblePages();
1371            } else {
1372                for (int i = 0; i < getPageCount(); i++) {
1373                    final CellLayout cl = (CellLayout) getChildAt(i);
1374                    cl.enableHardwareLayer(false);
1375                }
1376            }
1377        }
1378    }
1379
1380    private void enableHwLayersOnVisiblePages() {
1381        if (mChildrenLayersEnabled) {
1382            final int screenCount = getChildCount();
1383
1384            final int[] visibleScreens = getVisibleChildrenRange();
1385            int leftScreen = visibleScreens[0];
1386            int rightScreen = visibleScreens[1];
1387            if (mForceDrawAdjacentPages) {
1388                // In overview mode, make sure that the two side pages are visible.
1389                leftScreen = Utilities.boundToRange(getCurrentPage() - 1, 0, rightScreen);
1390                rightScreen = Utilities.boundToRange(getCurrentPage() + 1,
1391                    leftScreen, getPageCount() - 1);
1392            }
1393
1394            if (leftScreen == rightScreen) {
1395                // make sure we're caching at least two pages always
1396                if (rightScreen < screenCount - 1) {
1397                    rightScreen++;
1398                } else if (leftScreen > 0) {
1399                    leftScreen--;
1400                }
1401            }
1402
1403            for (int i = 0; i < screenCount; i++) {
1404                final CellLayout layout = (CellLayout) getPageAt(i);
1405                // enable layers between left and right screen inclusive.
1406                boolean enableLayer = leftScreen <= i && i <= rightScreen;
1407                layout.enableHardwareLayer(enableLayer);
1408            }
1409        }
1410    }
1411
1412    public void onWallpaperTap(MotionEvent ev) {
1413        final int[] position = mTempXY;
1414        getLocationOnScreen(position);
1415
1416        int pointerIndex = ev.getActionIndex();
1417        position[0] += (int) ev.getX(pointerIndex);
1418        position[1] += (int) ev.getY(pointerIndex);
1419
1420        mWallpaperManager.sendWallpaperCommand(getWindowToken(),
1421                ev.getAction() == MotionEvent.ACTION_UP
1422                        ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
1423                position[0], position[1], 0, null);
1424    }
1425
1426    public void prepareDragWithProvider(DragPreviewProvider outlineProvider) {
1427        mOutlineProvider = outlineProvider;
1428    }
1429
1430    public void snapToPageFromOverView(int whichPage) {
1431        snapToPage(whichPage, OVERVIEW_TRANSITION_MS, Interpolators.ZOOM_IN);
1432    }
1433
1434    private void onStartStateTransition(LauncherState state) {
1435        mIsSwitchingState = true;
1436        mTransitionProgress = 0;
1437
1438        updateChildrenLayersEnabled();
1439    }
1440
1441    private void onEndStateTransition() {
1442        mIsSwitchingState = false;
1443        mForceDrawAdjacentPages = false;
1444        mTransitionProgress = 1;
1445
1446        updateChildrenLayersEnabled();
1447        updateAccessibilityFlags();
1448    }
1449
1450    /**
1451     * Sets the current workspace {@link LauncherState} and updates the UI without any animations
1452     */
1453    @Override
1454    public void setState(LauncherState toState) {
1455        onStartStateTransition(toState);
1456        mStateTransitionAnimation.setState(toState);
1457        onEndStateTransition();
1458    }
1459
1460    /**
1461     * Sets the current workspace {@link LauncherState}, then animates the UI
1462     */
1463    @Override
1464    public void setStateWithAnimation(LauncherState toState,
1465            AnimatorSetBuilder builder, AnimationConfig config) {
1466        StateTransitionListener listener = new StateTransitionListener(toState);
1467        mStateTransitionAnimation.setStateWithAnimation(toState, builder, config);
1468
1469        // Invalidate the pages now, so that we have the visible pages before the
1470        // animation is started
1471        if (toState.hasMultipleVisiblePages) {
1472            mForceDrawAdjacentPages = true;
1473        }
1474        invalidate(); // This will call dispatchDraw(), which calls getVisiblePages().
1475
1476        ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1);
1477        stepAnimator.addUpdateListener(listener);
1478        stepAnimator.setDuration(config.duration);
1479        stepAnimator.addListener(listener);
1480        builder.play(stepAnimator);
1481    }
1482
1483    public void updateAccessibilityFlags() {
1484        // TODO: Update the accessibility flags appropriately when dragging.
1485        int accessibilityFlag = mLauncher.getStateManager().getState().workspaceAccessibilityFlag;
1486        if (!mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
1487            int total = getPageCount();
1488            for (int i = 0; i < total; i++) {
1489                updateAccessibilityFlags(accessibilityFlag, (CellLayout) getPageAt(i));
1490            }
1491            setImportantForAccessibility(accessibilityFlag);
1492        }
1493    }
1494
1495    private void updateAccessibilityFlags(int accessibilityFlag, CellLayout page) {
1496        page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
1497        page.getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
1498        page.setContentDescription(null);
1499        page.setAccessibilityDelegate(null);
1500    }
1501
1502    public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) {
1503        View child = cellInfo.cell;
1504
1505        mDragInfo = cellInfo;
1506        child.setVisibility(INVISIBLE);
1507
1508        if (options.isAccessibleDrag) {
1509            mDragController.addDragListener(new AccessibleDragListenerAdapter(
1510                    this, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG) {
1511                @Override
1512                protected void enableAccessibleDrag(boolean enable) {
1513                    super.enableAccessibleDrag(enable);
1514                    setEnableForLayout(mLauncher.getHotseat().getLayout(),enable);
1515                }
1516            });
1517        }
1518
1519        beginDragShared(child, this, options);
1520    }
1521
1522    public void beginDragShared(View child, DragSource source, DragOptions options) {
1523        Object dragObject = child.getTag();
1524        if (!(dragObject instanceof ItemInfo)) {
1525            String msg = "Drag started with a view that has no tag set. This "
1526                    + "will cause a crash (issue 11627249) down the line. "
1527                    + "View: " + child + "  tag: " + child.getTag();
1528            throw new IllegalStateException(msg);
1529        }
1530        beginDragShared(child, source, (ItemInfo) dragObject,
1531                new DragPreviewProvider(child), options);
1532    }
1533
1534    public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject,
1535            DragPreviewProvider previewProvider, DragOptions dragOptions) {
1536        float iconScale = 1f;
1537        if (child instanceof BubbleTextView) {
1538            Drawable icon = ((BubbleTextView) child).getIcon();
1539            if (icon instanceof FastBitmapDrawable) {
1540                iconScale = ((FastBitmapDrawable) icon).getAnimatedScale();
1541            }
1542        }
1543
1544        child.clearFocus();
1545        child.setPressed(false);
1546        mOutlineProvider = previewProvider;
1547
1548        // The drag bitmap follows the touch point around on the screen
1549        final Bitmap b = previewProvider.createDragBitmap();
1550        int halfPadding = previewProvider.previewPadding / 2;
1551
1552        float scale = previewProvider.getScaleAndPosition(b, mTempXY);
1553        int dragLayerX = mTempXY[0];
1554        int dragLayerY = mTempXY[1];
1555
1556        DeviceProfile grid = mLauncher.getDeviceProfile();
1557        Point dragVisualizeOffset = null;
1558        Rect dragRect = null;
1559        if (child instanceof BubbleTextView) {
1560            dragRect = new Rect();
1561            ((BubbleTextView) child).getIconBounds(dragRect);
1562            dragLayerY += dragRect.top;
1563            // Note: The dragRect is used to calculate drag layer offsets, but the
1564            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
1565            dragVisualizeOffset = new Point(- halfPadding, halfPadding);
1566        } else if (child instanceof FolderIcon) {
1567            int previewSize = grid.folderIconSizePx;
1568            dragVisualizeOffset = new Point(- halfPadding, halfPadding - child.getPaddingTop());
1569            dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
1570        } else if (previewProvider instanceof ShortcutDragPreviewProvider) {
1571            dragVisualizeOffset = new Point(- halfPadding, halfPadding);
1572        }
1573
1574        // Clear the pressed state if necessary
1575        if (child instanceof BubbleTextView) {
1576            BubbleTextView icon = (BubbleTextView) child;
1577            icon.clearPressedBackground();
1578        }
1579
1580        if (child.getParent() instanceof ShortcutAndWidgetContainer) {
1581            mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
1582        }
1583
1584        if (child instanceof BubbleTextView && !dragOptions.isAccessibleDrag) {
1585            PopupContainerWithArrow popupContainer = PopupContainerWithArrow
1586                    .showForIcon((BubbleTextView) child);
1587            if (popupContainer != null) {
1588                dragOptions.preDragCondition = popupContainer.createPreDragCondition();
1589
1590                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis("dragging started");
1591            }
1592        }
1593
1594        DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
1595                dragObject, dragVisualizeOffset, dragRect, scale * iconScale, scale, dragOptions);
1596        dv.setIntrinsicIconScaleFactor(dragOptions.intrinsicIconScaleFactor);
1597        return dv;
1598    }
1599
1600    private boolean transitionStateShouldAllowDrop() {
1601        return (!isSwitchingState() || mTransitionProgress > ALLOW_DROP_TRANSITION_PROGRESS) &&
1602                workspaceIconsCanBeDragged();
1603    }
1604
1605    /**
1606     * {@inheritDoc}
1607     */
1608    @Override
1609    public boolean acceptDrop(DragObject d) {
1610        // If it's an external drop (e.g. from All Apps), check if it should be accepted
1611        CellLayout dropTargetLayout = mDropToLayout;
1612        if (d.dragSource != this) {
1613            // Don't accept the drop if we're not over a screen at time of drop
1614            if (dropTargetLayout == null) {
1615                return false;
1616            }
1617            if (!transitionStateShouldAllowDrop()) return false;
1618
1619            mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
1620
1621            // We want the point to be mapped to the dragTarget.
1622            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
1623                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
1624            } else {
1625                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter);
1626            }
1627
1628            int spanX;
1629            int spanY;
1630            if (mDragInfo != null) {
1631                final CellLayout.CellInfo dragCellInfo = mDragInfo;
1632                spanX = dragCellInfo.spanX;
1633                spanY = dragCellInfo.spanY;
1634            } else {
1635                spanX = d.dragInfo.spanX;
1636                spanY = d.dragInfo.spanY;
1637            }
1638
1639            int minSpanX = spanX;
1640            int minSpanY = spanY;
1641            if (d.dragInfo instanceof PendingAddWidgetInfo) {
1642                minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
1643                minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
1644            }
1645
1646            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
1647                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
1648                    mTargetCell);
1649            float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
1650                    mDragViewVisualCenter[1], mTargetCell);
1651            if (mCreateUserFolderOnDrop && willCreateUserFolder(d.dragInfo,
1652                    dropTargetLayout, mTargetCell, distance, true)) {
1653                return true;
1654            }
1655
1656            if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder(d.dragInfo,
1657                    dropTargetLayout, mTargetCell, distance)) {
1658                return true;
1659            }
1660
1661            int[] resultSpan = new int[2];
1662            mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
1663                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
1664                    null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
1665            boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
1666
1667            // Don't accept the drop if there's no room for the item
1668            if (!foundCell) {
1669                onNoCellFound(dropTargetLayout);
1670                return false;
1671            }
1672        }
1673
1674        long screenId = getIdForScreen(dropTargetLayout);
1675        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
1676            commitExtraEmptyScreen();
1677        }
1678
1679        return true;
1680    }
1681
1682    boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell,
1683            float distance, boolean considerTimeout) {
1684        if (distance > mMaxDistanceForFolderCreation) return false;
1685        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
1686        return willCreateUserFolder(info, dropOverView, considerTimeout);
1687    }
1688
1689    boolean willCreateUserFolder(ItemInfo info, View dropOverView, boolean considerTimeout) {
1690        if (dropOverView != null) {
1691            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
1692            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) {
1693                return false;
1694            }
1695        }
1696
1697        boolean hasntMoved = false;
1698        if (mDragInfo != null) {
1699            hasntMoved = dropOverView == mDragInfo.cell;
1700        }
1701
1702        if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
1703            return false;
1704        }
1705
1706        boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
1707        boolean willBecomeShortcut =
1708                (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
1709                        info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
1710                        info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT);
1711
1712        return (aboveShortcut && willBecomeShortcut);
1713    }
1714
1715    boolean willAddToExistingUserFolder(ItemInfo dragInfo, CellLayout target, int[] targetCell,
1716            float distance) {
1717        if (distance > mMaxDistanceForFolderCreation) return false;
1718        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
1719        return willAddToExistingUserFolder(dragInfo, dropOverView);
1720
1721    }
1722    boolean willAddToExistingUserFolder(ItemInfo dragInfo, View dropOverView) {
1723        if (dropOverView != null) {
1724            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
1725            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) {
1726                return false;
1727            }
1728        }
1729
1730        if (dropOverView instanceof FolderIcon) {
1731            FolderIcon fi = (FolderIcon) dropOverView;
1732            if (fi.acceptDrop(dragInfo)) {
1733                return true;
1734            }
1735        }
1736        return false;
1737    }
1738
1739    boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
1740            int[] targetCell, float distance, boolean external, DragView dragView) {
1741        if (distance > mMaxDistanceForFolderCreation) return false;
1742        View v = target.getChildAt(targetCell[0], targetCell[1]);
1743
1744        boolean hasntMoved = false;
1745        if (mDragInfo != null) {
1746            CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
1747            hasntMoved = (mDragInfo.cellX == targetCell[0] &&
1748                    mDragInfo.cellY == targetCell[1]) && (cellParent == target);
1749        }
1750
1751        if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
1752        mCreateUserFolderOnDrop = false;
1753        final long screenId = getIdForScreen(target);
1754
1755        boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
1756        boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
1757
1758        if (aboveShortcut && willBecomeShortcut) {
1759            ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
1760            ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
1761            // if the drag started here, we need to remove it from the workspace
1762            if (!external) {
1763                getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
1764            }
1765
1766            Rect folderLocation = new Rect();
1767            float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
1768            target.removeView(v);
1769
1770            FolderIcon fi =
1771                mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
1772            destInfo.cellX = -1;
1773            destInfo.cellY = -1;
1774            sourceInfo.cellX = -1;
1775            sourceInfo.cellY = -1;
1776
1777            // If the dragView is null, we can't animate
1778            boolean animate = dragView != null;
1779            if (animate) {
1780                // In order to keep everything continuous, we hand off the currently rendered
1781                // folder background to the newly created icon. This preserves animation state.
1782                fi.setFolderBackground(mFolderCreateBg);
1783                mFolderCreateBg = new PreviewBackground();
1784                fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale
1785                );
1786            } else {
1787                fi.prepareCreateAnimation(v);
1788                fi.addItem(destInfo);
1789                fi.addItem(sourceInfo);
1790            }
1791            return true;
1792        }
1793        return false;
1794    }
1795
1796    boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
1797            float distance, DragObject d, boolean external) {
1798        if (distance > mMaxDistanceForFolderCreation) return false;
1799
1800        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
1801        if (!mAddToExistingFolderOnDrop) return false;
1802        mAddToExistingFolderOnDrop = false;
1803
1804        if (dropOverView instanceof FolderIcon) {
1805            FolderIcon fi = (FolderIcon) dropOverView;
1806            if (fi.acceptDrop(d.dragInfo)) {
1807                fi.onDrop(d, false /* itemReturnedOnFailedDrop */);
1808
1809                // if the drag started here, we need to remove it from the workspace
1810                if (!external) {
1811                    getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
1812                }
1813                return true;
1814            }
1815        }
1816        return false;
1817    }
1818
1819    @Override
1820    public void prepareAccessibilityDrop() { }
1821
1822    public void onDrop(final DragObject d, DragOptions options) {
1823        mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
1824        CellLayout dropTargetLayout = mDropToLayout;
1825
1826        // We want the point to be mapped to the dragTarget.
1827        if (dropTargetLayout != null) {
1828            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
1829                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
1830            } else {
1831                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter);
1832            }
1833        }
1834
1835        boolean droppedOnOriginalCell = false;
1836
1837        int snapScreen = -1;
1838        boolean resizeOnDrop = false;
1839        if (d.dragSource != this || mDragInfo == null) {
1840            final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
1841                    (int) mDragViewVisualCenter[1] };
1842            onDropExternal(touchXY, dropTargetLayout, d);
1843        } else {
1844            final View cell = mDragInfo.cell;
1845            boolean droppedOnOriginalCellDuringTransition = false;
1846            Runnable onCompleteRunnable = null;
1847
1848            if (dropTargetLayout != null && !d.cancelled) {
1849                // Move internally
1850                boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
1851                boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
1852                long container = hasMovedIntoHotseat ?
1853                        LauncherSettings.Favorites.CONTAINER_HOTSEAT :
1854                        LauncherSettings.Favorites.CONTAINER_DESKTOP;
1855                long screenId = (mTargetCell[0] < 0) ?
1856                        mDragInfo.screenId : getIdForScreen(dropTargetLayout);
1857                int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
1858                int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
1859                // First we find the cell nearest to point at which the item is
1860                // dropped, without any consideration to whether there is an item there.
1861
1862                mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
1863                        mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
1864                float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
1865                        mDragViewVisualCenter[1], mTargetCell);
1866
1867                // If the item being dropped is a shortcut and the nearest drop
1868                // cell also contains a shortcut, then create a folder with the two shortcuts.
1869                if (createUserFolderIfNecessary(cell, container,
1870                        dropTargetLayout, mTargetCell, distance, false, d.dragView) ||
1871                        addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
1872                                distance, d, false)) {
1873                    mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
1874                    return;
1875                }
1876
1877                // Aside from the special case where we're dropping a shortcut onto a shortcut,
1878                // we need to find the nearest cell location that is vacant
1879                ItemInfo item = d.dragInfo;
1880                int minSpanX = item.spanX;
1881                int minSpanY = item.spanY;
1882                if (item.minSpanX > 0 && item.minSpanY > 0) {
1883                    minSpanX = item.minSpanX;
1884                    minSpanY = item.minSpanY;
1885                }
1886
1887                droppedOnOriginalCell = item.screenId == screenId && item.container == container
1888                        && item.cellX == mTargetCell[0] && item.cellY == mTargetCell[1];
1889                droppedOnOriginalCellDuringTransition = droppedOnOriginalCell && mIsSwitchingState;
1890
1891                // When quickly moving an item, a user may accidentally rearrange their
1892                // workspace. So instead we move the icon back safely to its original position.
1893                boolean returnToOriginalCellToPreventShuffling = !isFinishedSwitchingState()
1894                        && !droppedOnOriginalCellDuringTransition && !dropTargetLayout
1895                        .isRegionVacant(mTargetCell[0], mTargetCell[1], spanX, spanY);
1896                int[] resultSpan = new int[2];
1897                if (returnToOriginalCellToPreventShuffling) {
1898                    mTargetCell[0] = mTargetCell[1] = -1;
1899                } else {
1900                    mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
1901                            (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
1902                            mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
1903                }
1904
1905                boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
1906
1907                // if the widget resizes on drop
1908                if (foundCell && (cell instanceof AppWidgetHostView) &&
1909                        (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
1910                    resizeOnDrop = true;
1911                    item.spanX = resultSpan[0];
1912                    item.spanY = resultSpan[1];
1913                    AppWidgetHostView awhv = (AppWidgetHostView) cell;
1914                    AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
1915                            resultSpan[1]);
1916                }
1917
1918                if (foundCell) {
1919                    if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
1920                        snapScreen = getPageIndexForScreenId(screenId);
1921                        snapToPage(snapScreen);
1922                    }
1923
1924                    final ItemInfo info = (ItemInfo) cell.getTag();
1925                    if (hasMovedLayouts) {
1926                        // Reparent the view
1927                        CellLayout parentCell = getParentCellLayoutForView(cell);
1928                        if (parentCell != null) {
1929                            parentCell.removeView(cell);
1930                        } else if (FeatureFlags.IS_DOGFOOD_BUILD) {
1931                            throw new NullPointerException("mDragInfo.cell has null parent");
1932                        }
1933                        addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
1934                                info.spanX, info.spanY);
1935                    }
1936
1937                    // update the item's position after drop
1938                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
1939                    lp.cellX = lp.tmpCellX = mTargetCell[0];
1940                    lp.cellY = lp.tmpCellY = mTargetCell[1];
1941                    lp.cellHSpan = item.spanX;
1942                    lp.cellVSpan = item.spanY;
1943                    lp.isLockedToGrid = true;
1944
1945                    if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
1946                            cell instanceof LauncherAppWidgetHostView) {
1947                        final CellLayout cellLayout = dropTargetLayout;
1948                        // We post this call so that the widget has a chance to be placed
1949                        // in its final location
1950
1951                        final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
1952                        AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo();
1953                        if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE
1954                                && !d.accessibleDrag) {
1955                            onCompleteRunnable = new Runnable() {
1956                                public void run() {
1957                                    if (!isPageInTransition()) {
1958                                        AppWidgetResizeFrame.showForWidget(hostView, cellLayout);
1959                                    }
1960                                }
1961                            };
1962                        }
1963                    }
1964
1965                    mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId,
1966                            lp.cellX, lp.cellY, item.spanX, item.spanY);
1967                } else {
1968                    if (!returnToOriginalCellToPreventShuffling) {
1969                        onNoCellFound(dropTargetLayout);
1970                    }
1971
1972                    // If we can't find a drop location, we return the item to its original position
1973                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
1974                    mTargetCell[0] = lp.cellX;
1975                    mTargetCell[1] = lp.cellY;
1976                    CellLayout layout = (CellLayout) cell.getParent().getParent();
1977                    layout.markCellsAsOccupiedForView(cell);
1978                }
1979            }
1980
1981            final CellLayout parent = (CellLayout) cell.getParent().getParent();
1982            if (d.dragView.hasDrawn()) {
1983                if (droppedOnOriginalCellDuringTransition) {
1984                    // Animate the item to its original position, while simultaneously exiting
1985                    // spring-loaded mode so the page meets the icon where it was picked up.
1986                    mLauncher.getDragController().animateDragViewToOriginalPosition(
1987                            onCompleteRunnable, cell, SPRING_LOADED_TRANSITION_MS);
1988                    mLauncher.getStateManager().goToState(NORMAL);
1989                    mLauncher.getDropTargetBar().onDragEnd();
1990                    parent.onDropChild(cell);
1991                    return;
1992                }
1993                final ItemInfo info = (ItemInfo) cell.getTag();
1994                boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
1995                        || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
1996                if (isWidget) {
1997                    int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
1998                            ANIMATE_INTO_POSITION_AND_DISAPPEAR;
1999                    animateWidgetDrop(info, parent, d.dragView, null, animationType, cell, false);
2000                } else {
2001                    int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
2002                    mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
2003                            this);
2004                }
2005            } else {
2006                d.deferDragViewCleanupPostAnimation = false;
2007                cell.setVisibility(VISIBLE);
2008            }
2009            parent.onDropChild(cell);
2010
2011            mLauncher.getStateManager().goToState(
2012                    NORMAL, SPRING_LOADED_EXIT_DELAY, onCompleteRunnable);
2013        }
2014
2015        if (d.stateAnnouncer != null && !droppedOnOriginalCell) {
2016            d.stateAnnouncer.completeAction(R.string.item_moved);
2017        }
2018    }
2019
2020    public void onNoCellFound(View dropTargetLayout) {
2021        if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2022            Hotseat hotseat = mLauncher.getHotseat();
2023            boolean droppedOnAllAppsIcon = !FeatureFlags.NO_ALL_APPS_ICON
2024                    && mTargetCell != null && !mLauncher.getDeviceProfile().inv.isAllAppsButtonRank(
2025                    hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]));
2026            if (!droppedOnAllAppsIcon) {
2027                // Only show message when hotseat is full and drop target was not AllApps button
2028                showOutOfSpaceMessage(true);
2029            }
2030        } else {
2031            showOutOfSpaceMessage(false);
2032        }
2033    }
2034
2035    private void showOutOfSpaceMessage(boolean isHotseatLayout) {
2036        int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
2037        Toast.makeText(mLauncher, mLauncher.getString(strId), Toast.LENGTH_SHORT).show();
2038    }
2039
2040    /**
2041     * Computes the area relative to dragLayer which is used to display a page.
2042     */
2043    public void getPageAreaRelativeToDragLayer(Rect outArea) {
2044        CellLayout child = (CellLayout) getChildAt(getNextPage());
2045        if (child == null) {
2046            return;
2047        }
2048        ShortcutAndWidgetContainer boundingLayout = child.getShortcutsAndWidgets();
2049
2050        // Use the absolute left instead of the child left, as we want the visible area
2051        // irrespective of the visible child. Since the view can only scroll horizontally, the
2052        // top position is not affected.
2053        mTempXY[0] = getPaddingLeft() + boundingLayout.getLeft();
2054        mTempXY[1] = child.getTop() + boundingLayout.getTop();
2055
2056        float scale = mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY);
2057        outArea.set(mTempXY[0], mTempXY[1],
2058                (int) (mTempXY[0] + scale * boundingLayout.getMeasuredWidth()),
2059                (int) (mTempXY[1] + scale * boundingLayout.getMeasuredHeight()));
2060    }
2061
2062    @Override
2063    public void onDragEnter(DragObject d) {
2064        if (ENFORCE_DRAG_EVENT_ORDER) {
2065            enforceDragParity("onDragEnter", 1, 1);
2066        }
2067
2068        mCreateUserFolderOnDrop = false;
2069        mAddToExistingFolderOnDrop = false;
2070
2071        mDropToLayout = null;
2072        mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
2073        setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1]);
2074    }
2075
2076    @Override
2077    public void onDragExit(DragObject d) {
2078        if (ENFORCE_DRAG_EVENT_ORDER) {
2079            enforceDragParity("onDragExit", -1, 0);
2080        }
2081
2082        // Here we store the final page that will be dropped to, if the workspace in fact
2083        // receives the drop
2084        mDropToLayout = mDragTargetLayout;
2085        if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
2086            mCreateUserFolderOnDrop = true;
2087        } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
2088            mAddToExistingFolderOnDrop = true;
2089        }
2090
2091        // Reset the previous drag target
2092        setCurrentDropLayout(null);
2093        setCurrentDragOverlappingLayout(null);
2094
2095        mSpringLoadedDragController.cancel();
2096    }
2097
2098    private void enforceDragParity(String event, int update, int expectedValue) {
2099        enforceDragParity(this, event, update, expectedValue);
2100        for (int i = 0; i < getChildCount(); i++) {
2101            enforceDragParity(getChildAt(i), event, update, expectedValue);
2102        }
2103    }
2104
2105    private void enforceDragParity(View v, String event, int update, int expectedValue) {
2106        Object tag = v.getTag(R.id.drag_event_parity);
2107        int value = tag == null ? 0 : (Integer) tag;
2108        value += update;
2109        v.setTag(R.id.drag_event_parity, value);
2110
2111        if (value != expectedValue) {
2112            Log.e(TAG, event + ": Drag contract violated: " + value);
2113        }
2114    }
2115
2116    void setCurrentDropLayout(CellLayout layout) {
2117        if (mDragTargetLayout != null) {
2118            mDragTargetLayout.revertTempState();
2119            mDragTargetLayout.onDragExit();
2120        }
2121        mDragTargetLayout = layout;
2122        if (mDragTargetLayout != null) {
2123            mDragTargetLayout.onDragEnter();
2124        }
2125        cleanupReorder(true);
2126        cleanupFolderCreation();
2127        setCurrentDropOverCell(-1, -1);
2128    }
2129
2130    void setCurrentDragOverlappingLayout(CellLayout layout) {
2131        if (mDragOverlappingLayout != null) {
2132            mDragOverlappingLayout.setIsDragOverlapping(false);
2133        }
2134        mDragOverlappingLayout = layout;
2135        if (mDragOverlappingLayout != null) {
2136            mDragOverlappingLayout.setIsDragOverlapping(true);
2137        }
2138        // Invalidating the scrim will also force this CellLayout
2139        // to be invalidated so that it is highlighted if necessary.
2140        mLauncher.getDragLayer().getScrim().invalidate();
2141    }
2142
2143    public CellLayout getCurrentDragOverlappingLayout() {
2144        return mDragOverlappingLayout;
2145    }
2146
2147    void setCurrentDropOverCell(int x, int y) {
2148        if (x != mDragOverX || y != mDragOverY) {
2149            mDragOverX = x;
2150            mDragOverY = y;
2151            setDragMode(DRAG_MODE_NONE);
2152        }
2153    }
2154
2155    void setDragMode(int dragMode) {
2156        if (dragMode != mDragMode) {
2157            if (dragMode == DRAG_MODE_NONE) {
2158                cleanupAddToFolder();
2159                // We don't want to cancel the re-order alarm every time the target cell changes
2160                // as this feels to slow / unresponsive.
2161                cleanupReorder(false);
2162                cleanupFolderCreation();
2163            } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
2164                cleanupReorder(true);
2165                cleanupFolderCreation();
2166            } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
2167                cleanupAddToFolder();
2168                cleanupReorder(true);
2169            } else if (dragMode == DRAG_MODE_REORDER) {
2170                cleanupAddToFolder();
2171                cleanupFolderCreation();
2172            }
2173            mDragMode = dragMode;
2174        }
2175    }
2176
2177    private void cleanupFolderCreation() {
2178        if (mFolderCreateBg != null) {
2179            mFolderCreateBg.animateToRest();
2180        }
2181        mFolderCreationAlarm.setOnAlarmListener(null);
2182        mFolderCreationAlarm.cancelAlarm();
2183    }
2184
2185    private void cleanupAddToFolder() {
2186        if (mDragOverFolderIcon != null) {
2187            mDragOverFolderIcon.onDragExit();
2188            mDragOverFolderIcon = null;
2189        }
2190    }
2191
2192    private void cleanupReorder(boolean cancelAlarm) {
2193        // Any pending reorders are canceled
2194        if (cancelAlarm) {
2195            mReorderAlarm.cancelAlarm();
2196        }
2197        mLastReorderX = -1;
2198        mLastReorderY = -1;
2199    }
2200
2201   /*
2202    *
2203    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
2204    * coordinate space. The argument xy is modified with the return result.
2205    */
2206   void mapPointFromSelfToChild(View v, float[] xy) {
2207       xy[0] = xy[0] - v.getLeft();
2208       xy[1] = xy[1] - v.getTop();
2209   }
2210
2211   boolean isPointInSelfOverHotseat(int x, int y) {
2212       mTempXY[0] = x;
2213       mTempXY[1] = y;
2214       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
2215       View hotseat = mLauncher.getHotseat();
2216       return mTempXY[0] >= hotseat.getLeft() &&
2217               mTempXY[0] <= hotseat.getRight() &&
2218               mTempXY[1] >= hotseat.getTop() &&
2219               mTempXY[1] <= hotseat.getBottom();
2220   }
2221
2222   void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
2223       mTempXY[0] = (int) xy[0];
2224       mTempXY[1] = (int) xy[1];
2225       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
2226       mLauncher.getDragLayer().mapCoordInSelfToDescendant(hotseat.getLayout(), mTempXY);
2227
2228       xy[0] = mTempXY[0];
2229       xy[1] = mTempXY[1];
2230   }
2231
2232    private boolean isDragWidget(DragObject d) {
2233        return (d.dragInfo instanceof LauncherAppWidgetInfo ||
2234                d.dragInfo instanceof PendingAddWidgetInfo);
2235    }
2236
2237    public void onDragOver(DragObject d) {
2238        // Skip drag over events while we are dragging over side pages
2239        if (!transitionStateShouldAllowDrop()) return;
2240
2241        ItemInfo item = d.dragInfo;
2242        if (item == null) {
2243            if (FeatureFlags.IS_DOGFOOD_BUILD) {
2244                throw new NullPointerException("DragObject has null info");
2245            }
2246            return;
2247        }
2248
2249        // Ensure that we have proper spans for the item that we are dropping
2250        if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
2251        mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
2252
2253        final View child = (mDragInfo == null) ? null : mDragInfo.cell;
2254        if (setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1])) {
2255            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
2256                mSpringLoadedDragController.cancel();
2257            } else {
2258                mSpringLoadedDragController.setAlarm(mDragTargetLayout);
2259            }
2260        }
2261
2262        // Handle the drag over
2263        if (mDragTargetLayout != null) {
2264            // We want the point to be mapped to the dragTarget.
2265            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
2266                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2267            } else {
2268                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter);
2269            }
2270
2271            int minSpanX = item.spanX;
2272            int minSpanY = item.spanY;
2273            if (item.minSpanX > 0 && item.minSpanY > 0) {
2274                minSpanX = item.minSpanX;
2275                minSpanY = item.minSpanY;
2276            }
2277
2278            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2279                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY,
2280                    mDragTargetLayout, mTargetCell);
2281            int reorderX = mTargetCell[0];
2282            int reorderY = mTargetCell[1];
2283
2284            setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
2285
2286            float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
2287                    mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
2288
2289            manageFolderFeedback(mDragTargetLayout, mTargetCell, targetCellDistance, d);
2290
2291            boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
2292                    mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
2293                    item.spanY, child, mTargetCell);
2294
2295            if (!nearestDropOccupied) {
2296                mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider,
2297                        mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, d);
2298            } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
2299                    && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
2300                    mLastReorderY != reorderY)) {
2301
2302                int[] resultSpan = new int[2];
2303                mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
2304                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY,
2305                        child, mTargetCell, resultSpan, CellLayout.MODE_SHOW_REORDER_HINT);
2306
2307                // Otherwise, if we aren't adding to or creating a folder and there's no pending
2308                // reorder, then we schedule a reorder
2309                ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
2310                        minSpanX, minSpanY, item.spanX, item.spanY, d, child);
2311                mReorderAlarm.setOnAlarmListener(listener);
2312                mReorderAlarm.setAlarm(REORDER_TIMEOUT);
2313            }
2314
2315            if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
2316                    !nearestDropOccupied) {
2317                if (mDragTargetLayout != null) {
2318                    mDragTargetLayout.revertTempState();
2319                }
2320            }
2321        }
2322    }
2323
2324    /**
2325     * Updates {@link #mDragTargetLayout} and {@link #mDragOverlappingLayout}
2326     * based on the DragObject's position.
2327     *
2328     * The layout will be:
2329     * - The Hotseat if the drag object is over it
2330     * - A side page if we are in spring-loaded mode and the drag object is over it
2331     * - The current page otherwise
2332     *
2333     * @return whether the layout is different from the current {@link #mDragTargetLayout}.
2334     */
2335    private boolean setDropLayoutForDragObject(DragObject d, float centerX, float centerY) {
2336        CellLayout layout = null;
2337        // Test to see if we are over the hotseat first
2338        if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
2339            if (isPointInSelfOverHotseat(d.x, d.y)) {
2340                layout = mLauncher.getHotseat().getLayout();
2341            }
2342        }
2343
2344        int nextPage = getNextPage();
2345        if (layout == null && !isPageInTransition()) {
2346            // Check if the item is dragged over left page
2347            mTempTouchCoordinates[0] = Math.min(centerX, d.x);
2348            mTempTouchCoordinates[1] = d.y;
2349            layout = verifyInsidePage(nextPage + (mIsRtl ? 1 : -1), mTempTouchCoordinates);
2350        }
2351
2352        if (layout == null && !isPageInTransition()) {
2353            // Check if the item is dragged over right page
2354            mTempTouchCoordinates[0] = Math.max(centerX, d.x);
2355            mTempTouchCoordinates[1] = d.y;
2356            layout = verifyInsidePage(nextPage + (mIsRtl ? -1 : 1), mTempTouchCoordinates);
2357        }
2358
2359        // Always pick the current page.
2360        if (layout == null && nextPage >= 0 && nextPage < getPageCount()) {
2361            layout = (CellLayout) getChildAt(nextPage);
2362        }
2363        if (layout != mDragTargetLayout) {
2364            setCurrentDropLayout(layout);
2365            setCurrentDragOverlappingLayout(layout);
2366            return true;
2367        }
2368        return false;
2369    }
2370
2371    /**
2372     * Returns the child CellLayout if the point is inside the page coordinates, null otherwise.
2373     */
2374    private CellLayout verifyInsidePage(int pageNo, float[] touchXy)  {
2375        if (pageNo >= 0 && pageNo < getPageCount()) {
2376            CellLayout cl = (CellLayout) getChildAt(pageNo);
2377            mapPointFromSelfToChild(cl, touchXy);
2378            if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
2379                    touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
2380                // This point is inside the cell layout
2381                return cl;
2382            }
2383        }
2384        return null;
2385    }
2386
2387    private void manageFolderFeedback(CellLayout targetLayout,
2388            int[] targetCell, float distance, DragObject dragObject) {
2389        if (distance > mMaxDistanceForFolderCreation) return;
2390
2391        final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]);
2392        ItemInfo info = dragObject.dragInfo;
2393        boolean userFolderPending = willCreateUserFolder(info, dragOverView, false);
2394        if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
2395                !mFolderCreationAlarm.alarmPending()) {
2396
2397            FolderCreationAlarmListener listener = new
2398                    FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]);
2399
2400            if (!dragObject.accessibleDrag) {
2401                mFolderCreationAlarm.setOnAlarmListener(listener);
2402                mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
2403            } else {
2404                listener.onAlarm(mFolderCreationAlarm);
2405            }
2406
2407            if (dragObject.stateAnnouncer != null) {
2408                dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
2409                        .getDescriptionForDropOver(dragOverView, getContext()));
2410            }
2411            return;
2412        }
2413
2414        boolean willAddToFolder = willAddToExistingUserFolder(info, dragOverView);
2415        if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
2416            mDragOverFolderIcon = ((FolderIcon) dragOverView);
2417            mDragOverFolderIcon.onDragEnter(info);
2418            if (targetLayout != null) {
2419                targetLayout.clearDragOutlines();
2420            }
2421            setDragMode(DRAG_MODE_ADD_TO_FOLDER);
2422
2423            if (dragObject.stateAnnouncer != null) {
2424                dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
2425                        .getDescriptionForDropOver(dragOverView, getContext()));
2426            }
2427            return;
2428        }
2429
2430        if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
2431            setDragMode(DRAG_MODE_NONE);
2432        }
2433        if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
2434            setDragMode(DRAG_MODE_NONE);
2435        }
2436    }
2437
2438    class FolderCreationAlarmListener implements OnAlarmListener {
2439        final CellLayout layout;
2440        final int cellX;
2441        final int cellY;
2442
2443        final PreviewBackground bg = new PreviewBackground();
2444
2445        public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
2446            this.layout = layout;
2447            this.cellX = cellX;
2448            this.cellY = cellY;
2449
2450            BubbleTextView cell = (BubbleTextView) layout.getChildAt(cellX, cellY);
2451            bg.setup(mLauncher, null, cell.getMeasuredWidth(), cell.getPaddingTop());
2452
2453            // The full preview background should appear behind the icon
2454            bg.isClipping = false;
2455        }
2456
2457        public void onAlarm(Alarm alarm) {
2458            mFolderCreateBg = bg;
2459            mFolderCreateBg.animateToAccept(layout, cellX, cellY);
2460            layout.clearDragOutlines();
2461            setDragMode(DRAG_MODE_CREATE_FOLDER);
2462        }
2463    }
2464
2465    class ReorderAlarmListener implements OnAlarmListener {
2466        final float[] dragViewCenter;
2467        final int minSpanX, minSpanY, spanX, spanY;
2468        final DragObject dragObject;
2469        final View child;
2470
2471        public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
2472                int spanY, DragObject dragObject, View child) {
2473            this.dragViewCenter = dragViewCenter;
2474            this.minSpanX = minSpanX;
2475            this.minSpanY = minSpanY;
2476            this.spanX = spanX;
2477            this.spanY = spanY;
2478            this.child = child;
2479            this.dragObject = dragObject;
2480        }
2481
2482        public void onAlarm(Alarm alarm) {
2483            int[] resultSpan = new int[2];
2484            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2485                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout,
2486                    mTargetCell);
2487            mLastReorderX = mTargetCell[0];
2488            mLastReorderY = mTargetCell[1];
2489
2490            mTargetCell = mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
2491                (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2492                child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
2493
2494            if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
2495                mDragTargetLayout.revertTempState();
2496            } else {
2497                setDragMode(DRAG_MODE_REORDER);
2498            }
2499
2500            boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
2501            mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider,
2502                mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize, dragObject);
2503        }
2504    }
2505
2506    @Override
2507    public void getHitRectRelativeToDragLayer(Rect outRect) {
2508        // We want the workspace to have the whole area of the display (it will find the correct
2509        // cell layout to drop to in the existing drag/drop logic.
2510        mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect);
2511    }
2512
2513    /**
2514     * Drop an item that didn't originate on one of the workspace screens.
2515     * It may have come from Launcher (e.g. from all apps or customize), or it may have
2516     * come from another app altogether.
2517     *
2518     * NOTE: This can also be called when we are outside of a drag event, when we want
2519     * to add an item to one of the workspace screens.
2520     */
2521    private void onDropExternal(final int[] touchXY, final CellLayout cellLayout, DragObject d) {
2522        if (d.dragInfo instanceof PendingAddShortcutInfo) {
2523            ShortcutInfo si = ((PendingAddShortcutInfo) d.dragInfo)
2524                    .activityInfo.createShortcutInfo();
2525            if (si != null) {
2526                d.dragInfo = si;
2527            }
2528        }
2529
2530        ItemInfo info = d.dragInfo;
2531        int spanX = info.spanX;
2532        int spanY = info.spanY;
2533        if (mDragInfo != null) {
2534            spanX = mDragInfo.spanX;
2535            spanY = mDragInfo.spanY;
2536        }
2537
2538        final long container = mLauncher.isHotseatLayout(cellLayout) ?
2539                LauncherSettings.Favorites.CONTAINER_HOTSEAT :
2540                    LauncherSettings.Favorites.CONTAINER_DESKTOP;
2541        final long screenId = getIdForScreen(cellLayout);
2542        if (!mLauncher.isHotseatLayout(cellLayout)
2543                && screenId != getScreenIdForPageIndex(mCurrentPage)
2544                && !mLauncher.isInState(SPRING_LOADED)) {
2545            snapToPage(getPageIndexForScreenId(screenId));
2546        }
2547
2548        if (info instanceof PendingAddItemInfo) {
2549            final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) info;
2550
2551            boolean findNearestVacantCell = true;
2552            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
2553                mTargetCell = findNearestArea(touchXY[0], touchXY[1], spanX, spanY,
2554                        cellLayout, mTargetCell);
2555                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2556                        mDragViewVisualCenter[1], mTargetCell);
2557                if (willCreateUserFolder(d.dragInfo, cellLayout, mTargetCell, distance, true)
2558                        || willAddToExistingUserFolder(
2559                                d.dragInfo, cellLayout, mTargetCell, distance)) {
2560                    findNearestVacantCell = false;
2561                }
2562            }
2563
2564            final ItemInfo item = d.dragInfo;
2565            boolean updateWidgetSize = false;
2566            if (findNearestVacantCell) {
2567                int minSpanX = item.spanX;
2568                int minSpanY = item.spanY;
2569                if (item.minSpanX > 0 && item.minSpanY > 0) {
2570                    minSpanX = item.minSpanX;
2571                    minSpanY = item.minSpanY;
2572                }
2573                int[] resultSpan = new int[2];
2574                mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
2575                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
2576                        null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
2577
2578                if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) {
2579                    updateWidgetSize = true;
2580                }
2581                item.spanX = resultSpan[0];
2582                item.spanY = resultSpan[1];
2583            }
2584
2585            Runnable onAnimationCompleteRunnable = new Runnable() {
2586                @Override
2587                public void run() {
2588                    // Normally removeExtraEmptyScreen is called in Workspace#onDragEnd, but when
2589                    // adding an item that may not be dropped right away (due to a config activity)
2590                    // we defer the removal until the activity returns.
2591                    deferRemoveExtraEmptyScreen();
2592
2593                    // When dragging and dropping from customization tray, we deal with creating
2594                    // widgets/shortcuts/folders in a slightly different way
2595                    mLauncher.addPendingItem(pendingInfo, container, screenId, mTargetCell,
2596                            item.spanX, item.spanY);
2597                }
2598            };
2599            boolean isWidget = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
2600                    || pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
2601
2602            AppWidgetHostView finalView = isWidget ?
2603                    ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
2604
2605            if (finalView != null && updateWidgetSize) {
2606                AppWidgetResizeFrame.updateWidgetSizeRanges(finalView, mLauncher, item.spanX,
2607                        item.spanY);
2608            }
2609
2610            int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
2611            if (isWidget && ((PendingAddWidgetInfo) pendingInfo).info != null &&
2612                    ((PendingAddWidgetInfo) pendingInfo).getHandler().needsConfigure()) {
2613                animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
2614            }
2615            animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
2616                    animationStyle, finalView, true);
2617        } else {
2618            // This is for other drag/drop cases, like dragging from All Apps
2619            mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
2620
2621            View view;
2622
2623            switch (info.itemType) {
2624            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
2625            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
2626            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
2627                if (info.container == NO_ID) {
2628                    // Came from all apps -- make a copy
2629                    if (info instanceof AppInfo) {
2630                        info = ((AppInfo) info).makeShortcut();
2631                        d.dragInfo = info;
2632                    } else if (info instanceof ShortcutInfo) {
2633                        info = new ShortcutInfo((ShortcutInfo) info);
2634                        d.dragInfo = info;
2635                    }
2636
2637                }
2638                view = mLauncher.createShortcut(cellLayout, (ShortcutInfo) info);
2639                break;
2640            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
2641                view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
2642                        (FolderInfo) info);
2643                break;
2644            default:
2645                throw new IllegalStateException("Unknown item type: " + info.itemType);
2646            }
2647
2648            // First we find the cell nearest to point at which the item is
2649            // dropped, without any consideration to whether there is an item there.
2650            if (touchXY != null) {
2651                mTargetCell = findNearestArea(touchXY[0], touchXY[1], spanX, spanY,
2652                        cellLayout, mTargetCell);
2653                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2654                        mDragViewVisualCenter[1], mTargetCell);
2655                if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
2656                        true, d.dragView)) {
2657                    return;
2658                }
2659                if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
2660                        true)) {
2661                    return;
2662                }
2663            }
2664
2665            if (touchXY != null) {
2666                // when dragging and dropping, just find the closest free spot
2667                mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
2668                        (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
2669                        null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
2670            } else {
2671                cellLayout.findCellForSpan(mTargetCell, 1, 1);
2672            }
2673            // Add the item to DB before adding to screen ensures that the container and other
2674            // values of the info is properly updated.
2675            mLauncher.getModelWriter().addOrMoveItemInDatabase(info, container, screenId,
2676                    mTargetCell[0], mTargetCell[1]);
2677
2678            addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1],
2679                    info.spanX, info.spanY);
2680            cellLayout.onDropChild(view);
2681            cellLayout.getShortcutsAndWidgets().measureChild(view);
2682
2683            if (d.dragView != null) {
2684                // We wrap the animation call in the temporary set and reset of the current
2685                // cellLayout to its final transform -- this means we animate the drag view to
2686                // the correct final location.
2687                setFinalTransitionTransform();
2688                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, this);
2689                resetTransitionTransform();
2690            }
2691        }
2692    }
2693
2694    public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
2695        int[] unScaledSize = estimateItemSize(widgetInfo);
2696        int visibility = layout.getVisibility();
2697        layout.setVisibility(VISIBLE);
2698
2699        int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
2700        int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
2701        Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
2702                Bitmap.Config.ARGB_8888);
2703        layout.measure(width, height);
2704        layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
2705        layout.draw(new Canvas(b));
2706        layout.setVisibility(visibility);
2707        return b;
2708    }
2709
2710    private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
2711            DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, boolean scale) {
2712        // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
2713        // location and size on the home screen.
2714        int spanX = info.spanX;
2715        int spanY = info.spanY;
2716
2717        Rect r = estimateItemPosition(layout, targetCell[0], targetCell[1], spanX, spanY);
2718        if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
2719            DeviceProfile profile = mLauncher.getDeviceProfile();
2720            Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
2721        }
2722        loc[0] = r.left;
2723        loc[1] = r.top;
2724
2725        setFinalTransitionTransform();
2726        float cellLayoutScale =
2727                mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
2728        resetTransitionTransform();
2729
2730        if (scale) {
2731            float dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
2732            float dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
2733
2734            // The animation will scale the dragView about its center, so we need to center about
2735            // the final location.
2736            loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2
2737                    - Math.ceil(layout.getUnusedHorizontalSpace() / 2f);
2738            loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
2739            scaleXY[0] = dragViewScaleX * cellLayoutScale;
2740            scaleXY[1] = dragViewScaleY * cellLayoutScale;
2741        } else {
2742            // Since we are not cross-fading the dragView, align the drag view to the
2743            // final cell position.
2744            float dragScale = dragView.getInitialScale() * cellLayoutScale;
2745            loc[0] += (dragScale - 1) * dragView.getWidth() / 2;
2746            loc[1] += (dragScale - 1) * dragView.getHeight() / 2;
2747            scaleXY[0] = scaleXY[1] = dragScale;
2748
2749            // If a dragRegion was provided, offset the final position accordingly.
2750            Rect dragRegion = dragView.getDragRegion();
2751            if (dragRegion != null) {
2752                loc[0] += cellLayoutScale * dragRegion.left;
2753                loc[1] += cellLayoutScale * dragRegion.top;
2754            }
2755        }
2756    }
2757
2758    public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, final DragView dragView,
2759            final Runnable onCompleteRunnable, int animationType, final View finalView,
2760            boolean external) {
2761        Rect from = new Rect();
2762        mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
2763
2764        int[] finalPos = new int[2];
2765        float scaleXY[] = new float[2];
2766        boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
2767        getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
2768                scalePreview);
2769
2770        Resources res = mLauncher.getResources();
2771        final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
2772
2773        boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET ||
2774                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
2775        if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
2776            Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
2777            dragView.setCrossFadeBitmap(crossFadeBitmap);
2778            dragView.crossFade((int) (duration * 0.8f));
2779        } else if (isWidget && external) {
2780            scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0],  scaleXY[1]);
2781        }
2782
2783        DragLayer dragLayer = mLauncher.getDragLayer();
2784        if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
2785            mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
2786                    DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
2787        } else {
2788            int endStyle;
2789            if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
2790                endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
2791            } else {
2792                endStyle = DragLayer.ANIMATION_END_DISAPPEAR;
2793            }
2794
2795            Runnable onComplete = new Runnable() {
2796                @Override
2797                public void run() {
2798                    if (finalView != null) {
2799                        finalView.setVisibility(VISIBLE);
2800                    }
2801                    if (onCompleteRunnable != null) {
2802                        onCompleteRunnable.run();
2803                    }
2804                }
2805            };
2806            dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
2807                    finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
2808                    duration, this);
2809        }
2810    }
2811
2812    public void setFinalTransitionTransform() {
2813        if (isSwitchingState()) {
2814            mCurrentScale = getScaleX();
2815            setScaleX(mStateTransitionAnimation.getFinalScale());
2816            setScaleY(mStateTransitionAnimation.getFinalScale());
2817        }
2818    }
2819    public void resetTransitionTransform() {
2820        if (isSwitchingState()) {
2821            setScaleX(mCurrentScale);
2822            setScaleY(mCurrentScale);
2823        }
2824    }
2825
2826    /**
2827     * Return the current CellInfo describing our current drag; this method exists
2828     * so that Launcher can sync this object with the correct info when the activity is created/
2829     * destroyed
2830     *
2831     */
2832    public CellLayout.CellInfo getDragInfo() {
2833        return mDragInfo;
2834    }
2835
2836    /**
2837     * Calculate the nearest cell where the given object would be dropped.
2838     *
2839     * pixelX and pixelY should be in the coordinate system of layout
2840     */
2841    @Thunk int[] findNearestArea(int pixelX, int pixelY,
2842            int spanX, int spanY, CellLayout layout, int[] recycle) {
2843        return layout.findNearestArea(
2844                pixelX, pixelY, spanX, spanY, recycle);
2845    }
2846
2847    void setup(DragController dragController) {
2848        mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
2849        mDragController = dragController;
2850
2851        // hardware layers on children are enabled on startup, but should be disabled until
2852        // needed
2853        updateChildrenLayersEnabled();
2854    }
2855
2856    /**
2857     * Called at the end of a drag which originated on the workspace.
2858     */
2859    public void onDropCompleted(final View target, final DragObject d,
2860            final boolean success) {
2861
2862        if (success) {
2863            if (target != this && mDragInfo != null) {
2864                removeWorkspaceItem(mDragInfo.cell);
2865            }
2866        } else if (mDragInfo != null) {
2867            final CellLayout cellLayout = mLauncher.getCellLayout(
2868                    mDragInfo.container, mDragInfo.screenId);
2869            if (cellLayout != null) {
2870                cellLayout.onDropChild(mDragInfo.cell);
2871            } else if (FeatureFlags.IS_DOGFOOD_BUILD) {
2872                throw new RuntimeException("Invalid state: cellLayout == null in "
2873                        + "Workspace#onDropCompleted. Please file a bug. ");
2874            }
2875        }
2876        View cell = getHomescreenIconByItemId(d.originalDragInfo.id);
2877        if (d.cancelled && cell != null) {
2878            cell.setVisibility(VISIBLE);
2879        }
2880        mDragInfo = null;
2881    }
2882
2883    /**
2884     * For opposite operation. See {@link #addInScreen}.
2885     */
2886    public void removeWorkspaceItem(View v) {
2887        CellLayout parentCell = getParentCellLayoutForView(v);
2888        if (parentCell != null) {
2889            parentCell.removeView(v);
2890        } else if (FeatureFlags.IS_DOGFOOD_BUILD) {
2891            // When an app is uninstalled using the drop target, we wait until resume to remove
2892            // the icon. We also remove all the corresponding items from the workspace at
2893            // {@link Launcher#bindComponentsRemoved}. That call can come before or after
2894            // {@link Launcher#mOnResumeCallbacks} depending on how busy the worker thread is.
2895            Log.e(TAG, "mDragInfo.cell has null parent");
2896        }
2897        if (v instanceof DropTarget) {
2898            mDragController.removeDropTarget((DropTarget) v);
2899        }
2900    }
2901
2902    /**
2903     * Removes all folder listeners
2904     */
2905    public void removeFolderListeners() {
2906        mapOverItems(false, new ItemOperator() {
2907            @Override
2908            public boolean evaluate(ItemInfo info, View view) {
2909                if (view instanceof FolderIcon) {
2910                    ((FolderIcon) view).removeListeners();
2911                }
2912                return false;
2913            }
2914        });
2915    }
2916
2917    public boolean isDropEnabled() {
2918        return true;
2919    }
2920
2921    @Override
2922    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
2923        // We don't dispatch restoreInstanceState to our children using this code path.
2924        // Some pages will be restored immediately as their items are bound immediately, and
2925        // others we will need to wait until after their items are bound.
2926        mSavedStates = container;
2927    }
2928
2929    public void restoreInstanceStateForChild(int child) {
2930        if (mSavedStates != null) {
2931            mRestoredPages.add(child);
2932            CellLayout cl = (CellLayout) getChildAt(child);
2933            if (cl != null) {
2934                cl.restoreInstanceState(mSavedStates);
2935            }
2936        }
2937    }
2938
2939    public void restoreInstanceStateForRemainingPages() {
2940        int count = getChildCount();
2941        for (int i = 0; i < count; i++) {
2942            if (!mRestoredPages.contains(i)) {
2943                restoreInstanceStateForChild(i);
2944            }
2945        }
2946        mRestoredPages.clear();
2947        mSavedStates = null;
2948    }
2949
2950    @Override
2951    public boolean scrollLeft() {
2952        boolean result = false;
2953        if (!workspaceInModalState() && !mIsSwitchingState) {
2954            result = super.scrollLeft();
2955        }
2956        Folder openFolder = Folder.getOpen(mLauncher);
2957        if (openFolder != null) {
2958            openFolder.completeDragExit();
2959        }
2960        return result;
2961    }
2962
2963    @Override
2964    public boolean scrollRight() {
2965        boolean result = false;
2966        if (!workspaceInModalState() && !mIsSwitchingState) {
2967            result = super.scrollRight();
2968        }
2969        Folder openFolder = Folder.getOpen(mLauncher);
2970        if (openFolder != null) {
2971            openFolder.completeDragExit();
2972        }
2973        return result;
2974    }
2975
2976    /**
2977     * Returns a specific CellLayout
2978     */
2979    CellLayout getParentCellLayoutForView(View v) {
2980        ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
2981        for (CellLayout layout : layouts) {
2982            if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
2983                return layout;
2984            }
2985        }
2986        return null;
2987    }
2988
2989    /**
2990     * Returns a list of all the CellLayouts in the workspace.
2991     */
2992    ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
2993        ArrayList<CellLayout> layouts = new ArrayList<>();
2994        int screenCount = getChildCount();
2995        for (int screen = 0; screen < screenCount; screen++) {
2996            layouts.add(((CellLayout) getChildAt(screen)));
2997        }
2998        if (mLauncher.getHotseat() != null) {
2999            layouts.add(mLauncher.getHotseat().getLayout());
3000        }
3001        return layouts;
3002    }
3003
3004    /**
3005     * We should only use this to search for specific children.  Do not use this method to modify
3006     * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
3007     * the hotseat and workspace pages
3008     */
3009    ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
3010        ArrayList<ShortcutAndWidgetContainer> childrenLayouts = new ArrayList<>();
3011        int screenCount = getChildCount();
3012        for (int screen = 0; screen < screenCount; screen++) {
3013            childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
3014        }
3015        if (mLauncher.getHotseat() != null) {
3016            childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
3017        }
3018        return childrenLayouts;
3019    }
3020
3021    public View getHomescreenIconByItemId(final long id) {
3022        return getFirstMatch(new ItemOperator() {
3023
3024            @Override
3025            public boolean evaluate(ItemInfo info, View v) {
3026                return info != null && info.id == id;
3027            }
3028        });
3029    }
3030
3031    public View getViewForTag(final Object tag) {
3032        return getFirstMatch(new ItemOperator() {
3033
3034            @Override
3035            public boolean evaluate(ItemInfo info, View v) {
3036                return info == tag;
3037            }
3038        });
3039    }
3040
3041    public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) {
3042        return (LauncherAppWidgetHostView) getFirstMatch(new ItemOperator() {
3043
3044            @Override
3045            public boolean evaluate(ItemInfo info, View v) {
3046                return (info instanceof LauncherAppWidgetInfo) &&
3047                        ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId;
3048            }
3049        });
3050    }
3051
3052    public View getFirstMatch(final ItemOperator operator) {
3053        final View[] value = new View[1];
3054        mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
3055            @Override
3056            public boolean evaluate(ItemInfo info, View v) {
3057                if (operator.evaluate(info, v)) {
3058                    value[0] = v;
3059                    return true;
3060                }
3061                return false;
3062            }
3063        });
3064        return value[0];
3065    }
3066
3067    void clearDropTargets() {
3068        mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
3069            @Override
3070            public boolean evaluate(ItemInfo info, View v) {
3071                if (v instanceof DropTarget) {
3072                    mDragController.removeDropTarget((DropTarget) v);
3073                }
3074                // not done, process all the shortcuts
3075                return false;
3076            }
3077        });
3078    }
3079
3080    /**
3081     * Removes items that match the {@param matcher}. When applications are removed
3082     * as a part of an update, this is called to ensure that other widgets and application
3083     * shortcuts are not removed.
3084     */
3085    public void removeItemsByMatcher(final ItemInfoMatcher matcher) {
3086        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
3087        for (final CellLayout layoutParent: cellLayouts) {
3088            final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
3089
3090            LongArrayMap<View> idToViewMap = new LongArrayMap<>();
3091            ArrayList<ItemInfo> items = new ArrayList<>();
3092            for (int j = 0; j < layout.getChildCount(); j++) {
3093                final View view = layout.getChildAt(j);
3094                if (view.getTag() instanceof ItemInfo) {
3095                    ItemInfo item = (ItemInfo) view.getTag();
3096                    items.add(item);
3097                    idToViewMap.put(item.id, view);
3098                }
3099            }
3100
3101            for (ItemInfo itemToRemove : matcher.filterItemInfos(items)) {
3102                View child = idToViewMap.get(itemToRemove.id);
3103
3104                if (child != null) {
3105                    // Note: We can not remove the view directly from CellLayoutChildren as this
3106                    // does not re-mark the spaces as unoccupied.
3107                    layoutParent.removeViewInLayout(child);
3108                    if (child instanceof DropTarget) {
3109                        mDragController.removeDropTarget((DropTarget) child);
3110                    }
3111                } else if (itemToRemove.container >= 0) {
3112                    // The item may belong to a folder.
3113                    View parent = idToViewMap.get(itemToRemove.container);
3114                    if (parent != null) {
3115                        FolderInfo folderInfo = (FolderInfo) parent.getTag();
3116                        folderInfo.prepareAutoUpdate();
3117                        folderInfo.remove((ShortcutInfo) itemToRemove, false);
3118                    }
3119                }
3120            }
3121        }
3122
3123        // Strip all the empty screens
3124        stripEmptyScreens();
3125    }
3126
3127    public interface ItemOperator {
3128        /**
3129         * Process the next itemInfo, possibly with side-effect on the next item.
3130         *
3131         * @param info info for the shortcut
3132         * @param view view for the shortcut
3133         * @return true if done, false to continue the map
3134         */
3135        boolean evaluate(ItemInfo info, View view);
3136    }
3137
3138    /**
3139     * Map the operator over the shortcuts and widgets, return the first-non-null value.
3140     *
3141     * @param recurse true: iterate over folder children. false: op get the folders themselves.
3142     * @param op the operator to map over the shortcuts
3143     */
3144    void mapOverItems(boolean recurse, ItemOperator op) {
3145        ArrayList<ShortcutAndWidgetContainer> containers = getAllShortcutAndWidgetContainers();
3146        final int containerCount = containers.size();
3147        for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) {
3148            ShortcutAndWidgetContainer container = containers.get(containerIdx);
3149            // map over all the shortcuts on the workspace
3150            final int itemCount = container.getChildCount();
3151            for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
3152                View item = container.getChildAt(itemIdx);
3153                ItemInfo info = (ItemInfo) item.getTag();
3154                if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) {
3155                    FolderIcon folder = (FolderIcon) item;
3156                    ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder();
3157                    // map over all the children in the folder
3158                    final int childCount = folderChildren.size();
3159                    for (int childIdx = 0; childIdx < childCount; childIdx++) {
3160                        View child = folderChildren.get(childIdx);
3161                        info = (ItemInfo) child.getTag();
3162                        if (op.evaluate(info, child)) {
3163                            return;
3164                        }
3165                    }
3166                } else {
3167                    if (op.evaluate(info, item)) {
3168                        return;
3169                    }
3170                }
3171            }
3172        }
3173    }
3174
3175    void updateShortcuts(ArrayList<ShortcutInfo> shortcuts) {
3176        int total  = shortcuts.size();
3177        final HashSet<ShortcutInfo> updates = new HashSet<>(total);
3178        final HashSet<Long> folderIds = new HashSet<>();
3179
3180        for (int i = 0; i < total; i++) {
3181            ShortcutInfo s = shortcuts.get(i);
3182            updates.add(s);
3183            folderIds.add(s.container);
3184        }
3185
3186        mapOverItems(MAP_RECURSE, new ItemOperator() {
3187            @Override
3188            public boolean evaluate(ItemInfo info, View v) {
3189                if (info instanceof ShortcutInfo && v instanceof BubbleTextView &&
3190                        updates.contains(info)) {
3191                    ShortcutInfo si = (ShortcutInfo) info;
3192                    BubbleTextView shortcut = (BubbleTextView) v;
3193                    Drawable oldIcon = shortcut.getIcon();
3194                    boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
3195                            && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
3196                    shortcut.applyFromShortcutInfo(si, si.isPromise() != oldPromiseState);
3197                }
3198                // process all the shortcuts
3199                return false;
3200            }
3201        });
3202
3203        // Update folder icons
3204        mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
3205            @Override
3206            public boolean evaluate(ItemInfo info, View v) {
3207                if (info instanceof FolderInfo && folderIds.contains(info.id)) {
3208                    ((FolderInfo) info).itemsChanged(false);
3209                }
3210                // process all the shortcuts
3211                return false;
3212            }
3213        });
3214    }
3215
3216    public void updateIconBadges(final Set<PackageUserKey> updatedBadges) {
3217        final PackageUserKey packageUserKey = new PackageUserKey(null, null);
3218        final HashSet<Long> folderIds = new HashSet<>();
3219        mapOverItems(MAP_RECURSE, new ItemOperator() {
3220            @Override
3221            public boolean evaluate(ItemInfo info, View v) {
3222                if (info instanceof ShortcutInfo && v instanceof BubbleTextView
3223                        && packageUserKey.updateFromItemInfo(info)) {
3224                    if (updatedBadges.contains(packageUserKey)) {
3225                        ((BubbleTextView) v).applyBadgeState(info, true /* animate */);
3226                        folderIds.add(info.container);
3227                    }
3228                }
3229                // process all the shortcuts
3230                return false;
3231            }
3232        });
3233
3234        // Update folder icons
3235        mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
3236            @Override
3237            public boolean evaluate(ItemInfo info, View v) {
3238                if (info instanceof FolderInfo && folderIds.contains(info.id)
3239                        && v instanceof FolderIcon) {
3240                    FolderBadgeInfo folderBadgeInfo = new FolderBadgeInfo();
3241                    for (ShortcutInfo si : ((FolderInfo) info).contents) {
3242                        folderBadgeInfo.addBadgeInfo(mLauncher.getBadgeInfoForItem(si));
3243                    }
3244                    ((FolderIcon) v).setBadgeInfo(folderBadgeInfo);
3245                }
3246                // process all the shortcuts
3247                return false;
3248            }
3249        });
3250    }
3251
3252    public void removeAbandonedPromise(String packageName, UserHandle user) {
3253        HashSet<String> packages = new HashSet<>(1);
3254        packages.add(packageName);
3255        ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packages, user);
3256        mLauncher.getModelWriter().deleteItemsFromDatabase(matcher);
3257        removeItemsByMatcher(matcher);
3258    }
3259
3260    public void updateRestoreItems(final HashSet<ItemInfo> updates) {
3261        mapOverItems(MAP_RECURSE, new ItemOperator() {
3262            @Override
3263            public boolean evaluate(ItemInfo info, View v) {
3264                if (info instanceof ShortcutInfo && v instanceof BubbleTextView
3265                        && updates.contains(info)) {
3266                    ((BubbleTextView) v).applyPromiseState(false /* promiseStateChanged */);
3267                } else if (v instanceof PendingAppWidgetHostView
3268                        && info instanceof LauncherAppWidgetInfo
3269                        && updates.contains(info)) {
3270                    ((PendingAppWidgetHostView) v).applyState();
3271                }
3272                // process all the shortcuts
3273                return false;
3274            }
3275        });
3276    }
3277
3278    public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) {
3279        if (!changedInfo.isEmpty()) {
3280            DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
3281                    mLauncher.getAppWidgetHost());
3282
3283            LauncherAppWidgetInfo item = changedInfo.get(0);
3284            final AppWidgetProviderInfo widgetInfo;
3285            if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
3286                widgetInfo = AppWidgetManagerCompat
3287                        .getInstance(mLauncher).findProvider(item.providerName, item.user);
3288            } else {
3289                widgetInfo = AppWidgetManagerCompat.getInstance(mLauncher)
3290                        .getLauncherAppWidgetInfo(item.appWidgetId);
3291            }
3292
3293            if (widgetInfo != null) {
3294                // Re-inflate the widgets which have changed status
3295                widgetRefresh.run();
3296            } else {
3297                // widgetRefresh will automatically run when the packages are updated.
3298                // For now just update the progress bars
3299                mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
3300                    @Override
3301                    public boolean evaluate(ItemInfo info, View view) {
3302                        if (view instanceof PendingAppWidgetHostView
3303                                && changedInfo.contains(info)) {
3304                            ((LauncherAppWidgetInfo) info).installProgress = 100;
3305                            ((PendingAppWidgetHostView) view).applyState();
3306                        }
3307                        // process all the shortcuts
3308                        return false;
3309                    }
3310                });
3311            }
3312        }
3313    }
3314
3315    void moveToDefaultScreen() {
3316        int page = DEFAULT_PAGE;
3317        if (!workspaceInModalState() && getNextPage() != page) {
3318            snapToPage(page);
3319        }
3320        View child = getChildAt(page);
3321        if (child != null) {
3322            child.requestFocus();
3323        }
3324    }
3325
3326    @Override
3327    public int getExpectedHeight() {
3328        return getMeasuredHeight() <= 0 || !mIsLayoutValid
3329                ? mLauncher.getDeviceProfile().heightPx : getMeasuredHeight();
3330    }
3331
3332    @Override
3333    public int getExpectedWidth() {
3334        return getMeasuredWidth() <= 0 || !mIsLayoutValid
3335                ? mLauncher.getDeviceProfile().widthPx : getMeasuredWidth();
3336    }
3337
3338    @Override
3339    protected boolean canAnnouncePageDescription() {
3340        // Disable announcements while overscrolling potentially to overlay screen because if we end
3341        // up on the overlay screen, it will take care of announcing itself.
3342        return Float.compare(mOverlayTranslation, 0f) == 0;
3343    }
3344
3345    @Override
3346    protected String getCurrentPageDescription() {
3347        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
3348        return getPageDescription(page);
3349    }
3350
3351    private String getPageDescription(int page) {
3352        int nScreens = getChildCount();
3353        int extraScreenId = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
3354        if (extraScreenId >= 0 && nScreens > 1) {
3355            if (page == extraScreenId) {
3356                return getContext().getString(R.string.workspace_new_page);
3357            }
3358            nScreens--;
3359        }
3360        if (nScreens == 0) {
3361            // When the workspace is not loaded, we do not know how many screen will be bound.
3362            return getContext().getString(R.string.all_apps_home_button_label);
3363        }
3364        return getContext().getString(R.string.workspace_scroll_format, page + 1, nScreens);
3365    }
3366
3367    @Override
3368    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
3369        target.gridX = info.cellX;
3370        target.gridY = info.cellY;
3371        target.pageIndex = getCurrentPage();
3372        targetParent.containerType = ContainerType.WORKSPACE;
3373        if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3374            target.rank = info.rank;
3375            targetParent.containerType = ContainerType.HOTSEAT;
3376        } else if (info.container >= 0) {
3377            targetParent.containerType = ContainerType.FOLDER;
3378        }
3379    }
3380
3381    /**
3382     * Used as a workaround to ensure that the AppWidgetService receives the
3383     * PACKAGE_ADDED broadcast before updating widgets.
3384     */
3385    private class DeferredWidgetRefresh implements Runnable, ProviderChangedListener {
3386        private final ArrayList<LauncherAppWidgetInfo> mInfos;
3387        private final LauncherAppWidgetHost mHost;
3388        private final Handler mHandler;
3389
3390        private boolean mRefreshPending;
3391
3392        DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos,
3393            LauncherAppWidgetHost host) {
3394            mInfos = infos;
3395            mHost = host;
3396            mHandler = new Handler();
3397            mRefreshPending = true;
3398
3399            mHost.addProviderChangeListener(this);
3400            // Force refresh after 10 seconds, if we don't get the provider changed event.
3401            // This could happen when the provider is no longer available in the app.
3402            mHandler.postDelayed(this, 10000);
3403        }
3404
3405        @Override
3406        public void run() {
3407            mHost.removeProviderChangeListener(this);
3408            mHandler.removeCallbacks(this);
3409
3410            if (!mRefreshPending) {
3411                return;
3412            }
3413
3414            mRefreshPending = false;
3415
3416            ArrayList<PendingAppWidgetHostView> views = new ArrayList<>(mInfos.size());
3417            mapOverItems(MAP_NO_RECURSE, (info, view) -> {
3418                if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
3419                    views.add((PendingAppWidgetHostView) view);
3420                }
3421                // process all children
3422                return false;
3423            });
3424            for (PendingAppWidgetHostView view : views) {
3425                view.reInflate();
3426            }
3427        }
3428
3429        @Override
3430        public void notifyWidgetProvidersChanged() {
3431            run();
3432        }
3433    }
3434
3435    private class StateTransitionListener extends AnimatorListenerAdapter
3436            implements AnimatorUpdateListener {
3437
3438        private final LauncherState mToState;
3439
3440        StateTransitionListener(LauncherState toState) {
3441            mToState = toState;
3442        }
3443
3444        @Override
3445        public void onAnimationUpdate(ValueAnimator anim) {
3446            mTransitionProgress = anim.getAnimatedFraction();
3447        }
3448
3449        @Override
3450        public void onAnimationStart(Animator animation) {
3451            onStartStateTransition(mToState);
3452        }
3453
3454        @Override
3455        public void onAnimationEnd(Animator animation) {
3456            onEndStateTransition();
3457        }
3458    }
3459}
3460