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