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