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