Workspace.java revision ab2d9d72ae0a37553a82fa845c412167c727a258
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
1799        boolean zoomIn = true;
1800        mNewScale = 1.0f;
1801
1802        if (oldStateIsOverview) {
1803            disableFreeScroll(snapPage);
1804        } else if (stateIsOverview) {
1805            enableFreeScroll();
1806        }
1807
1808        if (state != State.NORMAL) {
1809            if (stateIsSpringLoaded) {
1810                mNewScale = mSpringLoadedShrinkFactor;
1811            } else if (stateIsOverview) {
1812                mNewScale = mOverviewModeShrinkFactor;
1813            } else if (stateIsSmall){
1814                mNewScale = mOverviewModeShrinkFactor - 0.1f;
1815            }
1816            if (oldStateIsNormal && stateIsSmall) {
1817                zoomIn = false;
1818                updateChildrenLayersEnabled(false);
1819            } else {
1820                finalBackgroundAlpha = 1.0f;
1821            }
1822        }
1823        final int duration = zoomIn ?
1824                getResources().getInteger(R.integer.config_workspaceUnshrinkTime) :
1825                getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
1826        for (int i = 0; i < getChildCount(); i++) {
1827            final CellLayout cl = (CellLayout) getChildAt(i);
1828            float finalAlpha = (!mWorkspaceFadeInAdjacentScreens || stateIsSpringLoaded ||
1829                    (i == mCurrentPage)) ? 1f : 0f;
1830            float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
1831            float initialAlpha = currentAlpha;
1832
1833            // Determine the pages alpha during the state transition
1834            if ((oldStateIsSmall && stateIsNormal) ||
1835                (oldStateIsNormal && stateIsSmall)) {
1836                // To/from workspace - only show the current page unless the transition is not
1837                //                     animated and the animation end callback below doesn't run;
1838                //                     or, if we're in spring-loaded mode
1839                if (i == mCurrentPage || !animated || oldStateIsSpringLoaded) {
1840                    finalAlpha = 1f;
1841                } else {
1842                    initialAlpha = 0f;
1843                    finalAlpha = 0f;
1844                }
1845            }
1846
1847            mOldAlphas[i] = initialAlpha;
1848            mNewAlphas[i] = finalAlpha;
1849            if (animated) {
1850                mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
1851                mNewBackgroundAlphas[i] = finalBackgroundAlpha;
1852            } else {
1853                setScaleX(mNewScale);
1854                setScaleY(mNewScale);
1855                cl.setBackgroundAlpha(finalBackgroundAlpha);
1856                cl.setShortcutAndWidgetAlpha(finalAlpha);
1857            }
1858        }
1859
1860        if (animated) {
1861            LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this);
1862            scale.scaleX(mNewScale)
1863                .scaleY(mNewScale)
1864                .setInterpolator(mZoomInInterpolator);
1865            anim.play(scale);
1866            for (int index = 0; index < getChildCount(); index++) {
1867                final int i = index;
1868                final CellLayout cl = (CellLayout) getChildAt(i);
1869                float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
1870                if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) {
1871                    cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
1872                    cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
1873                } else {
1874
1875                    if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
1876                        LauncherViewPropertyAnimator alphaAnim =
1877                            new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
1878                        alphaAnim.alpha(mNewAlphas[i])
1879                            .setDuration(duration)
1880                            .setInterpolator(mZoomInInterpolator);
1881                        anim.play(alphaAnim);
1882                    }
1883                    if (mOldBackgroundAlphas[i] != 0 ||
1884                        mNewBackgroundAlphas[i] != 0) {
1885                        ValueAnimator bgAnim =
1886                                LauncherAnimUtils.ofFloat(cl, 0f, 1f).setDuration(duration);
1887                        bgAnim.setInterpolator(mZoomInInterpolator);
1888                        bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
1889                                public void onAnimationUpdate(float a, float b) {
1890                                    cl.setBackgroundAlpha(
1891                                            a * mOldBackgroundAlphas[i] +
1892                                            b * mNewBackgroundAlphas[i]);
1893                                }
1894                            });
1895                        anim.play(bgAnim);
1896                    }
1897                }
1898            }
1899            ObjectAnimator pageIndicatorAlpha = null;
1900            if (getPageIndicator() != null) {
1901                pageIndicatorAlpha = ObjectAnimator.ofFloat(getPageIndicator(), "alpha",
1902                        finalHotseatAndPageIndicatorAlpha);
1903            }
1904            ObjectAnimator hotseatAlpha = ObjectAnimator.ofFloat(mLauncher.getHotseat(), "alpha",
1905                    finalHotseatAndPageIndicatorAlpha);
1906            ObjectAnimator overviewPanelAlpha = ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
1907                    "alpha", finalOverviewPanelAlpha);
1908            overviewPanelAlpha.addUpdateListener(new AlphaUpdateListener(
1909                    mLauncher.getOverviewPanel()));
1910            hotseatAlpha.addUpdateListener(new AlphaUpdateListener(mLauncher.getHotseat()));
1911            if (getPageIndicator() != null) {
1912                pageIndicatorAlpha.addUpdateListener(new AlphaUpdateListener(getPageIndicator()));
1913            }
1914            anim.play(overviewPanelAlpha);
1915            anim.play(hotseatAlpha);
1916            anim.play(pageIndicatorAlpha);
1917            anim.setStartDelay(delay);
1918        } else {
1919            mLauncher.getOverviewPanel().setAlpha(finalOverviewPanelAlpha);
1920            AlphaUpdateListener.updateVisibility(mLauncher.getOverviewPanel());
1921            mLauncher.getHotseat().setAlpha(finalHotseatAndPageIndicatorAlpha);
1922            AlphaUpdateListener.updateVisibility(mLauncher.getHotseat());
1923            if (getPageIndicator() != null) {
1924                getPageIndicator().setAlpha(finalHotseatAndPageIndicatorAlpha);
1925                AlphaUpdateListener.updateVisibility(getPageIndicator());
1926            }
1927        }
1928
1929        if (stateIsSpringLoaded) {
1930            // Right now we're covered by Apps Customize
1931            // Show the background gradient immediately, so the gradient will
1932            // be showing once AppsCustomize disappears
1933            animateBackgroundGradient(getResources().getInteger(
1934                    R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, false);
1935        } else {
1936            // Fade the background gradient away
1937            animateBackgroundGradient(0f, true);
1938        }
1939        return anim;
1940    }
1941
1942    static class AlphaUpdateListener implements AnimatorUpdateListener {
1943        View view;
1944        public AlphaUpdateListener(View v) {
1945            view = v;
1946        }
1947
1948        @Override
1949        public void onAnimationUpdate(ValueAnimator arg0) {
1950            updateVisibility(view);
1951        }
1952
1953        public static void updateVisibility(View view) {
1954            if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != INVISIBLE) {
1955                view.setVisibility(INVISIBLE);
1956            } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
1957                    && view.getVisibility() != VISIBLE) {
1958                view.setVisibility(VISIBLE);
1959            }
1960        }
1961    }
1962
1963    @Override
1964    public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
1965        onTransitionPrepare();
1966    }
1967
1968    @Override
1969    public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
1970    }
1971
1972    @Override
1973    public void onLauncherTransitionStep(Launcher l, float t) {
1974        mTransitionProgress = t;
1975    }
1976
1977    @Override
1978    public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
1979        onTransitionEnd();
1980    }
1981
1982    private void onTransitionPrepare() {
1983        mIsSwitchingState = true;
1984        updateChildrenLayersEnabled(false);
1985        if (mState != Workspace.State.NORMAL) {
1986            if (hasCustomContent()) {
1987                mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE);
1988            }
1989        }
1990    }
1991
1992    private void onTransitionEnd() {
1993        mIsSwitchingState = false;
1994        updateChildrenLayersEnabled(false);
1995        // The code in getChangeStateAnimation to determine initialAlpha and finalAlpha will ensure
1996        // ensure that only the current page is visible during (and subsequently, after) the
1997        // transition animation.  If fade adjacent pages is disabled, then re-enable the page
1998        // visibility after the transition animation.
1999        if (!mWorkspaceFadeInAdjacentScreens) {
2000            for (int i = 0; i < getChildCount(); i++) {
2001                final CellLayout cl = (CellLayout) getChildAt(i);
2002                cl.setShortcutAndWidgetAlpha(1f);
2003            }
2004        }
2005        if (mState == Workspace.State.NORMAL) {
2006            if (hasCustomContent()) {
2007                mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(VISIBLE);
2008            }
2009        }
2010    }
2011
2012    @Override
2013    public View getContent() {
2014        return this;
2015    }
2016
2017    /**
2018     * Draw the View v into the given Canvas.
2019     *
2020     * @param v the view to draw
2021     * @param destCanvas the canvas to draw on
2022     * @param padding the horizontal and vertical padding to use when drawing
2023     */
2024    private void drawDragView(View v, Canvas destCanvas, int padding, boolean pruneToDrawable) {
2025        final Rect clipRect = mTempRect;
2026        v.getDrawingRect(clipRect);
2027
2028        boolean textVisible = false;
2029
2030        destCanvas.save();
2031        if (v instanceof TextView && pruneToDrawable) {
2032            Drawable d = ((TextView) v).getCompoundDrawables()[1];
2033            clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding);
2034            destCanvas.translate(padding / 2, padding / 2);
2035            d.draw(destCanvas);
2036        } else {
2037            if (v instanceof FolderIcon) {
2038                // For FolderIcons the text can bleed into the icon area, and so we need to
2039                // hide the text completely (which can't be achieved by clipping).
2040                if (((FolderIcon) v).getTextVisible()) {
2041                    ((FolderIcon) v).setTextVisible(false);
2042                    textVisible = true;
2043                }
2044            } else if (v instanceof BubbleTextView) {
2045                final BubbleTextView tv = (BubbleTextView) v;
2046                clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V +
2047                        tv.getLayout().getLineTop(0);
2048            } else if (v instanceof TextView) {
2049                final TextView tv = (TextView) v;
2050                clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() +
2051                        tv.getLayout().getLineTop(0);
2052            }
2053            destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
2054            destCanvas.clipRect(clipRect, Op.REPLACE);
2055            v.draw(destCanvas);
2056
2057            // Restore text visibility of FolderIcon if necessary
2058            if (textVisible) {
2059                ((FolderIcon) v).setTextVisible(true);
2060            }
2061        }
2062        destCanvas.restore();
2063    }
2064
2065    /**
2066     * Returns a new bitmap to show when the given View is being dragged around.
2067     * Responsibility for the bitmap is transferred to the caller.
2068     */
2069    public Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
2070        Bitmap b;
2071
2072        if (v instanceof TextView) {
2073            Drawable d = ((TextView) v).getCompoundDrawables()[1];
2074            b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding,
2075                    d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888);
2076        } else {
2077            b = Bitmap.createBitmap(
2078                    v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
2079        }
2080
2081        canvas.setBitmap(b);
2082        drawDragView(v, canvas, padding, true);
2083        canvas.setBitmap(null);
2084
2085        return b;
2086    }
2087
2088    /**
2089     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2090     * Responsibility for the bitmap is transferred to the caller.
2091     */
2092    private Bitmap createDragOutline(View v, Canvas canvas, int padding) {
2093        final int outlineColor = getResources().getColor(android.R.color.holo_blue_light);
2094        final Bitmap b = Bitmap.createBitmap(
2095                v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
2096
2097        canvas.setBitmap(b);
2098        drawDragView(v, canvas, padding, true);
2099        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
2100        canvas.setBitmap(null);
2101        return b;
2102    }
2103
2104    /**
2105     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2106     * Responsibility for the bitmap is transferred to the caller.
2107     */
2108    private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding, int w, int h,
2109            boolean clipAlpha) {
2110        final int outlineColor = getResources().getColor(android.R.color.holo_blue_light);
2111        final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
2112        canvas.setBitmap(b);
2113
2114        Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
2115        float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(),
2116                (h - padding) / (float) orig.getHeight());
2117        int scaledWidth = (int) (scaleFactor * orig.getWidth());
2118        int scaledHeight = (int) (scaleFactor * orig.getHeight());
2119        Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
2120
2121        // center the image
2122        dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
2123
2124        canvas.drawBitmap(orig, src, dst, null);
2125        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor,
2126                clipAlpha);
2127        canvas.setBitmap(null);
2128
2129        return b;
2130    }
2131
2132    void startDrag(CellLayout.CellInfo cellInfo) {
2133        View child = cellInfo.cell;
2134
2135        // Make sure the drag was started by a long press as opposed to a long click.
2136        if (!child.isInTouchMode()) {
2137            return;
2138        }
2139
2140        mDragInfo = cellInfo;
2141        child.setVisibility(INVISIBLE);
2142        CellLayout layout = (CellLayout) child.getParent().getParent();
2143        layout.prepareChildForDrag(child);
2144
2145        child.clearFocus();
2146        child.setPressed(false);
2147
2148        final Canvas canvas = new Canvas();
2149
2150        // The outline is used to visualize where the item will land if dropped
2151        mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING);
2152        beginDragShared(child, this);
2153    }
2154
2155    public void beginDragShared(View child, DragSource source) {
2156        // The drag bitmap follows the touch point around on the screen
2157        final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING);
2158
2159        final int bmpWidth = b.getWidth();
2160        final int bmpHeight = b.getHeight();
2161
2162        float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
2163        int dragLayerX =
2164                Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
2165        int dragLayerY =
2166                Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2
2167                        - DRAG_BITMAP_PADDING / 2);
2168
2169        LauncherAppState app = LauncherAppState.getInstance();
2170        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2171        Point dragVisualizeOffset = null;
2172        Rect dragRect = null;
2173        if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
2174            int iconSize = grid.iconSizePx;
2175            int top = child.getPaddingTop();
2176            int left = (bmpWidth - iconSize) / 2;
2177            int right = left + iconSize;
2178            int bottom = top + iconSize;
2179            dragLayerY += top;
2180            // Note: The drag region is used to calculate drag layer offsets, but the
2181            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2182            dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2, DRAG_BITMAP_PADDING / 2);
2183            dragRect = new Rect(left, top, right, bottom);
2184        } else if (child instanceof FolderIcon) {
2185            int previewSize = grid.folderIconSizePx;
2186            dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
2187        }
2188
2189        // Clear the pressed state if necessary
2190        if (child instanceof BubbleTextView) {
2191            BubbleTextView icon = (BubbleTextView) child;
2192            icon.clearPressedOrFocusedBackground();
2193        }
2194
2195        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
2196                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
2197
2198        if (child.getParent() instanceof ShortcutAndWidgetContainer) {
2199            mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
2200        }
2201
2202        b.recycle();
2203    }
2204
2205    void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, long screenId,
2206            int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
2207        View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
2208
2209        final int[] cellXY = new int[2];
2210        target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
2211        addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
2212
2213        LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, cellXY[0],
2214                cellXY[1]);
2215    }
2216
2217    public boolean transitionStateShouldAllowDrop() {
2218        return ((!isSwitchingState() || mTransitionProgress > 0.5f) && mState != State.SMALL);
2219    }
2220
2221    /**
2222     * {@inheritDoc}
2223     */
2224    public boolean acceptDrop(DragObject d) {
2225        // If it's an external drop (e.g. from All Apps), check if it should be accepted
2226        CellLayout dropTargetLayout = mDropToLayout;
2227        if (d.dragSource != this) {
2228            // Don't accept the drop if we're not over a screen at time of drop
2229            if (dropTargetLayout == null) {
2230                return false;
2231            }
2232            if (!transitionStateShouldAllowDrop()) return false;
2233
2234            mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
2235                    d.dragView, mDragViewVisualCenter);
2236
2237            // We want the point to be mapped to the dragTarget.
2238            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2239                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2240            } else {
2241                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2242            }
2243
2244            int spanX = 1;
2245            int spanY = 1;
2246            if (mDragInfo != null) {
2247                final CellLayout.CellInfo dragCellInfo = mDragInfo;
2248                spanX = dragCellInfo.spanX;
2249                spanY = dragCellInfo.spanY;
2250            } else {
2251                final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
2252                spanX = dragInfo.spanX;
2253                spanY = dragInfo.spanY;
2254            }
2255
2256            int minSpanX = spanX;
2257            int minSpanY = spanY;
2258            if (d.dragInfo instanceof PendingAddWidgetInfo) {
2259                minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
2260                minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
2261            }
2262
2263            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2264                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
2265                    mTargetCell);
2266            float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2267                    mDragViewVisualCenter[1], mTargetCell);
2268            if (willCreateUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
2269                    mTargetCell, distance, true)) {
2270                return true;
2271            }
2272            if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
2273                    mTargetCell, distance)) {
2274                return true;
2275            }
2276
2277            int[] resultSpan = new int[2];
2278            mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
2279                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2280                    null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
2281            boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2282
2283            // Don't accept the drop if there's no room for the item
2284            if (!foundCell) {
2285                // Don't show the message if we are dropping on the AllApps button and the hotseat
2286                // is full
2287                boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2288                if (mTargetCell != null && isHotseat) {
2289                    Hotseat hotseat = mLauncher.getHotseat();
2290                    if (hotseat.isAllAppsButtonRank(
2291                            hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
2292                        return false;
2293                    }
2294                }
2295
2296                mLauncher.showOutOfSpaceMessage(isHotseat);
2297                return false;
2298            }
2299        }
2300
2301        long screenId = getIdForScreen(dropTargetLayout);
2302        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
2303            commitExtraEmptyScreen();
2304        }
2305
2306        return true;
2307    }
2308
2309    boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float
2310            distance, boolean considerTimeout) {
2311        if (distance > mMaxDistanceForFolderCreation) return false;
2312        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2313
2314        if (dropOverView != null) {
2315            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2316            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2317                return false;
2318            }
2319        }
2320
2321        boolean hasntMoved = false;
2322        if (mDragInfo != null) {
2323            hasntMoved = dropOverView == mDragInfo.cell;
2324        }
2325
2326        if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
2327            return false;
2328        }
2329
2330        boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
2331        boolean willBecomeShortcut =
2332                (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
2333                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
2334
2335        return (aboveShortcut && willBecomeShortcut);
2336    }
2337
2338    boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
2339            float distance) {
2340        if (distance > mMaxDistanceForFolderCreation) return false;
2341        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2342
2343        if (dropOverView != null) {
2344            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2345            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2346                return false;
2347            }
2348        }
2349
2350        if (dropOverView instanceof FolderIcon) {
2351            FolderIcon fi = (FolderIcon) dropOverView;
2352            if (fi.acceptDrop(dragInfo)) {
2353                return true;
2354            }
2355        }
2356        return false;
2357    }
2358
2359    boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
2360            int[] targetCell, float distance, boolean external, DragView dragView,
2361            Runnable postAnimationRunnable) {
2362        if (distance > mMaxDistanceForFolderCreation) return false;
2363        View v = target.getChildAt(targetCell[0], targetCell[1]);
2364
2365        boolean hasntMoved = false;
2366        if (mDragInfo != null) {
2367            CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
2368            hasntMoved = (mDragInfo.cellX == targetCell[0] &&
2369                    mDragInfo.cellY == targetCell[1]) && (cellParent == target);
2370        }
2371
2372        if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
2373        mCreateUserFolderOnDrop = false;
2374        final long screenId = (targetCell == null) ? mDragInfo.screenId : getIdForScreen(target);
2375
2376        boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
2377        boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
2378
2379        if (aboveShortcut && willBecomeShortcut) {
2380            ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
2381            ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
2382            // if the drag started here, we need to remove it from the workspace
2383            if (!external) {
2384                getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2385            }
2386
2387            Rect folderLocation = new Rect();
2388            float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
2389            target.removeView(v);
2390
2391            FolderIcon fi =
2392                mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
2393            destInfo.cellX = -1;
2394            destInfo.cellY = -1;
2395            sourceInfo.cellX = -1;
2396            sourceInfo.cellY = -1;
2397
2398            // If the dragView is null, we can't animate
2399            boolean animate = dragView != null;
2400            if (animate) {
2401                fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
2402                        postAnimationRunnable);
2403            } else {
2404                fi.addItem(destInfo);
2405                fi.addItem(sourceInfo);
2406            }
2407            return true;
2408        }
2409        return false;
2410    }
2411
2412    boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
2413            float distance, DragObject d, boolean external) {
2414        if (distance > mMaxDistanceForFolderCreation) return false;
2415
2416        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2417        if (!mAddToExistingFolderOnDrop) return false;
2418        mAddToExistingFolderOnDrop = false;
2419
2420        if (dropOverView instanceof FolderIcon) {
2421            FolderIcon fi = (FolderIcon) dropOverView;
2422            if (fi.acceptDrop(d.dragInfo)) {
2423                fi.onDrop(d);
2424
2425                // if the drag started here, we need to remove it from the workspace
2426                if (!external) {
2427                    getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2428                }
2429                return true;
2430            }
2431        }
2432        return false;
2433    }
2434
2435    public void onDrop(final DragObject d) {
2436        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
2437                mDragViewVisualCenter);
2438
2439        CellLayout dropTargetLayout = mDropToLayout;
2440
2441        // We want the point to be mapped to the dragTarget.
2442        if (dropTargetLayout != null) {
2443            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2444                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2445            } else {
2446                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2447            }
2448        }
2449
2450        int snapScreen = -1;
2451        boolean resizeOnDrop = false;
2452        if (d.dragSource != this) {
2453            final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
2454                    (int) mDragViewVisualCenter[1] };
2455            onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
2456        } else if (mDragInfo != null) {
2457            final View cell = mDragInfo.cell;
2458
2459            Runnable resizeRunnable = null;
2460            if (dropTargetLayout != null && !d.cancelled) {
2461                // Move internally
2462                boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
2463                boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2464                long container = hasMovedIntoHotseat ?
2465                        LauncherSettings.Favorites.CONTAINER_HOTSEAT :
2466                        LauncherSettings.Favorites.CONTAINER_DESKTOP;
2467                long screenId = (mTargetCell[0] < 0) ?
2468                        mDragInfo.screenId : getIdForScreen(dropTargetLayout);
2469                int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
2470                int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
2471                // First we find the cell nearest to point at which the item is
2472                // dropped, without any consideration to whether there is an item there.
2473
2474                mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
2475                        mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
2476                float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2477                        mDragViewVisualCenter[1], mTargetCell);
2478
2479                // If the item being dropped is a shortcut and the nearest drop
2480                // cell also contains a shortcut, then create a folder with the two shortcuts.
2481                if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
2482                        dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
2483                    stripEmptyScreens();
2484                    return;
2485                }
2486
2487                if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
2488                        distance, d, false)) {
2489                    stripEmptyScreens();
2490                    return;
2491                }
2492
2493                // Aside from the special case where we're dropping a shortcut onto a shortcut,
2494                // we need to find the nearest cell location that is vacant
2495                ItemInfo item = (ItemInfo) d.dragInfo;
2496                int minSpanX = item.spanX;
2497                int minSpanY = item.spanY;
2498                if (item.minSpanX > 0 && item.minSpanY > 0) {
2499                    minSpanX = item.minSpanX;
2500                    minSpanY = item.minSpanY;
2501                }
2502
2503                int[] resultSpan = new int[2];
2504                mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
2505                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
2506                        mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
2507
2508                boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2509
2510                // if the widget resizes on drop
2511                if (foundCell && (cell instanceof AppWidgetHostView) &&
2512                        (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
2513                    resizeOnDrop = true;
2514                    item.spanX = resultSpan[0];
2515                    item.spanY = resultSpan[1];
2516                    AppWidgetHostView awhv = (AppWidgetHostView) cell;
2517                    AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
2518                            resultSpan[1]);
2519                }
2520
2521                if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
2522                    snapScreen = getPageIndexForScreenId(screenId);
2523                    snapToPage(snapScreen);
2524                }
2525
2526                if (foundCell) {
2527                    final ItemInfo info = (ItemInfo) cell.getTag();
2528                    if (hasMovedLayouts) {
2529                        // Reparent the view
2530                        getParentCellLayoutForView(cell).removeView(cell);
2531                        addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
2532                                info.spanX, info.spanY);
2533                    }
2534
2535                    // update the item's position after drop
2536                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2537                    lp.cellX = lp.tmpCellX = mTargetCell[0];
2538                    lp.cellY = lp.tmpCellY = mTargetCell[1];
2539                    lp.cellHSpan = item.spanX;
2540                    lp.cellVSpan = item.spanY;
2541                    lp.isLockedToGrid = true;
2542                    cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screenId,
2543                            mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
2544
2545                    if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
2546                            cell instanceof LauncherAppWidgetHostView) {
2547                        final CellLayout cellLayout = dropTargetLayout;
2548                        // We post this call so that the widget has a chance to be placed
2549                        // in its final location
2550
2551                        final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
2552                        AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
2553                        if (pinfo != null &&
2554                                pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
2555                            final Runnable addResizeFrame = new Runnable() {
2556                                public void run() {
2557                                    DragLayer dragLayer = mLauncher.getDragLayer();
2558                                    dragLayer.addResizeFrame(info, hostView, cellLayout);
2559                                }
2560                            };
2561                            resizeRunnable = (new Runnable() {
2562                                public void run() {
2563                                    if (!isPageMoving()) {
2564                                        addResizeFrame.run();
2565                                    } else {
2566                                        mDelayedResizeRunnable = addResizeFrame;
2567                                    }
2568                                }
2569                            });
2570                        }
2571                    }
2572
2573                    LauncherModel.moveItemInDatabase(mLauncher, info, container, screenId, lp.cellX,
2574                            lp.cellY);
2575                } else {
2576                    // If we can't find a drop location, we return the item to its original position
2577                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2578                    mTargetCell[0] = lp.cellX;
2579                    mTargetCell[1] = lp.cellY;
2580                    CellLayout layout = (CellLayout) cell.getParent().getParent();
2581                    layout.markCellsAsOccupiedForView(cell);
2582                }
2583            }
2584
2585            final CellLayout parent = (CellLayout) cell.getParent().getParent();
2586            final Runnable finalResizeRunnable = resizeRunnable;
2587            // Prepare it to be animated into its new position
2588            // This must be called after the view has been re-parented
2589            final Runnable onCompleteRunnable = new Runnable() {
2590                @Override
2591                public void run() {
2592                    mAnimatingViewIntoPlace = false;
2593                    updateChildrenLayersEnabled(false);
2594                    if (finalResizeRunnable != null) {
2595                        finalResizeRunnable.run();
2596                    }
2597                    stripEmptyScreens();
2598                }
2599            };
2600            mAnimatingViewIntoPlace = true;
2601            if (d.dragView.hasDrawn()) {
2602                final ItemInfo info = (ItemInfo) cell.getTag();
2603                if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
2604                    int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
2605                            ANIMATE_INTO_POSITION_AND_DISAPPEAR;
2606                    animateWidgetDrop(info, parent, d.dragView,
2607                            onCompleteRunnable, animationType, cell, false);
2608                } else {
2609                    int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
2610                    mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
2611                            onCompleteRunnable, this);
2612                }
2613            } else {
2614                d.deferDragViewCleanupPostAnimation = false;
2615                cell.setVisibility(VISIBLE);
2616            }
2617            parent.onDropChild(cell);
2618        }
2619    }
2620
2621    public void setFinalScrollForPageChange(int pageIndex) {
2622        CellLayout cl = (CellLayout) getChildAt(pageIndex);
2623        if (cl != null) {
2624            mSavedScrollX = getScrollX();
2625            mSavedTranslationX = cl.getTranslationX();
2626            mSavedRotationY = cl.getRotationY();
2627            final int newX = getScrollForPage(pageIndex);
2628            setScrollX(newX);
2629            cl.setTranslationX(0f);
2630            cl.setRotationY(0f);
2631        }
2632    }
2633
2634    public void resetFinalScrollForPageChange(int pageIndex) {
2635        if (pageIndex >= 0) {
2636            CellLayout cl = (CellLayout) getChildAt(pageIndex);
2637            setScrollX(mSavedScrollX);
2638            cl.setTranslationX(mSavedTranslationX);
2639            cl.setRotationY(mSavedRotationY);
2640        }
2641    }
2642
2643    public void getViewLocationRelativeToSelf(View v, int[] location) {
2644        getLocationInWindow(location);
2645        int x = location[0];
2646        int y = location[1];
2647
2648        v.getLocationInWindow(location);
2649        int vX = location[0];
2650        int vY = location[1];
2651
2652        location[0] = vX - x;
2653        location[1] = vY - y;
2654    }
2655
2656    public void onDragEnter(DragObject d) {
2657        mDragEnforcer.onDragEnter();
2658        mCreateUserFolderOnDrop = false;
2659        mAddToExistingFolderOnDrop = false;
2660
2661        mDropToLayout = null;
2662        CellLayout layout = getCurrentDropLayout();
2663        setCurrentDropLayout(layout);
2664        setCurrentDragOverlappingLayout(layout);
2665
2666        // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we
2667        // don't need to show the outlines
2668        if (LauncherAppState.getInstance().isScreenLarge()) {
2669            showOutlines();
2670        }
2671    }
2672
2673    static Rect getCellLayoutMetrics(Launcher launcher, int orientation) {
2674        LauncherAppState app = LauncherAppState.getInstance();
2675        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2676
2677        Resources res = launcher.getResources();
2678        Display display = launcher.getWindowManager().getDefaultDisplay();
2679        Point smallestSize = new Point();
2680        Point largestSize = new Point();
2681        display.getCurrentSizeRange(smallestSize, largestSize);
2682        int countX = (int) grid.numColumns;
2683        int countY = (int) grid.numRows;
2684        if (orientation == CellLayout.LANDSCAPE) {
2685            if (mLandscapeCellLayoutMetrics == null) {
2686                Rect padding = grid.getWorkspacePadding(CellLayout.LANDSCAPE);
2687                int width = largestSize.x - padding.left - padding.right;
2688                int height = smallestSize.y - padding.top - padding.bottom;
2689                mLandscapeCellLayoutMetrics = new Rect();
2690                CellLayout.getMetrics(mLandscapeCellLayoutMetrics, width, height,
2691                        countX, countY);
2692            }
2693            return mLandscapeCellLayoutMetrics;
2694        } else if (orientation == CellLayout.PORTRAIT) {
2695            if (mPortraitCellLayoutMetrics == null) {
2696                Rect padding = grid.getWorkspacePadding(CellLayout.PORTRAIT);
2697                int width = smallestSize.x - padding.left - padding.right;
2698                int height = largestSize.y - padding.top - padding.bottom;
2699                mPortraitCellLayoutMetrics = new Rect();
2700                CellLayout.getMetrics(mPortraitCellLayoutMetrics, width, height,
2701                        countX, countY);
2702            }
2703            return mPortraitCellLayoutMetrics;
2704        }
2705        return null;
2706    }
2707
2708    public void onDragExit(DragObject d) {
2709        mDragEnforcer.onDragExit();
2710
2711        // Here we store the final page that will be dropped to, if the workspace in fact
2712        // receives the drop
2713        if (mInScrollArea) {
2714            if (isPageMoving()) {
2715                // If the user drops while the page is scrolling, we should use that page as the
2716                // destination instead of the page that is being hovered over.
2717                mDropToLayout = (CellLayout) getPageAt(getNextPage());
2718            } else {
2719                mDropToLayout = mDragOverlappingLayout;
2720            }
2721        } else {
2722            mDropToLayout = mDragTargetLayout;
2723        }
2724
2725        if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
2726            mCreateUserFolderOnDrop = true;
2727        } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
2728            mAddToExistingFolderOnDrop = true;
2729        }
2730
2731        // Reset the scroll area and previous drag target
2732        onResetScrollArea();
2733        setCurrentDropLayout(null);
2734        setCurrentDragOverlappingLayout(null);
2735
2736        mSpringLoadedDragController.cancel();
2737
2738        if (!mIsPageMoving) {
2739            hideOutlines();
2740        }
2741    }
2742
2743    void setCurrentDropLayout(CellLayout layout) {
2744        if (mDragTargetLayout != null) {
2745            mDragTargetLayout.revertTempState();
2746            mDragTargetLayout.onDragExit();
2747        }
2748        mDragTargetLayout = layout;
2749        if (mDragTargetLayout != null) {
2750            mDragTargetLayout.onDragEnter();
2751        }
2752        cleanupReorder(true);
2753        cleanupFolderCreation();
2754        setCurrentDropOverCell(-1, -1);
2755    }
2756
2757    void setCurrentDragOverlappingLayout(CellLayout layout) {
2758        if (mDragOverlappingLayout != null) {
2759            mDragOverlappingLayout.setIsDragOverlapping(false);
2760        }
2761        mDragOverlappingLayout = layout;
2762        if (mDragOverlappingLayout != null) {
2763            mDragOverlappingLayout.setIsDragOverlapping(true);
2764        }
2765        invalidate();
2766    }
2767
2768    void setCurrentDropOverCell(int x, int y) {
2769        if (x != mDragOverX || y != mDragOverY) {
2770            mDragOverX = x;
2771            mDragOverY = y;
2772            setDragMode(DRAG_MODE_NONE);
2773        }
2774    }
2775
2776    void setDragMode(int dragMode) {
2777        if (dragMode != mDragMode) {
2778            if (dragMode == DRAG_MODE_NONE) {
2779                cleanupAddToFolder();
2780                // We don't want to cancel the re-order alarm every time the target cell changes
2781                // as this feels to slow / unresponsive.
2782                cleanupReorder(false);
2783                cleanupFolderCreation();
2784            } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
2785                cleanupReorder(true);
2786                cleanupFolderCreation();
2787            } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
2788                cleanupAddToFolder();
2789                cleanupReorder(true);
2790            } else if (dragMode == DRAG_MODE_REORDER) {
2791                cleanupAddToFolder();
2792                cleanupFolderCreation();
2793            }
2794            mDragMode = dragMode;
2795        }
2796    }
2797
2798    private void cleanupFolderCreation() {
2799        if (mDragFolderRingAnimator != null) {
2800            mDragFolderRingAnimator.animateToNaturalState();
2801        }
2802        mFolderCreationAlarm.cancelAlarm();
2803    }
2804
2805    private void cleanupAddToFolder() {
2806        if (mDragOverFolderIcon != null) {
2807            mDragOverFolderIcon.onDragExit(null);
2808            mDragOverFolderIcon = null;
2809        }
2810    }
2811
2812    private void cleanupReorder(boolean cancelAlarm) {
2813        // Any pending reorders are canceled
2814        if (cancelAlarm) {
2815            mReorderAlarm.cancelAlarm();
2816        }
2817        mLastReorderX = -1;
2818        mLastReorderY = -1;
2819    }
2820
2821   /*
2822    *
2823    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
2824    * coordinate space. The argument xy is modified with the return result.
2825    *
2826    * if cachedInverseMatrix is not null, this method will just use that matrix instead of
2827    * computing it itself; we use this to avoid redundant matrix inversions in
2828    * findMatchingPageForDragOver
2829    *
2830    */
2831   void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
2832       xy[0] = xy[0] - v.getLeft();
2833       xy[1] = xy[1] - v.getTop();
2834   }
2835
2836   boolean isPointInSelfOverHotseat(int x, int y, Rect r) {
2837       if (r == null) {
2838           r = new Rect();
2839       }
2840       mTempPt[0] = x;
2841       mTempPt[1] = y;
2842       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
2843       mLauncher.getHotseat().getHitRect(r);
2844       if (r.contains(mTempPt[0], mTempPt[1])) {
2845           return true;
2846       }
2847       return false;
2848   }
2849
2850   void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
2851       mTempPt[0] = (int) xy[0];
2852       mTempPt[1] = (int) xy[1];
2853       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
2854       mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempPt);
2855
2856       xy[0] = mTempPt[0];
2857       xy[1] = mTempPt[1];
2858   }
2859
2860   /*
2861    *
2862    * Convert the 2D coordinate xy from this CellLayout's coordinate space to
2863    * the parent View's coordinate space. The argument xy is modified with the return result.
2864    *
2865    */
2866   void mapPointFromChildToSelf(View v, float[] xy) {
2867       xy[0] += v.getLeft();
2868       xy[1] += v.getTop();
2869   }
2870
2871   static private float squaredDistance(float[] point1, float[] point2) {
2872        float distanceX = point1[0] - point2[0];
2873        float distanceY = point2[1] - point2[1];
2874        return distanceX * distanceX + distanceY * distanceY;
2875   }
2876
2877    /*
2878     *
2879     * This method returns the CellLayout that is currently being dragged to. In order to drag
2880     * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
2881     * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
2882     *
2883     * Return null if no CellLayout is currently being dragged over
2884     *
2885     */
2886    private CellLayout findMatchingPageForDragOver(
2887            DragView dragView, float originX, float originY, boolean exact) {
2888        // We loop through all the screens (ie CellLayouts) and see which ones overlap
2889        // with the item being dragged and then choose the one that's closest to the touch point
2890        final int screenCount = getChildCount();
2891        CellLayout bestMatchingScreen = null;
2892        float smallestDistSoFar = Float.MAX_VALUE;
2893
2894        for (int i = 0; i < screenCount; i++) {
2895            CellLayout cl = (CellLayout) getChildAt(i);
2896
2897            final float[] touchXy = {originX, originY};
2898            // Transform the touch coordinates to the CellLayout's local coordinates
2899            // If the touch point is within the bounds of the cell layout, we can return immediately
2900            cl.getMatrix().invert(mTempInverseMatrix);
2901            mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
2902
2903            if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
2904                    touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
2905                return cl;
2906            }
2907
2908            if (!exact) {
2909                // Get the center of the cell layout in screen coordinates
2910                final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
2911                cellLayoutCenter[0] = cl.getWidth()/2;
2912                cellLayoutCenter[1] = cl.getHeight()/2;
2913                mapPointFromChildToSelf(cl, cellLayoutCenter);
2914
2915                touchXy[0] = originX;
2916                touchXy[1] = originY;
2917
2918                // Calculate the distance between the center of the CellLayout
2919                // and the touch point
2920                float dist = squaredDistance(touchXy, cellLayoutCenter);
2921
2922                if (dist < smallestDistSoFar) {
2923                    smallestDistSoFar = dist;
2924                    bestMatchingScreen = cl;
2925                }
2926            }
2927        }
2928        return bestMatchingScreen;
2929    }
2930
2931    // This is used to compute the visual center of the dragView. This point is then
2932    // used to visualize drop locations and determine where to drop an item. The idea is that
2933    // the visual center represents the user's interpretation of where the item is, and hence
2934    // is the appropriate point to use when determining drop location.
2935    private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
2936            DragView dragView, float[] recycle) {
2937        float res[];
2938        if (recycle == null) {
2939            res = new float[2];
2940        } else {
2941            res = recycle;
2942        }
2943
2944        // First off, the drag view has been shifted in a way that is not represented in the
2945        // x and y values or the x/yOffsets. Here we account for that shift.
2946        x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX);
2947        y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
2948
2949        // These represent the visual top and left of drag view if a dragRect was provided.
2950        // If a dragRect was not provided, then they correspond to the actual view left and
2951        // top, as the dragRect is in that case taken to be the entire dragView.
2952        // R.dimen.dragViewOffsetY.
2953        int left = x - xOffset;
2954        int top = y - yOffset;
2955
2956        // In order to find the visual center, we shift by half the dragRect
2957        res[0] = left + dragView.getDragRegion().width() / 2;
2958        res[1] = top + dragView.getDragRegion().height() / 2;
2959
2960        return res;
2961    }
2962
2963    private boolean isDragWidget(DragObject d) {
2964        return (d.dragInfo instanceof LauncherAppWidgetInfo ||
2965                d.dragInfo instanceof PendingAddWidgetInfo);
2966    }
2967    private boolean isExternalDragWidget(DragObject d) {
2968        return d.dragSource != this && isDragWidget(d);
2969    }
2970
2971    public void onDragOver(DragObject d) {
2972        // Skip drag over events while we are dragging over side pages
2973        if (mInScrollArea || mIsSwitchingState || mState == State.SMALL) return;
2974
2975        Rect r = new Rect();
2976        CellLayout layout = null;
2977        ItemInfo item = (ItemInfo) d.dragInfo;
2978
2979        // Ensure that we have proper spans for the item that we are dropping
2980        if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
2981        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
2982            d.dragView, mDragViewVisualCenter);
2983
2984        final View child = (mDragInfo == null) ? null : mDragInfo.cell;
2985        // Identify whether we have dragged over a side page
2986        if (isSmall()) {
2987            if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
2988                if (isPointInSelfOverHotseat(d.x, d.y, r)) {
2989                    layout = mLauncher.getHotseat().getLayout();
2990                }
2991            }
2992            if (layout == null) {
2993                layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false);
2994            }
2995            if (layout != mDragTargetLayout) {
2996                setCurrentDropLayout(layout);
2997                setCurrentDragOverlappingLayout(layout);
2998
2999                boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
3000                if (isInSpringLoadedMode) {
3001                    if (mLauncher.isHotseatLayout(layout)) {
3002                        mSpringLoadedDragController.cancel();
3003                    } else {
3004                        mSpringLoadedDragController.setAlarm(mDragTargetLayout);
3005                    }
3006                }
3007            }
3008        } else {
3009            // Test to see if we are over the hotseat otherwise just use the current page
3010            if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
3011                if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3012                    layout = mLauncher.getHotseat().getLayout();
3013                }
3014            }
3015            if (layout == null) {
3016                layout = getCurrentDropLayout();
3017            }
3018            if (layout != mDragTargetLayout) {
3019                setCurrentDropLayout(layout);
3020                setCurrentDragOverlappingLayout(layout);
3021            }
3022        }
3023
3024        // Handle the drag over
3025        if (mDragTargetLayout != null) {
3026            // We want the point to be mapped to the dragTarget.
3027            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
3028                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
3029            } else {
3030                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
3031            }
3032
3033            ItemInfo info = (ItemInfo) d.dragInfo;
3034
3035            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3036                    (int) mDragViewVisualCenter[1], item.spanX, item.spanY,
3037                    mDragTargetLayout, mTargetCell);
3038
3039            setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
3040
3041            float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
3042                    mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
3043
3044            final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0],
3045                    mTargetCell[1]);
3046
3047            manageFolderFeedback(info, mDragTargetLayout, mTargetCell,
3048                    targetCellDistance, dragOverView);
3049
3050            int minSpanX = item.spanX;
3051            int minSpanY = item.spanY;
3052            if (item.minSpanX > 0 && item.minSpanY > 0) {
3053                minSpanX = item.minSpanX;
3054                minSpanY = item.minSpanY;
3055            }
3056
3057            boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
3058                    mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
3059                    item.spanY, child, mTargetCell);
3060
3061            if (!nearestDropOccupied) {
3062                mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3063                        (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
3064                        mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
3065                        d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
3066            } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
3067                    && !mReorderAlarm.alarmPending() && (mLastReorderX != mTargetCell[0] ||
3068                    mLastReorderY != mTargetCell[1])) {
3069
3070                // Otherwise, if we aren't adding to or creating a folder and there's no pending
3071                // reorder, then we schedule a reorder
3072                ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
3073                        minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child);
3074                mReorderAlarm.setOnAlarmListener(listener);
3075                mReorderAlarm.setAlarm(REORDER_TIMEOUT);
3076            }
3077
3078            if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
3079                    !nearestDropOccupied) {
3080                if (mDragTargetLayout != null) {
3081                    mDragTargetLayout.revertTempState();
3082                }
3083            }
3084        }
3085    }
3086
3087    private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
3088            int[] targetCell, float distance, View dragOverView) {
3089        boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
3090                false);
3091
3092        if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
3093                !mFolderCreationAlarm.alarmPending()) {
3094            mFolderCreationAlarm.setOnAlarmListener(new
3095                    FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
3096            mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
3097            return;
3098        }
3099
3100        boolean willAddToFolder =
3101                willAddToExistingUserFolder(info, targetLayout, targetCell, distance);
3102
3103        if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
3104            mDragOverFolderIcon = ((FolderIcon) dragOverView);
3105            mDragOverFolderIcon.onDragEnter(info);
3106            if (targetLayout != null) {
3107                targetLayout.clearDragOutlines();
3108            }
3109            setDragMode(DRAG_MODE_ADD_TO_FOLDER);
3110            return;
3111        }
3112
3113        if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
3114            setDragMode(DRAG_MODE_NONE);
3115        }
3116        if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
3117            setDragMode(DRAG_MODE_NONE);
3118        }
3119
3120        return;
3121    }
3122
3123    class FolderCreationAlarmListener implements OnAlarmListener {
3124        CellLayout layout;
3125        int cellX;
3126        int cellY;
3127
3128        public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
3129            this.layout = layout;
3130            this.cellX = cellX;
3131            this.cellY = cellY;
3132        }
3133
3134        public void onAlarm(Alarm alarm) {
3135            if (mDragFolderRingAnimator == null) {
3136                mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
3137            }
3138            mDragFolderRingAnimator.setCell(cellX, cellY);
3139            mDragFolderRingAnimator.setCellLayout(layout);
3140            mDragFolderRingAnimator.animateToAcceptState();
3141            layout.showFolderAccept(mDragFolderRingAnimator);
3142            layout.clearDragOutlines();
3143            setDragMode(DRAG_MODE_CREATE_FOLDER);
3144        }
3145    }
3146
3147    class ReorderAlarmListener implements OnAlarmListener {
3148        float[] dragViewCenter;
3149        int minSpanX, minSpanY, spanX, spanY;
3150        DragView dragView;
3151        View child;
3152
3153        public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
3154                int spanY, DragView dragView, View child) {
3155            this.dragViewCenter = dragViewCenter;
3156            this.minSpanX = minSpanX;
3157            this.minSpanY = minSpanY;
3158            this.spanX = spanX;
3159            this.spanY = spanY;
3160            this.child = child;
3161            this.dragView = dragView;
3162        }
3163
3164        public void onAlarm(Alarm alarm) {
3165            int[] resultSpan = new int[2];
3166            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3167                    (int) mDragViewVisualCenter[1], spanX, spanY, mDragTargetLayout, mTargetCell);
3168            mLastReorderX = mTargetCell[0];
3169            mLastReorderY = mTargetCell[1];
3170
3171            mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
3172                (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
3173                child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
3174
3175            if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
3176                mDragTargetLayout.revertTempState();
3177            } else {
3178                setDragMode(DRAG_MODE_REORDER);
3179            }
3180
3181            boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
3182            mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3183                (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
3184                mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize,
3185                dragView.getDragVisualizeOffset(), dragView.getDragRegion());
3186        }
3187    }
3188
3189    @Override
3190    public void getHitRectRelativeToDragLayer(Rect outRect) {
3191        // We want the workspace to have the whole area of the display (it will find the correct
3192        // cell layout to drop to in the existing drag/drop logic.
3193        mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect);
3194    }
3195
3196    /**
3197     * Add the item specified by dragInfo to the given layout.
3198     * @return true if successful
3199     */
3200    public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
3201        if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
3202            onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
3203            return true;
3204        }
3205        mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
3206        return false;
3207    }
3208
3209    private void onDropExternal(int[] touchXY, Object dragInfo,
3210            CellLayout cellLayout, boolean insertAtFirst) {
3211        onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
3212    }
3213
3214    /**
3215     * Drop an item that didn't originate on one of the workspace screens.
3216     * It may have come from Launcher (e.g. from all apps or customize), or it may have
3217     * come from another app altogether.
3218     *
3219     * NOTE: This can also be called when we are outside of a drag event, when we want
3220     * to add an item to one of the workspace screens.
3221     */
3222    private void onDropExternal(final int[] touchXY, final Object dragInfo,
3223            final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
3224        final Runnable exitSpringLoadedRunnable = new Runnable() {
3225            @Override
3226            public void run() {
3227                mLauncher.exitSpringLoadedDragModeDelayed(true, false, null);
3228            }
3229        };
3230
3231        ItemInfo info = (ItemInfo) dragInfo;
3232        int spanX = info.spanX;
3233        int spanY = info.spanY;
3234        if (mDragInfo != null) {
3235            spanX = mDragInfo.spanX;
3236            spanY = mDragInfo.spanY;
3237        }
3238
3239        final long container = mLauncher.isHotseatLayout(cellLayout) ?
3240                LauncherSettings.Favorites.CONTAINER_HOTSEAT :
3241                    LauncherSettings.Favorites.CONTAINER_DESKTOP;
3242        final long screenId = getIdForScreen(cellLayout);
3243        if (!mLauncher.isHotseatLayout(cellLayout)
3244                && screenId != getScreenIdForPageIndex(mCurrentPage)
3245                && mState != State.SPRING_LOADED) {
3246            snapToScreenId(screenId, null);
3247        }
3248
3249        if (info instanceof PendingAddItemInfo) {
3250            final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
3251
3252            boolean findNearestVacantCell = true;
3253            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
3254                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3255                        cellLayout, mTargetCell);
3256                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3257                        mDragViewVisualCenter[1], mTargetCell);
3258                if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell,
3259                        distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
3260                                cellLayout, mTargetCell, distance)) {
3261                    findNearestVacantCell = false;
3262                }
3263            }
3264
3265            final ItemInfo item = (ItemInfo) d.dragInfo;
3266            boolean updateWidgetSize = false;
3267            if (findNearestVacantCell) {
3268                int minSpanX = item.spanX;
3269                int minSpanY = item.spanY;
3270                if (item.minSpanX > 0 && item.minSpanY > 0) {
3271                    minSpanX = item.minSpanX;
3272                    minSpanY = item.minSpanY;
3273                }
3274                int[] resultSpan = new int[2];
3275                mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
3276                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
3277                        null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
3278
3279                if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) {
3280                    updateWidgetSize = true;
3281                }
3282                item.spanX = resultSpan[0];
3283                item.spanY = resultSpan[1];
3284            }
3285
3286            Runnable onAnimationCompleteRunnable = new Runnable() {
3287                @Override
3288                public void run() {
3289                    // When dragging and dropping from customization tray, we deal with creating
3290                    // widgets/shortcuts/folders in a slightly different way
3291                    switch (pendingInfo.itemType) {
3292                    case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
3293                        int span[] = new int[2];
3294                        span[0] = item.spanX;
3295                        span[1] = item.spanY;
3296                        mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
3297                                container, screenId, mTargetCell, span, null);
3298                        break;
3299                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3300                        mLauncher.processShortcutFromDrop(pendingInfo.componentName,
3301                                container, screenId, mTargetCell, null);
3302                        break;
3303                    default:
3304                        throw new IllegalStateException("Unknown item type: " +
3305                                pendingInfo.itemType);
3306                    }
3307                }
3308            };
3309            View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
3310                    ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
3311
3312            if (finalView instanceof AppWidgetHostView && updateWidgetSize) {
3313                AppWidgetHostView awhv = (AppWidgetHostView) finalView;
3314                AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, item.spanX,
3315                        item.spanY);
3316            }
3317
3318            int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3319            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
3320                    ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
3321                animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
3322            }
3323            animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
3324                    animationStyle, finalView, true);
3325        } else {
3326            // This is for other drag/drop cases, like dragging from All Apps
3327            View view = null;
3328
3329            switch (info.itemType) {
3330            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3331            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3332                if (info.container == NO_ID && info instanceof AppInfo) {
3333                    // Came from all apps -- make a copy
3334                    info = new ShortcutInfo((AppInfo) info);
3335                }
3336                view = mLauncher.createShortcut(R.layout.application, cellLayout,
3337                        (ShortcutInfo) info);
3338                break;
3339            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3340                view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
3341                        (FolderInfo) info, mIconCache);
3342                break;
3343            default:
3344                throw new IllegalStateException("Unknown item type: " + info.itemType);
3345            }
3346
3347            // First we find the cell nearest to point at which the item is
3348            // dropped, without any consideration to whether there is an item there.
3349            if (touchXY != null) {
3350                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3351                        cellLayout, mTargetCell);
3352                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3353                        mDragViewVisualCenter[1], mTargetCell);
3354                d.postAnimationRunnable = exitSpringLoadedRunnable;
3355                if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
3356                        true, d.dragView, d.postAnimationRunnable)) {
3357                    return;
3358                }
3359                if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
3360                        true)) {
3361                    return;
3362                }
3363            }
3364
3365            if (touchXY != null) {
3366                // when dragging and dropping, just find the closest free spot
3367                mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
3368                        (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
3369                        null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
3370            } else {
3371                cellLayout.findCellForSpan(mTargetCell, 1, 1);
3372            }
3373            addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX,
3374                    info.spanY, insertAtFirst);
3375            cellLayout.onDropChild(view);
3376            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
3377            cellLayout.getShortcutsAndWidgets().measureChild(view);
3378
3379            LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
3380                    lp.cellX, lp.cellY);
3381
3382            if (d.dragView != null) {
3383                // We wrap the animation call in the temporary set and reset of the current
3384                // cellLayout to its final transform -- this means we animate the drag view to
3385                // the correct final location.
3386                setFinalTransitionTransform(cellLayout);
3387                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
3388                        exitSpringLoadedRunnable);
3389                resetTransitionTransform(cellLayout);
3390            }
3391        }
3392    }
3393
3394    public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
3395        int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
3396                widgetInfo.spanY, widgetInfo, false);
3397        int visibility = layout.getVisibility();
3398        layout.setVisibility(VISIBLE);
3399
3400        int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
3401        int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
3402        Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
3403                Bitmap.Config.ARGB_8888);
3404        Canvas c = new Canvas(b);
3405
3406        layout.measure(width, height);
3407        layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
3408        layout.draw(c);
3409        c.setBitmap(null);
3410        layout.setVisibility(visibility);
3411        return b;
3412    }
3413
3414    private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
3415            DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell,
3416            boolean external, boolean scale) {
3417        // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
3418        // location and size on the home screen.
3419        int spanX = info.spanX;
3420        int spanY = info.spanY;
3421
3422        Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
3423        loc[0] = r.left;
3424        loc[1] = r.top;
3425
3426        setFinalTransitionTransform(layout);
3427        float cellLayoutScale =
3428                mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
3429        resetTransitionTransform(layout);
3430
3431        float dragViewScaleX;
3432        float dragViewScaleY;
3433        if (scale) {
3434            dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
3435            dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
3436        } else {
3437            dragViewScaleX = 1f;
3438            dragViewScaleY = 1f;
3439        }
3440
3441        // The animation will scale the dragView about its center, so we need to center about
3442        // the final location.
3443        loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
3444        loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
3445
3446        scaleXY[0] = dragViewScaleX * cellLayoutScale;
3447        scaleXY[1] = dragViewScaleY * cellLayoutScale;
3448    }
3449
3450    public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
3451            final Runnable onCompleteRunnable, int animationType, final View finalView,
3452            boolean external) {
3453        Rect from = new Rect();
3454        mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
3455
3456        int[] finalPos = new int[2];
3457        float scaleXY[] = new float[2];
3458        boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
3459        getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
3460                external, scalePreview);
3461
3462        Resources res = mLauncher.getResources();
3463        int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
3464
3465        // In the case where we've prebound the widget, we remove it from the DragLayer
3466        if (finalView instanceof AppWidgetHostView && external) {
3467            Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView");
3468            mLauncher.getDragLayer().removeView(finalView);
3469        }
3470        if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
3471            Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
3472            dragView.setCrossFadeBitmap(crossFadeBitmap);
3473            dragView.crossFade((int) (duration * 0.8f));
3474        } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) {
3475            scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0],  scaleXY[1]);
3476        }
3477
3478        DragLayer dragLayer = mLauncher.getDragLayer();
3479        if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
3480            mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
3481                    DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
3482        } else {
3483            int endStyle;
3484            if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
3485                endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
3486            } else {
3487                endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
3488            }
3489
3490            Runnable onComplete = new Runnable() {
3491                @Override
3492                public void run() {
3493                    if (finalView != null) {
3494                        finalView.setVisibility(VISIBLE);
3495                    }
3496                    if (onCompleteRunnable != null) {
3497                        onCompleteRunnable.run();
3498                    }
3499                }
3500            };
3501            dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
3502                    finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
3503                    duration, this);
3504        }
3505    }
3506
3507    public void setFinalTransitionTransform(CellLayout layout) {
3508        if (isSwitchingState()) {
3509            mCurrentScale = getScaleX();
3510            setScaleX(mNewScale);
3511            setScaleY(mNewScale);
3512        }
3513    }
3514    public void resetTransitionTransform(CellLayout layout) {
3515        if (isSwitchingState()) {
3516            setScaleX(mCurrentScale);
3517            setScaleY(mCurrentScale);
3518        }
3519    }
3520
3521    /**
3522     * Return the current {@link CellLayout}, correctly picking the destination
3523     * screen while a scroll is in progress.
3524     */
3525    public CellLayout getCurrentDropLayout() {
3526        return (CellLayout) getChildAt(getNextPage());
3527    }
3528
3529    /**
3530     * Return the current CellInfo describing our current drag; this method exists
3531     * so that Launcher can sync this object with the correct info when the activity is created/
3532     * destroyed
3533     *
3534     */
3535    public CellLayout.CellInfo getDragInfo() {
3536        return mDragInfo;
3537    }
3538
3539    /**
3540     * Calculate the nearest cell where the given object would be dropped.
3541     *
3542     * pixelX and pixelY should be in the coordinate system of layout
3543     */
3544    private int[] findNearestArea(int pixelX, int pixelY,
3545            int spanX, int spanY, CellLayout layout, int[] recycle) {
3546        return layout.findNearestArea(
3547                pixelX, pixelY, spanX, spanY, recycle);
3548    }
3549
3550    void setup(DragController dragController) {
3551        mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
3552        mDragController = dragController;
3553
3554        // hardware layers on children are enabled on startup, but should be disabled until
3555        // needed
3556        updateChildrenLayersEnabled(false);
3557        setWallpaperDimension();
3558    }
3559
3560    /**
3561     * Called at the end of a drag which originated on the workspace.
3562     */
3563    public void onDropCompleted(final View target, final DragObject d,
3564            final boolean isFlingToDelete, final boolean success) {
3565        if (mDeferDropAfterUninstall) {
3566            mDeferredAction = new Runnable() {
3567                    public void run() {
3568                        onDropCompleted(target, d, isFlingToDelete, success);
3569                        mDeferredAction = null;
3570                    }
3571                };
3572            return;
3573        }
3574
3575        boolean beingCalledAfterUninstall = mDeferredAction != null;
3576
3577        if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) {
3578            if (target != this && mDragInfo != null) {
3579                CellLayout parentCell = getParentCellLayoutForView(mDragInfo.cell);
3580                if (parentCell != null) {
3581                    parentCell.removeView(mDragInfo.cell);
3582                }
3583                if (mDragInfo.cell instanceof DropTarget) {
3584                    mDragController.removeDropTarget((DropTarget) mDragInfo.cell);
3585                }
3586                // If we move the item to anything not on the Workspace, check if any empty
3587                // screens need to be removed. If we dropped back on the workspace, this will
3588                // be done post drop animation.
3589                stripEmptyScreens();
3590            }
3591        } else if (mDragInfo != null) {
3592            CellLayout cellLayout;
3593            if (mLauncher.isHotseatLayout(target)) {
3594                cellLayout = mLauncher.getHotseat().getLayout();
3595            } else {
3596                cellLayout = getScreenWithId(mDragInfo.screenId);
3597            }
3598            cellLayout.onDropChild(mDragInfo.cell);
3599        }
3600        if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful))
3601                && mDragInfo.cell != null) {
3602            mDragInfo.cell.setVisibility(VISIBLE);
3603        }
3604        mDragOutline = null;
3605        mDragInfo = null;
3606    }
3607
3608    public void deferCompleteDropAfterUninstallActivity() {
3609        mDeferDropAfterUninstall = true;
3610    }
3611
3612    /// maybe move this into a smaller part
3613    public void onUninstallActivityReturned(boolean success) {
3614        mDeferDropAfterUninstall = false;
3615        mUninstallSuccessful = success;
3616        if (mDeferredAction != null) {
3617            mDeferredAction.run();
3618        }
3619    }
3620
3621    void updateItemLocationsInDatabase(CellLayout cl) {
3622        int count = cl.getShortcutsAndWidgets().getChildCount();
3623
3624        long screenId = getIdForScreen(cl);
3625        int container = Favorites.CONTAINER_DESKTOP;
3626
3627        if (mLauncher.isHotseatLayout(cl)) {
3628            screenId = -1;
3629            container = Favorites.CONTAINER_HOTSEAT;
3630        }
3631
3632        for (int i = 0; i < count; i++) {
3633            View v = cl.getShortcutsAndWidgets().getChildAt(i);
3634            ItemInfo info = (ItemInfo) v.getTag();
3635            // Null check required as the AllApps button doesn't have an item info
3636            if (info != null && info.requiresDbUpdate) {
3637                info.requiresDbUpdate = false;
3638                LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, info.cellX,
3639                        info.cellY, info.spanX, info.spanY);
3640            }
3641        }
3642    }
3643
3644    ArrayList<ComponentName> getUniqueComponents(boolean stripDuplicates, ArrayList<ComponentName> duplicates) {
3645        ArrayList<ComponentName> uniqueIntents = new ArrayList<ComponentName>();
3646        getUniqueIntents((CellLayout) mLauncher.getHotseat().getLayout(), uniqueIntents, duplicates, false);
3647        int count = getChildCount();
3648        for (int i = 0; i < count; i++) {
3649            CellLayout cl = (CellLayout) getChildAt(i);
3650            getUniqueIntents(cl, uniqueIntents, duplicates, false);
3651        }
3652        return uniqueIntents;
3653    }
3654
3655    void getUniqueIntents(CellLayout cl, ArrayList<ComponentName> uniqueIntents,
3656            ArrayList<ComponentName> duplicates, boolean stripDuplicates) {
3657        int count = cl.getShortcutsAndWidgets().getChildCount();
3658
3659        ArrayList<View> children = new ArrayList<View>();
3660        for (int i = 0; i < count; i++) {
3661            View v = cl.getShortcutsAndWidgets().getChildAt(i);
3662            children.add(v);
3663        }
3664
3665        for (int i = 0; i < count; i++) {
3666            View v = children.get(i);
3667            ItemInfo info = (ItemInfo) v.getTag();
3668            // Null check required as the AllApps button doesn't have an item info
3669            if (info instanceof ShortcutInfo) {
3670                ShortcutInfo si = (ShortcutInfo) info;
3671                ComponentName cn = si.intent.getComponent();
3672
3673                Uri dataUri = si.intent.getData();
3674                // If dataUri is not null / empty or if this component isn't one that would
3675                // have previously showed up in the AllApps list, then this is a widget-type
3676                // shortcut, so ignore it.
3677                if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
3678                    continue;
3679                }
3680
3681                if (!uniqueIntents.contains(cn)) {
3682                    uniqueIntents.add(cn);
3683                } else {
3684                    if (stripDuplicates) {
3685                        cl.removeViewInLayout(v);
3686                        LauncherModel.deleteItemFromDatabase(mLauncher, si);
3687                    }
3688                    if (duplicates != null) {
3689                        duplicates.add(cn);
3690                    }
3691                }
3692            }
3693            if (v instanceof FolderIcon) {
3694                FolderIcon fi = (FolderIcon) v;
3695                ArrayList<View> items = fi.getFolder().getItemsInReadingOrder();
3696                for (int j = 0; j < items.size(); j++) {
3697                    if (items.get(j).getTag() instanceof ShortcutInfo) {
3698                        ShortcutInfo si = (ShortcutInfo) items.get(j).getTag();
3699                        ComponentName cn = si.intent.getComponent();
3700
3701                        Uri dataUri = si.intent.getData();
3702                        // If dataUri is not null / empty or if this component isn't one that would
3703                        // have previously showed up in the AllApps list, then this is a widget-type
3704                        // shortcut, so ignore it.
3705                        if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
3706                            continue;
3707                        }
3708
3709                        if (!uniqueIntents.contains(cn)) {
3710                            uniqueIntents.add(cn);
3711                        }  else {
3712                            if (stripDuplicates) {
3713                                fi.getFolderInfo().remove(si);
3714                                LauncherModel.deleteItemFromDatabase(mLauncher, si);
3715                            }
3716                            if (duplicates != null) {
3717                                duplicates.add(cn);
3718                            }
3719                        }
3720                    }
3721                }
3722            }
3723        }
3724    }
3725
3726    void saveWorkspaceToDb() {
3727        saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout());
3728        int count = getChildCount();
3729        for (int i = 0; i < count; i++) {
3730            CellLayout cl = (CellLayout) getChildAt(i);
3731            saveWorkspaceScreenToDb(cl);
3732        }
3733    }
3734
3735    void saveWorkspaceScreenToDb(CellLayout cl) {
3736        int count = cl.getShortcutsAndWidgets().getChildCount();
3737
3738        long screenId = getIdForScreen(cl);
3739        int container = Favorites.CONTAINER_DESKTOP;
3740
3741        Hotseat hotseat = mLauncher.getHotseat();
3742        if (mLauncher.isHotseatLayout(cl)) {
3743            screenId = -1;
3744            container = Favorites.CONTAINER_HOTSEAT;
3745        }
3746
3747        for (int i = 0; i < count; i++) {
3748            View v = cl.getShortcutsAndWidgets().getChildAt(i);
3749            ItemInfo info = (ItemInfo) v.getTag();
3750            // Null check required as the AllApps button doesn't have an item info
3751            if (info != null) {
3752                int cellX = info.cellX;
3753                int cellY = info.cellY;
3754                if (container == Favorites.CONTAINER_HOTSEAT) {
3755                    cellX = hotseat.getCellXFromOrder((int) info.screenId);
3756                    cellY = hotseat.getCellYFromOrder((int) info.screenId);
3757                }
3758                LauncherModel.addItemToDatabase(mLauncher, info, container, screenId, cellX,
3759                        cellY, false);
3760            }
3761            if (v instanceof FolderIcon) {
3762                FolderIcon fi = (FolderIcon) v;
3763                fi.getFolder().addItemLocationsInDatabase();
3764            }
3765        }
3766    }
3767
3768    @Override
3769    public boolean supportsFlingToDelete() {
3770        return true;
3771    }
3772
3773    @Override
3774    public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
3775        // Do nothing
3776    }
3777
3778    @Override
3779    public void onFlingToDeleteCompleted() {
3780        // Do nothing
3781    }
3782
3783    public boolean isDropEnabled() {
3784        return true;
3785    }
3786
3787    @Override
3788    protected void onRestoreInstanceState(Parcelable state) {
3789        super.onRestoreInstanceState(state);
3790        Launcher.setScreen(mCurrentPage);
3791    }
3792
3793    @Override
3794    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
3795        // We don't dispatch restoreInstanceState to our children using this code path.
3796        // Some pages will be restored immediately as their items are bound immediately, and
3797        // others we will need to wait until after their items are bound.
3798        mSavedStates = container;
3799    }
3800
3801    public void restoreInstanceStateForChild(int child) {
3802        if (mSavedStates != null) {
3803            mRestoredPages.add(child);
3804            CellLayout cl = (CellLayout) getChildAt(child);
3805            cl.restoreInstanceState(mSavedStates);
3806        }
3807    }
3808
3809    public void restoreInstanceStateForRemainingPages() {
3810        int count = getChildCount();
3811        for (int i = 0; i < count; i++) {
3812            if (!mRestoredPages.contains(i)) {
3813                restoreInstanceStateForChild(i);
3814            }
3815        }
3816        mRestoredPages.clear();
3817    }
3818
3819    @Override
3820    public void scrollLeft() {
3821        if (!isSmall() && !mIsSwitchingState) {
3822            super.scrollLeft();
3823        }
3824        Folder openFolder = getOpenFolder();
3825        if (openFolder != null) {
3826            openFolder.completeDragExit();
3827        }
3828    }
3829
3830    @Override
3831    public void scrollRight() {
3832        if (!isSmall() && !mIsSwitchingState) {
3833            super.scrollRight();
3834        }
3835        Folder openFolder = getOpenFolder();
3836        if (openFolder != null) {
3837            openFolder.completeDragExit();
3838        }
3839    }
3840
3841    @Override
3842    public boolean onEnterScrollArea(int x, int y, int direction) {
3843        // Ignore the scroll area if we are dragging over the hot seat
3844        boolean isPortrait = !LauncherAppState.isScreenLandscape(getContext());
3845        if (mLauncher.getHotseat() != null && isPortrait) {
3846            Rect r = new Rect();
3847            mLauncher.getHotseat().getHitRect(r);
3848            if (r.contains(x, y)) {
3849                return false;
3850            }
3851        }
3852
3853        boolean result = false;
3854        if (!isSmall() && !mIsSwitchingState && getOpenFolder() == null) {
3855            mInScrollArea = true;
3856
3857            final int page = getNextPage() +
3858                       (direction == DragController.SCROLL_LEFT ? -1 : 1);
3859            // We always want to exit the current layout to ensure parity of enter / exit
3860            setCurrentDropLayout(null);
3861
3862            if (0 <= page && page < getChildCount()) {
3863                // Ensure that we are not dragging over to the custom content screen
3864                if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) {
3865                    return false;
3866                }
3867
3868                CellLayout layout = (CellLayout) getChildAt(page);
3869                setCurrentDragOverlappingLayout(layout);
3870
3871                // Workspace is responsible for drawing the edge glow on adjacent pages,
3872                // so we need to redraw the workspace when this may have changed.
3873                invalidate();
3874                result = true;
3875            }
3876        }
3877        return result;
3878    }
3879
3880    @Override
3881    public boolean onExitScrollArea() {
3882        boolean result = false;
3883        if (mInScrollArea) {
3884            invalidate();
3885            CellLayout layout = getCurrentDropLayout();
3886            setCurrentDropLayout(layout);
3887            setCurrentDragOverlappingLayout(layout);
3888
3889            result = true;
3890            mInScrollArea = false;
3891        }
3892        return result;
3893    }
3894
3895    private void onResetScrollArea() {
3896        setCurrentDragOverlappingLayout(null);
3897        mInScrollArea = false;
3898    }
3899
3900    /**
3901     * Returns a specific CellLayout
3902     */
3903    CellLayout getParentCellLayoutForView(View v) {
3904        ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
3905        for (CellLayout layout : layouts) {
3906            if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
3907                return layout;
3908            }
3909        }
3910        return null;
3911    }
3912
3913    /**
3914     * Returns a list of all the CellLayouts in the workspace.
3915     */
3916    ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
3917        ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
3918        int screenCount = getChildCount();
3919        for (int screen = 0; screen < screenCount; screen++) {
3920            layouts.add(((CellLayout) getChildAt(screen)));
3921        }
3922        if (mLauncher.getHotseat() != null) {
3923            layouts.add(mLauncher.getHotseat().getLayout());
3924        }
3925        return layouts;
3926    }
3927
3928    /**
3929     * We should only use this to search for specific children.  Do not use this method to modify
3930     * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
3931     * the hotseat and workspace pages
3932     */
3933    ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
3934        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3935                new ArrayList<ShortcutAndWidgetContainer>();
3936        int screenCount = getChildCount();
3937        for (int screen = 0; screen < screenCount; screen++) {
3938            childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
3939        }
3940        if (mLauncher.getHotseat() != null) {
3941            childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
3942        }
3943        return childrenLayouts;
3944    }
3945
3946    public Folder getFolderForTag(Object tag) {
3947        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3948                getAllShortcutAndWidgetContainers();
3949        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
3950            int count = layout.getChildCount();
3951            for (int i = 0; i < count; i++) {
3952                View child = layout.getChildAt(i);
3953                if (child instanceof Folder) {
3954                    Folder f = (Folder) child;
3955                    if (f.getInfo() == tag && f.getInfo().opened) {
3956                        return f;
3957                    }
3958                }
3959            }
3960        }
3961        return null;
3962    }
3963
3964    public View getViewForTag(Object tag) {
3965        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3966                getAllShortcutAndWidgetContainers();
3967        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
3968            int count = layout.getChildCount();
3969            for (int i = 0; i < count; i++) {
3970                View child = layout.getChildAt(i);
3971                if (child.getTag() == tag) {
3972                    return child;
3973                }
3974            }
3975        }
3976        return null;
3977    }
3978
3979    void clearDropTargets() {
3980        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3981                getAllShortcutAndWidgetContainers();
3982        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
3983            int childCount = layout.getChildCount();
3984            for (int j = 0; j < childCount; j++) {
3985                View v = layout.getChildAt(j);
3986                if (v instanceof DropTarget) {
3987                    mDragController.removeDropTarget((DropTarget) v);
3988                }
3989            }
3990        }
3991    }
3992
3993    // Removes ALL items that match a given package name, this is usually called when a package
3994    // has been removed and we want to remove all components (widgets, shortcuts, apps) that
3995    // belong to that package.
3996    void removeItemsByPackageName(final ArrayList<String> packages) {
3997        final HashSet<String> packageNames = new HashSet<String>();
3998        packageNames.addAll(packages);
3999
4000        // Filter out all the ItemInfos that this is going to affect
4001        final HashSet<ItemInfo> infos = new HashSet<ItemInfo>();
4002        final HashSet<ComponentName> cns = new HashSet<ComponentName>();
4003        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4004        for (CellLayout layoutParent : cellLayouts) {
4005            ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4006            int childCount = layout.getChildCount();
4007            for (int i = 0; i < childCount; ++i) {
4008                View view = layout.getChildAt(i);
4009                infos.add((ItemInfo) view.getTag());
4010            }
4011        }
4012        LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4013            @Override
4014            public boolean filterItem(ItemInfo parent, ItemInfo info,
4015                                      ComponentName cn) {
4016                if (packageNames.contains(cn.getPackageName())) {
4017                    cns.add(cn);
4018                    return true;
4019                }
4020                return false;
4021            }
4022        };
4023        LauncherModel.filterItemInfos(infos, filter);
4024
4025        // Remove the affected components
4026        removeItemsByComponentName(cns);
4027    }
4028
4029    // Removes items that match the application info specified, when applications are removed
4030    // as a part of an update, this is called to ensure that other widgets and application
4031    // shortcuts are not removed.
4032    void removeItemsByApplicationInfo(final ArrayList<AppInfo> appInfos) {
4033        // Just create a hash table of all the specific components that this will affect
4034        HashSet<ComponentName> cns = new HashSet<ComponentName>();
4035        for (AppInfo info : appInfos) {
4036            cns.add(info.componentName);
4037        }
4038
4039        // Remove all the things
4040        removeItemsByComponentName(cns);
4041    }
4042
4043    void removeItemsByComponentName(final HashSet<ComponentName> componentNames) {
4044        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4045        for (final CellLayout layoutParent: cellLayouts) {
4046            final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4047
4048            final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
4049            for (int j = 0; j < layout.getChildCount(); j++) {
4050                final View view = layout.getChildAt(j);
4051                children.put((ItemInfo) view.getTag(), view);
4052            }
4053
4054            final ArrayList<View> childrenToRemove = new ArrayList<View>();
4055            final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove =
4056                    new HashMap<FolderInfo, ArrayList<ShortcutInfo>>();
4057            LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4058                @Override
4059                public boolean filterItem(ItemInfo parent, ItemInfo info,
4060                                          ComponentName cn) {
4061                    if (parent instanceof FolderInfo) {
4062                        if (componentNames.contains(cn)) {
4063                            FolderInfo folder = (FolderInfo) parent;
4064                            ArrayList<ShortcutInfo> appsToRemove;
4065                            if (folderAppsToRemove.containsKey(folder)) {
4066                                appsToRemove = folderAppsToRemove.get(folder);
4067                            } else {
4068                                appsToRemove = new ArrayList<ShortcutInfo>();
4069                                folderAppsToRemove.put(folder, appsToRemove);
4070                            }
4071                            appsToRemove.add((ShortcutInfo) info);
4072                            return true;
4073                        }
4074                    } else {
4075                        if (componentNames.contains(cn)) {
4076                            childrenToRemove.add(children.get(info));
4077                            return true;
4078                        }
4079                    }
4080                    return false;
4081                }
4082            };
4083            LauncherModel.filterItemInfos(children.keySet(), filter);
4084
4085            // Remove all the apps from their folders
4086            for (FolderInfo folder : folderAppsToRemove.keySet()) {
4087                ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
4088                for (ShortcutInfo info : appsToRemove) {
4089                    folder.remove(info);
4090                }
4091            }
4092
4093            // Remove all the other children
4094            for (View child : childrenToRemove) {
4095                // Note: We can not remove the view directly from CellLayoutChildren as this
4096                // does not re-mark the spaces as unoccupied.
4097                layoutParent.removeViewInLayout(child);
4098                if (child instanceof DropTarget) {
4099                    mDragController.removeDropTarget((DropTarget) child);
4100                }
4101            }
4102
4103            if (childrenToRemove.size() > 0) {
4104                layout.requestLayout();
4105                layout.invalidate();
4106            }
4107        }
4108
4109        // Strip all the empty screens
4110        stripEmptyScreens();
4111    }
4112
4113    void updateShortcuts(ArrayList<AppInfo> apps) {
4114        ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers();
4115        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
4116            int childCount = layout.getChildCount();
4117            for (int j = 0; j < childCount; j++) {
4118                final View view = layout.getChildAt(j);
4119                Object tag = view.getTag();
4120
4121                if (LauncherModel.isShortcutInfoUpdateable((ItemInfo) tag)) {
4122                    ShortcutInfo info = (ShortcutInfo) tag;
4123
4124                    final Intent intent = info.intent;
4125                    final ComponentName name = intent.getComponent();
4126                    final int appCount = apps.size();
4127                    for (int k = 0; k < appCount; k++) {
4128                        AppInfo app = apps.get(k);
4129                        if (app.componentName.equals(name)) {
4130                            BubbleTextView shortcut = (BubbleTextView) view;
4131                            info.updateIcon(mIconCache);
4132                            info.title = app.title.toString();
4133                            shortcut.applyFromShortcutInfo(info, mIconCache);
4134                        }
4135                    }
4136                }
4137            }
4138        }
4139    }
4140
4141    private void moveToScreen(int page, boolean animate) {
4142        if (!isSmall()) {
4143            if (animate) {
4144                snapToPage(page);
4145            } else {
4146                setCurrentPage(page);
4147            }
4148        }
4149        View child = getChildAt(page);
4150        if (child != null) {
4151            child.requestFocus();
4152        }
4153    }
4154
4155    void moveToDefaultScreen(boolean animate) {
4156        moveToScreen(mDefaultPage, animate);
4157    }
4158
4159    void moveToCustomContentScreen(boolean animate) {
4160        if (hasCustomContent()) {
4161            int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
4162            if (animate) {
4163                snapToPage(ccIndex);
4164            } else {
4165                setCurrentPage(ccIndex);
4166            }
4167            View child = getChildAt(ccIndex);
4168            if (child != null) {
4169                child.requestFocus();
4170            }
4171         }
4172    }
4173
4174    @Override
4175    protected int getPageIndicatorMarker(int pageIndex) {
4176        if (getScreenIdForPageIndex(pageIndex) == CUSTOM_CONTENT_SCREEN_ID) {
4177            return R.layout.custom_content_page_indicator_marker;
4178        }
4179        return super.getPageIndicatorMarker(pageIndex);
4180    }
4181
4182    @Override
4183    public void syncPages() {
4184    }
4185
4186    @Override
4187    public void syncPageItems(int page, boolean immediate) {
4188    }
4189
4190    protected String getCurrentPageDescription() {
4191        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
4192        return String.format(getContext().getString(R.string.workspace_scroll_format),
4193                page + 1, getChildCount());
4194    }
4195
4196    public void getLocationInDragLayer(int[] loc) {
4197        mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
4198    }
4199}
4200