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