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