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