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