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