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