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