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