Workspace.java revision 2e1efb480a9b77a97cb623d4f5faf6802a417422
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 glowing
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    @Override
1425    protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
1426        if (!isSwitchingState()) {
1427            super.determineScrollingStart(ev, touchSlopScale);
1428        }
1429    }
1430
1431    @Override
1432    public void announceForAccessibility(CharSequence text) {
1433        // Don't announce if apps is on top of us.
1434        if (!mLauncher.isAppsViewVisible()) {
1435            super.announceForAccessibility(text);
1436        }
1437    }
1438
1439    public void showOutlinesTemporarily() {
1440        if (!mIsPageMoving && !isTouchActive()) {
1441            snapToPage(mCurrentPage);
1442        }
1443    }
1444
1445    private void updatePageAlphaValues(int screenCenter) {
1446        if (mWorkspaceFadeInAdjacentScreens &&
1447                !workspaceInModalState() &&
1448                !mIsSwitchingState) {
1449            for (int i = numCustomPages(); i < getChildCount(); i++) {
1450                CellLayout child = (CellLayout) getChildAt(i);
1451                if (child != null) {
1452                    float scrollProgress = getScrollProgress(screenCenter, child, i);
1453                    float alpha = 1 - Math.abs(scrollProgress);
1454                    child.getShortcutsAndWidgets().setAlpha(alpha);
1455                }
1456            }
1457        }
1458    }
1459
1460    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
1461    @Override
1462    public void enableAccessibleDrag(boolean enable) {
1463        for (int i = 0; i < getChildCount(); i++) {
1464            CellLayout child = (CellLayout) getChildAt(i);
1465            child.enableAccessibleDrag(enable, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
1466        }
1467
1468        if (enable) {
1469            // We need to allow our individual children to become click handlers in this case
1470            setOnClickListener(null);
1471        } else {
1472            // Reset our click listener
1473            setOnClickListener(mLauncher);
1474        }
1475        mLauncher.getSearchDropTargetBar().enableAccessibleDrag(enable);
1476        mLauncher.getAppInfoDropTargetBar().enableAccessibleDrag(enable);
1477        mLauncher.getHotseat().getLayout()
1478            .enableAccessibleDrag(enable, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
1479    }
1480
1481    public boolean hasCustomContent() {
1482        return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID);
1483    }
1484
1485    public int numCustomPages() {
1486        return hasCustomContent() ? 1 : 0;
1487    }
1488
1489    public boolean isOnOrMovingToCustomContent() {
1490        return hasCustomContent() && getNextPage() == 0 && mRestorePage == INVALID_RESTORE_PAGE;
1491    }
1492
1493    private void updateStateForCustomContent(int screenCenter) {
1494        float translationX = 0;
1495        float progress = 0;
1496        if (hasCustomContent()) {
1497            int index = mScreenOrder.indexOf(CUSTOM_CONTENT_SCREEN_ID);
1498
1499            int scrollDelta = getScrollX() - getScrollForPage(index) -
1500                    getLayoutTransitionOffsetForPage(index);
1501            float scrollRange = getScrollForPage(index + 1) - getScrollForPage(index);
1502            translationX = scrollRange - scrollDelta;
1503            progress = (scrollRange - scrollDelta) / scrollRange;
1504
1505            if (mIsRtl) {
1506                translationX = Math.min(0, translationX);
1507            } else {
1508                translationX = Math.max(0, translationX);
1509            }
1510            progress = Math.max(0, progress);
1511        }
1512
1513        if (Float.compare(progress, mLastCustomContentScrollProgress) == 0) return;
1514
1515        CellLayout cc = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1516        if (progress > 0 && cc.getVisibility() != VISIBLE && !workspaceInModalState()) {
1517            cc.setVisibility(VISIBLE);
1518        }
1519
1520        mLastCustomContentScrollProgress = progress;
1521
1522        // We should only update the drag layer background alpha if we are not in all apps or the
1523        // widgets tray
1524        if (mState == State.NORMAL) {
1525            mLauncher.getDragLayer().setBackgroundAlpha(progress == 1 ? 0 : progress * 0.8f);
1526        }
1527
1528        if (mLauncher.getHotseat() != null) {
1529            mLauncher.getHotseat().setTranslationX(translationX);
1530        }
1531
1532        if (getPageIndicator() != null) {
1533            getPageIndicator().setTranslationX(translationX);
1534        }
1535
1536        if (mCustomContentCallbacks != null) {
1537            mCustomContentCallbacks.onScrollProgressChanged(progress);
1538        }
1539    }
1540
1541    @Override
1542    protected OnClickListener getPageIndicatorClickListener() {
1543        AccessibilityManager am = (AccessibilityManager)
1544                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
1545        if (!am.isTouchExplorationEnabled()) {
1546            return null;
1547        }
1548        return new OnClickListener() {
1549            @Override
1550            public void onClick(View arg0) {
1551                mLauncher.showOverviewMode(true);
1552            }
1553        };
1554    }
1555
1556    @Override
1557    protected void screenScrolled(int screenCenter) {
1558        updatePageAlphaValues(screenCenter);
1559        updateStateForCustomContent(screenCenter);
1560        enableHwLayersOnVisiblePages();
1561    }
1562
1563    protected void onAttachedToWindow() {
1564        super.onAttachedToWindow();
1565        IBinder windowToken = getWindowToken();
1566        mWallpaperOffset.setWindowToken(windowToken);
1567        computeScroll();
1568        mDragController.setWindowToken(windowToken);
1569    }
1570
1571    protected void onDetachedFromWindow() {
1572        super.onDetachedFromWindow();
1573        mWallpaperOffset.setWindowToken(null);
1574    }
1575
1576    protected void onResume() {
1577        if (getPageIndicator() != null) {
1578            // In case accessibility state has changed, we need to perform this on every
1579            // attach to window
1580            OnClickListener listener = getPageIndicatorClickListener();
1581            if (listener != null) {
1582                getPageIndicator().setOnClickListener(listener);
1583            }
1584        }
1585
1586        // Update wallpaper dimensions if they were changed since last onResume
1587        // (we also always set the wallpaper dimensions in the constructor)
1588        if (LauncherAppState.getInstance().hasWallpaperChangedSinceLastCheck()) {
1589            setWallpaperDimension();
1590        }
1591        mWallpaperOffset.onResume();
1592    }
1593
1594    @Override
1595    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1596        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
1597            mWallpaperOffset.syncWithScroll();
1598            mWallpaperOffset.jumpToFinal();
1599        }
1600        super.onLayout(changed, left, top, right, bottom);
1601    }
1602
1603    @Override
1604    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1605        if (!mLauncher.isAppsViewVisible()) {
1606            final Folder openFolder = getOpenFolder();
1607            if (openFolder != null) {
1608                return openFolder.requestFocus(direction, previouslyFocusedRect);
1609            } else {
1610                return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
1611            }
1612        }
1613        return false;
1614    }
1615
1616    @Override
1617    public int getDescendantFocusability() {
1618        if (workspaceInModalState()) {
1619            return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
1620        }
1621        return super.getDescendantFocusability();
1622    }
1623
1624    @Override
1625    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1626        if (!mLauncher.isAppsViewVisible()) {
1627            final Folder openFolder = getOpenFolder();
1628            if (openFolder != null) {
1629                openFolder.addFocusables(views, direction);
1630            } else {
1631                super.addFocusables(views, direction, focusableMode);
1632            }
1633        }
1634    }
1635
1636    public boolean workspaceInModalState() {
1637        return mState != State.NORMAL;
1638    }
1639
1640    void enableChildrenCache(int fromPage, int toPage) {
1641        if (fromPage > toPage) {
1642            final int temp = fromPage;
1643            fromPage = toPage;
1644            toPage = temp;
1645        }
1646
1647        final int screenCount = getChildCount();
1648
1649        fromPage = Math.max(fromPage, 0);
1650        toPage = Math.min(toPage, screenCount - 1);
1651
1652        for (int i = fromPage; i <= toPage; i++) {
1653            final CellLayout layout = (CellLayout) getChildAt(i);
1654            layout.setChildrenDrawnWithCacheEnabled(true);
1655            layout.setChildrenDrawingCacheEnabled(true);
1656        }
1657    }
1658
1659    void clearChildrenCache() {
1660        final int screenCount = getChildCount();
1661        for (int i = 0; i < screenCount; i++) {
1662            final CellLayout layout = (CellLayout) getChildAt(i);
1663            layout.setChildrenDrawnWithCacheEnabled(false);
1664            // In software mode, we don't want the items to continue to be drawn into bitmaps
1665            if (!isHardwareAccelerated()) {
1666                layout.setChildrenDrawingCacheEnabled(false);
1667            }
1668        }
1669    }
1670
1671    @Thunk void updateChildrenLayersEnabled(boolean force) {
1672        boolean small = mState == State.OVERVIEW || mIsSwitchingState;
1673        boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving();
1674
1675        if (enableChildrenLayers != mChildrenLayersEnabled) {
1676            mChildrenLayersEnabled = enableChildrenLayers;
1677            if (mChildrenLayersEnabled) {
1678                enableHwLayersOnVisiblePages();
1679            } else {
1680                for (int i = 0; i < getPageCount(); i++) {
1681                    final CellLayout cl = (CellLayout) getChildAt(i);
1682                    cl.enableHardwareLayer(false);
1683                }
1684            }
1685        }
1686    }
1687
1688    private void enableHwLayersOnVisiblePages() {
1689        if (mChildrenLayersEnabled) {
1690            final int screenCount = getChildCount();
1691            getVisiblePages(mTempVisiblePagesRange);
1692            int leftScreen = mTempVisiblePagesRange[0];
1693            int rightScreen = mTempVisiblePagesRange[1];
1694            if (leftScreen == rightScreen) {
1695                // make sure we're caching at least two pages always
1696                if (rightScreen < screenCount - 1) {
1697                    rightScreen++;
1698                } else if (leftScreen > 0) {
1699                    leftScreen--;
1700                }
1701            }
1702
1703            final CellLayout customScreen = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1704            for (int i = 0; i < screenCount; i++) {
1705                final CellLayout layout = (CellLayout) getPageAt(i);
1706
1707                // enable layers between left and right screen inclusive, except for the
1708                // customScreen, which may animate its content during transitions.
1709                boolean enableLayer = layout != customScreen &&
1710                        leftScreen <= i && i <= rightScreen && shouldDrawChild(layout);
1711                layout.enableHardwareLayer(enableLayer);
1712            }
1713        }
1714    }
1715
1716    public void buildPageHardwareLayers() {
1717        // force layers to be enabled just for the call to buildLayer
1718        updateChildrenLayersEnabled(true);
1719        if (getWindowToken() != null) {
1720            final int childCount = getChildCount();
1721            for (int i = 0; i < childCount; i++) {
1722                CellLayout cl = (CellLayout) getChildAt(i);
1723                cl.buildHardwareLayer();
1724            }
1725        }
1726        updateChildrenLayersEnabled(false);
1727    }
1728
1729    @Override
1730    protected void getVisiblePages(int[] range) {
1731        super.getVisiblePages(range);
1732        if (mForceDrawAdjacentPages) {
1733            // In overview mode, make sure that the two side pages are visible.
1734            range[0] = Utilities.boundInRange(getCurrentPage() - 1, numCustomPages(), range[1]);
1735            range[1] = Utilities.boundInRange(getCurrentPage() + 1, range[0], getPageCount() - 1);
1736        }
1737    }
1738
1739    protected void onWallpaperTap(MotionEvent ev) {
1740        final int[] position = mTempXY;
1741        getLocationOnScreen(position);
1742
1743        int pointerIndex = ev.getActionIndex();
1744        position[0] += (int) ev.getX(pointerIndex);
1745        position[1] += (int) ev.getY(pointerIndex);
1746
1747        mWallpaperManager.sendWallpaperCommand(getWindowToken(),
1748                ev.getAction() == MotionEvent.ACTION_UP
1749                        ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
1750                position[0], position[1], 0, null);
1751    }
1752
1753    /*
1754    *
1755    * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we
1756    * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
1757    *
1758    * These methods mark the appropriate pages as accepting drops (which alters their visual
1759    * appearance).
1760    *
1761    */
1762    private static Rect getDrawableBounds(Drawable d) {
1763        Rect bounds = new Rect();
1764        d.copyBounds(bounds);
1765        if (bounds.width() == 0 || bounds.height() == 0) {
1766            bounds.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
1767        } else {
1768            bounds.offsetTo(0, 0);
1769        }
1770        if (d instanceof PreloadIconDrawable) {
1771            int inset = -((PreloadIconDrawable) d).getOutset();
1772            bounds.inset(inset, inset);
1773        }
1774        return bounds;
1775    }
1776
1777    public void onExternalDragStartedWithItem(View v) {
1778        // Compose a drag bitmap with the view scaled to the icon size
1779        DeviceProfile grid = mLauncher.getDeviceProfile();
1780        int iconSize = grid.iconSizePx;
1781        int bmpWidth = v.getMeasuredWidth();
1782        int bmpHeight = v.getMeasuredHeight();
1783
1784        // If this is a text view, use its drawable instead
1785        if (v instanceof TextView) {
1786            Drawable d = getTextViewIcon((TextView) v);
1787            Rect bounds = getDrawableBounds(d);
1788            bmpWidth = bounds.width();
1789            bmpHeight = bounds.height();
1790        }
1791
1792        // Compose the bitmap to create the icon from
1793        Bitmap b = Bitmap.createBitmap(bmpWidth, bmpHeight,
1794                Bitmap.Config.ARGB_8888);
1795        mCanvas.setBitmap(b);
1796        drawDragView(v, mCanvas, 0);
1797        mCanvas.setBitmap(null);
1798
1799        // The outline is used to visualize where the item will land if dropped
1800        mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, iconSize, iconSize, true);
1801    }
1802
1803    public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
1804        // Find a page that has enough space to place this widget (after rearranging/resizing).
1805        // Start at the current page and search right (on LTR) until finding a page with enough
1806        // space. Since an empty screen is the furthest right, a page must be found.
1807        int currentPageInOverview = getPageNearestToCenterOfScreen();
1808        for (int pageIndex = currentPageInOverview; pageIndex < getPageCount(); pageIndex++) {
1809            CellLayout page = (CellLayout) getPageAt(pageIndex);
1810            if (page.hasReorderSolution(info)) {
1811                setCurrentPage(pageIndex);
1812                break;
1813            }
1814        }
1815
1816        int[] size = estimateItemSize(info, false);
1817
1818        // The outline is used to visualize where the item will land if dropped
1819        mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, size[0], size[1], clipAlpha);
1820    }
1821
1822    public void exitWidgetResizeMode() {
1823        DragLayer dragLayer = mLauncher.getDragLayer();
1824        dragLayer.clearAllResizeFrames();
1825    }
1826
1827    @Override
1828    protected void getFreeScrollPageRange(int[] range) {
1829        getOverviewModePages(range);
1830    }
1831
1832    private void getOverviewModePages(int[] range) {
1833        int start = numCustomPages();
1834        int end = getChildCount() - 1;
1835
1836        range[0] = Math.max(0, Math.min(start, getChildCount() - 1));
1837        range[1] = Math.max(0,  end);
1838    }
1839
1840    public void onStartReordering() {
1841        super.onStartReordering();
1842        // Reordering handles its own animations, disable the automatic ones.
1843        disableLayoutTransitions();
1844    }
1845
1846    public void onEndReordering() {
1847        super.onEndReordering();
1848
1849        if (mLauncher.isWorkspaceLoading()) {
1850            // Invalid and dangerous operation if workspace is loading
1851            return;
1852        }
1853
1854        mScreenOrder.clear();
1855        int count = getChildCount();
1856        for (int i = 0; i < count; i++) {
1857            CellLayout cl = ((CellLayout) getChildAt(i));
1858            mScreenOrder.add(getIdForScreen(cl));
1859        }
1860
1861        mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
1862
1863        // Re-enable auto layout transitions for page deletion.
1864        enableLayoutTransitions();
1865    }
1866
1867    public boolean isInOverviewMode() {
1868        return mState == State.OVERVIEW;
1869    }
1870
1871    public void snapToPageFromOverView(int whichPage) {
1872        mStateTransitionAnimation.snapToPageFromOverView(whichPage);
1873    }
1874
1875    int getOverviewModeTranslationY() {
1876        DeviceProfile grid = mLauncher.getDeviceProfile();
1877        Rect workspacePadding = grid.getWorkspacePadding(Utilities.isRtl(getResources()));
1878        int overviewButtonBarHeight = grid.getOverviewModeButtonBarHeight();
1879
1880        int scaledHeight = (int) (mOverviewModeShrinkFactor * getNormalChildHeight());
1881        int workspaceTop = mInsets.top + workspacePadding.top;
1882        int workspaceBottom = getViewportHeight() - mInsets.bottom - workspacePadding.bottom;
1883        int overviewTop = mInsets.top;
1884        int overviewBottom = getViewportHeight() - mInsets.bottom - overviewButtonBarHeight;
1885        int workspaceOffsetTopEdge = workspaceTop + ((workspaceBottom - workspaceTop) - scaledHeight) / 2;
1886        int overviewOffsetTopEdge = overviewTop + (overviewBottom - overviewTop - scaledHeight) / 2;
1887        return -workspaceOffsetTopEdge + overviewOffsetTopEdge;
1888    }
1889
1890    int getSpringLoadedTranslationY() {
1891        DeviceProfile grid = mLauncher.getDeviceProfile();
1892        Rect workspacePadding = grid.getWorkspacePadding(Utilities.isRtl(getResources()));
1893        int scaledHeight = (int) (mSpringLoadedShrinkFactor * getNormalChildHeight());
1894        int workspaceTop = mInsets.top + workspacePadding.top;
1895        int workspaceBottom = getViewportHeight() - mInsets.bottom - workspacePadding.bottom;
1896        int workspaceHeight = workspaceBottom - workspaceTop;
1897        // Center the spring-loaded pages by translating it up by half of the reduced height.
1898        return -(workspaceHeight - scaledHeight) / 2;
1899    }
1900
1901    float getOverviewModeShrinkFactor() {
1902        return mOverviewModeShrinkFactor;
1903    }
1904
1905    /**
1906     * Sets the current workspace {@link State}, returning an animation transitioning the workspace
1907     * to that new state.
1908     */
1909    public Animator setStateWithAnimation(State toState, boolean animated,
1910            HashMap<View, Integer> layerViews) {
1911        // Create the animation to the new state
1912        Animator workspaceAnim =  mStateTransitionAnimation.getAnimationToState(mState,
1913                toState, animated, layerViews);
1914
1915        boolean shouldNotifyWidgetChange = !mState.shouldUpdateWidget
1916                && toState.shouldUpdateWidget;
1917        // Update the current state
1918        mState = toState;
1919        updateAccessibilityFlags();
1920        if (mState == State.OVERVIEW || mState == State.SPRING_LOADED) {
1921            // Redraw pages, as we might want to draw pages which were not visible.
1922            mForceDrawAdjacentPages = true;
1923            invalidate(); // This will call dispatchDraw(), which calls getVisiblePages().
1924        }
1925
1926        if (shouldNotifyWidgetChange) {
1927            mLauncher.notifyWidgetProvidersChanged();
1928        }
1929
1930        return workspaceAnim;
1931    }
1932
1933    State getState() {
1934        return mState;
1935    }
1936
1937    public void updateAccessibilityFlags() {
1938        if (Utilities.ATLEAST_LOLLIPOP) {
1939            int total = getPageCount();
1940            for (int i = numCustomPages(); i < total; i++) {
1941                updateAccessibilityFlags((CellLayout) getPageAt(i), i);
1942            }
1943            setImportantForAccessibility((mState == State.NORMAL || mState == State.OVERVIEW)
1944                    ? IMPORTANT_FOR_ACCESSIBILITY_AUTO
1945                    : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
1946        } else {
1947            int accessible = mState == State.NORMAL ?
1948                    IMPORTANT_FOR_ACCESSIBILITY_AUTO :
1949                        IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
1950            setImportantForAccessibility(accessible);
1951        }
1952    }
1953
1954    private void updateAccessibilityFlags(CellLayout page, int pageNo) {
1955        if (mState == State.OVERVIEW) {
1956            page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1957            page.getShortcutsAndWidgets().setImportantForAccessibility(
1958                    IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
1959            page.setContentDescription(getPageDescription(pageNo));
1960
1961            if (mPagesAccessibilityDelegate == null) {
1962                mPagesAccessibilityDelegate = new OverviewScreenAccessibilityDelegate(this);
1963            }
1964            page.setAccessibilityDelegate(mPagesAccessibilityDelegate);
1965        } else {
1966            int accessible = mState == State.NORMAL ?
1967                    IMPORTANT_FOR_ACCESSIBILITY_AUTO :
1968                        IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
1969            page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
1970            page.getShortcutsAndWidgets().setImportantForAccessibility(accessible);
1971            page.setContentDescription(null);
1972            page.setAccessibilityDelegate(null);
1973        }
1974    }
1975
1976    @Override
1977    public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
1978        mIsSwitchingState = true;
1979        mTransitionProgress = 0;
1980
1981        // Invalidate here to ensure that the pages are rendered during the state change transition.
1982        invalidate();
1983
1984        updateChildrenLayersEnabled(false);
1985        hideCustomContentIfNecessary();
1986    }
1987
1988    @Override
1989    public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
1990    }
1991
1992    @Override
1993    public void onLauncherTransitionStep(Launcher l, float t) {
1994        mTransitionProgress = t;
1995    }
1996
1997    @Override
1998    public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
1999        mIsSwitchingState = false;
2000        updateChildrenLayersEnabled(false);
2001        showCustomContentIfNecessary();
2002        mForceDrawAdjacentPages = false;
2003    }
2004
2005    void updateCustomContentVisibility() {
2006        int visibility = mState == Workspace.State.NORMAL ? VISIBLE : INVISIBLE;
2007        setCustomContentVisibility(visibility);
2008    }
2009
2010    void setCustomContentVisibility(int visibility) {
2011        if (hasCustomContent()) {
2012            mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(visibility);
2013        }
2014    }
2015
2016    void showCustomContentIfNecessary() {
2017        boolean show  = mState == Workspace.State.NORMAL;
2018        if (show && hasCustomContent()) {
2019            mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(VISIBLE);
2020        }
2021    }
2022
2023    void hideCustomContentIfNecessary() {
2024        boolean hide  = mState != Workspace.State.NORMAL;
2025        if (hide && hasCustomContent()) {
2026            disableLayoutTransitions();
2027            mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE);
2028            enableLayoutTransitions();
2029        }
2030    }
2031
2032    /**
2033     * Returns the drawable for the given text view.
2034     */
2035    public static Drawable getTextViewIcon(TextView tv) {
2036        final Drawable[] drawables = tv.getCompoundDrawables();
2037        for (int i = 0; i < drawables.length; i++) {
2038            if (drawables[i] != null) {
2039                return drawables[i];
2040            }
2041        }
2042        return null;
2043    }
2044
2045    /**
2046     * Draw the View v into the given Canvas.
2047     *
2048     * @param v the view to draw
2049     * @param destCanvas the canvas to draw on
2050     * @param padding the horizontal and vertical padding to use when drawing
2051     */
2052    private static void drawDragView(View v, Canvas destCanvas, int padding) {
2053        destCanvas.save();
2054        if (v instanceof TextView) {
2055            Drawable d = getTextViewIcon((TextView) v);
2056            Rect bounds = getDrawableBounds(d);
2057            destCanvas.translate(padding / 2 - bounds.left, padding / 2 - bounds.top);
2058            d.draw(destCanvas);
2059        } else {
2060            final Rect clipRect = sTempRect;
2061            v.getDrawingRect(clipRect);
2062
2063            boolean textVisible = false;
2064            if (v instanceof FolderIcon) {
2065                // For FolderIcons the text can bleed into the icon area, and so we need to
2066                // hide the text completely (which can't be achieved by clipping).
2067                if (((FolderIcon) v).getTextVisible()) {
2068                    ((FolderIcon) v).setTextVisible(false);
2069                    textVisible = true;
2070                }
2071            }
2072            destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
2073            destCanvas.clipRect(clipRect, Op.REPLACE);
2074            v.draw(destCanvas);
2075
2076            // Restore text visibility of FolderIcon if necessary
2077            if (textVisible) {
2078                ((FolderIcon) v).setTextVisible(true);
2079            }
2080        }
2081        destCanvas.restore();
2082    }
2083
2084    /**
2085     * Returns a new bitmap to show when the given View is being dragged around.
2086     * Responsibility for the bitmap is transferred to the caller.
2087     * @param expectedPadding padding to add to the drag view. If a different padding was used
2088     * its value will be changed
2089     */
2090    public Bitmap createDragBitmap(View v, AtomicInteger expectedPadding) {
2091        Bitmap b;
2092
2093        int padding = expectedPadding.get();
2094        if (v instanceof TextView) {
2095            Drawable d = getTextViewIcon((TextView) v);
2096            Rect bounds = getDrawableBounds(d);
2097            b = Bitmap.createBitmap(bounds.width() + padding,
2098                    bounds.height() + padding, Bitmap.Config.ARGB_8888);
2099            expectedPadding.set(padding - bounds.left - bounds.top);
2100        } else {
2101            b = Bitmap.createBitmap(
2102                    v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
2103        }
2104
2105        mCanvas.setBitmap(b);
2106        drawDragView(v, mCanvas, padding);
2107        mCanvas.setBitmap(null);
2108
2109        return b;
2110    }
2111
2112    /**
2113     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2114     * Responsibility for the bitmap is transferred to the caller.
2115     */
2116    private Bitmap createDragOutline(View v, int padding) {
2117        final int outlineColor = getResources().getColor(R.color.outline_color);
2118        final Bitmap b = Bitmap.createBitmap(
2119                v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
2120
2121        mCanvas.setBitmap(b);
2122        drawDragView(v, mCanvas, padding);
2123        mOutlineHelper.applyExpensiveOutlineWithBlur(b, mCanvas, outlineColor, outlineColor);
2124        mCanvas.setBitmap(null);
2125        return b;
2126    }
2127
2128    /**
2129     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2130     * Responsibility for the bitmap is transferred to the caller.
2131     */
2132    private Bitmap createDragOutline(Bitmap orig, int padding, int w, int h,
2133            boolean clipAlpha) {
2134        final int outlineColor = getResources().getColor(R.color.outline_color);
2135        final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
2136        mCanvas.setBitmap(b);
2137
2138        Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
2139        float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(),
2140                (h - padding) / (float) orig.getHeight());
2141        int scaledWidth = (int) (scaleFactor * orig.getWidth());
2142        int scaledHeight = (int) (scaleFactor * orig.getHeight());
2143        Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
2144
2145        // center the image
2146        dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
2147
2148        mCanvas.drawBitmap(orig, src, dst, null);
2149        mOutlineHelper.applyExpensiveOutlineWithBlur(b, mCanvas, outlineColor, outlineColor,
2150                clipAlpha);
2151        mCanvas.setBitmap(null);
2152
2153        return b;
2154    }
2155
2156    public void startDrag(CellLayout.CellInfo cellInfo) {
2157        startDrag(cellInfo, false);
2158    }
2159
2160    @Override
2161    public void startDrag(CellLayout.CellInfo cellInfo, boolean accessible) {
2162        View child = cellInfo.cell;
2163
2164        // Make sure the drag was started by a long press as opposed to a long click.
2165        if (!child.isInTouchMode()) {
2166            return;
2167        }
2168
2169        mDragInfo = cellInfo;
2170        child.setVisibility(INVISIBLE);
2171        CellLayout layout = (CellLayout) child.getParent().getParent();
2172        layout.prepareChildForDrag(child);
2173
2174        beginDragShared(child, this, accessible);
2175    }
2176
2177    public void beginDragShared(View child, DragSource source, boolean accessible) {
2178        beginDragShared(child, new Point(), source, accessible);
2179    }
2180
2181    public void beginDragShared(View child, Point relativeTouchPos, DragSource source,
2182            boolean accessible) {
2183        child.clearFocus();
2184        child.setPressed(false);
2185
2186        // The outline is used to visualize where the item will land if dropped
2187        mDragOutline = createDragOutline(child, DRAG_BITMAP_PADDING);
2188
2189        mLauncher.onDragStarted(child);
2190        // The drag bitmap follows the touch point around on the screen
2191        AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
2192        final Bitmap b = createDragBitmap(child, padding);
2193
2194        final int bmpWidth = b.getWidth();
2195        final int bmpHeight = b.getHeight();
2196
2197        float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
2198        int dragLayerX = Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
2199        int dragLayerY = Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2
2200                        - padding.get() / 2);
2201
2202        DeviceProfile grid = mLauncher.getDeviceProfile();
2203        Point dragVisualizeOffset = null;
2204        Rect dragRect = null;
2205        if (child instanceof BubbleTextView) {
2206            BubbleTextView icon = (BubbleTextView) child;
2207            int iconSize = grid.iconSizePx;
2208            int top = child.getPaddingTop();
2209            int left = (bmpWidth - iconSize) / 2;
2210            int right = left + iconSize;
2211            int bottom = top + iconSize;
2212            if (icon.isLayoutHorizontal()) {
2213                // If the layout is horizontal, then if we are just picking up the icon, then just
2214                // use the child position since the icon is top-left aligned.  Otherwise, offset
2215                // the drag layer position horizontally so that the icon is under the current
2216                // touch position.
2217                if (icon.getIcon().getBounds().contains(relativeTouchPos.x, relativeTouchPos.y)) {
2218                    dragLayerX = Math.round(mTempXY[0]);
2219                } else {
2220                    dragLayerX = Math.round(mTempXY[0] + relativeTouchPos.x - (bmpWidth / 2));
2221                }
2222            }
2223            dragLayerY += top;
2224            // Note: The drag region is used to calculate drag layer offsets, but the
2225            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2226            dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2);
2227            dragRect = new Rect(left, top, right, bottom);
2228        } else if (child instanceof FolderIcon) {
2229            int previewSize = grid.folderIconSizePx;
2230            dragVisualizeOffset = new Point(-padding.get() / 2,
2231                    padding.get() / 2 - child.getPaddingTop());
2232            dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
2233        }
2234
2235        // Clear the pressed state if necessary
2236        if (child instanceof BubbleTextView) {
2237            BubbleTextView icon = (BubbleTextView) child;
2238            icon.clearPressedBackground();
2239        }
2240
2241        Object dragObject = child.getTag();
2242        if (!(dragObject instanceof ItemInfo)) {
2243            String msg = "Drag started with a view that has no tag set. This "
2244                    + "will cause a crash (issue 11627249) down the line. "
2245                    + "View: " + child + "  tag: " + child.getTag();
2246            throw new IllegalStateException(msg);
2247        }
2248
2249        if (child.getParent() instanceof ShortcutAndWidgetContainer) {
2250            mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
2251        }
2252
2253        DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
2254                (ItemInfo) dragObject, DragController.DRAG_ACTION_MOVE, dragVisualizeOffset,
2255                dragRect, scale, accessible);
2256        dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
2257
2258        b.recycle();
2259
2260        if (!FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
2261            mLauncher.enterSpringLoadedDragMode();
2262        }
2263    }
2264
2265    public void beginExternalDragShared(View child, DragSource source) {
2266        DeviceProfile grid = mLauncher.getDeviceProfile();
2267        int iconSize = grid.iconSizePx;
2268
2269        // Notify launcher of drag start
2270        mLauncher.onDragStarted(child);
2271
2272        // Compose a new drag bitmap that is of the icon size
2273        AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
2274        final Bitmap tmpB = createDragBitmap(child, padding);
2275        Bitmap b = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
2276        Paint p = new Paint();
2277        p.setFilterBitmap(true);
2278        mCanvas.setBitmap(b);
2279        mCanvas.drawBitmap(tmpB, new Rect(0, 0, tmpB.getWidth(), tmpB.getHeight()),
2280                new Rect(0, 0, iconSize, iconSize), p);
2281        mCanvas.setBitmap(null);
2282
2283        // Find the child's location on the screen
2284        int bmpWidth = tmpB.getWidth();
2285        float iconScale = (float) bmpWidth / iconSize;
2286        float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY) * iconScale;
2287        int dragLayerX = Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
2288        int dragLayerY = Math.round(mTempXY[1]);
2289
2290        // Note: The drag region is used to calculate drag layer offsets, but the
2291        // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2292        Point dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2);
2293        Rect dragRect = new Rect(0, 0, iconSize, iconSize);
2294
2295        Object dragObject = child.getTag();
2296        if (!(dragObject instanceof ItemInfo)) {
2297            String msg = "Drag started with a view that has no tag set. This "
2298                    + "will cause a crash (issue 11627249) down the line. "
2299                    + "View: " + child + "  tag: " + child.getTag();
2300            throw new IllegalStateException(msg);
2301        }
2302
2303        // Start the drag
2304        DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
2305                (ItemInfo) dragObject, DragController.DRAG_ACTION_MOVE, dragVisualizeOffset,
2306                dragRect, scale, false);
2307        dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
2308
2309        // Recycle temporary bitmaps
2310        tmpB.recycle();
2311
2312        if (!FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
2313            mLauncher.enterSpringLoadedDragMode();
2314        }
2315    }
2316
2317    public boolean transitionStateShouldAllowDrop() {
2318        return ((!isSwitchingState() || mTransitionProgress > 0.5f) &&
2319                (mState == State.NORMAL || mState == State.SPRING_LOADED));
2320    }
2321
2322    /**
2323     * {@inheritDoc}
2324     */
2325    public boolean acceptDrop(DragObject d) {
2326        // If it's an external drop (e.g. from All Apps), check if it should be accepted
2327        CellLayout dropTargetLayout = mDropToLayout;
2328        if (d.dragSource != this) {
2329            // Don't accept the drop if we're not over a screen at time of drop
2330            if (dropTargetLayout == null) {
2331                return false;
2332            }
2333            if (!transitionStateShouldAllowDrop()) return false;
2334
2335            mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
2336
2337            // We want the point to be mapped to the dragTarget.
2338            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2339                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2340            } else {
2341                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter);
2342            }
2343
2344            int spanX = 1;
2345            int spanY = 1;
2346            if (mDragInfo != null) {
2347                final CellLayout.CellInfo dragCellInfo = mDragInfo;
2348                spanX = dragCellInfo.spanX;
2349                spanY = dragCellInfo.spanY;
2350            } else {
2351                spanX = d.dragInfo.spanX;
2352                spanY = d.dragInfo.spanY;
2353            }
2354
2355            int minSpanX = spanX;
2356            int minSpanY = spanY;
2357            if (d.dragInfo instanceof PendingAddWidgetInfo) {
2358                minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
2359                minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
2360            }
2361
2362            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2363                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
2364                    mTargetCell);
2365            float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2366                    mDragViewVisualCenter[1], mTargetCell);
2367            if (mCreateUserFolderOnDrop && willCreateUserFolder(d.dragInfo,
2368                    dropTargetLayout, mTargetCell, distance, true)) {
2369                return true;
2370            }
2371
2372            if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder(d.dragInfo,
2373                    dropTargetLayout, mTargetCell, distance)) {
2374                return true;
2375            }
2376
2377            int[] resultSpan = new int[2];
2378            mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
2379                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2380                    null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
2381            boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2382
2383            // Don't accept the drop if there's no room for the item
2384            if (!foundCell) {
2385                // Don't show the message if we are dropping on the AllApps button and the hotseat
2386                // is full
2387                boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2388                if (mTargetCell != null && isHotseat) {
2389                    Hotseat hotseat = mLauncher.getHotseat();
2390                    if (hotseat.isAllAppsButtonRank(
2391                            hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
2392                        return false;
2393                    }
2394                }
2395
2396                mLauncher.showOutOfSpaceMessage(isHotseat);
2397                return false;
2398            }
2399        }
2400
2401        long screenId = getIdForScreen(dropTargetLayout);
2402        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
2403            commitExtraEmptyScreen();
2404        }
2405
2406        return true;
2407    }
2408
2409    boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell,
2410            float distance, boolean considerTimeout) {
2411        if (distance > mMaxDistanceForFolderCreation) return false;
2412        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2413        return willCreateUserFolder(info, dropOverView, considerTimeout);
2414    }
2415
2416    boolean willCreateUserFolder(ItemInfo info, View dropOverView, boolean considerTimeout) {
2417        if (dropOverView != null) {
2418            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2419            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) {
2420                return false;
2421            }
2422        }
2423
2424        boolean hasntMoved = false;
2425        if (mDragInfo != null) {
2426            hasntMoved = dropOverView == mDragInfo.cell;
2427        }
2428
2429        if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
2430            return false;
2431        }
2432
2433        boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
2434        boolean willBecomeShortcut =
2435                (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
2436                        info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
2437
2438        return (aboveShortcut && willBecomeShortcut);
2439    }
2440
2441    boolean willAddToExistingUserFolder(ItemInfo dragInfo, CellLayout target, int[] targetCell,
2442            float distance) {
2443        if (distance > mMaxDistanceForFolderCreation) return false;
2444        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2445        return willAddToExistingUserFolder(dragInfo, dropOverView);
2446
2447    }
2448    boolean willAddToExistingUserFolder(ItemInfo dragInfo, View dropOverView) {
2449        if (dropOverView != null) {
2450            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2451            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) {
2452                return false;
2453            }
2454        }
2455
2456        if (dropOverView instanceof FolderIcon) {
2457            FolderIcon fi = (FolderIcon) dropOverView;
2458            if (fi.acceptDrop(dragInfo)) {
2459                return true;
2460            }
2461        }
2462        return false;
2463    }
2464
2465    boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
2466            int[] targetCell, float distance, boolean external, DragView dragView,
2467            Runnable postAnimationRunnable) {
2468        if (distance > mMaxDistanceForFolderCreation) return false;
2469        View v = target.getChildAt(targetCell[0], targetCell[1]);
2470
2471        boolean hasntMoved = false;
2472        if (mDragInfo != null) {
2473            CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
2474            hasntMoved = (mDragInfo.cellX == targetCell[0] &&
2475                    mDragInfo.cellY == targetCell[1]) && (cellParent == target);
2476        }
2477
2478        if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
2479        mCreateUserFolderOnDrop = false;
2480        final long screenId = getIdForScreen(target);
2481
2482        boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
2483        boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
2484
2485        if (aboveShortcut && willBecomeShortcut) {
2486            ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
2487            ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
2488            // if the drag started here, we need to remove it from the workspace
2489            if (!external) {
2490                getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2491            }
2492
2493            Rect folderLocation = new Rect();
2494            float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
2495            target.removeView(v);
2496
2497            FolderIcon fi =
2498                mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
2499            destInfo.cellX = -1;
2500            destInfo.cellY = -1;
2501            sourceInfo.cellX = -1;
2502            sourceInfo.cellY = -1;
2503
2504            // If the dragView is null, we can't animate
2505            boolean animate = dragView != null;
2506            if (animate) {
2507                // In order to keep everything continuous, we hand off the currently rendered
2508                // folder background to the newly created icon. This preserves animation state.
2509                fi.setFolderBackground(mFolderCreateBg);
2510                mFolderCreateBg = new FolderIcon.PreviewBackground();
2511                fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
2512                        postAnimationRunnable);
2513            } else {
2514                fi.addItem(destInfo);
2515                fi.addItem(sourceInfo);
2516            }
2517            return true;
2518        }
2519        return false;
2520    }
2521
2522    boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
2523            float distance, DragObject d, boolean external) {
2524        if (distance > mMaxDistanceForFolderCreation) return false;
2525
2526        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2527        if (!mAddToExistingFolderOnDrop) return false;
2528        mAddToExistingFolderOnDrop = false;
2529
2530        if (dropOverView instanceof FolderIcon) {
2531            FolderIcon fi = (FolderIcon) dropOverView;
2532            if (fi.acceptDrop(d.dragInfo)) {
2533                fi.onDrop(d);
2534
2535                // if the drag started here, we need to remove it from the workspace
2536                if (!external) {
2537                    getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2538                }
2539                return true;
2540            }
2541        }
2542        return false;
2543    }
2544
2545    @Override
2546    public void prepareAccessibilityDrop() { }
2547
2548    public void onDrop(final DragObject d) {
2549        mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
2550        CellLayout dropTargetLayout = mDropToLayout;
2551
2552        // We want the point to be mapped to the dragTarget.
2553        if (dropTargetLayout != null) {
2554            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2555                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2556            } else {
2557                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter);
2558            }
2559        }
2560
2561        int snapScreen = -1;
2562        boolean resizeOnDrop = false;
2563        if (d.dragSource != this) {
2564            final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
2565                    (int) mDragViewVisualCenter[1] };
2566            onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
2567        } else if (mDragInfo != null) {
2568            final View cell = mDragInfo.cell;
2569
2570            if (dropTargetLayout != null && !d.cancelled) {
2571                // Move internally
2572                boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
2573                boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2574                long container = hasMovedIntoHotseat ?
2575                        LauncherSettings.Favorites.CONTAINER_HOTSEAT :
2576                        LauncherSettings.Favorites.CONTAINER_DESKTOP;
2577                long screenId = (mTargetCell[0] < 0) ?
2578                        mDragInfo.screenId : getIdForScreen(dropTargetLayout);
2579                int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
2580                int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
2581                // First we find the cell nearest to point at which the item is
2582                // dropped, without any consideration to whether there is an item there.
2583
2584                mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
2585                        mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
2586                float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2587                        mDragViewVisualCenter[1], mTargetCell);
2588
2589                // If the item being dropped is a shortcut and the nearest drop
2590                // cell also contains a shortcut, then create a folder with the two shortcuts.
2591                if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
2592                        dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
2593                    return;
2594                }
2595
2596                if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
2597                        distance, d, false)) {
2598                    return;
2599                }
2600
2601                // Aside from the special case where we're dropping a shortcut onto a shortcut,
2602                // we need to find the nearest cell location that is vacant
2603                ItemInfo item = d.dragInfo;
2604                int minSpanX = item.spanX;
2605                int minSpanY = item.spanY;
2606                if (item.minSpanX > 0 && item.minSpanY > 0) {
2607                    minSpanX = item.minSpanX;
2608                    minSpanY = item.minSpanY;
2609                }
2610
2611                int[] resultSpan = new int[2];
2612                mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
2613                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
2614                        mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
2615
2616                boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2617
2618                // if the widget resizes on drop
2619                if (foundCell && (cell instanceof AppWidgetHostView) &&
2620                        (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
2621                    resizeOnDrop = true;
2622                    item.spanX = resultSpan[0];
2623                    item.spanY = resultSpan[1];
2624                    AppWidgetHostView awhv = (AppWidgetHostView) cell;
2625                    AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
2626                            resultSpan[1]);
2627                }
2628
2629                if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
2630                    snapScreen = getPageIndexForScreenId(screenId);
2631                    snapToPage(snapScreen);
2632                }
2633
2634                if (foundCell) {
2635                    final ItemInfo info = (ItemInfo) cell.getTag();
2636                    if (hasMovedLayouts) {
2637                        // Reparent the view
2638                        CellLayout parentCell = getParentCellLayoutForView(cell);
2639                        if (parentCell != null) {
2640                            parentCell.removeView(cell);
2641                        } else if (ProviderConfig.IS_DOGFOOD_BUILD) {
2642                            throw new NullPointerException("mDragInfo.cell has null parent");
2643                        }
2644                        addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
2645                                info.spanX, info.spanY);
2646                    }
2647
2648                    // update the item's position after drop
2649                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2650                    lp.cellX = lp.tmpCellX = mTargetCell[0];
2651                    lp.cellY = lp.tmpCellY = mTargetCell[1];
2652                    lp.cellHSpan = item.spanX;
2653                    lp.cellVSpan = item.spanY;
2654                    lp.isLockedToGrid = true;
2655
2656                    if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
2657                            cell instanceof LauncherAppWidgetHostView) {
2658                        final CellLayout cellLayout = dropTargetLayout;
2659                        // We post this call so that the widget has a chance to be placed
2660                        // in its final location
2661
2662                        final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
2663                        AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo();
2664                        if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE
2665                                && !d.accessibleDrag) {
2666                            mDelayedResizeRunnable = new Runnable() {
2667                                public void run() {
2668                                    if (!isPageMoving() && !mIsSwitchingState) {
2669                                        DragLayer dragLayer = mLauncher.getDragLayer();
2670                                        dragLayer.addResizeFrame(info, hostView, cellLayout);
2671                                    }
2672                                }
2673                            };
2674                        }
2675                    }
2676
2677                    LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX,
2678                            lp.cellY, item.spanX, item.spanY);
2679                } else {
2680                    // If we can't find a drop location, we return the item to its original position
2681                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2682                    mTargetCell[0] = lp.cellX;
2683                    mTargetCell[1] = lp.cellY;
2684                    CellLayout layout = (CellLayout) cell.getParent().getParent();
2685                    layout.markCellsAsOccupiedForView(cell);
2686                }
2687            }
2688
2689            final CellLayout parent = (CellLayout) cell.getParent().getParent();
2690            // Prepare it to be animated into its new position
2691            // This must be called after the view has been re-parented
2692            final Runnable onCompleteRunnable = new Runnable() {
2693                @Override
2694                public void run() {
2695                    mAnimatingViewIntoPlace = false;
2696                    updateChildrenLayersEnabled(false);
2697                }
2698            };
2699            mAnimatingViewIntoPlace = true;
2700            if (d.dragView.hasDrawn()) {
2701                final ItemInfo info = (ItemInfo) cell.getTag();
2702                boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
2703                        || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
2704                if (isWidget) {
2705                    int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
2706                            ANIMATE_INTO_POSITION_AND_DISAPPEAR;
2707                    animateWidgetDrop(info, parent, d.dragView,
2708                            onCompleteRunnable, animationType, cell, false);
2709                } else {
2710                    int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
2711                    mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
2712                            onCompleteRunnable, this);
2713                }
2714            } else {
2715                d.deferDragViewCleanupPostAnimation = false;
2716                cell.setVisibility(VISIBLE);
2717            }
2718            parent.onDropChild(cell);
2719        }
2720    }
2721
2722    /**
2723     * Computes the area relative to dragLayer which is used to display a page.
2724     */
2725    public void getPageAreaRelativeToDragLayer(Rect outArea) {
2726        CellLayout child = (CellLayout) getChildAt(getNextPage());
2727        if (child == null) {
2728            return;
2729        }
2730        ShortcutAndWidgetContainer boundingLayout = child.getShortcutsAndWidgets();
2731
2732        // Use the absolute left instead of the child left, as we want the visible area
2733        // irrespective of the visible child. Since the view can only scroll horizontally, the
2734        // top position is not affected.
2735        mTempXY[0] = getViewportOffsetX() + getPaddingLeft() + boundingLayout.getLeft();
2736        mTempXY[1] = child.getTop() + boundingLayout.getTop();
2737
2738        float scale = mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY);
2739        outArea.set(mTempXY[0], mTempXY[1],
2740                (int) (mTempXY[0] + scale * boundingLayout.getMeasuredWidth()),
2741                (int) (mTempXY[1] + scale * boundingLayout.getMeasuredHeight()));
2742    }
2743
2744    @Override
2745    public void onDragEnter(DragObject d) {
2746        if (ENFORCE_DRAG_EVENT_ORDER) {
2747            enfoceDragParity("onDragEnter", 1, 1);
2748        }
2749
2750        mCreateUserFolderOnDrop = false;
2751        mAddToExistingFolderOnDrop = false;
2752
2753        mDropToLayout = null;
2754        CellLayout layout = getCurrentDropLayout();
2755        setCurrentDropLayout(layout);
2756        setCurrentDragOverlappingLayout(layout);
2757
2758        if (!workspaceInModalState() && FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
2759            mLauncher.getDragLayer().showPageHints();
2760        }
2761    }
2762
2763    @Override
2764    public void onDragExit(DragObject d) {
2765        if (ENFORCE_DRAG_EVENT_ORDER) {
2766            enfoceDragParity("onDragExit", -1, 0);
2767        }
2768
2769        // Here we store the final page that will be dropped to, if the workspace in fact
2770        // receives the drop
2771        if (mInScrollArea) {
2772            if (isPageMoving()) {
2773                // If the user drops while the page is scrolling, we should use that page as the
2774                // destination instead of the page that is being hovered over.
2775                mDropToLayout = (CellLayout) getPageAt(getNextPage());
2776            } else {
2777                mDropToLayout = mDragOverlappingLayout;
2778            }
2779        } else {
2780            mDropToLayout = mDragTargetLayout;
2781        }
2782
2783        if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
2784            mCreateUserFolderOnDrop = true;
2785        } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
2786            mAddToExistingFolderOnDrop = true;
2787        }
2788
2789        // Reset the scroll area and previous drag target
2790        onResetScrollArea();
2791        setCurrentDropLayout(null);
2792        setCurrentDragOverlappingLayout(null);
2793
2794        mSpringLoadedDragController.cancel();
2795
2796        mLauncher.getDragLayer().hidePageHints();
2797    }
2798
2799    private void enfoceDragParity(String event, int update, int expectedValue) {
2800        enfoceDragParity(this, event, update, expectedValue);
2801        for (int i = 0; i < getChildCount(); i++) {
2802            enfoceDragParity(getChildAt(i), event, update, expectedValue);
2803        }
2804    }
2805
2806    private void enfoceDragParity(View v, String event, int update, int expectedValue) {
2807        Object tag = v.getTag(R.id.drag_event_parity);
2808        int value = tag == null ? 0 : (Integer) tag;
2809        value += update;
2810        v.setTag(R.id.drag_event_parity, value);
2811
2812        if (value != expectedValue) {
2813            Log.e(TAG, event + ": Drag contract violated: " + value);
2814        }
2815    }
2816
2817    void setCurrentDropLayout(CellLayout layout) {
2818        if (mDragTargetLayout != null) {
2819            mDragTargetLayout.revertTempState();
2820            mDragTargetLayout.onDragExit();
2821        }
2822        mDragTargetLayout = layout;
2823        if (mDragTargetLayout != null) {
2824            mDragTargetLayout.onDragEnter();
2825        }
2826        cleanupReorder(true);
2827        cleanupFolderCreation();
2828        setCurrentDropOverCell(-1, -1);
2829    }
2830
2831    void setCurrentDragOverlappingLayout(CellLayout layout) {
2832        if (mDragOverlappingLayout != null) {
2833            mDragOverlappingLayout.setIsDragOverlapping(false);
2834        }
2835        mDragOverlappingLayout = layout;
2836        if (mDragOverlappingLayout != null) {
2837            mDragOverlappingLayout.setIsDragOverlapping(true);
2838        }
2839        invalidate();
2840    }
2841
2842    void setCurrentDropOverCell(int x, int y) {
2843        if (x != mDragOverX || y != mDragOverY) {
2844            mDragOverX = x;
2845            mDragOverY = y;
2846            setDragMode(DRAG_MODE_NONE);
2847        }
2848    }
2849
2850    void setDragMode(int dragMode) {
2851        if (dragMode != mDragMode) {
2852            if (dragMode == DRAG_MODE_NONE) {
2853                cleanupAddToFolder();
2854                // We don't want to cancel the re-order alarm every time the target cell changes
2855                // as this feels to slow / unresponsive.
2856                cleanupReorder(false);
2857                cleanupFolderCreation();
2858            } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
2859                cleanupReorder(true);
2860                cleanupFolderCreation();
2861            } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
2862                cleanupAddToFolder();
2863                cleanupReorder(true);
2864            } else if (dragMode == DRAG_MODE_REORDER) {
2865                cleanupAddToFolder();
2866                cleanupFolderCreation();
2867            }
2868            mDragMode = dragMode;
2869        }
2870    }
2871
2872    private void cleanupFolderCreation() {
2873        mFolderCreateBg.animateToRest();
2874        mFolderCreationAlarm.setOnAlarmListener(null);
2875        mFolderCreationAlarm.cancelAlarm();
2876    }
2877
2878    private void cleanupAddToFolder() {
2879        if (mDragOverFolderIcon != null) {
2880            mDragOverFolderIcon.onDragExit(null);
2881            mDragOverFolderIcon = null;
2882        }
2883    }
2884
2885    private void cleanupReorder(boolean cancelAlarm) {
2886        // Any pending reorders are canceled
2887        if (cancelAlarm) {
2888            mReorderAlarm.cancelAlarm();
2889        }
2890        mLastReorderX = -1;
2891        mLastReorderY = -1;
2892    }
2893
2894   /*
2895    *
2896    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
2897    * coordinate space. The argument xy is modified with the return result.
2898    */
2899   void mapPointFromSelfToChild(View v, float[] xy) {
2900       xy[0] = xy[0] - v.getLeft();
2901       xy[1] = xy[1] - v.getTop();
2902   }
2903
2904   boolean isPointInSelfOverHotseat(int x, int y) {
2905       mTempXY[0] = x;
2906       mTempXY[1] = y;
2907       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
2908       return mLauncher.getDeviceProfile().isInHotseatRect(mTempXY[0], mTempXY[1]);
2909   }
2910
2911   void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
2912       mTempXY[0] = (int) xy[0];
2913       mTempXY[1] = (int) xy[1];
2914       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
2915       mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempXY);
2916
2917       xy[0] = mTempXY[0];
2918       xy[1] = mTempXY[1];
2919   }
2920
2921   /*
2922    *
2923    * Convert the 2D coordinate xy from this CellLayout's coordinate space to
2924    * the parent View's coordinate space. The argument xy is modified with the return result.
2925    *
2926    */
2927   void mapPointFromChildToSelf(View v, float[] xy) {
2928       xy[0] += v.getLeft();
2929       xy[1] += v.getTop();
2930   }
2931
2932   static private float squaredDistance(float[] point1, float[] point2) {
2933        float distanceX = point1[0] - point2[0];
2934        float distanceY = point2[1] - point2[1];
2935        return distanceX * distanceX + distanceY * distanceY;
2936   }
2937
2938    /*
2939     *
2940     * This method returns the CellLayout that is currently being dragged to. In order to drag
2941     * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
2942     * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
2943     *
2944     * Return null if no CellLayout is currently being dragged over
2945     *
2946     */
2947    private CellLayout findMatchingPageForDragOver(
2948            DragView dragView, float originX, float originY, boolean exact) {
2949        // We loop through all the screens (ie CellLayouts) and see which ones overlap
2950        // with the item being dragged and then choose the one that's closest to the touch point
2951        final int screenCount = getChildCount();
2952        CellLayout bestMatchingScreen = null;
2953        float smallestDistSoFar = Float.MAX_VALUE;
2954
2955        for (int i = 0; i < screenCount; i++) {
2956            // The custom content screen is not a valid drag over option
2957            if (mScreenOrder.get(i) == CUSTOM_CONTENT_SCREEN_ID) {
2958                continue;
2959            }
2960
2961            CellLayout cl = (CellLayout) getChildAt(i);
2962
2963            final float[] touchXy = {originX, originY};
2964            mapPointFromSelfToChild(cl, touchXy);
2965
2966            if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
2967                    touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
2968                return cl;
2969            }
2970
2971            if (!exact) {
2972                // Get the center of the cell layout in screen coordinates
2973                final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
2974                cellLayoutCenter[0] = cl.getWidth()/2;
2975                cellLayoutCenter[1] = cl.getHeight()/2;
2976                mapPointFromChildToSelf(cl, cellLayoutCenter);
2977
2978                touchXy[0] = originX;
2979                touchXy[1] = originY;
2980
2981                // Calculate the distance between the center of the CellLayout
2982                // and the touch point
2983                float dist = squaredDistance(touchXy, cellLayoutCenter);
2984
2985                if (dist < smallestDistSoFar) {
2986                    smallestDistSoFar = dist;
2987                    bestMatchingScreen = cl;
2988                }
2989            }
2990        }
2991        return bestMatchingScreen;
2992    }
2993
2994    private boolean isDragWidget(DragObject d) {
2995        return (d.dragInfo instanceof LauncherAppWidgetInfo ||
2996                d.dragInfo instanceof PendingAddWidgetInfo);
2997    }
2998    private boolean isExternalDragWidget(DragObject d) {
2999        return d.dragSource != this && isDragWidget(d);
3000    }
3001
3002    public void onDragOver(DragObject d) {
3003        // Skip drag over events while we are dragging over side pages
3004        if (mInScrollArea || !transitionStateShouldAllowDrop()) return;
3005
3006        CellLayout layout = null;
3007        ItemInfo item = d.dragInfo;
3008        if (item == null) {
3009            if (ProviderConfig.IS_DOGFOOD_BUILD) {
3010                throw new NullPointerException("DragObject has null info");
3011            }
3012            return;
3013        }
3014
3015        // Ensure that we have proper spans for the item that we are dropping
3016        if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
3017        mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
3018
3019        final View child = (mDragInfo == null) ? null : mDragInfo.cell;
3020        // Identify whether we have dragged over a side page
3021        if (workspaceInModalState()) {
3022            if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
3023                if (isPointInSelfOverHotseat(d.x, d.y)) {
3024                    layout = mLauncher.getHotseat().getLayout();
3025                }
3026            }
3027            if (layout == null) {
3028                layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false);
3029            }
3030            if (layout != mDragTargetLayout) {
3031                setCurrentDropLayout(layout);
3032                setCurrentDragOverlappingLayout(layout);
3033
3034                boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
3035                if (isInSpringLoadedMode) {
3036                    if (mLauncher.isHotseatLayout(layout)) {
3037                        mSpringLoadedDragController.cancel();
3038                    } else {
3039                        mSpringLoadedDragController.setAlarm(mDragTargetLayout);
3040                    }
3041                }
3042            }
3043        } else {
3044            // Test to see if we are over the hotseat otherwise just use the current page
3045            if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
3046                if (isPointInSelfOverHotseat(d.x, d.y)) {
3047                    layout = mLauncher.getHotseat().getLayout();
3048                }
3049            }
3050            if (layout == null) {
3051                layout = getCurrentDropLayout();
3052            }
3053            if (layout != mDragTargetLayout) {
3054                setCurrentDropLayout(layout);
3055                setCurrentDragOverlappingLayout(layout);
3056            }
3057        }
3058
3059        // Handle the drag over
3060        if (mDragTargetLayout != null) {
3061            // We want the point to be mapped to the dragTarget.
3062            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
3063                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
3064            } else {
3065                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter);
3066            }
3067
3068            int minSpanX = item.spanX;
3069            int minSpanY = item.spanY;
3070            if (item.minSpanX > 0 && item.minSpanY > 0) {
3071                minSpanX = item.minSpanX;
3072                minSpanY = item.minSpanY;
3073            }
3074
3075            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3076                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY,
3077                    mDragTargetLayout, mTargetCell);
3078            int reorderX = mTargetCell[0];
3079            int reorderY = mTargetCell[1];
3080
3081            setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
3082
3083            float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
3084                    mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
3085
3086            manageFolderFeedback(mDragTargetLayout, mTargetCell, targetCellDistance, d);
3087
3088            boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
3089                    mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
3090                    item.spanY, child, mTargetCell);
3091
3092            if (!nearestDropOccupied) {
3093                mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3094                        mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, d);
3095            } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
3096                    && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
3097                    mLastReorderY != reorderY)) {
3098
3099                int[] resultSpan = new int[2];
3100                mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
3101                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY,
3102                        child, mTargetCell, resultSpan, CellLayout.MODE_SHOW_REORDER_HINT);
3103
3104                // Otherwise, if we aren't adding to or creating a folder and there's no pending
3105                // reorder, then we schedule a reorder
3106                ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
3107                        minSpanX, minSpanY, item.spanX, item.spanY, d, child);
3108                mReorderAlarm.setOnAlarmListener(listener);
3109                mReorderAlarm.setAlarm(REORDER_TIMEOUT);
3110            }
3111
3112            if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
3113                    !nearestDropOccupied) {
3114                if (mDragTargetLayout != null) {
3115                    mDragTargetLayout.revertTempState();
3116                }
3117            }
3118        }
3119    }
3120
3121    private void manageFolderFeedback(CellLayout targetLayout,
3122            int[] targetCell, float distance, DragObject dragObject) {
3123        if (distance > mMaxDistanceForFolderCreation) return;
3124
3125        final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]);
3126        ItemInfo info = dragObject.dragInfo;
3127        boolean userFolderPending = willCreateUserFolder(info, dragOverView, false);
3128        if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
3129                !mFolderCreationAlarm.alarmPending()) {
3130
3131            FolderCreationAlarmListener listener = new
3132                    FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]);
3133
3134            if (!dragObject.accessibleDrag) {
3135                mFolderCreationAlarm.setOnAlarmListener(listener);
3136                mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
3137            } else {
3138                listener.onAlarm(mFolderCreationAlarm);
3139            }
3140
3141            if (dragObject.stateAnnouncer != null) {
3142                dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
3143                        .getDescriptionForDropOver(dragOverView, getContext()));
3144            }
3145            return;
3146        }
3147
3148        boolean willAddToFolder = willAddToExistingUserFolder(info, dragOverView);
3149        if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
3150            mDragOverFolderIcon = ((FolderIcon) dragOverView);
3151            mDragOverFolderIcon.onDragEnter(info);
3152            if (targetLayout != null) {
3153                targetLayout.clearDragOutlines();
3154            }
3155            setDragMode(DRAG_MODE_ADD_TO_FOLDER);
3156
3157            if (dragObject.stateAnnouncer != null) {
3158                dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
3159                        .getDescriptionForDropOver(dragOverView, getContext()));
3160            }
3161            return;
3162        }
3163
3164        if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
3165            setDragMode(DRAG_MODE_NONE);
3166        }
3167        if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
3168            setDragMode(DRAG_MODE_NONE);
3169        }
3170    }
3171
3172    class FolderCreationAlarmListener implements OnAlarmListener {
3173        CellLayout layout;
3174        int cellX;
3175        int cellY;
3176
3177        public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
3178            this.layout = layout;
3179            this.cellX = cellX;
3180            this.cellY = cellY;
3181
3182            DeviceProfile grid = mLauncher.getDeviceProfile();
3183            BubbleTextView cell = (BubbleTextView) layout.getChildAt(cellX, cellY);
3184
3185            mFolderCreateBg.setup(getResources().getDisplayMetrics(), grid, null,
3186                    cell.getMeasuredWidth(), cell.getPaddingTop());
3187        }
3188
3189        public void onAlarm(Alarm alarm) {
3190            mFolderCreateBg.animateToAccept(layout, cellX, cellY);
3191            layout.clearDragOutlines();
3192            setDragMode(DRAG_MODE_CREATE_FOLDER);
3193        }
3194    }
3195
3196    class ReorderAlarmListener implements OnAlarmListener {
3197        float[] dragViewCenter;
3198        int minSpanX, minSpanY, spanX, spanY;
3199        DragObject dragObject;
3200        View child;
3201
3202        public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
3203                int spanY, DragObject dragObject, View child) {
3204            this.dragViewCenter = dragViewCenter;
3205            this.minSpanX = minSpanX;
3206            this.minSpanY = minSpanY;
3207            this.spanX = spanX;
3208            this.spanY = spanY;
3209            this.child = child;
3210            this.dragObject = dragObject;
3211        }
3212
3213        public void onAlarm(Alarm alarm) {
3214            int[] resultSpan = new int[2];
3215            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3216                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout,
3217                    mTargetCell);
3218            mLastReorderX = mTargetCell[0];
3219            mLastReorderY = mTargetCell[1];
3220
3221            mTargetCell = mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
3222                (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
3223                child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
3224
3225            if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
3226                mDragTargetLayout.revertTempState();
3227            } else {
3228                setDragMode(DRAG_MODE_REORDER);
3229            }
3230
3231            boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
3232            mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3233                mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize, dragObject);
3234        }
3235    }
3236
3237    @Override
3238    public void getHitRectRelativeToDragLayer(Rect outRect) {
3239        // We want the workspace to have the whole area of the display (it will find the correct
3240        // cell layout to drop to in the existing drag/drop logic.
3241        mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect);
3242    }
3243
3244    /**
3245     * Drop an item that didn't originate on one of the workspace screens.
3246     * It may have come from Launcher (e.g. from all apps or customize), or it may have
3247     * come from another app altogether.
3248     *
3249     * NOTE: This can also be called when we are outside of a drag event, when we want
3250     * to add an item to one of the workspace screens.
3251     */
3252    private void onDropExternal(final int[] touchXY, final ItemInfo dragInfo,
3253            final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
3254        final Runnable exitSpringLoadedRunnable = new Runnable() {
3255            @Override
3256            public void run() {
3257                mLauncher.exitSpringLoadedDragModeDelayed(true,
3258                        Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
3259            }
3260        };
3261
3262        ItemInfo info = dragInfo;
3263        int spanX = info.spanX;
3264        int spanY = info.spanY;
3265        if (mDragInfo != null) {
3266            spanX = mDragInfo.spanX;
3267            spanY = mDragInfo.spanY;
3268        }
3269
3270        final long container = mLauncher.isHotseatLayout(cellLayout) ?
3271                LauncherSettings.Favorites.CONTAINER_HOTSEAT :
3272                    LauncherSettings.Favorites.CONTAINER_DESKTOP;
3273        final long screenId = getIdForScreen(cellLayout);
3274        if (!mLauncher.isHotseatLayout(cellLayout)
3275                && screenId != getScreenIdForPageIndex(mCurrentPage)
3276                && mState != State.SPRING_LOADED) {
3277            snapToScreenId(screenId, null);
3278        }
3279
3280        if (info instanceof PendingAddItemInfo) {
3281            final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
3282
3283            boolean findNearestVacantCell = true;
3284            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
3285                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3286                        cellLayout, mTargetCell);
3287                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3288                        mDragViewVisualCenter[1], mTargetCell);
3289                if (willCreateUserFolder(d.dragInfo, cellLayout, mTargetCell, distance, true)
3290                        || willAddToExistingUserFolder(
3291                                d.dragInfo, cellLayout, mTargetCell, distance)) {
3292                    findNearestVacantCell = false;
3293                }
3294            }
3295
3296            final ItemInfo item = d.dragInfo;
3297            boolean updateWidgetSize = false;
3298            if (findNearestVacantCell) {
3299                int minSpanX = item.spanX;
3300                int minSpanY = item.spanY;
3301                if (item.minSpanX > 0 && item.minSpanY > 0) {
3302                    minSpanX = item.minSpanX;
3303                    minSpanY = item.minSpanY;
3304                }
3305                int[] resultSpan = new int[2];
3306                mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
3307                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
3308                        null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
3309
3310                if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) {
3311                    updateWidgetSize = true;
3312                }
3313                item.spanX = resultSpan[0];
3314                item.spanY = resultSpan[1];
3315            }
3316
3317            Runnable onAnimationCompleteRunnable = new Runnable() {
3318                @Override
3319                public void run() {
3320                    // Normally removeExtraEmptyScreen is called in Workspace#onDragEnd, but when
3321                    // adding an item that may not be dropped right away (due to a config activity)
3322                    // we defer the removal until the activity returns.
3323                    deferRemoveExtraEmptyScreen();
3324
3325                    // When dragging and dropping from customization tray, we deal with creating
3326                    // widgets/shortcuts/folders in a slightly different way
3327                    mLauncher.addPendingItem(pendingInfo, container, screenId, mTargetCell,
3328                            item.spanX, item.spanY);
3329                }
3330            };
3331            boolean isWidget = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
3332                    || pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
3333
3334            AppWidgetHostView finalView = isWidget ?
3335                    ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
3336
3337            if (finalView != null && updateWidgetSize) {
3338                AppWidgetResizeFrame.updateWidgetSizeRanges(finalView, mLauncher, item.spanX,
3339                        item.spanY);
3340            }
3341
3342            int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3343            if (isWidget && ((PendingAddWidgetInfo) pendingInfo).info != null &&
3344                    ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
3345                animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
3346            }
3347            animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
3348                    animationStyle, finalView, true);
3349        } else {
3350            // This is for other drag/drop cases, like dragging from All Apps
3351            View view = null;
3352
3353            switch (info.itemType) {
3354            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3355            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3356                if (info.container == NO_ID && info instanceof AppInfo) {
3357                    // Came from all apps -- make a copy
3358                    info = ((AppInfo) info).makeShortcut();
3359                }
3360                view = mLauncher.createShortcut(cellLayout, (ShortcutInfo) info);
3361                break;
3362            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3363                view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
3364                        (FolderInfo) info, mIconCache);
3365                break;
3366            default:
3367                throw new IllegalStateException("Unknown item type: " + info.itemType);
3368            }
3369
3370            // First we find the cell nearest to point at which the item is
3371            // dropped, without any consideration to whether there is an item there.
3372            if (touchXY != null) {
3373                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3374                        cellLayout, mTargetCell);
3375                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3376                        mDragViewVisualCenter[1], mTargetCell);
3377                d.postAnimationRunnable = exitSpringLoadedRunnable;
3378                if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
3379                        true, d.dragView, d.postAnimationRunnable)) {
3380                    return;
3381                }
3382                if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
3383                        true)) {
3384                    return;
3385                }
3386            }
3387
3388            if (touchXY != null) {
3389                // when dragging and dropping, just find the closest free spot
3390                mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
3391                        (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
3392                        null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
3393            } else {
3394                cellLayout.findCellForSpan(mTargetCell, 1, 1);
3395            }
3396            // Add the item to DB before adding to screen ensures that the container and other
3397            // values of the info is properly updated.
3398            LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
3399                    mTargetCell[0], mTargetCell[1]);
3400
3401            addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX,
3402                    info.spanY, insertAtFirst);
3403            cellLayout.onDropChild(view);
3404            cellLayout.getShortcutsAndWidgets().measureChild(view);
3405
3406            if (d.dragView != null) {
3407                // We wrap the animation call in the temporary set and reset of the current
3408                // cellLayout to its final transform -- this means we animate the drag view to
3409                // the correct final location.
3410                setFinalTransitionTransform(cellLayout);
3411                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
3412                        exitSpringLoadedRunnable, this);
3413                resetTransitionTransform(cellLayout);
3414            }
3415        }
3416    }
3417
3418    public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
3419        int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo, false);
3420        int visibility = layout.getVisibility();
3421        layout.setVisibility(VISIBLE);
3422
3423        int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
3424        int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
3425        Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
3426                Bitmap.Config.ARGB_8888);
3427        mCanvas.setBitmap(b);
3428
3429        layout.measure(width, height);
3430        layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
3431        layout.draw(mCanvas);
3432        mCanvas.setBitmap(null);
3433        layout.setVisibility(visibility);
3434        return b;
3435    }
3436
3437    private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
3438            DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, boolean scale) {
3439        // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
3440        // location and size on the home screen.
3441        int spanX = info.spanX;
3442        int spanY = info.spanY;
3443
3444        Rect r = estimateItemPosition(layout, targetCell[0], targetCell[1], spanX, spanY);
3445        loc[0] = r.left;
3446        loc[1] = r.top;
3447
3448        setFinalTransitionTransform(layout);
3449        float cellLayoutScale =
3450                mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
3451        resetTransitionTransform(layout);
3452
3453        float dragViewScaleX;
3454        float dragViewScaleY;
3455        if (scale) {
3456            dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
3457            dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
3458        } else {
3459            dragViewScaleX = 1f;
3460            dragViewScaleY = 1f;
3461        }
3462
3463        // The animation will scale the dragView about its center, so we need to center about
3464        // the final location.
3465        loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2
3466                - Math.ceil(layout.getUnusedHorizontalSpace() / 2f);
3467        loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
3468
3469        scaleXY[0] = dragViewScaleX * cellLayoutScale;
3470        scaleXY[1] = dragViewScaleY * cellLayoutScale;
3471    }
3472
3473    public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, final DragView dragView,
3474            final Runnable onCompleteRunnable, int animationType, final View finalView,
3475            boolean external) {
3476        Rect from = new Rect();
3477        mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
3478
3479        int[] finalPos = new int[2];
3480        float scaleXY[] = new float[2];
3481        boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
3482        getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
3483                scalePreview);
3484
3485        Resources res = mLauncher.getResources();
3486        final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
3487
3488        boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET ||
3489                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
3490        if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
3491            Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
3492            dragView.setCrossFadeBitmap(crossFadeBitmap);
3493            dragView.crossFade((int) (duration * 0.8f));
3494        } else if (isWidget && external) {
3495            scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0],  scaleXY[1]);
3496        }
3497
3498        DragLayer dragLayer = mLauncher.getDragLayer();
3499        if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
3500            mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
3501                    DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
3502        } else {
3503            int endStyle;
3504            if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
3505                endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
3506            } else {
3507                endStyle = DragLayer.ANIMATION_END_DISAPPEAR;
3508            }
3509
3510            Runnable onComplete = new Runnable() {
3511                @Override
3512                public void run() {
3513                    if (finalView != null) {
3514                        finalView.setVisibility(VISIBLE);
3515                    }
3516                    if (onCompleteRunnable != null) {
3517                        onCompleteRunnable.run();
3518                    }
3519                }
3520            };
3521            dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
3522                    finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
3523                    duration, this);
3524        }
3525    }
3526
3527    public void setFinalTransitionTransform(CellLayout layout) {
3528        if (isSwitchingState()) {
3529            mCurrentScale = getScaleX();
3530            setScaleX(mStateTransitionAnimation.getFinalScale());
3531            setScaleY(mStateTransitionAnimation.getFinalScale());
3532        }
3533    }
3534    public void resetTransitionTransform(CellLayout layout) {
3535        if (isSwitchingState()) {
3536            setScaleX(mCurrentScale);
3537            setScaleY(mCurrentScale);
3538        }
3539    }
3540
3541    public WorkspaceStateTransitionAnimation getStateTransitionAnimation() {
3542        return mStateTransitionAnimation;
3543    }
3544
3545    /**
3546     * Return the current {@link CellLayout}, correctly picking the destination
3547     * screen while a scroll is in progress.
3548     */
3549    public CellLayout getCurrentDropLayout() {
3550        return (CellLayout) getChildAt(getNextPage());
3551    }
3552
3553    /**
3554     * Return the current CellInfo describing our current drag; this method exists
3555     * so that Launcher can sync this object with the correct info when the activity is created/
3556     * destroyed
3557     *
3558     */
3559    public CellLayout.CellInfo getDragInfo() {
3560        return mDragInfo;
3561    }
3562
3563    public int getCurrentPageOffsetFromCustomContent() {
3564        return getNextPage() - numCustomPages();
3565    }
3566
3567    /**
3568     * Calculate the nearest cell where the given object would be dropped.
3569     *
3570     * pixelX and pixelY should be in the coordinate system of layout
3571     */
3572    @Thunk int[] findNearestArea(int pixelX, int pixelY,
3573            int spanX, int spanY, CellLayout layout, int[] recycle) {
3574        return layout.findNearestArea(
3575                pixelX, pixelY, spanX, spanY, recycle);
3576    }
3577
3578    void setup(DragController dragController) {
3579        mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
3580        mDragController = dragController;
3581
3582        // hardware layers on children are enabled on startup, but should be disabled until
3583        // needed
3584        updateChildrenLayersEnabled(false);
3585    }
3586
3587    /**
3588     * Called at the end of a drag which originated on the workspace.
3589     */
3590    public void onDropCompleted(final View target, final DragObject d,
3591            final boolean isFlingToDelete, final boolean success) {
3592        if (mDeferDropAfterUninstall) {
3593            mDeferredAction = new Runnable() {
3594                public void run() {
3595                    onDropCompleted(target, d, isFlingToDelete, success);
3596                    mDeferredAction = null;
3597                }
3598            };
3599            return;
3600        }
3601
3602        boolean beingCalledAfterUninstall = mDeferredAction != null;
3603
3604        if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) {
3605            if (target != this && mDragInfo != null) {
3606                removeWorkspaceItem(mDragInfo.cell);
3607            }
3608        } else if (mDragInfo != null) {
3609            final CellLayout cellLayout = mLauncher.getCellLayout(
3610                    mDragInfo.container, mDragInfo.screenId);
3611            if (cellLayout != null) {
3612                cellLayout.onDropChild(mDragInfo.cell);
3613            } else if (ProviderConfig.IS_DOGFOOD_BUILD) {
3614                throw new RuntimeException("Invalid state: cellLayout == null in "
3615                        + "Workspace#onDropCompleted. Please file a bug. ");
3616            };
3617        }
3618        if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful))
3619                && mDragInfo.cell != null) {
3620            mDragInfo.cell.setVisibility(VISIBLE);
3621        }
3622        mDragOutline = null;
3623        mDragInfo = null;
3624
3625        if (!isFlingToDelete) {
3626            // Fling to delete already exits spring loaded mode after the animation finishes.
3627            mLauncher.exitSpringLoadedDragModeDelayed(success,
3628                    Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, mDelayedResizeRunnable);
3629            mDelayedResizeRunnable = null;
3630        }
3631    }
3632
3633    /**
3634     * For opposite operation. See {@link #addInScreen}.
3635     */
3636    public void removeWorkspaceItem(View v) {
3637        CellLayout parentCell = getParentCellLayoutForView(v);
3638        if (parentCell != null) {
3639            parentCell.removeView(v);
3640        } else if (ProviderConfig.IS_DOGFOOD_BUILD) {
3641            // When an app is uninstalled using the drop target, we wait until resume to remove
3642            // the icon. We also remove all the corresponding items from the workspace at
3643            // {@link Launcher#bindComponentsRemoved}. That call can come before or after
3644            // {@link Launcher#mOnResumeCallbacks} depending on how busy the worker thread is.
3645            Log.e(TAG, "mDragInfo.cell has null parent");
3646        }
3647        if (v instanceof DropTarget) {
3648            mDragController.removeDropTarget((DropTarget) v);
3649        }
3650    }
3651
3652    @Override
3653    public void deferCompleteDropAfterUninstallActivity() {
3654        mDeferDropAfterUninstall = true;
3655    }
3656
3657    /// maybe move this into a smaller part
3658    @Override
3659    public void onUninstallActivityReturned(boolean success) {
3660        mDeferDropAfterUninstall = false;
3661        mUninstallSuccessful = success;
3662        if (mDeferredAction != null) {
3663            mDeferredAction.run();
3664        }
3665    }
3666
3667    @Override
3668    public float getIntrinsicIconScaleFactor() {
3669        return 1f;
3670    }
3671
3672    @Override
3673    public boolean supportsFlingToDelete() {
3674        return true;
3675    }
3676
3677    @Override
3678    public boolean supportsAppInfoDropTarget() {
3679        return !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND;
3680    }
3681
3682    @Override
3683    public boolean supportsDeleteDropTarget() {
3684        return true;
3685    }
3686
3687    @Override
3688    public void onFlingToDelete(DragObject d, PointF vec) {
3689        // Do nothing
3690    }
3691
3692    @Override
3693    public void onFlingToDeleteCompleted() {
3694        // Do nothing
3695    }
3696
3697    public boolean isDropEnabled() {
3698        return true;
3699    }
3700
3701    @Override
3702    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
3703        // We don't dispatch restoreInstanceState to our children using this code path.
3704        // Some pages will be restored immediately as their items are bound immediately, and
3705        // others we will need to wait until after their items are bound.
3706        mSavedStates = container;
3707    }
3708
3709    public void restoreInstanceStateForChild(int child) {
3710        if (mSavedStates != null) {
3711            mRestoredPages.add(child);
3712            CellLayout cl = (CellLayout) getChildAt(child);
3713            if (cl != null) {
3714                cl.restoreInstanceState(mSavedStates);
3715            }
3716        }
3717    }
3718
3719    public void restoreInstanceStateForRemainingPages() {
3720        int count = getChildCount();
3721        for (int i = 0; i < count; i++) {
3722            if (!mRestoredPages.contains(i)) {
3723                restoreInstanceStateForChild(i);
3724            }
3725        }
3726        mRestoredPages.clear();
3727        mSavedStates = null;
3728    }
3729
3730    @Override
3731    public void scrollLeft() {
3732        if (!workspaceInModalState() && !mIsSwitchingState) {
3733            super.scrollLeft();
3734        }
3735        Folder openFolder = getOpenFolder();
3736        if (openFolder != null) {
3737            openFolder.completeDragExit();
3738        }
3739    }
3740
3741    @Override
3742    public void scrollRight() {
3743        if (!workspaceInModalState() && !mIsSwitchingState) {
3744            super.scrollRight();
3745        }
3746        Folder openFolder = getOpenFolder();
3747        if (openFolder != null) {
3748            openFolder.completeDragExit();
3749        }
3750    }
3751
3752    @Override
3753    public boolean onEnterScrollArea(int x, int y, int direction) {
3754        // Ignore the scroll area if we are dragging over the hot seat
3755        boolean isPortrait = !mLauncher.getDeviceProfile().isLandscape;
3756        if (mLauncher.getHotseat() != null && isPortrait) {
3757            Rect r = new Rect();
3758            mLauncher.getHotseat().getHitRect(r);
3759            if (r.contains(x, y)) {
3760                return false;
3761            }
3762        }
3763
3764        boolean result = false;
3765        if (!workspaceInModalState() && !mIsSwitchingState && getOpenFolder() == null) {
3766            mInScrollArea = true;
3767
3768            final int page = getNextPage() +
3769                       (direction == DragController.SCROLL_LEFT ? -1 : 1);
3770            // We always want to exit the current layout to ensure parity of enter / exit
3771            setCurrentDropLayout(null);
3772
3773            if (0 <= page && page < getChildCount()) {
3774                // Ensure that we are not dragging over to the custom content screen
3775                if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) {
3776                    return false;
3777                }
3778
3779                CellLayout layout = (CellLayout) getChildAt(page);
3780                setCurrentDragOverlappingLayout(layout);
3781
3782                // Workspace is responsible for drawing the edge glow on adjacent pages,
3783                // so we need to redraw the workspace when this may have changed.
3784                invalidate();
3785                result = true;
3786            }
3787        }
3788        return result;
3789    }
3790
3791    @Override
3792    public boolean onExitScrollArea() {
3793        boolean result = false;
3794        if (mInScrollArea) {
3795            invalidate();
3796            CellLayout layout = getCurrentDropLayout();
3797            setCurrentDropLayout(layout);
3798            setCurrentDragOverlappingLayout(layout);
3799
3800            result = true;
3801            mInScrollArea = false;
3802        }
3803        return result;
3804    }
3805
3806    private void onResetScrollArea() {
3807        setCurrentDragOverlappingLayout(null);
3808        mInScrollArea = false;
3809    }
3810
3811    /**
3812     * Returns a specific CellLayout
3813     */
3814    CellLayout getParentCellLayoutForView(View v) {
3815        ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
3816        for (CellLayout layout : layouts) {
3817            if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
3818                return layout;
3819            }
3820        }
3821        return null;
3822    }
3823
3824    /**
3825     * Returns a list of all the CellLayouts in the workspace.
3826     */
3827    ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
3828        ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
3829        int screenCount = getChildCount();
3830        for (int screen = 0; screen < screenCount; screen++) {
3831            layouts.add(((CellLayout) getChildAt(screen)));
3832        }
3833        if (mLauncher.getHotseat() != null) {
3834            layouts.add(mLauncher.getHotseat().getLayout());
3835        }
3836        return layouts;
3837    }
3838
3839    /**
3840     * We should only use this to search for specific children.  Do not use this method to modify
3841     * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
3842     * the hotseat and workspace pages
3843     */
3844    ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
3845        ArrayList<ShortcutAndWidgetContainer> childrenLayouts = new ArrayList<>();
3846        int screenCount = getChildCount();
3847        for (int screen = 0; screen < screenCount; screen++) {
3848            childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
3849        }
3850        if (mLauncher.getHotseat() != null) {
3851            childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
3852        }
3853        return childrenLayouts;
3854    }
3855
3856    public View getHomescreenIconByItemId(final long id) {
3857        return getFirstMatch(new ItemOperator() {
3858
3859            @Override
3860            public boolean evaluate(ItemInfo info, View v, View parent) {
3861                return info != null && info.id == id;
3862            }
3863        });
3864    }
3865
3866    public View getViewForTag(final Object tag) {
3867        return getFirstMatch(new ItemOperator() {
3868
3869            @Override
3870            public boolean evaluate(ItemInfo info, View v, View parent) {
3871                return info == tag;
3872            }
3873        });
3874    }
3875
3876    public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) {
3877        return (LauncherAppWidgetHostView) getFirstMatch(new ItemOperator() {
3878
3879            @Override
3880            public boolean evaluate(ItemInfo info, View v, View parent) {
3881                return (info instanceof LauncherAppWidgetInfo) &&
3882                        ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId;
3883            }
3884        });
3885    }
3886
3887    private View getFirstMatch(final ItemOperator operator) {
3888        final View[] value = new View[1];
3889        mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
3890            @Override
3891            public boolean evaluate(ItemInfo info, View v, View parent) {
3892                if (operator.evaluate(info, v, parent)) {
3893                    value[0] = v;
3894                    return true;
3895                }
3896                return false;
3897            }
3898        });
3899        return value[0];
3900    }
3901
3902    void clearDropTargets() {
3903        mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
3904            @Override
3905            public boolean evaluate(ItemInfo info, View v, View parent) {
3906                if (v instanceof DropTarget) {
3907                    mDragController.removeDropTarget((DropTarget) v);
3908                }
3909                // not done, process all the shortcuts
3910                return false;
3911            }
3912        });
3913    }
3914
3915    public void disableShortcutsByPackageName(final ArrayList<String> packages,
3916            final UserHandleCompat user, final int reason) {
3917        final HashSet<String> packageNames = new HashSet<String>();
3918        packageNames.addAll(packages);
3919
3920        mapOverItems(MAP_RECURSE, new ItemOperator() {
3921            @Override
3922            public boolean evaluate(ItemInfo info, View v, View parent) {
3923                if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
3924                    ShortcutInfo shortcutInfo = (ShortcutInfo) info;
3925                    ComponentName cn = shortcutInfo.getTargetComponent();
3926                    if (user.equals(shortcutInfo.user) && cn != null
3927                            && packageNames.contains(cn.getPackageName())) {
3928                        shortcutInfo.isDisabled |= reason;
3929                        BubbleTextView shortcut = (BubbleTextView) v;
3930                        shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache);
3931
3932                        if (parent != null) {
3933                            parent.invalidate();
3934                        }
3935                    }
3936                }
3937                // process all the shortcuts
3938                return false;
3939            }
3940        });
3941    }
3942
3943    // Removes ALL items that match a given package name, this is usually called when a package
3944    // has been removed and we want to remove all components (widgets, shortcuts, apps) that
3945    // belong to that package.
3946    void removeItemsByPackageName(final ArrayList<String> packages, final UserHandleCompat user) {
3947        final HashSet<String> packageNames = new HashSet<String>();
3948        packageNames.addAll(packages);
3949
3950        // Filter out all the ItemInfos that this is going to affect
3951        final HashSet<ItemInfo> infos = new HashSet<ItemInfo>();
3952        final HashSet<ComponentName> cns = new HashSet<ComponentName>();
3953        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
3954        for (CellLayout layoutParent : cellLayouts) {
3955            ViewGroup layout = layoutParent.getShortcutsAndWidgets();
3956            int childCount = layout.getChildCount();
3957            for (int i = 0; i < childCount; ++i) {
3958                View view = layout.getChildAt(i);
3959                infos.add((ItemInfo) view.getTag());
3960            }
3961        }
3962        LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
3963            @Override
3964            public boolean filterItem(ItemInfo parent, ItemInfo info,
3965                                      ComponentName cn) {
3966                if (packageNames.contains(cn.getPackageName())
3967                        && info.user.equals(user)) {
3968                    cns.add(cn);
3969                    return true;
3970                }
3971                return false;
3972            }
3973        };
3974        LauncherModel.filterItemInfos(infos, filter);
3975
3976        // Remove the affected components
3977        removeItemsByComponentName(cns, user);
3978    }
3979
3980    /**
3981     * Removes items that match the item info specified. When applications are removed
3982     * as a part of an update, this is called to ensure that other widgets and application
3983     * shortcuts are not removed.
3984     */
3985    void removeItemsByComponentName(final HashSet<ComponentName> componentNames,
3986            final UserHandleCompat user) {
3987        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
3988        for (final CellLayout layoutParent: cellLayouts) {
3989            final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
3990
3991            final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
3992            for (int j = 0; j < layout.getChildCount(); j++) {
3993                final View view = layout.getChildAt(j);
3994                children.put((ItemInfo) view.getTag(), view);
3995            }
3996
3997            final ArrayList<View> childrenToRemove = new ArrayList<View>();
3998            final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove =
3999                    new HashMap<FolderInfo, ArrayList<ShortcutInfo>>();
4000            LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4001                @Override
4002                public boolean filterItem(ItemInfo parent, ItemInfo info,
4003                                          ComponentName cn) {
4004                    if (parent instanceof FolderInfo) {
4005                        if (componentNames.contains(cn) && info.user.equals(user)) {
4006                            FolderInfo folder = (FolderInfo) parent;
4007                            ArrayList<ShortcutInfo> appsToRemove;
4008                            if (folderAppsToRemove.containsKey(folder)) {
4009                                appsToRemove = folderAppsToRemove.get(folder);
4010                            } else {
4011                                appsToRemove = new ArrayList<ShortcutInfo>();
4012                                folderAppsToRemove.put(folder, appsToRemove);
4013                            }
4014                            appsToRemove.add((ShortcutInfo) info);
4015                            return true;
4016                        }
4017                    } else {
4018                        if (componentNames.contains(cn) && info.user.equals(user)) {
4019                            childrenToRemove.add(children.get(info));
4020                            return true;
4021                        }
4022                    }
4023                    return false;
4024                }
4025            };
4026            LauncherModel.filterItemInfos(children.keySet(), filter);
4027
4028            // Remove all the apps from their folders
4029            for (FolderInfo folder : folderAppsToRemove.keySet()) {
4030                ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
4031                for (ShortcutInfo info : appsToRemove) {
4032                    folder.remove(info);
4033                }
4034            }
4035
4036            // Remove all the other children
4037            for (View child : childrenToRemove) {
4038                // Note: We can not remove the view directly from CellLayoutChildren as this
4039                // does not re-mark the spaces as unoccupied.
4040                layoutParent.removeViewInLayout(child);
4041                if (child instanceof DropTarget) {
4042                    mDragController.removeDropTarget((DropTarget) child);
4043                }
4044            }
4045
4046            if (childrenToRemove.size() > 0) {
4047                layout.requestLayout();
4048                layout.invalidate();
4049            }
4050        }
4051
4052        // Strip all the empty screens
4053        stripEmptyScreens();
4054    }
4055
4056    public interface ItemOperator {
4057        /**
4058         * Process the next itemInfo, possibly with side-effect on {@link ItemOperator#value}.
4059         *
4060         * @param info info for the shortcut
4061         * @param view view for the shortcut
4062         * @param parent containing folder, or null
4063         * @return true if done, false to continue the map
4064         */
4065        public boolean evaluate(ItemInfo info, View view, View parent);
4066    }
4067
4068    /**
4069     * Map the operator over the shortcuts and widgets, return the first-non-null value.
4070     *
4071     * @param recurse true: iterate over folder children. false: op get the folders themselves.
4072     * @param op the operator to map over the shortcuts
4073     */
4074    void mapOverItems(boolean recurse, ItemOperator op) {
4075        ArrayList<ShortcutAndWidgetContainer> containers = getAllShortcutAndWidgetContainers();
4076        final int containerCount = containers.size();
4077        for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) {
4078            ShortcutAndWidgetContainer container = containers.get(containerIdx);
4079            // map over all the shortcuts on the workspace
4080            final int itemCount = container.getChildCount();
4081            for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
4082                View item = container.getChildAt(itemIdx);
4083                ItemInfo info = (ItemInfo) item.getTag();
4084                if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) {
4085                    FolderIcon folder = (FolderIcon) item;
4086                    ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder();
4087                    // map over all the children in the folder
4088                    final int childCount = folderChildren.size();
4089                    for (int childIdx = 0; childIdx < childCount; childIdx++) {
4090                        View child = folderChildren.get(childIdx);
4091                        info = (ItemInfo) child.getTag();
4092                        if (op.evaluate(info, child, folder)) {
4093                            return;
4094                        }
4095                    }
4096                } else {
4097                    if (op.evaluate(info, item, null)) {
4098                        return;
4099                    }
4100                }
4101            }
4102        }
4103    }
4104
4105    void updateShortcuts(ArrayList<ShortcutInfo> shortcuts) {
4106        final HashSet<ShortcutInfo> updates = new HashSet<ShortcutInfo>(shortcuts);
4107        mapOverItems(MAP_RECURSE, new ItemOperator() {
4108            @Override
4109            public boolean evaluate(ItemInfo info, View v, View parent) {
4110                if (info instanceof ShortcutInfo && v instanceof BubbleTextView &&
4111                        updates.contains(info)) {
4112                    ShortcutInfo si = (ShortcutInfo) info;
4113                    BubbleTextView shortcut = (BubbleTextView) v;
4114                    Drawable oldIcon = getTextViewIcon(shortcut);
4115                    boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
4116                            && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
4117                    shortcut.applyFromShortcutInfo(si, mIconCache,
4118                            si.isPromise() != oldPromiseState);
4119
4120                    if (parent != null) {
4121                        parent.invalidate();
4122                    }
4123                }
4124                // process all the shortcuts
4125                return false;
4126            }
4127        });
4128    }
4129
4130    public void removeAbandonedPromise(String packageName, UserHandleCompat user) {
4131        ArrayList<String> packages = new ArrayList<String>(1);
4132        packages.add(packageName);
4133        LauncherModel.deletePackageFromDatabase(mLauncher, packageName, user);
4134        removeItemsByPackageName(packages, user);
4135    }
4136
4137    public void updateRestoreItems(final HashSet<ItemInfo> updates) {
4138        mapOverItems(MAP_RECURSE, new ItemOperator() {
4139            @Override
4140            public boolean evaluate(ItemInfo info, View v, View parent) {
4141                if (info instanceof ShortcutInfo && v instanceof BubbleTextView
4142                        && updates.contains(info)) {
4143                    ((BubbleTextView) v).applyState(false);
4144                } else if (v instanceof PendingAppWidgetHostView
4145                        && info instanceof LauncherAppWidgetInfo
4146                        && updates.contains(info)) {
4147                    ((PendingAppWidgetHostView) v).applyState();
4148                }
4149                // process all the shortcuts
4150                return false;
4151            }
4152        });
4153    }
4154
4155    public void widgetsRestored(ArrayList<LauncherAppWidgetInfo> changedInfo) {
4156        if (!changedInfo.isEmpty()) {
4157            DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
4158                    mLauncher.getAppWidgetHost());
4159
4160            LauncherAppWidgetInfo item = changedInfo.get(0);
4161            final AppWidgetProviderInfo widgetInfo;
4162            if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
4163                widgetInfo = AppWidgetManagerCompat
4164                        .getInstance(mLauncher).findProvider(item.providerName, item.user);
4165            } else {
4166                widgetInfo = AppWidgetManagerCompat.getInstance(mLauncher)
4167                        .getAppWidgetInfo(item.appWidgetId);
4168            }
4169
4170            if (widgetInfo != null) {
4171                // Re-inflate the widgets which have changed status
4172                widgetRefresh.run();
4173            } else {
4174                // widgetRefresh will automatically run when the packages are updated.
4175                // For now just update the progress bars
4176                for (LauncherAppWidgetInfo info : changedInfo) {
4177                    if (info.hostView instanceof PendingAppWidgetHostView) {
4178                        info.installProgress = 100;
4179                        ((PendingAppWidgetHostView) info.hostView).applyState();
4180                    }
4181                }
4182            }
4183        }
4184    }
4185
4186    private void moveToScreen(int page, boolean animate) {
4187        if (!workspaceInModalState()) {
4188            if (animate) {
4189                snapToPage(page);
4190            } else {
4191                setCurrentPage(page);
4192            }
4193        }
4194        View child = getChildAt(page);
4195        if (child != null) {
4196            child.requestFocus();
4197        }
4198    }
4199
4200    void moveToDefaultScreen(boolean animate) {
4201        moveToScreen(getDefaultPage(), animate);
4202    }
4203
4204    void moveToCustomContentScreen(boolean animate) {
4205        if (hasCustomContent()) {
4206            int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
4207            if (animate) {
4208                snapToPage(ccIndex);
4209            } else {
4210                setCurrentPage(ccIndex);
4211            }
4212            View child = getChildAt(ccIndex);
4213            if (child != null) {
4214                child.requestFocus();
4215            }
4216         }
4217        exitWidgetResizeMode();
4218    }
4219
4220    @Override
4221    protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) {
4222        long screenId = getScreenIdForPageIndex(pageIndex);
4223        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
4224            int count = mScreenOrder.size() - numCustomPages();
4225            if (count > 1) {
4226                return new PageIndicator.PageMarkerResources(R.drawable.ic_pageindicator_current,
4227                        R.drawable.ic_pageindicator_add);
4228            }
4229        }
4230
4231        return super.getPageIndicatorMarker(pageIndex);
4232    }
4233
4234    protected String getPageIndicatorDescription() {
4235        String settings = getResources().getString(R.string.settings_button_text);
4236        return getCurrentPageDescription() + ", " + settings;
4237    }
4238
4239    protected String getCurrentPageDescription() {
4240        if (hasCustomContent() && getNextPage() == 0) {
4241            return mCustomContentDescription;
4242        }
4243        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
4244        return getPageDescription(page);
4245    }
4246
4247    private String getPageDescription(int page) {
4248        int delta = numCustomPages();
4249        int nScreens = getChildCount() - delta;
4250        int extraScreenId = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
4251        if (extraScreenId >= 0 && nScreens > 1) {
4252            if (page == extraScreenId) {
4253                return getContext().getString(R.string.workspace_new_page);
4254            }
4255            nScreens--;
4256        }
4257        return getContext().getString(R.string.workspace_scroll_format,
4258                page + 1 - delta, nScreens);
4259    }
4260
4261    @Override
4262    public void fillInLaunchSourceData(View v, Bundle sourceData) {
4263        sourceData.putString(Stats.SOURCE_EXTRA_CONTAINER, Stats.CONTAINER_HOMESCREEN);
4264        sourceData.putInt(Stats.SOURCE_EXTRA_CONTAINER_PAGE, getCurrentPage());
4265    }
4266
4267    /**
4268     * Used as a workaround to ensure that the AppWidgetService receives the
4269     * PACKAGE_ADDED broadcast before updating widgets.
4270     */
4271    private class DeferredWidgetRefresh implements Runnable {
4272        private final ArrayList<LauncherAppWidgetInfo> mInfos;
4273        private final LauncherAppWidgetHost mHost;
4274        private final Handler mHandler;
4275
4276        private boolean mRefreshPending;
4277
4278        public DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos,
4279                LauncherAppWidgetHost host) {
4280            mInfos = infos;
4281            mHost = host;
4282            mHandler = new Handler();
4283            mRefreshPending = true;
4284
4285            mHost.addProviderChangeListener(this);
4286            // Force refresh after 10 seconds, if we don't get the provider changed event.
4287            // This could happen when the provider is no longer available in the app.
4288            mHandler.postDelayed(this, 10000);
4289        }
4290
4291        @Override
4292        public void run() {
4293            mHost.removeProviderChangeListener(this);
4294            mHandler.removeCallbacks(this);
4295
4296            if (!mRefreshPending) {
4297                return;
4298            }
4299
4300            mRefreshPending = false;
4301
4302            for (LauncherAppWidgetInfo info : mInfos) {
4303                if (info.hostView instanceof PendingAppWidgetHostView) {
4304                    // Remove and rebind the current widget, but don't delete it from the database
4305                    PendingAppWidgetHostView view = (PendingAppWidgetHostView) info.hostView;
4306                    mLauncher.removeItem(view, info, false /* deleteFromDb */);
4307                    mLauncher.bindAppWidget(info);
4308                }
4309            }
4310        }
4311    }
4312}
4313