Workspace.java revision dc734ef2c0a7152b0ada9425fd54c556128f3be4
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 offsetForLayoutTransitionAnimation = isLayoutRtl() ?
1203                        getPageAt(getChildCount() - 1).getLeft() - getFirstChildLeft() : 0;
1204                int adjustedScroll =
1205                        getScrollX() - firstPageScrollX - offsetForLayoutTransitionAnimation;
1206                float offset = Math.min(1, adjustedScroll / (float) scrollRange);
1207                offset = Math.max(0, offset);
1208                // Don't use up all the wallpaper parallax until you have at least
1209                // MIN_PARALLAX_PAGE_SPAN pages
1210                int numScrollingPages = getNumScreensExcludingEmptyAndCustom();
1211                int parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1);
1212                // On RTL devices, push the wallpaper offset to the right if we don't have enough
1213                // pages (ie if numScrollingPages < MIN_PARALLAX_PAGE_SPAN)
1214                int padding = isLayoutRtl() ? parallaxPageSpan - numScrollingPages + 1 : 0;
1215                return offset * (padding + numScrollingPages - 1) / parallaxPageSpan;
1216            }
1217        }
1218
1219        private int numEmptyScreensToIgnore() {
1220            int numScrollingPages = getChildCount() - numCustomPages();
1221            if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && hasExtraEmptyScreen()) {
1222                return 1;
1223            } else {
1224                return 0;
1225            }
1226        }
1227
1228        private int getNumScreensExcludingEmptyAndCustom() {
1229            int numScrollingPages = getChildCount() - numEmptyScreensToIgnore() - numCustomPages();
1230            return numScrollingPages;
1231        }
1232
1233        public void syncWithScroll() {
1234            float offset = wallpaperOffsetForCurrentScroll();
1235            mWallpaperOffset.setFinalX(offset);
1236            updateOffset(true);
1237        }
1238
1239        public float getCurrX() {
1240            return mCurrentOffset;
1241        }
1242
1243        public float getFinalX() {
1244            return mFinalOffset;
1245        }
1246
1247        private void animateToFinal() {
1248            mAnimating = true;
1249            mAnimationStartOffset = mCurrentOffset;
1250            mAnimationStartTime = System.currentTimeMillis();
1251        }
1252
1253        private void setWallpaperOffsetSteps() {
1254            // Set wallpaper offset steps (1 / (number of screens - 1))
1255            mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 1.0f);
1256        }
1257
1258        public void setFinalX(float x) {
1259            scheduleUpdate();
1260            mFinalOffset = Math.max(0f, Math.min(x, 1.0f));
1261            if (getNumScreensExcludingEmptyAndCustom() != mNumScreens) {
1262                if (mNumScreens > 0) {
1263                    // Don't animate if we're going from 0 screens
1264                    animateToFinal();
1265                }
1266                mNumScreens = getNumScreensExcludingEmptyAndCustom();
1267            }
1268        }
1269
1270        private void scheduleUpdate() {
1271            if (!mWaitingForUpdate) {
1272                mChoreographer.postFrameCallback(this);
1273                mWaitingForUpdate = true;
1274            }
1275        }
1276
1277        public void jumpToFinal() {
1278            mCurrentOffset = mFinalOffset;
1279        }
1280    }
1281
1282    @Override
1283    public void computeScroll() {
1284        super.computeScroll();
1285        mWallpaperOffset.syncWithScroll();
1286    }
1287
1288    void showOutlines() {
1289        if (!isSmall() && !mIsSwitchingState) {
1290            if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
1291            if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
1292            mChildrenOutlineFadeInAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 1.0f);
1293            mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
1294            mChildrenOutlineFadeInAnimation.start();
1295        }
1296    }
1297
1298    void hideOutlines() {
1299        if (!isSmall() && !mIsSwitchingState) {
1300            if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
1301            if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
1302            mChildrenOutlineFadeOutAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 0.0f);
1303            mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
1304            mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
1305            mChildrenOutlineFadeOutAnimation.start();
1306        }
1307    }
1308
1309    public void showOutlinesTemporarily() {
1310        if (!mIsPageMoving && !isTouchActive()) {
1311            snapToPage(mCurrentPage);
1312        }
1313    }
1314
1315    public void setChildrenOutlineAlpha(float alpha) {
1316        mChildrenOutlineAlpha = alpha;
1317        for (int i = 0; i < getChildCount(); i++) {
1318            CellLayout cl = (CellLayout) getChildAt(i);
1319            cl.setBackgroundAlpha(alpha);
1320        }
1321    }
1322
1323    public float getChildrenOutlineAlpha() {
1324        return mChildrenOutlineAlpha;
1325    }
1326
1327    void disableBackground() {
1328        mDrawBackground = false;
1329    }
1330    void enableBackground() {
1331        mDrawBackground = true;
1332    }
1333
1334    private void animateBackgroundGradient(float finalAlpha, boolean animated) {
1335        if (mBackground == null) return;
1336        if (mBackgroundFadeInAnimation != null) {
1337            mBackgroundFadeInAnimation.cancel();
1338            mBackgroundFadeInAnimation = null;
1339        }
1340        if (mBackgroundFadeOutAnimation != null) {
1341            mBackgroundFadeOutAnimation.cancel();
1342            mBackgroundFadeOutAnimation = null;
1343        }
1344        float startAlpha = getBackgroundAlpha();
1345        if (finalAlpha != startAlpha) {
1346            if (animated) {
1347                mBackgroundFadeOutAnimation =
1348                        LauncherAnimUtils.ofFloat(this, startAlpha, finalAlpha);
1349                mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() {
1350                    public void onAnimationUpdate(ValueAnimator animation) {
1351                        setBackgroundAlpha(((Float) animation.getAnimatedValue()).floatValue());
1352                    }
1353                });
1354                mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
1355                mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
1356                mBackgroundFadeOutAnimation.start();
1357            } else {
1358                setBackgroundAlpha(finalAlpha);
1359            }
1360        }
1361    }
1362
1363    public void setBackgroundAlpha(float alpha) {
1364        if (alpha != mBackgroundAlpha) {
1365            mBackgroundAlpha = alpha;
1366            invalidate();
1367        }
1368    }
1369
1370    public float getBackgroundAlpha() {
1371        return mBackgroundAlpha;
1372    }
1373
1374    float backgroundAlphaInterpolator(float r) {
1375        float pivotA = 0.1f;
1376        float pivotB = 0.4f;
1377        if (r < pivotA) {
1378            return 0;
1379        } else if (r > pivotB) {
1380            return 1.0f;
1381        } else {
1382            return (r - pivotA)/(pivotB - pivotA);
1383        }
1384    }
1385
1386    private void updatePageAlphaValues(int screenCenter) {
1387        boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
1388        if (mWorkspaceFadeInAdjacentScreens &&
1389                mState == State.NORMAL &&
1390                !mIsSwitchingState &&
1391                !isInOverscroll) {
1392            for (int i = 0; i < getChildCount(); i++) {
1393                CellLayout child = (CellLayout) getChildAt(i);
1394                if (child != null) {
1395                    float scrollProgress = getScrollProgress(screenCenter, child, i);
1396                    float alpha = 1 - Math.abs(scrollProgress);
1397                    child.getShortcutsAndWidgets().setAlpha(alpha);
1398                    if (!mIsDragOccuring) {
1399                        child.setBackgroundAlphaMultiplier(
1400                                backgroundAlphaInterpolator(Math.abs(scrollProgress)));
1401                    } else {
1402                        child.setBackgroundAlphaMultiplier(1f);
1403                    }
1404                }
1405            }
1406        }
1407    }
1408
1409    private void setChildrenBackgroundAlphaMultipliers(float a) {
1410        for (int i = 0; i < getChildCount(); i++) {
1411            CellLayout child = (CellLayout) getChildAt(i);
1412            child.setBackgroundAlphaMultiplier(a);
1413        }
1414    }
1415
1416    public boolean hasCustomContent() {
1417        return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID);
1418    }
1419
1420    public int numCustomPages() {
1421        return hasCustomContent() ? 1 : 0;
1422    }
1423
1424    public boolean isOnOrMovingToCustomContent() {
1425        return hasCustomContent() && getNextPage() == 0;
1426    }
1427
1428    private void updateStateForCustomContent(int screenCenter) {
1429        float translationX = 0;
1430        float progress = 0;
1431        if (hasCustomContent()) {
1432            int index = mScreenOrder.indexOf(CUSTOM_CONTENT_SCREEN_ID);
1433
1434            int scrollDelta = getScrollForPage(index + 1) - getScrollX() +
1435                    getLayoutTransitionOffsetForPage(index + 1);
1436            translationX = scrollDelta;
1437            progress = (1.0f * scrollDelta) /
1438                    (getScrollForPage(index + 1) - getScrollForPage(index));
1439
1440            if (isLayoutRtl()) {
1441                translationX = Math.min(0, translationX);
1442            } else {
1443                translationX = Math.max(0, translationX);
1444            }
1445            progress = Math.max(0, progress);
1446        }
1447
1448        if (Float.compare(progress, mLastCustomContentScrollProgress) == 0) return;
1449        mLastCustomContentScrollProgress = progress;
1450
1451        setBackgroundAlpha(progress * 0.8f);
1452
1453        if (mLauncher.getHotseat() != null) {
1454            mLauncher.getHotseat().setTranslationX(translationX);
1455        }
1456
1457        if (getPageIndicator() != null) {
1458            getPageIndicator().setTranslationX(translationX);
1459        }
1460
1461        if (mCustomContentCallbacks != null) {
1462            mCustomContentCallbacks.onScrollProgressChanged(progress);
1463        }
1464    }
1465
1466    @Override
1467    protected OnClickListener getPageIndicatorClickListener() {
1468        AccessibilityManager am = (AccessibilityManager)
1469                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
1470        if (!am.isTouchExplorationEnabled()) {
1471            return null;
1472        }
1473        OnClickListener listener = new OnClickListener() {
1474            @Override
1475            public void onClick(View arg0) {
1476                enterOverviewMode();
1477            }
1478        };
1479        return listener;
1480    }
1481
1482    @Override
1483    protected void screenScrolled(int screenCenter) {
1484        final boolean isRtl = isLayoutRtl();
1485        super.screenScrolled(screenCenter);
1486
1487        updatePageAlphaValues(screenCenter);
1488        updateStateForCustomContent(screenCenter);
1489        enableHwLayersOnVisiblePages();
1490
1491        boolean shouldOverScroll = (mOverScrollX < 0 && (!hasCustomContent() || isLayoutRtl())) ||
1492                (mOverScrollX > mMaxScrollX && (!hasCustomContent() || !isLayoutRtl()));
1493
1494        if (shouldOverScroll) {
1495            int index = 0;
1496            float pivotX = 0f;
1497            final float leftBiasedPivot = 0.25f;
1498            final float rightBiasedPivot = 0.75f;
1499            final int lowerIndex = 0;
1500            final int upperIndex = getChildCount() - 1;
1501
1502            final boolean isLeftPage = mOverScrollX < 0;
1503            index = (!isRtl && isLeftPage) || (isRtl && !isLeftPage) ? lowerIndex : upperIndex;
1504            pivotX = isLeftPage ? rightBiasedPivot : leftBiasedPivot;
1505
1506            CellLayout cl = (CellLayout) getChildAt(index);
1507            float scrollProgress = getScrollProgress(screenCenter, cl, index);
1508            cl.setOverScrollAmount(Math.abs(scrollProgress), isLeftPage);
1509            float rotation = -WORKSPACE_OVERSCROLL_ROTATION * scrollProgress;
1510            cl.setRotationY(rotation);
1511
1512            if (!mOverscrollTransformsSet || Float.compare(mLastOverscrollPivotX, pivotX) != 0) {
1513                mOverscrollTransformsSet = true;
1514                mLastOverscrollPivotX = pivotX;
1515                cl.setCameraDistance(mDensity * mCameraDistance);
1516                cl.setPivotX(cl.getMeasuredWidth() * pivotX);
1517                cl.setPivotY(cl.getMeasuredHeight() * 0.5f);
1518                cl.setOverscrollTransformsDirty(true);
1519            }
1520        } else {
1521            if (mOverscrollTransformsSet) {
1522                mOverscrollTransformsSet = false;
1523                ((CellLayout) getChildAt(0)).resetOverscrollTransforms();
1524                ((CellLayout) getChildAt(getChildCount() - 1)).resetOverscrollTransforms();
1525            }
1526        }
1527    }
1528
1529    @Override
1530    protected void overScroll(float amount) {
1531        acceleratedOverScroll(amount);
1532    }
1533
1534    protected void onAttachedToWindow() {
1535        super.onAttachedToWindow();
1536        mWindowToken = getWindowToken();
1537        computeScroll();
1538        mDragController.setWindowToken(mWindowToken);
1539    }
1540
1541    protected void onDetachedFromWindow() {
1542        super.onDetachedFromWindow();
1543        mWindowToken = null;
1544    }
1545
1546    protected void onResume() {
1547        if (getPageIndicator() != null) {
1548            // In case accessibility state has changed, we need to perform this on every
1549            // attach to window
1550            OnClickListener listener = getPageIndicatorClickListener();
1551            if (listener != null) {
1552                getPageIndicator().setOnClickListener(listener);
1553            }
1554        }
1555        AccessibilityManager am = (AccessibilityManager)
1556                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
1557        sAccessibilityEnabled = am.isEnabled();
1558    }
1559
1560    @Override
1561    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1562        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
1563            mWallpaperOffset.syncWithScroll();
1564            mWallpaperOffset.jumpToFinal();
1565        }
1566        super.onLayout(changed, left, top, right, bottom);
1567    }
1568
1569    @Override
1570    protected void onDraw(Canvas canvas) {
1571        // Draw the background gradient if necessary
1572        if (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground) {
1573            int alpha = (int) (mBackgroundAlpha * 255);
1574            mBackground.setAlpha(alpha);
1575            mBackground.setBounds(getScrollX(), 0, getScrollX() + getMeasuredWidth(),
1576                    getMeasuredHeight());
1577            mBackground.draw(canvas);
1578        }
1579
1580        super.onDraw(canvas);
1581
1582        // Call back to LauncherModel to finish binding after the first draw
1583        post(mBindPages);
1584    }
1585
1586    boolean isDrawingBackgroundGradient() {
1587        return (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground);
1588    }
1589
1590    @Override
1591    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1592        if (!mLauncher.isAllAppsVisible()) {
1593            final Folder openFolder = getOpenFolder();
1594            if (openFolder != null) {
1595                return openFolder.requestFocus(direction, previouslyFocusedRect);
1596            } else {
1597                return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
1598            }
1599        }
1600        return false;
1601    }
1602
1603    @Override
1604    public int getDescendantFocusability() {
1605        if (isSmall()) {
1606            return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
1607        }
1608        return super.getDescendantFocusability();
1609    }
1610
1611    @Override
1612    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1613        if (!mLauncher.isAllAppsVisible()) {
1614            final Folder openFolder = getOpenFolder();
1615            if (openFolder != null) {
1616                openFolder.addFocusables(views, direction);
1617            } else {
1618                super.addFocusables(views, direction, focusableMode);
1619            }
1620        }
1621    }
1622
1623    public boolean isSmall() {
1624        return mState == State.SMALL || mState == State.SPRING_LOADED || mState == State.OVERVIEW;
1625    }
1626
1627    void enableChildrenCache(int fromPage, int toPage) {
1628        if (fromPage > toPage) {
1629            final int temp = fromPage;
1630            fromPage = toPage;
1631            toPage = temp;
1632        }
1633
1634        final int screenCount = getChildCount();
1635
1636        fromPage = Math.max(fromPage, 0);
1637        toPage = Math.min(toPage, screenCount - 1);
1638
1639        for (int i = fromPage; i <= toPage; i++) {
1640            final CellLayout layout = (CellLayout) getChildAt(i);
1641            layout.setChildrenDrawnWithCacheEnabled(true);
1642            layout.setChildrenDrawingCacheEnabled(true);
1643        }
1644    }
1645
1646    void clearChildrenCache() {
1647        final int screenCount = getChildCount();
1648        for (int i = 0; i < screenCount; i++) {
1649            final CellLayout layout = (CellLayout) getChildAt(i);
1650            layout.setChildrenDrawnWithCacheEnabled(false);
1651            // In software mode, we don't want the items to continue to be drawn into bitmaps
1652            if (!isHardwareAccelerated()) {
1653                layout.setChildrenDrawingCacheEnabled(false);
1654            }
1655        }
1656    }
1657
1658    private void updateChildrenLayersEnabled(boolean force) {
1659        boolean small = mState == State.SMALL || mState == State.OVERVIEW || mIsSwitchingState;
1660        boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving();
1661
1662        if (enableChildrenLayers != mChildrenLayersEnabled) {
1663            mChildrenLayersEnabled = enableChildrenLayers;
1664            if (mChildrenLayersEnabled) {
1665                enableHwLayersOnVisiblePages();
1666            } else {
1667                for (int i = 0; i < getPageCount(); i++) {
1668                    final CellLayout cl = (CellLayout) getChildAt(i);
1669                    cl.enableHardwareLayer(false);
1670                }
1671            }
1672        }
1673    }
1674
1675    private void enableHwLayersOnVisiblePages() {
1676        if (mChildrenLayersEnabled) {
1677            final int screenCount = getChildCount();
1678            getVisiblePages(mTempVisiblePagesRange);
1679            int leftScreen = mTempVisiblePagesRange[0];
1680            int rightScreen = mTempVisiblePagesRange[1];
1681            if (leftScreen == rightScreen) {
1682                // make sure we're caching at least two pages always
1683                if (rightScreen < screenCount - 1) {
1684                    rightScreen++;
1685                } else if (leftScreen > 0) {
1686                    leftScreen--;
1687                }
1688            }
1689
1690            final CellLayout customScreen = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1691            for (int i = 0; i < screenCount; i++) {
1692                final CellLayout layout = (CellLayout) getPageAt(i);
1693
1694                // enable layers between left and right screen inclusive, except for the
1695                // customScreen, which may animate its content during transitions.
1696                boolean enableLayer = layout != customScreen &&
1697                        leftScreen <= i && i <= rightScreen && shouldDrawChild(layout);
1698                layout.enableHardwareLayer(enableLayer);
1699            }
1700        }
1701    }
1702
1703    public void buildPageHardwareLayers() {
1704        // force layers to be enabled just for the call to buildLayer
1705        updateChildrenLayersEnabled(true);
1706        if (getWindowToken() != null) {
1707            final int childCount = getChildCount();
1708            for (int i = 0; i < childCount; i++) {
1709                CellLayout cl = (CellLayout) getChildAt(i);
1710                cl.buildHardwareLayer();
1711            }
1712        }
1713        updateChildrenLayersEnabled(false);
1714    }
1715
1716    protected void onWallpaperTap(MotionEvent ev) {
1717        final int[] position = mTempCell;
1718        getLocationOnScreen(position);
1719
1720        int pointerIndex = ev.getActionIndex();
1721        position[0] += (int) ev.getX(pointerIndex);
1722        position[1] += (int) ev.getY(pointerIndex);
1723
1724        mWallpaperManager.sendWallpaperCommand(getWindowToken(),
1725                ev.getAction() == MotionEvent.ACTION_UP
1726                        ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
1727                position[0], position[1], 0, null);
1728    }
1729
1730    /*
1731     * This interpolator emulates the rate at which the perceived scale of an object changes
1732     * as its distance from a camera increases. When this interpolator is applied to a scale
1733     * animation on a view, it evokes the sense that the object is shrinking due to moving away
1734     * from the camera.
1735     */
1736    static class ZInterpolator implements TimeInterpolator {
1737        private float focalLength;
1738
1739        public ZInterpolator(float foc) {
1740            focalLength = foc;
1741        }
1742
1743        public float getInterpolation(float input) {
1744            return (1.0f - focalLength / (focalLength + input)) /
1745                (1.0f - focalLength / (focalLength + 1.0f));
1746        }
1747    }
1748
1749    /*
1750     * The exact reverse of ZInterpolator.
1751     */
1752    static class InverseZInterpolator implements TimeInterpolator {
1753        private ZInterpolator zInterpolator;
1754        public InverseZInterpolator(float foc) {
1755            zInterpolator = new ZInterpolator(foc);
1756        }
1757        public float getInterpolation(float input) {
1758            return 1 - zInterpolator.getInterpolation(1 - input);
1759        }
1760    }
1761
1762    /*
1763     * ZInterpolator compounded with an ease-out.
1764     */
1765    static class ZoomOutInterpolator implements TimeInterpolator {
1766        private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75f);
1767        private final ZInterpolator zInterpolator = new ZInterpolator(0.13f);
1768
1769        public float getInterpolation(float input) {
1770            return decelerate.getInterpolation(zInterpolator.getInterpolation(input));
1771        }
1772    }
1773
1774    /*
1775     * InvereZInterpolator compounded with an ease-out.
1776     */
1777    static class ZoomInInterpolator implements TimeInterpolator {
1778        private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
1779        private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
1780
1781        public float getInterpolation(float input) {
1782            return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
1783        }
1784    }
1785
1786    private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
1787
1788    /*
1789    *
1790    * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we
1791    * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
1792    *
1793    * These methods mark the appropriate pages as accepting drops (which alters their visual
1794    * appearance).
1795    *
1796    */
1797    public void onDragStartedWithItem(View v) {
1798        final Canvas canvas = new Canvas();
1799
1800        // The outline is used to visualize where the item will land if dropped
1801        mDragOutline = createDragOutline(v, canvas, DRAG_BITMAP_PADDING);
1802    }
1803
1804    public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
1805        final Canvas canvas = new Canvas();
1806
1807        int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
1808
1809        // The outline is used to visualize where the item will land if dropped
1810        mDragOutline = createDragOutline(b, canvas, DRAG_BITMAP_PADDING, size[0],
1811                size[1], clipAlpha);
1812    }
1813
1814    public void exitWidgetResizeMode() {
1815        DragLayer dragLayer = mLauncher.getDragLayer();
1816        dragLayer.clearAllResizeFrames();
1817    }
1818
1819    private void initAnimationArrays() {
1820        final int childCount = getChildCount();
1821        if (mLastChildCount == childCount) return;
1822
1823        mOldBackgroundAlphas = new float[childCount];
1824        mOldAlphas = new float[childCount];
1825        mNewBackgroundAlphas = new float[childCount];
1826        mNewAlphas = new float[childCount];
1827    }
1828
1829    Animator getChangeStateAnimation(final State state, boolean animated) {
1830        return getChangeStateAnimation(state, animated, 0, -1);
1831    }
1832
1833    @Override
1834    protected void getOverviewModePages(int[] range) {
1835        int start = numCustomPages();
1836        int end = getChildCount() - 1;
1837
1838        range[0] = Math.max(0, Math.min(start, getChildCount() - 1));
1839        range[1] = Math.max(0,  end);
1840     }
1841
1842    protected void onStartReordering() {
1843        super.onStartReordering();
1844        showOutlines();
1845        // Reordering handles its own animations, disable the automatic ones.
1846        disableLayoutTransitions();
1847    }
1848
1849    protected void onEndReordering() {
1850        super.onEndReordering();
1851
1852        hideOutlines();
1853        mScreenOrder.clear();
1854        int count = getChildCount();
1855        for (int i = 0; i < count; i++) {
1856            CellLayout cl = ((CellLayout) getChildAt(i));
1857            mScreenOrder.add(getIdForScreen(cl));
1858        }
1859
1860        mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
1861
1862        // Re-enable auto layout transitions for page deletion.
1863        enableLayoutTransitions();
1864    }
1865
1866    public boolean isInOverviewMode() {
1867        return mState == State.OVERVIEW;
1868    }
1869
1870    public boolean enterOverviewMode() {
1871        if (mTouchState != TOUCH_STATE_REST) {
1872            return false;
1873        }
1874        enableOverviewMode(true, -1, true);
1875        return true;
1876    }
1877
1878    public void exitOverviewMode(boolean animated) {
1879        exitOverviewMode(-1, animated);
1880    }
1881
1882    public void exitOverviewMode(int snapPage, boolean animated) {
1883        enableOverviewMode(false, snapPage, animated);
1884    }
1885
1886    private void enableOverviewMode(boolean enable, int snapPage, boolean animated) {
1887        State finalState = Workspace.State.OVERVIEW;
1888        if (!enable) {
1889            finalState = Workspace.State.NORMAL;
1890        }
1891
1892        Animator workspaceAnim = getChangeStateAnimation(finalState, animated, 0, snapPage);
1893        if (workspaceAnim != null) {
1894            onTransitionPrepare();
1895            workspaceAnim.addListener(new AnimatorListenerAdapter() {
1896                @Override
1897                public void onAnimationEnd(Animator arg0) {
1898                    onTransitionEnd();
1899                }
1900            });
1901            workspaceAnim.start();
1902        }
1903    }
1904
1905    int getOverviewModeTranslationY() {
1906        int childHeight = getNormalChildHeight();
1907        int viewPortHeight = getViewportHeight();
1908        int scaledChildHeight = (int) (mOverviewModeShrinkFactor * childHeight);
1909
1910        int offset = (viewPortHeight - scaledChildHeight) / 2;
1911        int offsetDelta = mOverviewModePageOffset - offset + mInsets.top;
1912
1913        return offsetDelta;
1914    }
1915
1916    boolean shouldVoiceButtonProxyBeVisible() {
1917        if (isOnOrMovingToCustomContent()) {
1918            return false;
1919        }
1920        if (mState != State.NORMAL) {
1921            return false;
1922        }
1923        return true;
1924    }
1925
1926    public void updateInteractionForState() {
1927        if (mState != State.NORMAL) {
1928            mLauncher.onInteractionBegin();
1929        } else {
1930            mLauncher.onInteractionEnd();
1931        }
1932    }
1933
1934    private void setState(State state) {
1935        mState = state;
1936        updateInteractionForState();
1937        updateAccessibilityFlags();
1938    }
1939
1940    private void updateAccessibilityFlags() {
1941        int accessible = mState == State.NORMAL ?
1942                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES :
1943                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
1944        setImportantForAccessibility(accessible);
1945    }
1946
1947    Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage) {
1948        if (mState == state) {
1949            return null;
1950        }
1951
1952        // Initialize animation arrays for the first time if necessary
1953        initAnimationArrays();
1954
1955        AnimatorSet anim = animated ? LauncherAnimUtils.createAnimatorSet() : null;
1956
1957        final State oldState = mState;
1958        final boolean oldStateIsNormal = (oldState == State.NORMAL);
1959        final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED);
1960        final boolean oldStateIsSmall = (oldState == State.SMALL);
1961        final boolean oldStateIsOverview = (oldState == State.OVERVIEW);
1962        setState(state);
1963        final boolean stateIsNormal = (state == State.NORMAL);
1964        final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED);
1965        final boolean stateIsSmall = (state == State.SMALL);
1966        final boolean stateIsOverview = (state == State.OVERVIEW);
1967        float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0f : 0f;
1968        float finalHotseatAndPageIndicatorAlpha = (stateIsOverview || stateIsSmall) ? 0f : 1f;
1969        float finalOverviewPanelAlpha = stateIsOverview ? 1f : 0f;
1970        float finalSearchBarAlpha = !stateIsNormal ? 0f : 1f;
1971        float finalWorkspaceTranslationY = stateIsOverview ? getOverviewModeTranslationY() : 0;
1972
1973        boolean workspaceToAllApps = (oldStateIsNormal && stateIsSmall);
1974        boolean allAppsToWorkspace = (oldStateIsSmall && stateIsNormal);
1975        mNewScale = 1.0f;
1976
1977        if (oldStateIsOverview) {
1978            disableFreeScroll(snapPage);
1979        } else if (stateIsOverview) {
1980            enableFreeScroll();
1981        }
1982
1983        if (state != State.NORMAL) {
1984            if (stateIsSpringLoaded) {
1985                mNewScale = mSpringLoadedShrinkFactor;
1986            } else if (stateIsOverview) {
1987                mNewScale = mOverviewModeShrinkFactor;
1988            } else if (stateIsSmall){
1989                mNewScale = mOverviewModeShrinkFactor - 0.3f;
1990            }
1991            if (workspaceToAllApps) {
1992                updateChildrenLayersEnabled(false);
1993            }
1994        }
1995
1996        final int duration = workspaceToAllApps ?
1997                getResources().getInteger(R.integer.config_workspaceUnshrinkTime) :
1998                getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
1999        for (int i = 0; i < getChildCount(); i++) {
2000            final CellLayout cl = (CellLayout) getChildAt(i);
2001            boolean isCurrentPage = (i == getNextPage());
2002            float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
2003            float finalAlpha = stateIsSmall ? 0f : 1f;
2004
2005            // If we are animating to/from the small state, then hide the side pages and fade the
2006            // current page in
2007            if (!mIsSwitchingState) {
2008                if (workspaceToAllApps || allAppsToWorkspace) {
2009                    if (allAppsToWorkspace && isCurrentPage) {
2010                        initialAlpha = 0f;
2011                    } else if (!isCurrentPage) {
2012                        initialAlpha = finalAlpha = 0f;
2013                    }
2014                    cl.setShortcutAndWidgetAlpha(initialAlpha);
2015                }
2016            }
2017
2018            mOldAlphas[i] = initialAlpha;
2019            mNewAlphas[i] = finalAlpha;
2020            if (animated) {
2021                mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
2022                mNewBackgroundAlphas[i] = finalBackgroundAlpha;
2023            } else {
2024                cl.setBackgroundAlpha(finalBackgroundAlpha);
2025                cl.setShortcutAndWidgetAlpha(finalAlpha);
2026            }
2027        }
2028
2029        final View searchBar = mLauncher.getQsbBar();
2030        final View overviewPanel = mLauncher.getOverviewPanel();
2031        final View hotseat = mLauncher.getHotseat();
2032        if (animated) {
2033            LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this);
2034            scale.scaleX(mNewScale)
2035                .scaleY(mNewScale)
2036                .translationY(finalWorkspaceTranslationY)
2037                .setInterpolator(mZoomInInterpolator);
2038            anim.play(scale);
2039            for (int index = 0; index < getChildCount(); index++) {
2040                final int i = index;
2041                final CellLayout cl = (CellLayout) getChildAt(i);
2042                float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
2043                if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) {
2044                    cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
2045                    cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
2046                } else {
2047                    if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
2048                        LauncherViewPropertyAnimator alphaAnim =
2049                            new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
2050                        alphaAnim.alpha(mNewAlphas[i])
2051                            .setDuration(duration)
2052                            .setInterpolator(mZoomInInterpolator);
2053                        anim.play(alphaAnim);
2054                    }
2055                    if (mOldBackgroundAlphas[i] != 0 ||
2056                        mNewBackgroundAlphas[i] != 0) {
2057                        ValueAnimator bgAnim =
2058                                LauncherAnimUtils.ofFloat(cl, 0f, 1f).setDuration(duration);
2059                        bgAnim.setInterpolator(mZoomInInterpolator);
2060                        bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
2061                                public void onAnimationUpdate(float a, float b) {
2062                                    cl.setBackgroundAlpha(
2063                                            a * mOldBackgroundAlphas[i] +
2064                                            b * mNewBackgroundAlphas[i]);
2065                                }
2066                            });
2067                        anim.play(bgAnim);
2068                    }
2069                }
2070            }
2071            ObjectAnimator pageIndicatorAlpha = null;
2072            if (getPageIndicator() != null) {
2073                pageIndicatorAlpha = ObjectAnimator.ofFloat(getPageIndicator(), "alpha",
2074                        finalHotseatAndPageIndicatorAlpha);
2075            }
2076            ObjectAnimator hotseatAlpha = ObjectAnimator.ofFloat(hotseat, "alpha",
2077                    finalHotseatAndPageIndicatorAlpha);
2078            ObjectAnimator searchBarAlpha = ObjectAnimator.ofFloat(searchBar,
2079                    "alpha", finalSearchBarAlpha);
2080            ObjectAnimator overviewPanelAlpha = ObjectAnimator.ofFloat(overviewPanel,
2081                    "alpha", finalOverviewPanelAlpha);
2082
2083            overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel));
2084            hotseatAlpha.addListener(new AlphaUpdateListener(hotseat));
2085            searchBarAlpha.addListener(new AlphaUpdateListener(searchBar));
2086
2087            if (getPageIndicator() != null) {
2088                pageIndicatorAlpha.addListener(new AlphaUpdateListener(getPageIndicator()));
2089            }
2090
2091            anim.play(overviewPanelAlpha);
2092            anim.play(hotseatAlpha);
2093            anim.play(searchBarAlpha);
2094            anim.play(pageIndicatorAlpha);
2095            anim.setStartDelay(delay);
2096        } else {
2097            overviewPanel.setAlpha(finalOverviewPanelAlpha);
2098            AlphaUpdateListener.updateVisibility(overviewPanel);
2099            hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha);
2100            AlphaUpdateListener.updateVisibility(hotseat);
2101            if (getPageIndicator() != null) {
2102                getPageIndicator().setAlpha(finalHotseatAndPageIndicatorAlpha);
2103                AlphaUpdateListener.updateVisibility(getPageIndicator());
2104            }
2105            searchBar.setAlpha(finalSearchBarAlpha);
2106            AlphaUpdateListener.updateVisibility(searchBar);
2107            updateCustomContentVisibility();
2108            setScaleX(mNewScale);
2109            setScaleY(mNewScale);
2110            setTranslationY(finalWorkspaceTranslationY);
2111        }
2112        mLauncher.updateVoiceButtonProxyVisible(false);
2113
2114        if (stateIsSpringLoaded) {
2115            // Right now we're covered by Apps Customize
2116            // Show the background gradient immediately, so the gradient will
2117            // be showing once AppsCustomize disappears
2118            animateBackgroundGradient(getResources().getInteger(
2119                    R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, false);
2120        } else if (stateIsOverview) {
2121            animateBackgroundGradient(getResources().getInteger(
2122                    R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, true);
2123        } else {
2124            // Fade the background gradient away
2125            animateBackgroundGradient(0f, animated);
2126        }
2127        return anim;
2128    }
2129
2130    static class AlphaUpdateListener implements AnimatorUpdateListener, AnimatorListener {
2131        View view;
2132        public AlphaUpdateListener(View v) {
2133            view = v;
2134        }
2135
2136        @Override
2137        public void onAnimationUpdate(ValueAnimator arg0) {
2138            updateVisibility(view);
2139        }
2140
2141        public static void updateVisibility(View view) {
2142            // We want to avoid the extra layout pass by setting the views to GONE unless
2143            // accessibility is on, in which case not setting them to GONE causes a glitch.
2144            int invisibleState = sAccessibilityEnabled ? GONE : INVISIBLE;
2145            if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != invisibleState) {
2146                view.setVisibility(invisibleState);
2147            } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
2148                    && view.getVisibility() != VISIBLE) {
2149                view.setVisibility(VISIBLE);
2150            }
2151        }
2152
2153        @Override
2154        public void onAnimationCancel(Animator arg0) {
2155        }
2156
2157        @Override
2158        public void onAnimationEnd(Animator arg0) {
2159            updateVisibility(view);
2160        }
2161
2162        @Override
2163        public void onAnimationRepeat(Animator arg0) {
2164        }
2165
2166        @Override
2167        public void onAnimationStart(Animator arg0) {
2168            // We want the views to be visible for animation, so fade-in/out is visible
2169            view.setVisibility(VISIBLE);
2170        }
2171    }
2172
2173    @Override
2174    public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
2175        onTransitionPrepare();
2176    }
2177
2178    @Override
2179    public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
2180    }
2181
2182    @Override
2183    public void onLauncherTransitionStep(Launcher l, float t) {
2184        mTransitionProgress = t;
2185    }
2186
2187    @Override
2188    public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
2189        onTransitionEnd();
2190    }
2191
2192    private void onTransitionPrepare() {
2193        mIsSwitchingState = true;
2194
2195        // Invalidate here to ensure that the pages are rendered during the state change transition.
2196        invalidate();
2197
2198        updateChildrenLayersEnabled(false);
2199        hideCustomContentIfNecessary();
2200    }
2201
2202    void updateCustomContentVisibility() {
2203        int visibility = mState == Workspace.State.NORMAL ? VISIBLE : INVISIBLE;
2204        if (hasCustomContent()) {
2205            mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(visibility);
2206        }
2207    }
2208
2209    void showCustomContentIfNecessary() {
2210        boolean show  = mState == Workspace.State.NORMAL;
2211        if (show && hasCustomContent()) {
2212            mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(VISIBLE);
2213        }
2214    }
2215
2216    void hideCustomContentIfNecessary() {
2217        boolean hide  = mState != Workspace.State.NORMAL;
2218        if (hide && hasCustomContent()) {
2219            mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE);
2220        }
2221    }
2222
2223    private void onTransitionEnd() {
2224        mIsSwitchingState = false;
2225        updateChildrenLayersEnabled(false);
2226        // The code in getChangeStateAnimation to determine initialAlpha and finalAlpha will ensure
2227        // ensure that only the current page is visible during (and subsequently, after) the
2228        // transition animation.  If fade adjacent pages is disabled, then re-enable the page
2229        // visibility after the transition animation.
2230        if (!mWorkspaceFadeInAdjacentScreens) {
2231            for (int i = 0; i < getChildCount(); i++) {
2232                final CellLayout cl = (CellLayout) getChildAt(i);
2233                cl.setShortcutAndWidgetAlpha(1f);
2234            }
2235        }
2236        showCustomContentIfNecessary();
2237    }
2238
2239    @Override
2240    public View getContent() {
2241        return this;
2242    }
2243
2244    /**
2245     * Draw the View v into the given Canvas.
2246     *
2247     * @param v the view to draw
2248     * @param destCanvas the canvas to draw on
2249     * @param padding the horizontal and vertical padding to use when drawing
2250     */
2251    private void drawDragView(View v, Canvas destCanvas, int padding, boolean pruneToDrawable) {
2252        final Rect clipRect = mTempRect;
2253        v.getDrawingRect(clipRect);
2254
2255        boolean textVisible = false;
2256
2257        destCanvas.save();
2258        if (v instanceof TextView && pruneToDrawable) {
2259            Drawable d = ((TextView) v).getCompoundDrawables()[1];
2260            clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding);
2261            destCanvas.translate(padding / 2, padding / 2);
2262            d.draw(destCanvas);
2263        } else {
2264            if (v instanceof FolderIcon) {
2265                // For FolderIcons the text can bleed into the icon area, and so we need to
2266                // hide the text completely (which can't be achieved by clipping).
2267                if (((FolderIcon) v).getTextVisible()) {
2268                    ((FolderIcon) v).setTextVisible(false);
2269                    textVisible = true;
2270                }
2271            } else if (v instanceof BubbleTextView) {
2272                final BubbleTextView tv = (BubbleTextView) v;
2273                clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V +
2274                        tv.getLayout().getLineTop(0);
2275            } else if (v instanceof TextView) {
2276                final TextView tv = (TextView) v;
2277                clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() +
2278                        tv.getLayout().getLineTop(0);
2279            }
2280            destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
2281            destCanvas.clipRect(clipRect, Op.REPLACE);
2282            v.draw(destCanvas);
2283
2284            // Restore text visibility of FolderIcon if necessary
2285            if (textVisible) {
2286                ((FolderIcon) v).setTextVisible(true);
2287            }
2288        }
2289        destCanvas.restore();
2290    }
2291
2292    /**
2293     * Returns a new bitmap to show when the given View is being dragged around.
2294     * Responsibility for the bitmap is transferred to the caller.
2295     */
2296    public Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
2297        Bitmap b;
2298
2299        if (v instanceof TextView) {
2300            Drawable d = ((TextView) v).getCompoundDrawables()[1];
2301            b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding,
2302                    d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888);
2303        } else {
2304            b = Bitmap.createBitmap(
2305                    v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
2306        }
2307
2308        canvas.setBitmap(b);
2309        drawDragView(v, canvas, padding, true);
2310        canvas.setBitmap(null);
2311
2312        return b;
2313    }
2314
2315    /**
2316     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2317     * Responsibility for the bitmap is transferred to the caller.
2318     */
2319    private Bitmap createDragOutline(View v, Canvas canvas, int padding) {
2320        final int outlineColor = getResources().getColor(R.color.outline_color);
2321        final Bitmap b = Bitmap.createBitmap(
2322                v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
2323
2324        canvas.setBitmap(b);
2325        drawDragView(v, canvas, padding, true);
2326        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
2327        canvas.setBitmap(null);
2328        return b;
2329    }
2330
2331    /**
2332     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2333     * Responsibility for the bitmap is transferred to the caller.
2334     */
2335    private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding, int w, int h,
2336            boolean clipAlpha) {
2337        final int outlineColor = getResources().getColor(R.color.outline_color);
2338        final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
2339        canvas.setBitmap(b);
2340
2341        Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
2342        float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(),
2343                (h - padding) / (float) orig.getHeight());
2344        int scaledWidth = (int) (scaleFactor * orig.getWidth());
2345        int scaledHeight = (int) (scaleFactor * orig.getHeight());
2346        Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
2347
2348        // center the image
2349        dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
2350
2351        canvas.drawBitmap(orig, src, dst, null);
2352        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor,
2353                clipAlpha);
2354        canvas.setBitmap(null);
2355
2356        return b;
2357    }
2358
2359    void startDrag(CellLayout.CellInfo cellInfo) {
2360        View child = cellInfo.cell;
2361
2362        // Make sure the drag was started by a long press as opposed to a long click.
2363        if (!child.isInTouchMode()) {
2364            return;
2365        }
2366
2367        mDragInfo = cellInfo;
2368        child.setVisibility(INVISIBLE);
2369        CellLayout layout = (CellLayout) child.getParent().getParent();
2370        layout.prepareChildForDrag(child);
2371
2372        child.clearFocus();
2373        child.setPressed(false);
2374
2375        final Canvas canvas = new Canvas();
2376
2377        // The outline is used to visualize where the item will land if dropped
2378        mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING);
2379        beginDragShared(child, this);
2380    }
2381
2382    public void beginDragShared(View child, DragSource source) {
2383        // The drag bitmap follows the touch point around on the screen
2384        final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING);
2385
2386        final int bmpWidth = b.getWidth();
2387        final int bmpHeight = b.getHeight();
2388
2389        float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
2390        int dragLayerX =
2391                Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
2392        int dragLayerY =
2393                Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2
2394                        - DRAG_BITMAP_PADDING / 2);
2395
2396        LauncherAppState app = LauncherAppState.getInstance();
2397        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2398        Point dragVisualizeOffset = null;
2399        Rect dragRect = null;
2400        if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
2401            int iconSize = grid.iconSizePx;
2402            int top = child.getPaddingTop();
2403            int left = (bmpWidth - iconSize) / 2;
2404            int right = left + iconSize;
2405            int bottom = top + iconSize;
2406            dragLayerY += top;
2407            // Note: The drag region is used to calculate drag layer offsets, but the
2408            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2409            dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2, DRAG_BITMAP_PADDING / 2);
2410            dragRect = new Rect(left, top, right, bottom);
2411        } else if (child instanceof FolderIcon) {
2412            int previewSize = grid.folderIconSizePx;
2413            dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
2414        }
2415
2416        // Clear the pressed state if necessary
2417        if (child instanceof BubbleTextView) {
2418            BubbleTextView icon = (BubbleTextView) child;
2419            icon.clearPressedOrFocusedBackground();
2420        }
2421
2422        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
2423                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
2424
2425        if (child.getParent() instanceof ShortcutAndWidgetContainer) {
2426            mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
2427        }
2428
2429        b.recycle();
2430    }
2431
2432    void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, long screenId,
2433            int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
2434        View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
2435
2436        final int[] cellXY = new int[2];
2437        target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
2438        addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
2439
2440        LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, cellXY[0],
2441                cellXY[1]);
2442    }
2443
2444    public boolean transitionStateShouldAllowDrop() {
2445        return ((!isSwitchingState() || mTransitionProgress > 0.5f) && mState != State.SMALL);
2446    }
2447
2448    /**
2449     * {@inheritDoc}
2450     */
2451    public boolean acceptDrop(DragObject d) {
2452        // If it's an external drop (e.g. from All Apps), check if it should be accepted
2453        CellLayout dropTargetLayout = mDropToLayout;
2454        if (d.dragSource != this) {
2455            // Don't accept the drop if we're not over a screen at time of drop
2456            if (dropTargetLayout == null) {
2457                return false;
2458            }
2459            if (!transitionStateShouldAllowDrop()) return false;
2460
2461            mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
2462                    d.dragView, mDragViewVisualCenter);
2463
2464            // We want the point to be mapped to the dragTarget.
2465            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2466                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2467            } else {
2468                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2469            }
2470
2471            int spanX = 1;
2472            int spanY = 1;
2473            if (mDragInfo != null) {
2474                final CellLayout.CellInfo dragCellInfo = mDragInfo;
2475                spanX = dragCellInfo.spanX;
2476                spanY = dragCellInfo.spanY;
2477            } else {
2478                final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
2479                spanX = dragInfo.spanX;
2480                spanY = dragInfo.spanY;
2481            }
2482
2483            int minSpanX = spanX;
2484            int minSpanY = spanY;
2485            if (d.dragInfo instanceof PendingAddWidgetInfo) {
2486                minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
2487                minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
2488            }
2489
2490            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2491                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
2492                    mTargetCell);
2493            float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2494                    mDragViewVisualCenter[1], mTargetCell);
2495            if (willCreateUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
2496                    mTargetCell, distance, true)) {
2497                return true;
2498            }
2499            if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
2500                    mTargetCell, distance)) {
2501                return true;
2502            }
2503
2504            int[] resultSpan = new int[2];
2505            mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
2506                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2507                    null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
2508            boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2509
2510            // Don't accept the drop if there's no room for the item
2511            if (!foundCell) {
2512                // Don't show the message if we are dropping on the AllApps button and the hotseat
2513                // is full
2514                boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2515                if (mTargetCell != null && isHotseat) {
2516                    Hotseat hotseat = mLauncher.getHotseat();
2517                    if (hotseat.isAllAppsButtonRank(
2518                            hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
2519                        return false;
2520                    }
2521                }
2522
2523                mLauncher.showOutOfSpaceMessage(isHotseat);
2524                return false;
2525            }
2526        }
2527
2528        long screenId = getIdForScreen(dropTargetLayout);
2529        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
2530            commitExtraEmptyScreen();
2531        }
2532
2533        return true;
2534    }
2535
2536    boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float
2537            distance, boolean considerTimeout) {
2538        if (distance > mMaxDistanceForFolderCreation) return false;
2539        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2540
2541        if (dropOverView != null) {
2542            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2543            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2544                return false;
2545            }
2546        }
2547
2548        boolean hasntMoved = false;
2549        if (mDragInfo != null) {
2550            hasntMoved = dropOverView == mDragInfo.cell;
2551        }
2552
2553        if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
2554            return false;
2555        }
2556
2557        boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
2558        boolean willBecomeShortcut =
2559                (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
2560                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
2561
2562        return (aboveShortcut && willBecomeShortcut);
2563    }
2564
2565    boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
2566            float distance) {
2567        if (distance > mMaxDistanceForFolderCreation) return false;
2568        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2569
2570        if (dropOverView != null) {
2571            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2572            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2573                return false;
2574            }
2575        }
2576
2577        if (dropOverView instanceof FolderIcon) {
2578            FolderIcon fi = (FolderIcon) dropOverView;
2579            if (fi.acceptDrop(dragInfo)) {
2580                return true;
2581            }
2582        }
2583        return false;
2584    }
2585
2586    boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
2587            int[] targetCell, float distance, boolean external, DragView dragView,
2588            Runnable postAnimationRunnable) {
2589        if (distance > mMaxDistanceForFolderCreation) return false;
2590        View v = target.getChildAt(targetCell[0], targetCell[1]);
2591
2592        boolean hasntMoved = false;
2593        if (mDragInfo != null) {
2594            CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
2595            hasntMoved = (mDragInfo.cellX == targetCell[0] &&
2596                    mDragInfo.cellY == targetCell[1]) && (cellParent == target);
2597        }
2598
2599        if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
2600        mCreateUserFolderOnDrop = false;
2601        final long screenId = (targetCell == null) ? mDragInfo.screenId : getIdForScreen(target);
2602
2603        boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
2604        boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
2605
2606        if (aboveShortcut && willBecomeShortcut) {
2607            ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
2608            ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
2609            // if the drag started here, we need to remove it from the workspace
2610            if (!external) {
2611                getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2612            }
2613
2614            Rect folderLocation = new Rect();
2615            float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
2616            target.removeView(v);
2617
2618            FolderIcon fi =
2619                mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
2620            destInfo.cellX = -1;
2621            destInfo.cellY = -1;
2622            sourceInfo.cellX = -1;
2623            sourceInfo.cellY = -1;
2624
2625            // If the dragView is null, we can't animate
2626            boolean animate = dragView != null;
2627            if (animate) {
2628                fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
2629                        postAnimationRunnable);
2630            } else {
2631                fi.addItem(destInfo);
2632                fi.addItem(sourceInfo);
2633            }
2634            return true;
2635        }
2636        return false;
2637    }
2638
2639    boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
2640            float distance, DragObject d, boolean external) {
2641        if (distance > mMaxDistanceForFolderCreation) return false;
2642
2643        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2644        if (!mAddToExistingFolderOnDrop) return false;
2645        mAddToExistingFolderOnDrop = false;
2646
2647        if (dropOverView instanceof FolderIcon) {
2648            FolderIcon fi = (FolderIcon) dropOverView;
2649            if (fi.acceptDrop(d.dragInfo)) {
2650                fi.onDrop(d);
2651
2652                // if the drag started here, we need to remove it from the workspace
2653                if (!external) {
2654                    getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2655                }
2656                return true;
2657            }
2658        }
2659        return false;
2660    }
2661
2662    public void onDrop(final DragObject d) {
2663        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
2664                mDragViewVisualCenter);
2665
2666        CellLayout dropTargetLayout = mDropToLayout;
2667
2668        // We want the point to be mapped to the dragTarget.
2669        if (dropTargetLayout != null) {
2670            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2671                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2672            } else {
2673                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2674            }
2675        }
2676
2677        int snapScreen = -1;
2678        boolean resizeOnDrop = false;
2679        if (d.dragSource != this) {
2680            final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
2681                    (int) mDragViewVisualCenter[1] };
2682            onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
2683        } else if (mDragInfo != null) {
2684            final View cell = mDragInfo.cell;
2685
2686            Runnable resizeRunnable = null;
2687            if (dropTargetLayout != null && !d.cancelled) {
2688                // Move internally
2689                boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
2690                boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2691                long container = hasMovedIntoHotseat ?
2692                        LauncherSettings.Favorites.CONTAINER_HOTSEAT :
2693                        LauncherSettings.Favorites.CONTAINER_DESKTOP;
2694                long screenId = (mTargetCell[0] < 0) ?
2695                        mDragInfo.screenId : getIdForScreen(dropTargetLayout);
2696                int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
2697                int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
2698                // First we find the cell nearest to point at which the item is
2699                // dropped, without any consideration to whether there is an item there.
2700
2701                mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
2702                        mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
2703                float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2704                        mDragViewVisualCenter[1], mTargetCell);
2705
2706                // If the item being dropped is a shortcut and the nearest drop
2707                // cell also contains a shortcut, then create a folder with the two shortcuts.
2708                if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
2709                        dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
2710                    stripEmptyScreens();
2711                    return;
2712                }
2713
2714                if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
2715                        distance, d, false)) {
2716                    stripEmptyScreens();
2717                    return;
2718                }
2719
2720                // Aside from the special case where we're dropping a shortcut onto a shortcut,
2721                // we need to find the nearest cell location that is vacant
2722                ItemInfo item = (ItemInfo) d.dragInfo;
2723                int minSpanX = item.spanX;
2724                int minSpanY = item.spanY;
2725                if (item.minSpanX > 0 && item.minSpanY > 0) {
2726                    minSpanX = item.minSpanX;
2727                    minSpanY = item.minSpanY;
2728                }
2729
2730                int[] resultSpan = new int[2];
2731                mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
2732                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
2733                        mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
2734
2735                boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2736
2737                // if the widget resizes on drop
2738                if (foundCell && (cell instanceof AppWidgetHostView) &&
2739                        (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
2740                    resizeOnDrop = true;
2741                    item.spanX = resultSpan[0];
2742                    item.spanY = resultSpan[1];
2743                    AppWidgetHostView awhv = (AppWidgetHostView) cell;
2744                    AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
2745                            resultSpan[1]);
2746                }
2747
2748                if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
2749                    snapScreen = getPageIndexForScreenId(screenId);
2750                    snapToPage(snapScreen);
2751                }
2752
2753                if (foundCell) {
2754                    final ItemInfo info = (ItemInfo) cell.getTag();
2755                    if (hasMovedLayouts) {
2756                        // Reparent the view
2757                        getParentCellLayoutForView(cell).removeView(cell);
2758                        addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
2759                                info.spanX, info.spanY);
2760                    }
2761
2762                    // update the item's position after drop
2763                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2764                    lp.cellX = lp.tmpCellX = mTargetCell[0];
2765                    lp.cellY = lp.tmpCellY = mTargetCell[1];
2766                    lp.cellHSpan = item.spanX;
2767                    lp.cellVSpan = item.spanY;
2768                    lp.isLockedToGrid = true;
2769                    cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screenId,
2770                            mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
2771
2772                    if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
2773                            cell instanceof LauncherAppWidgetHostView) {
2774                        final CellLayout cellLayout = dropTargetLayout;
2775                        // We post this call so that the widget has a chance to be placed
2776                        // in its final location
2777
2778                        final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
2779                        AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
2780                        if (pinfo != null &&
2781                                pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
2782                            final Runnable addResizeFrame = new Runnable() {
2783                                public void run() {
2784                                    DragLayer dragLayer = mLauncher.getDragLayer();
2785                                    dragLayer.addResizeFrame(info, hostView, cellLayout);
2786                                }
2787                            };
2788                            resizeRunnable = (new Runnable() {
2789                                public void run() {
2790                                    if (!isPageMoving()) {
2791                                        addResizeFrame.run();
2792                                    } else {
2793                                        mDelayedResizeRunnable = addResizeFrame;
2794                                    }
2795                                }
2796                            });
2797                        }
2798                    }
2799
2800                    LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX,
2801                            lp.cellY, item.spanX, item.spanY);
2802                } else {
2803                    // If we can't find a drop location, we return the item to its original position
2804                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2805                    mTargetCell[0] = lp.cellX;
2806                    mTargetCell[1] = lp.cellY;
2807                    CellLayout layout = (CellLayout) cell.getParent().getParent();
2808                    layout.markCellsAsOccupiedForView(cell);
2809                }
2810            }
2811
2812            final CellLayout parent = (CellLayout) cell.getParent().getParent();
2813            final Runnable finalResizeRunnable = resizeRunnable;
2814            // Prepare it to be animated into its new position
2815            // This must be called after the view has been re-parented
2816            final Runnable onCompleteRunnable = new Runnable() {
2817                @Override
2818                public void run() {
2819                    mAnimatingViewIntoPlace = false;
2820                    updateChildrenLayersEnabled(false);
2821                    if (finalResizeRunnable != null) {
2822                        finalResizeRunnable.run();
2823                    }
2824                    stripEmptyScreens();
2825                }
2826            };
2827            mAnimatingViewIntoPlace = true;
2828            if (d.dragView.hasDrawn()) {
2829                final ItemInfo info = (ItemInfo) cell.getTag();
2830                if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
2831                    int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
2832                            ANIMATE_INTO_POSITION_AND_DISAPPEAR;
2833                    animateWidgetDrop(info, parent, d.dragView,
2834                            onCompleteRunnable, animationType, cell, false);
2835                } else {
2836                    int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
2837                    mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
2838                            onCompleteRunnable, this);
2839                }
2840            } else {
2841                d.deferDragViewCleanupPostAnimation = false;
2842                cell.setVisibility(VISIBLE);
2843            }
2844            parent.onDropChild(cell);
2845        }
2846    }
2847
2848    public void setFinalScrollForPageChange(int pageIndex) {
2849        CellLayout cl = (CellLayout) getChildAt(pageIndex);
2850        if (cl != null) {
2851            mSavedScrollX = getScrollX();
2852            mSavedTranslationX = cl.getTranslationX();
2853            mSavedRotationY = cl.getRotationY();
2854            final int newX = getScrollForPage(pageIndex);
2855            setScrollX(newX);
2856            cl.setTranslationX(0f);
2857            cl.setRotationY(0f);
2858        }
2859    }
2860
2861    public void resetFinalScrollForPageChange(int pageIndex) {
2862        if (pageIndex >= 0) {
2863            CellLayout cl = (CellLayout) getChildAt(pageIndex);
2864            setScrollX(mSavedScrollX);
2865            cl.setTranslationX(mSavedTranslationX);
2866            cl.setRotationY(mSavedRotationY);
2867        }
2868    }
2869
2870    public void getViewLocationRelativeToSelf(View v, int[] location) {
2871        getLocationInWindow(location);
2872        int x = location[0];
2873        int y = location[1];
2874
2875        v.getLocationInWindow(location);
2876        int vX = location[0];
2877        int vY = location[1];
2878
2879        location[0] = vX - x;
2880        location[1] = vY - y;
2881    }
2882
2883    public void onDragEnter(DragObject d) {
2884        mDragEnforcer.onDragEnter();
2885        mCreateUserFolderOnDrop = false;
2886        mAddToExistingFolderOnDrop = false;
2887
2888        mDropToLayout = null;
2889        CellLayout layout = getCurrentDropLayout();
2890        setCurrentDropLayout(layout);
2891        setCurrentDragOverlappingLayout(layout);
2892
2893        // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we
2894        // don't need to show the outlines
2895        if (LauncherAppState.getInstance().isScreenLarge()) {
2896            showOutlines();
2897        }
2898    }
2899
2900    /** Return a rect that has the cellWidth/cellHeight (left, top), and
2901     * widthGap/heightGap (right, bottom) */
2902    static Rect getCellLayoutMetrics(Launcher launcher, int orientation) {
2903        LauncherAppState app = LauncherAppState.getInstance();
2904        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2905
2906        Resources res = launcher.getResources();
2907        Display display = launcher.getWindowManager().getDefaultDisplay();
2908        Point smallestSize = new Point();
2909        Point largestSize = new Point();
2910        display.getCurrentSizeRange(smallestSize, largestSize);
2911        int countX = (int) grid.numColumns;
2912        int countY = (int) grid.numRows;
2913        int constrainedLongEdge = largestSize.y;
2914        int constrainedShortEdge = smallestSize.y;
2915        if (orientation == CellLayout.LANDSCAPE) {
2916            if (mLandscapeCellLayoutMetrics == null) {
2917                Rect padding = grid.getWorkspacePadding(CellLayout.LANDSCAPE);
2918                int width = constrainedLongEdge - padding.left - padding.right;
2919                int height = constrainedShortEdge - padding.top - padding.bottom;
2920                mLandscapeCellLayoutMetrics = new Rect();
2921                mLandscapeCellLayoutMetrics.set(
2922                        grid.calculateCellWidth(width, countX),
2923                        grid.calculateCellHeight(height, countY), 0, 0);
2924            }
2925            return mLandscapeCellLayoutMetrics;
2926        } else if (orientation == CellLayout.PORTRAIT) {
2927            if (mPortraitCellLayoutMetrics == null) {
2928                Rect padding = grid.getWorkspacePadding(CellLayout.PORTRAIT);
2929                int width = constrainedShortEdge - padding.left - padding.right;
2930                int height = constrainedLongEdge - padding.top - padding.bottom;
2931                mPortraitCellLayoutMetrics = new Rect();
2932                mPortraitCellLayoutMetrics.set(
2933                        grid.calculateCellWidth(width, countX),
2934                        grid.calculateCellHeight(height, countY), 0, 0);
2935            }
2936            return mPortraitCellLayoutMetrics;
2937        }
2938        return null;
2939    }
2940
2941    public void onDragExit(DragObject d) {
2942        mDragEnforcer.onDragExit();
2943
2944        // Here we store the final page that will be dropped to, if the workspace in fact
2945        // receives the drop
2946        if (mInScrollArea) {
2947            if (isPageMoving()) {
2948                // If the user drops while the page is scrolling, we should use that page as the
2949                // destination instead of the page that is being hovered over.
2950                mDropToLayout = (CellLayout) getPageAt(getNextPage());
2951            } else {
2952                mDropToLayout = mDragOverlappingLayout;
2953            }
2954        } else {
2955            mDropToLayout = mDragTargetLayout;
2956        }
2957
2958        if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
2959            mCreateUserFolderOnDrop = true;
2960        } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
2961            mAddToExistingFolderOnDrop = true;
2962        }
2963
2964        // Reset the scroll area and previous drag target
2965        onResetScrollArea();
2966        setCurrentDropLayout(null);
2967        setCurrentDragOverlappingLayout(null);
2968
2969        mSpringLoadedDragController.cancel();
2970
2971        if (!mIsPageMoving) {
2972            hideOutlines();
2973        }
2974    }
2975
2976    void setCurrentDropLayout(CellLayout layout) {
2977        if (mDragTargetLayout != null) {
2978            mDragTargetLayout.revertTempState();
2979            mDragTargetLayout.onDragExit();
2980        }
2981        mDragTargetLayout = layout;
2982        if (mDragTargetLayout != null) {
2983            mDragTargetLayout.onDragEnter();
2984        }
2985        cleanupReorder(true);
2986        cleanupFolderCreation();
2987        setCurrentDropOverCell(-1, -1);
2988    }
2989
2990    void setCurrentDragOverlappingLayout(CellLayout layout) {
2991        if (mDragOverlappingLayout != null) {
2992            mDragOverlappingLayout.setIsDragOverlapping(false);
2993        }
2994        mDragOverlappingLayout = layout;
2995        if (mDragOverlappingLayout != null) {
2996            mDragOverlappingLayout.setIsDragOverlapping(true);
2997        }
2998        invalidate();
2999    }
3000
3001    void setCurrentDropOverCell(int x, int y) {
3002        if (x != mDragOverX || y != mDragOverY) {
3003            mDragOverX = x;
3004            mDragOverY = y;
3005            setDragMode(DRAG_MODE_NONE);
3006        }
3007    }
3008
3009    void setDragMode(int dragMode) {
3010        if (dragMode != mDragMode) {
3011            if (dragMode == DRAG_MODE_NONE) {
3012                cleanupAddToFolder();
3013                // We don't want to cancel the re-order alarm every time the target cell changes
3014                // as this feels to slow / unresponsive.
3015                cleanupReorder(false);
3016                cleanupFolderCreation();
3017            } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
3018                cleanupReorder(true);
3019                cleanupFolderCreation();
3020            } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
3021                cleanupAddToFolder();
3022                cleanupReorder(true);
3023            } else if (dragMode == DRAG_MODE_REORDER) {
3024                cleanupAddToFolder();
3025                cleanupFolderCreation();
3026            }
3027            mDragMode = dragMode;
3028        }
3029    }
3030
3031    private void cleanupFolderCreation() {
3032        if (mDragFolderRingAnimator != null) {
3033            mDragFolderRingAnimator.animateToNaturalState();
3034        }
3035        mFolderCreationAlarm.cancelAlarm();
3036    }
3037
3038    private void cleanupAddToFolder() {
3039        if (mDragOverFolderIcon != null) {
3040            mDragOverFolderIcon.onDragExit(null);
3041            mDragOverFolderIcon = null;
3042        }
3043    }
3044
3045    private void cleanupReorder(boolean cancelAlarm) {
3046        // Any pending reorders are canceled
3047        if (cancelAlarm) {
3048            mReorderAlarm.cancelAlarm();
3049        }
3050        mLastReorderX = -1;
3051        mLastReorderY = -1;
3052    }
3053
3054   /*
3055    *
3056    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
3057    * coordinate space. The argument xy is modified with the return result.
3058    *
3059    * if cachedInverseMatrix is not null, this method will just use that matrix instead of
3060    * computing it itself; we use this to avoid redundant matrix inversions in
3061    * findMatchingPageForDragOver
3062    *
3063    */
3064   void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
3065       xy[0] = xy[0] - v.getLeft();
3066       xy[1] = xy[1] - v.getTop();
3067   }
3068
3069   boolean isPointInSelfOverHotseat(int x, int y, Rect r) {
3070       if (r == null) {
3071           r = new Rect();
3072       }
3073       mTempPt[0] = x;
3074       mTempPt[1] = y;
3075       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
3076
3077       LauncherAppState app = LauncherAppState.getInstance();
3078       DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
3079       r = grid.getHotseatRect();
3080       if (r.contains(mTempPt[0], mTempPt[1])) {
3081           return true;
3082       }
3083       return false;
3084   }
3085
3086   void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
3087       mTempPt[0] = (int) xy[0];
3088       mTempPt[1] = (int) xy[1];
3089       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
3090       mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempPt);
3091
3092       xy[0] = mTempPt[0];
3093       xy[1] = mTempPt[1];
3094   }
3095
3096   /*
3097    *
3098    * Convert the 2D coordinate xy from this CellLayout's coordinate space to
3099    * the parent View's coordinate space. The argument xy is modified with the return result.
3100    *
3101    */
3102   void mapPointFromChildToSelf(View v, float[] xy) {
3103       xy[0] += v.getLeft();
3104       xy[1] += v.getTop();
3105   }
3106
3107   static private float squaredDistance(float[] point1, float[] point2) {
3108        float distanceX = point1[0] - point2[0];
3109        float distanceY = point2[1] - point2[1];
3110        return distanceX * distanceX + distanceY * distanceY;
3111   }
3112
3113    /*
3114     *
3115     * This method returns the CellLayout that is currently being dragged to. In order to drag
3116     * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
3117     * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
3118     *
3119     * Return null if no CellLayout is currently being dragged over
3120     *
3121     */
3122    private CellLayout findMatchingPageForDragOver(
3123            DragView dragView, float originX, float originY, boolean exact) {
3124        // We loop through all the screens (ie CellLayouts) and see which ones overlap
3125        // with the item being dragged and then choose the one that's closest to the touch point
3126        final int screenCount = getChildCount();
3127        CellLayout bestMatchingScreen = null;
3128        float smallestDistSoFar = Float.MAX_VALUE;
3129
3130        for (int i = 0; i < screenCount; i++) {
3131            // The custom content screen is not a valid drag over option
3132            if (mScreenOrder.get(i) == CUSTOM_CONTENT_SCREEN_ID) {
3133                continue;
3134            }
3135
3136            CellLayout cl = (CellLayout) getChildAt(i);
3137
3138            final float[] touchXy = {originX, originY};
3139            // Transform the touch coordinates to the CellLayout's local coordinates
3140            // If the touch point is within the bounds of the cell layout, we can return immediately
3141            cl.getMatrix().invert(mTempInverseMatrix);
3142            mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
3143
3144            if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
3145                    touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
3146                return cl;
3147            }
3148
3149            if (!exact) {
3150                // Get the center of the cell layout in screen coordinates
3151                final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
3152                cellLayoutCenter[0] = cl.getWidth()/2;
3153                cellLayoutCenter[1] = cl.getHeight()/2;
3154                mapPointFromChildToSelf(cl, cellLayoutCenter);
3155
3156                touchXy[0] = originX;
3157                touchXy[1] = originY;
3158
3159                // Calculate the distance between the center of the CellLayout
3160                // and the touch point
3161                float dist = squaredDistance(touchXy, cellLayoutCenter);
3162
3163                if (dist < smallestDistSoFar) {
3164                    smallestDistSoFar = dist;
3165                    bestMatchingScreen = cl;
3166                }
3167            }
3168        }
3169        return bestMatchingScreen;
3170    }
3171
3172    // This is used to compute the visual center of the dragView. This point is then
3173    // used to visualize drop locations and determine where to drop an item. The idea is that
3174    // the visual center represents the user's interpretation of where the item is, and hence
3175    // is the appropriate point to use when determining drop location.
3176    private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
3177            DragView dragView, float[] recycle) {
3178        float res[];
3179        if (recycle == null) {
3180            res = new float[2];
3181        } else {
3182            res = recycle;
3183        }
3184
3185        // First off, the drag view has been shifted in a way that is not represented in the
3186        // x and y values or the x/yOffsets. Here we account for that shift.
3187        x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX);
3188        y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
3189
3190        // These represent the visual top and left of drag view if a dragRect was provided.
3191        // If a dragRect was not provided, then they correspond to the actual view left and
3192        // top, as the dragRect is in that case taken to be the entire dragView.
3193        // R.dimen.dragViewOffsetY.
3194        int left = x - xOffset;
3195        int top = y - yOffset;
3196
3197        // In order to find the visual center, we shift by half the dragRect
3198        res[0] = left + dragView.getDragRegion().width() / 2;
3199        res[1] = top + dragView.getDragRegion().height() / 2;
3200
3201        return res;
3202    }
3203
3204    private boolean isDragWidget(DragObject d) {
3205        return (d.dragInfo instanceof LauncherAppWidgetInfo ||
3206                d.dragInfo instanceof PendingAddWidgetInfo);
3207    }
3208    private boolean isExternalDragWidget(DragObject d) {
3209        return d.dragSource != this && isDragWidget(d);
3210    }
3211
3212    public void onDragOver(DragObject d) {
3213        // Skip drag over events while we are dragging over side pages
3214        if (mInScrollArea || mIsSwitchingState || mState == State.SMALL) return;
3215
3216        Rect r = new Rect();
3217        CellLayout layout = null;
3218        ItemInfo item = (ItemInfo) d.dragInfo;
3219
3220        // Ensure that we have proper spans for the item that we are dropping
3221        if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
3222        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
3223            d.dragView, mDragViewVisualCenter);
3224
3225        final View child = (mDragInfo == null) ? null : mDragInfo.cell;
3226        // Identify whether we have dragged over a side page
3227        if (isSmall()) {
3228            if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
3229                if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3230                    layout = mLauncher.getHotseat().getLayout();
3231                }
3232            }
3233            if (layout == null) {
3234                layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false);
3235            }
3236            if (layout != mDragTargetLayout) {
3237                setCurrentDropLayout(layout);
3238                setCurrentDragOverlappingLayout(layout);
3239
3240                boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
3241                if (isInSpringLoadedMode) {
3242                    if (mLauncher.isHotseatLayout(layout)) {
3243                        mSpringLoadedDragController.cancel();
3244                    } else {
3245                        mSpringLoadedDragController.setAlarm(mDragTargetLayout);
3246                    }
3247                }
3248            }
3249        } else {
3250            // Test to see if we are over the hotseat otherwise just use the current page
3251            if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
3252                if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3253                    layout = mLauncher.getHotseat().getLayout();
3254                }
3255            }
3256            if (layout == null) {
3257                layout = getCurrentDropLayout();
3258            }
3259            if (layout != mDragTargetLayout) {
3260                setCurrentDropLayout(layout);
3261                setCurrentDragOverlappingLayout(layout);
3262            }
3263        }
3264
3265        // Handle the drag over
3266        if (mDragTargetLayout != null) {
3267            // We want the point to be mapped to the dragTarget.
3268            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
3269                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
3270            } else {
3271                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
3272            }
3273
3274            ItemInfo info = (ItemInfo) d.dragInfo;
3275
3276            int minSpanX = item.spanX;
3277            int minSpanY = item.spanY;
3278            if (item.minSpanX > 0 && item.minSpanY > 0) {
3279                minSpanX = item.minSpanX;
3280                minSpanY = item.minSpanY;
3281            }
3282
3283            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3284                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY,
3285                    mDragTargetLayout, mTargetCell);
3286            int reorderX = mTargetCell[0];
3287            int reorderY = mTargetCell[1];
3288
3289            setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
3290
3291            float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
3292                    mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
3293
3294            final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0],
3295                    mTargetCell[1]);
3296
3297            manageFolderFeedback(info, mDragTargetLayout, mTargetCell,
3298                    targetCellDistance, dragOverView);
3299
3300            boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
3301                    mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
3302                    item.spanY, child, mTargetCell);
3303
3304            if (!nearestDropOccupied) {
3305                mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3306                        (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
3307                        mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
3308                        d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
3309            } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
3310                    && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
3311                    mLastReorderY != reorderY)) {
3312
3313                // Otherwise, if we aren't adding to or creating a folder and there's no pending
3314                // reorder, then we schedule a reorder
3315                ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
3316                        minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child);
3317                mReorderAlarm.setOnAlarmListener(listener);
3318                mReorderAlarm.setAlarm(REORDER_TIMEOUT);
3319            }
3320
3321            if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
3322                    !nearestDropOccupied) {
3323                if (mDragTargetLayout != null) {
3324                    mDragTargetLayout.revertTempState();
3325                }
3326            }
3327        }
3328    }
3329
3330    private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
3331            int[] targetCell, float distance, View dragOverView) {
3332        boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
3333                false);
3334
3335        if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
3336                !mFolderCreationAlarm.alarmPending()) {
3337            mFolderCreationAlarm.setOnAlarmListener(new
3338                    FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
3339            mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
3340            return;
3341        }
3342
3343        boolean willAddToFolder =
3344                willAddToExistingUserFolder(info, targetLayout, targetCell, distance);
3345
3346        if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
3347            mDragOverFolderIcon = ((FolderIcon) dragOverView);
3348            mDragOverFolderIcon.onDragEnter(info);
3349            if (targetLayout != null) {
3350                targetLayout.clearDragOutlines();
3351            }
3352            setDragMode(DRAG_MODE_ADD_TO_FOLDER);
3353            return;
3354        }
3355
3356        if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
3357            setDragMode(DRAG_MODE_NONE);
3358        }
3359        if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
3360            setDragMode(DRAG_MODE_NONE);
3361        }
3362
3363        return;
3364    }
3365
3366    class FolderCreationAlarmListener implements OnAlarmListener {
3367        CellLayout layout;
3368        int cellX;
3369        int cellY;
3370
3371        public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
3372            this.layout = layout;
3373            this.cellX = cellX;
3374            this.cellY = cellY;
3375        }
3376
3377        public void onAlarm(Alarm alarm) {
3378            if (mDragFolderRingAnimator == null) {
3379                mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
3380            }
3381            mDragFolderRingAnimator.setCell(cellX, cellY);
3382            mDragFolderRingAnimator.setCellLayout(layout);
3383            mDragFolderRingAnimator.animateToAcceptState();
3384            layout.showFolderAccept(mDragFolderRingAnimator);
3385            layout.clearDragOutlines();
3386            setDragMode(DRAG_MODE_CREATE_FOLDER);
3387        }
3388    }
3389
3390    class ReorderAlarmListener implements OnAlarmListener {
3391        float[] dragViewCenter;
3392        int minSpanX, minSpanY, spanX, spanY;
3393        DragView dragView;
3394        View child;
3395
3396        public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
3397                int spanY, DragView dragView, View child) {
3398            this.dragViewCenter = dragViewCenter;
3399            this.minSpanX = minSpanX;
3400            this.minSpanY = minSpanY;
3401            this.spanX = spanX;
3402            this.spanY = spanY;
3403            this.child = child;
3404            this.dragView = dragView;
3405        }
3406
3407        public void onAlarm(Alarm alarm) {
3408            int[] resultSpan = new int[2];
3409            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3410                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout,
3411                    mTargetCell);
3412            mLastReorderX = mTargetCell[0];
3413            mLastReorderY = mTargetCell[1];
3414
3415            mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
3416                (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
3417                child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
3418
3419            if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
3420                mDragTargetLayout.revertTempState();
3421            } else {
3422                setDragMode(DRAG_MODE_REORDER);
3423            }
3424
3425            boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
3426            mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3427                (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
3428                mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize,
3429                dragView.getDragVisualizeOffset(), dragView.getDragRegion());
3430        }
3431    }
3432
3433    @Override
3434    public void getHitRectRelativeToDragLayer(Rect outRect) {
3435        // We want the workspace to have the whole area of the display (it will find the correct
3436        // cell layout to drop to in the existing drag/drop logic.
3437        mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect);
3438    }
3439
3440    /**
3441     * Add the item specified by dragInfo to the given layout.
3442     * @return true if successful
3443     */
3444    public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
3445        if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
3446            onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
3447            return true;
3448        }
3449        mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
3450        return false;
3451    }
3452
3453    private void onDropExternal(int[] touchXY, Object dragInfo,
3454            CellLayout cellLayout, boolean insertAtFirst) {
3455        onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
3456    }
3457
3458    /**
3459     * Drop an item that didn't originate on one of the workspace screens.
3460     * It may have come from Launcher (e.g. from all apps or customize), or it may have
3461     * come from another app altogether.
3462     *
3463     * NOTE: This can also be called when we are outside of a drag event, when we want
3464     * to add an item to one of the workspace screens.
3465     */
3466    private void onDropExternal(final int[] touchXY, final Object dragInfo,
3467            final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
3468        final Runnable exitSpringLoadedRunnable = new Runnable() {
3469            @Override
3470            public void run() {
3471                mLauncher.exitSpringLoadedDragModeDelayed(true, false, null);
3472            }
3473        };
3474
3475        ItemInfo info = (ItemInfo) dragInfo;
3476        int spanX = info.spanX;
3477        int spanY = info.spanY;
3478        if (mDragInfo != null) {
3479            spanX = mDragInfo.spanX;
3480            spanY = mDragInfo.spanY;
3481        }
3482
3483        final long container = mLauncher.isHotseatLayout(cellLayout) ?
3484                LauncherSettings.Favorites.CONTAINER_HOTSEAT :
3485                    LauncherSettings.Favorites.CONTAINER_DESKTOP;
3486        final long screenId = getIdForScreen(cellLayout);
3487        if (!mLauncher.isHotseatLayout(cellLayout)
3488                && screenId != getScreenIdForPageIndex(mCurrentPage)
3489                && mState != State.SPRING_LOADED) {
3490            snapToScreenId(screenId, null);
3491        }
3492
3493        if (info instanceof PendingAddItemInfo) {
3494            final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
3495
3496            boolean findNearestVacantCell = true;
3497            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
3498                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3499                        cellLayout, mTargetCell);
3500                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3501                        mDragViewVisualCenter[1], mTargetCell);
3502                if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell,
3503                        distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
3504                                cellLayout, mTargetCell, distance)) {
3505                    findNearestVacantCell = false;
3506                }
3507            }
3508
3509            final ItemInfo item = (ItemInfo) d.dragInfo;
3510            boolean updateWidgetSize = false;
3511            if (findNearestVacantCell) {
3512                int minSpanX = item.spanX;
3513                int minSpanY = item.spanY;
3514                if (item.minSpanX > 0 && item.minSpanY > 0) {
3515                    minSpanX = item.minSpanX;
3516                    minSpanY = item.minSpanY;
3517                }
3518                int[] resultSpan = new int[2];
3519                mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
3520                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
3521                        null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
3522
3523                if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) {
3524                    updateWidgetSize = true;
3525                }
3526                item.spanX = resultSpan[0];
3527                item.spanY = resultSpan[1];
3528            }
3529
3530            Runnable onAnimationCompleteRunnable = new Runnable() {
3531                @Override
3532                public void run() {
3533                    // When dragging and dropping from customization tray, we deal with creating
3534                    // widgets/shortcuts/folders in a slightly different way
3535                    switch (pendingInfo.itemType) {
3536                    case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
3537                        int span[] = new int[2];
3538                        span[0] = item.spanX;
3539                        span[1] = item.spanY;
3540                        mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
3541                                container, screenId, mTargetCell, span, null);
3542                        break;
3543                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3544                        mLauncher.processShortcutFromDrop(pendingInfo.componentName,
3545                                container, screenId, mTargetCell, null);
3546                        break;
3547                    default:
3548                        throw new IllegalStateException("Unknown item type: " +
3549                                pendingInfo.itemType);
3550                    }
3551                }
3552            };
3553            View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
3554                    ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
3555
3556            if (finalView instanceof AppWidgetHostView && updateWidgetSize) {
3557                AppWidgetHostView awhv = (AppWidgetHostView) finalView;
3558                AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, item.spanX,
3559                        item.spanY);
3560            }
3561
3562            int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3563            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
3564                    ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
3565                animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
3566            }
3567            animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
3568                    animationStyle, finalView, true);
3569        } else {
3570            // This is for other drag/drop cases, like dragging from All Apps
3571            View view = null;
3572
3573            switch (info.itemType) {
3574            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3575            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3576                if (info.container == NO_ID && info instanceof AppInfo) {
3577                    // Came from all apps -- make a copy
3578                    info = new ShortcutInfo((AppInfo) info);
3579                }
3580                view = mLauncher.createShortcut(R.layout.application, cellLayout,
3581                        (ShortcutInfo) info);
3582                break;
3583            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3584                view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
3585                        (FolderInfo) info, mIconCache);
3586                break;
3587            default:
3588                throw new IllegalStateException("Unknown item type: " + info.itemType);
3589            }
3590
3591            // First we find the cell nearest to point at which the item is
3592            // dropped, without any consideration to whether there is an item there.
3593            if (touchXY != null) {
3594                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3595                        cellLayout, mTargetCell);
3596                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3597                        mDragViewVisualCenter[1], mTargetCell);
3598                d.postAnimationRunnable = exitSpringLoadedRunnable;
3599                if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
3600                        true, d.dragView, d.postAnimationRunnable)) {
3601                    return;
3602                }
3603                if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
3604                        true)) {
3605                    return;
3606                }
3607            }
3608
3609            if (touchXY != null) {
3610                // when dragging and dropping, just find the closest free spot
3611                mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
3612                        (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
3613                        null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
3614            } else {
3615                cellLayout.findCellForSpan(mTargetCell, 1, 1);
3616            }
3617            addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX,
3618                    info.spanY, insertAtFirst);
3619            cellLayout.onDropChild(view);
3620            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
3621            cellLayout.getShortcutsAndWidgets().measureChild(view);
3622
3623            LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
3624                    lp.cellX, lp.cellY);
3625
3626            if (d.dragView != null) {
3627                // We wrap the animation call in the temporary set and reset of the current
3628                // cellLayout to its final transform -- this means we animate the drag view to
3629                // the correct final location.
3630                setFinalTransitionTransform(cellLayout);
3631                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
3632                        exitSpringLoadedRunnable);
3633                resetTransitionTransform(cellLayout);
3634            }
3635        }
3636    }
3637
3638    public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
3639        int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
3640                widgetInfo.spanY, widgetInfo, false);
3641        int visibility = layout.getVisibility();
3642        layout.setVisibility(VISIBLE);
3643
3644        int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
3645        int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
3646        Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
3647                Bitmap.Config.ARGB_8888);
3648        Canvas c = new Canvas(b);
3649
3650        layout.measure(width, height);
3651        layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
3652        layout.draw(c);
3653        c.setBitmap(null);
3654        layout.setVisibility(visibility);
3655        return b;
3656    }
3657
3658    private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
3659            DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell,
3660            boolean external, boolean scale) {
3661        // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
3662        // location and size on the home screen.
3663        int spanX = info.spanX;
3664        int spanY = info.spanY;
3665
3666        Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
3667        loc[0] = r.left;
3668        loc[1] = r.top;
3669
3670        setFinalTransitionTransform(layout);
3671        float cellLayoutScale =
3672                mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
3673        resetTransitionTransform(layout);
3674
3675        float dragViewScaleX;
3676        float dragViewScaleY;
3677        if (scale) {
3678            dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
3679            dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
3680        } else {
3681            dragViewScaleX = 1f;
3682            dragViewScaleY = 1f;
3683        }
3684
3685        // The animation will scale the dragView about its center, so we need to center about
3686        // the final location.
3687        loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
3688        loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
3689
3690        scaleXY[0] = dragViewScaleX * cellLayoutScale;
3691        scaleXY[1] = dragViewScaleY * cellLayoutScale;
3692    }
3693
3694    public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
3695            final Runnable onCompleteRunnable, int animationType, final View finalView,
3696            boolean external) {
3697        Rect from = new Rect();
3698        mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
3699
3700        int[] finalPos = new int[2];
3701        float scaleXY[] = new float[2];
3702        boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
3703        getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
3704                external, scalePreview);
3705
3706        Resources res = mLauncher.getResources();
3707        int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
3708
3709        // In the case where we've prebound the widget, we remove it from the DragLayer
3710        if (finalView instanceof AppWidgetHostView && external) {
3711            Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView");
3712            mLauncher.getDragLayer().removeView(finalView);
3713        }
3714        if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
3715            Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
3716            dragView.setCrossFadeBitmap(crossFadeBitmap);
3717            dragView.crossFade((int) (duration * 0.8f));
3718        } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) {
3719            scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0],  scaleXY[1]);
3720        }
3721
3722        DragLayer dragLayer = mLauncher.getDragLayer();
3723        if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
3724            mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
3725                    DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
3726        } else {
3727            int endStyle;
3728            if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
3729                endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
3730            } else {
3731                endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
3732            }
3733
3734            Runnable onComplete = new Runnable() {
3735                @Override
3736                public void run() {
3737                    if (finalView != null) {
3738                        finalView.setVisibility(VISIBLE);
3739                    }
3740                    if (onCompleteRunnable != null) {
3741                        onCompleteRunnable.run();
3742                    }
3743                }
3744            };
3745            dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
3746                    finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
3747                    duration, this);
3748        }
3749    }
3750
3751    public void setFinalTransitionTransform(CellLayout layout) {
3752        if (isSwitchingState()) {
3753            mCurrentScale = getScaleX();
3754            setScaleX(mNewScale);
3755            setScaleY(mNewScale);
3756        }
3757    }
3758    public void resetTransitionTransform(CellLayout layout) {
3759        if (isSwitchingState()) {
3760            setScaleX(mCurrentScale);
3761            setScaleY(mCurrentScale);
3762        }
3763    }
3764
3765    /**
3766     * Return the current {@link CellLayout}, correctly picking the destination
3767     * screen while a scroll is in progress.
3768     */
3769    public CellLayout getCurrentDropLayout() {
3770        return (CellLayout) getChildAt(getNextPage());
3771    }
3772
3773    /**
3774     * Return the current CellInfo describing our current drag; this method exists
3775     * so that Launcher can sync this object with the correct info when the activity is created/
3776     * destroyed
3777     *
3778     */
3779    public CellLayout.CellInfo getDragInfo() {
3780        return mDragInfo;
3781    }
3782
3783    public int getRestorePage() {
3784        return getNextPage() - numCustomPages();
3785    }
3786
3787    /**
3788     * Calculate the nearest cell where the given object would be dropped.
3789     *
3790     * pixelX and pixelY should be in the coordinate system of layout
3791     */
3792    private int[] findNearestArea(int pixelX, int pixelY,
3793            int spanX, int spanY, CellLayout layout, int[] recycle) {
3794        return layout.findNearestArea(
3795                pixelX, pixelY, spanX, spanY, recycle);
3796    }
3797
3798    void setup(DragController dragController) {
3799        mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
3800        mDragController = dragController;
3801
3802        // hardware layers on children are enabled on startup, but should be disabled until
3803        // needed
3804        updateChildrenLayersEnabled(false);
3805        setWallpaperDimension();
3806    }
3807
3808    /**
3809     * Called at the end of a drag which originated on the workspace.
3810     */
3811    public void onDropCompleted(final View target, final DragObject d,
3812            final boolean isFlingToDelete, final boolean success) {
3813        if (mDeferDropAfterUninstall) {
3814            mDeferredAction = new Runnable() {
3815                    public void run() {
3816                        onDropCompleted(target, d, isFlingToDelete, success);
3817                        mDeferredAction = null;
3818                    }
3819                };
3820            return;
3821        }
3822
3823        boolean beingCalledAfterUninstall = mDeferredAction != null;
3824
3825        if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) {
3826            if (target != this && mDragInfo != null) {
3827                CellLayout parentCell = getParentCellLayoutForView(mDragInfo.cell);
3828                if (parentCell != null) {
3829                    parentCell.removeView(mDragInfo.cell);
3830                }
3831                if (mDragInfo.cell instanceof DropTarget) {
3832                    mDragController.removeDropTarget((DropTarget) mDragInfo.cell);
3833                }
3834                // If we move the item to anything not on the Workspace, check if any empty
3835                // screens need to be removed. If we dropped back on the workspace, this will
3836                // be done post drop animation.
3837                stripEmptyScreens();
3838            }
3839        } else if (mDragInfo != null) {
3840            CellLayout cellLayout;
3841            if (mLauncher.isHotseatLayout(target)) {
3842                cellLayout = mLauncher.getHotseat().getLayout();
3843            } else {
3844                cellLayout = getScreenWithId(mDragInfo.screenId);
3845            }
3846            cellLayout.onDropChild(mDragInfo.cell);
3847        }
3848        if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful))
3849                && mDragInfo.cell != null) {
3850            mDragInfo.cell.setVisibility(VISIBLE);
3851        }
3852        mDragOutline = null;
3853        mDragInfo = null;
3854    }
3855
3856    public void deferCompleteDropAfterUninstallActivity() {
3857        mDeferDropAfterUninstall = true;
3858    }
3859
3860    /// maybe move this into a smaller part
3861    public void onUninstallActivityReturned(boolean success) {
3862        mDeferDropAfterUninstall = false;
3863        mUninstallSuccessful = success;
3864        if (mDeferredAction != null) {
3865            mDeferredAction.run();
3866        }
3867    }
3868
3869    void updateItemLocationsInDatabase(CellLayout cl) {
3870        int count = cl.getShortcutsAndWidgets().getChildCount();
3871
3872        long screenId = getIdForScreen(cl);
3873        int container = Favorites.CONTAINER_DESKTOP;
3874
3875        if (mLauncher.isHotseatLayout(cl)) {
3876            screenId = -1;
3877            container = Favorites.CONTAINER_HOTSEAT;
3878        }
3879
3880        for (int i = 0; i < count; i++) {
3881            View v = cl.getShortcutsAndWidgets().getChildAt(i);
3882            ItemInfo info = (ItemInfo) v.getTag();
3883            // Null check required as the AllApps button doesn't have an item info
3884            if (info != null && info.requiresDbUpdate) {
3885                info.requiresDbUpdate = false;
3886                LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, info.cellX,
3887                        info.cellY, info.spanX, info.spanY);
3888            }
3889        }
3890    }
3891
3892    ArrayList<ComponentName> getUniqueComponents(boolean stripDuplicates, ArrayList<ComponentName> duplicates) {
3893        ArrayList<ComponentName> uniqueIntents = new ArrayList<ComponentName>();
3894        getUniqueIntents((CellLayout) mLauncher.getHotseat().getLayout(), uniqueIntents, duplicates, false);
3895        int count = getChildCount();
3896        for (int i = 0; i < count; i++) {
3897            CellLayout cl = (CellLayout) getChildAt(i);
3898            getUniqueIntents(cl, uniqueIntents, duplicates, false);
3899        }
3900        return uniqueIntents;
3901    }
3902
3903    void getUniqueIntents(CellLayout cl, ArrayList<ComponentName> uniqueIntents,
3904            ArrayList<ComponentName> duplicates, boolean stripDuplicates) {
3905        int count = cl.getShortcutsAndWidgets().getChildCount();
3906
3907        ArrayList<View> children = new ArrayList<View>();
3908        for (int i = 0; i < count; i++) {
3909            View v = cl.getShortcutsAndWidgets().getChildAt(i);
3910            children.add(v);
3911        }
3912
3913        for (int i = 0; i < count; i++) {
3914            View v = children.get(i);
3915            ItemInfo info = (ItemInfo) v.getTag();
3916            // Null check required as the AllApps button doesn't have an item info
3917            if (info instanceof ShortcutInfo) {
3918                ShortcutInfo si = (ShortcutInfo) info;
3919                ComponentName cn = si.intent.getComponent();
3920
3921                Uri dataUri = si.intent.getData();
3922                // If dataUri is not null / empty or if this component isn't one that would
3923                // have previously showed up in the AllApps list, then this is a widget-type
3924                // shortcut, so ignore it.
3925                if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
3926                    continue;
3927                }
3928
3929                if (!uniqueIntents.contains(cn)) {
3930                    uniqueIntents.add(cn);
3931                } else {
3932                    if (stripDuplicates) {
3933                        cl.removeViewInLayout(v);
3934                        LauncherModel.deleteItemFromDatabase(mLauncher, si);
3935                    }
3936                    if (duplicates != null) {
3937                        duplicates.add(cn);
3938                    }
3939                }
3940            }
3941            if (v instanceof FolderIcon) {
3942                FolderIcon fi = (FolderIcon) v;
3943                ArrayList<View> items = fi.getFolder().getItemsInReadingOrder();
3944                for (int j = 0; j < items.size(); j++) {
3945                    if (items.get(j).getTag() instanceof ShortcutInfo) {
3946                        ShortcutInfo si = (ShortcutInfo) items.get(j).getTag();
3947                        ComponentName cn = si.intent.getComponent();
3948
3949                        Uri dataUri = si.intent.getData();
3950                        // If dataUri is not null / empty or if this component isn't one that would
3951                        // have previously showed up in the AllApps list, then this is a widget-type
3952                        // shortcut, so ignore it.
3953                        if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
3954                            continue;
3955                        }
3956
3957                        if (!uniqueIntents.contains(cn)) {
3958                            uniqueIntents.add(cn);
3959                        }  else {
3960                            if (stripDuplicates) {
3961                                fi.getFolderInfo().remove(si);
3962                                LauncherModel.deleteItemFromDatabase(mLauncher, si);
3963                            }
3964                            if (duplicates != null) {
3965                                duplicates.add(cn);
3966                            }
3967                        }
3968                    }
3969                }
3970            }
3971        }
3972    }
3973
3974    void saveWorkspaceToDb() {
3975        saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout());
3976        int count = getChildCount();
3977        for (int i = 0; i < count; i++) {
3978            CellLayout cl = (CellLayout) getChildAt(i);
3979            saveWorkspaceScreenToDb(cl);
3980        }
3981    }
3982
3983    void saveWorkspaceScreenToDb(CellLayout cl) {
3984        int count = cl.getShortcutsAndWidgets().getChildCount();
3985
3986        long screenId = getIdForScreen(cl);
3987        int container = Favorites.CONTAINER_DESKTOP;
3988
3989        Hotseat hotseat = mLauncher.getHotseat();
3990        if (mLauncher.isHotseatLayout(cl)) {
3991            screenId = -1;
3992            container = Favorites.CONTAINER_HOTSEAT;
3993        }
3994
3995        for (int i = 0; i < count; i++) {
3996            View v = cl.getShortcutsAndWidgets().getChildAt(i);
3997            ItemInfo info = (ItemInfo) v.getTag();
3998            // Null check required as the AllApps button doesn't have an item info
3999            if (info != null) {
4000                int cellX = info.cellX;
4001                int cellY = info.cellY;
4002                if (container == Favorites.CONTAINER_HOTSEAT) {
4003                    cellX = hotseat.getCellXFromOrder((int) info.screenId);
4004                    cellY = hotseat.getCellYFromOrder((int) info.screenId);
4005                }
4006                LauncherModel.addItemToDatabase(mLauncher, info, container, screenId, cellX,
4007                        cellY, false);
4008            }
4009            if (v instanceof FolderIcon) {
4010                FolderIcon fi = (FolderIcon) v;
4011                fi.getFolder().addItemLocationsInDatabase();
4012            }
4013        }
4014    }
4015
4016    @Override
4017    public boolean supportsFlingToDelete() {
4018        return true;
4019    }
4020
4021    @Override
4022    public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
4023        // Do nothing
4024    }
4025
4026    @Override
4027    public void onFlingToDeleteCompleted() {
4028        // Do nothing
4029    }
4030
4031    public boolean isDropEnabled() {
4032        return true;
4033    }
4034
4035    @Override
4036    protected void onRestoreInstanceState(Parcelable state) {
4037        super.onRestoreInstanceState(state);
4038        Launcher.setScreen(mCurrentPage);
4039    }
4040
4041    @Override
4042    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
4043        // We don't dispatch restoreInstanceState to our children using this code path.
4044        // Some pages will be restored immediately as their items are bound immediately, and
4045        // others we will need to wait until after their items are bound.
4046        mSavedStates = container;
4047    }
4048
4049    public void restoreInstanceStateForChild(int child) {
4050        if (mSavedStates != null) {
4051            mRestoredPages.add(child);
4052            CellLayout cl = (CellLayout) getChildAt(child);
4053            cl.restoreInstanceState(mSavedStates);
4054        }
4055    }
4056
4057    public void restoreInstanceStateForRemainingPages() {
4058        int count = getChildCount();
4059        for (int i = 0; i < count; i++) {
4060            if (!mRestoredPages.contains(i)) {
4061                restoreInstanceStateForChild(i);
4062            }
4063        }
4064        mRestoredPages.clear();
4065    }
4066
4067    @Override
4068    public void scrollLeft() {
4069        if (!isSmall() && !mIsSwitchingState) {
4070            super.scrollLeft();
4071        }
4072        Folder openFolder = getOpenFolder();
4073        if (openFolder != null) {
4074            openFolder.completeDragExit();
4075        }
4076    }
4077
4078    @Override
4079    public void scrollRight() {
4080        if (!isSmall() && !mIsSwitchingState) {
4081            super.scrollRight();
4082        }
4083        Folder openFolder = getOpenFolder();
4084        if (openFolder != null) {
4085            openFolder.completeDragExit();
4086        }
4087    }
4088
4089    @Override
4090    public boolean onEnterScrollArea(int x, int y, int direction) {
4091        // Ignore the scroll area if we are dragging over the hot seat
4092        boolean isPortrait = !LauncherAppState.isScreenLandscape(getContext());
4093        if (mLauncher.getHotseat() != null && isPortrait) {
4094            Rect r = new Rect();
4095            mLauncher.getHotseat().getHitRect(r);
4096            if (r.contains(x, y)) {
4097                return false;
4098            }
4099        }
4100
4101        boolean result = false;
4102        if (!isSmall() && !mIsSwitchingState && getOpenFolder() == null) {
4103            mInScrollArea = true;
4104
4105            final int page = getNextPage() +
4106                       (direction == DragController.SCROLL_LEFT ? -1 : 1);
4107            // We always want to exit the current layout to ensure parity of enter / exit
4108            setCurrentDropLayout(null);
4109
4110            if (0 <= page && page < getChildCount()) {
4111                // Ensure that we are not dragging over to the custom content screen
4112                if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) {
4113                    return false;
4114                }
4115
4116                CellLayout layout = (CellLayout) getChildAt(page);
4117                setCurrentDragOverlappingLayout(layout);
4118
4119                // Workspace is responsible for drawing the edge glow on adjacent pages,
4120                // so we need to redraw the workspace when this may have changed.
4121                invalidate();
4122                result = true;
4123            }
4124        }
4125        return result;
4126    }
4127
4128    @Override
4129    public boolean onExitScrollArea() {
4130        boolean result = false;
4131        if (mInScrollArea) {
4132            invalidate();
4133            CellLayout layout = getCurrentDropLayout();
4134            setCurrentDropLayout(layout);
4135            setCurrentDragOverlappingLayout(layout);
4136
4137            result = true;
4138            mInScrollArea = false;
4139        }
4140        return result;
4141    }
4142
4143    private void onResetScrollArea() {
4144        setCurrentDragOverlappingLayout(null);
4145        mInScrollArea = false;
4146    }
4147
4148    /**
4149     * Returns a specific CellLayout
4150     */
4151    CellLayout getParentCellLayoutForView(View v) {
4152        ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
4153        for (CellLayout layout : layouts) {
4154            if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
4155                return layout;
4156            }
4157        }
4158        return null;
4159    }
4160
4161    /**
4162     * Returns a list of all the CellLayouts in the workspace.
4163     */
4164    ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
4165        ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
4166        int screenCount = getChildCount();
4167        for (int screen = 0; screen < screenCount; screen++) {
4168            layouts.add(((CellLayout) getChildAt(screen)));
4169        }
4170        if (mLauncher.getHotseat() != null) {
4171            layouts.add(mLauncher.getHotseat().getLayout());
4172        }
4173        return layouts;
4174    }
4175
4176    /**
4177     * We should only use this to search for specific children.  Do not use this method to modify
4178     * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
4179     * the hotseat and workspace pages
4180     */
4181    ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
4182        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
4183                new ArrayList<ShortcutAndWidgetContainer>();
4184        int screenCount = getChildCount();
4185        for (int screen = 0; screen < screenCount; screen++) {
4186            childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
4187        }
4188        if (mLauncher.getHotseat() != null) {
4189            childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
4190        }
4191        return childrenLayouts;
4192    }
4193
4194    public Folder getFolderForTag(Object tag) {
4195        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
4196                getAllShortcutAndWidgetContainers();
4197        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
4198            int count = layout.getChildCount();
4199            for (int i = 0; i < count; i++) {
4200                View child = layout.getChildAt(i);
4201                if (child instanceof Folder) {
4202                    Folder f = (Folder) child;
4203                    if (f.getInfo() == tag && f.getInfo().opened) {
4204                        return f;
4205                    }
4206                }
4207            }
4208        }
4209        return null;
4210    }
4211
4212    public View getViewForTag(Object tag) {
4213        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
4214                getAllShortcutAndWidgetContainers();
4215        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
4216            int count = layout.getChildCount();
4217            for (int i = 0; i < count; i++) {
4218                View child = layout.getChildAt(i);
4219                if (child.getTag() == tag) {
4220                    return child;
4221                }
4222            }
4223        }
4224        return null;
4225    }
4226
4227    void clearDropTargets() {
4228        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
4229                getAllShortcutAndWidgetContainers();
4230        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
4231            int childCount = layout.getChildCount();
4232            for (int j = 0; j < childCount; j++) {
4233                View v = layout.getChildAt(j);
4234                if (v instanceof DropTarget) {
4235                    mDragController.removeDropTarget((DropTarget) v);
4236                }
4237            }
4238        }
4239    }
4240
4241    // Removes ALL items that match a given package name, this is usually called when a package
4242    // has been removed and we want to remove all components (widgets, shortcuts, apps) that
4243    // belong to that package.
4244    void removeItemsByPackageName(final ArrayList<String> packages) {
4245        final HashSet<String> packageNames = new HashSet<String>();
4246        packageNames.addAll(packages);
4247
4248        // Filter out all the ItemInfos that this is going to affect
4249        final HashSet<ItemInfo> infos = new HashSet<ItemInfo>();
4250        final HashSet<ComponentName> cns = new HashSet<ComponentName>();
4251        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4252        for (CellLayout layoutParent : cellLayouts) {
4253            ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4254            int childCount = layout.getChildCount();
4255            for (int i = 0; i < childCount; ++i) {
4256                View view = layout.getChildAt(i);
4257                infos.add((ItemInfo) view.getTag());
4258            }
4259        }
4260        LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4261            @Override
4262            public boolean filterItem(ItemInfo parent, ItemInfo info,
4263                                      ComponentName cn) {
4264                if (packageNames.contains(cn.getPackageName())) {
4265                    cns.add(cn);
4266                    return true;
4267                }
4268                return false;
4269            }
4270        };
4271        LauncherModel.filterItemInfos(infos, filter);
4272
4273        // Remove the affected components
4274        removeItemsByComponentName(cns);
4275    }
4276
4277    // Removes items that match the application info specified, when applications are removed
4278    // as a part of an update, this is called to ensure that other widgets and application
4279    // shortcuts are not removed.
4280    void removeItemsByApplicationInfo(final ArrayList<AppInfo> appInfos) {
4281        // Just create a hash table of all the specific components that this will affect
4282        HashSet<ComponentName> cns = new HashSet<ComponentName>();
4283        for (AppInfo info : appInfos) {
4284            cns.add(info.componentName);
4285        }
4286
4287        // Remove all the things
4288        removeItemsByComponentName(cns);
4289    }
4290
4291    void removeItemsByComponentName(final HashSet<ComponentName> componentNames) {
4292        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4293        for (final CellLayout layoutParent: cellLayouts) {
4294            final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4295
4296            final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
4297            for (int j = 0; j < layout.getChildCount(); j++) {
4298                final View view = layout.getChildAt(j);
4299                children.put((ItemInfo) view.getTag(), view);
4300            }
4301
4302            final ArrayList<View> childrenToRemove = new ArrayList<View>();
4303            final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove =
4304                    new HashMap<FolderInfo, ArrayList<ShortcutInfo>>();
4305            LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4306                @Override
4307                public boolean filterItem(ItemInfo parent, ItemInfo info,
4308                                          ComponentName cn) {
4309                    if (parent instanceof FolderInfo) {
4310                        if (componentNames.contains(cn)) {
4311                            FolderInfo folder = (FolderInfo) parent;
4312                            ArrayList<ShortcutInfo> appsToRemove;
4313                            if (folderAppsToRemove.containsKey(folder)) {
4314                                appsToRemove = folderAppsToRemove.get(folder);
4315                            } else {
4316                                appsToRemove = new ArrayList<ShortcutInfo>();
4317                                folderAppsToRemove.put(folder, appsToRemove);
4318                            }
4319                            appsToRemove.add((ShortcutInfo) info);
4320                            return true;
4321                        }
4322                    } else {
4323                        if (componentNames.contains(cn)) {
4324                            childrenToRemove.add(children.get(info));
4325                            return true;
4326                        }
4327                    }
4328                    return false;
4329                }
4330            };
4331            LauncherModel.filterItemInfos(children.keySet(), filter);
4332
4333            // Remove all the apps from their folders
4334            for (FolderInfo folder : folderAppsToRemove.keySet()) {
4335                ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
4336                for (ShortcutInfo info : appsToRemove) {
4337                    folder.remove(info);
4338                }
4339            }
4340
4341            // Remove all the other children
4342            for (View child : childrenToRemove) {
4343                // Note: We can not remove the view directly from CellLayoutChildren as this
4344                // does not re-mark the spaces as unoccupied.
4345                layoutParent.removeViewInLayout(child);
4346                if (child instanceof DropTarget) {
4347                    mDragController.removeDropTarget((DropTarget) child);
4348                }
4349            }
4350
4351            if (childrenToRemove.size() > 0) {
4352                layout.requestLayout();
4353                layout.invalidate();
4354            }
4355        }
4356
4357        // Strip all the empty screens
4358        stripEmptyScreens();
4359    }
4360
4361    void updateShortcuts(ArrayList<AppInfo> apps) {
4362        ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers();
4363        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
4364            int childCount = layout.getChildCount();
4365            for (int j = 0; j < childCount; j++) {
4366                final View view = layout.getChildAt(j);
4367                Object tag = view.getTag();
4368
4369                if (LauncherModel.isShortcutInfoUpdateable((ItemInfo) tag)) {
4370                    ShortcutInfo info = (ShortcutInfo) tag;
4371
4372                    final Intent intent = info.intent;
4373                    final ComponentName name = intent.getComponent();
4374                    final int appCount = apps.size();
4375                    for (int k = 0; k < appCount; k++) {
4376                        AppInfo app = apps.get(k);
4377                        if (app.componentName.equals(name)) {
4378                            BubbleTextView shortcut = (BubbleTextView) view;
4379                            info.updateIcon(mIconCache);
4380                            info.title = app.title.toString();
4381                            shortcut.applyFromShortcutInfo(info, mIconCache);
4382                        }
4383                    }
4384                }
4385            }
4386        }
4387    }
4388
4389    private void moveToScreen(int page, boolean animate) {
4390        if (!isSmall()) {
4391            if (animate) {
4392                snapToPage(page);
4393            } else {
4394                setCurrentPage(page);
4395            }
4396        }
4397        View child = getChildAt(page);
4398        if (child != null) {
4399            child.requestFocus();
4400        }
4401    }
4402
4403    void moveToDefaultScreen(boolean animate) {
4404        moveToScreen(mDefaultPage, animate);
4405    }
4406
4407    void moveToCustomContentScreen(boolean animate) {
4408        if (hasCustomContent()) {
4409            int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
4410            if (animate) {
4411                snapToPage(ccIndex);
4412            } else {
4413                setCurrentPage(ccIndex);
4414            }
4415            View child = getChildAt(ccIndex);
4416            if (child != null) {
4417                child.requestFocus();
4418            }
4419         }
4420    }
4421
4422    @Override
4423    protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) {
4424        long screenId = getScreenIdForPageIndex(pageIndex);
4425        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
4426            int count = mScreenOrder.size() - numCustomPages();
4427            if (count > 1) {
4428                return new PageIndicator.PageMarkerResources(R.drawable.ic_pageindicator_current,
4429                        R.drawable.ic_pageindicator_add);
4430            }
4431        }
4432
4433        return super.getPageIndicatorMarker(pageIndex);
4434    }
4435
4436    @Override
4437    public void syncPages() {
4438    }
4439
4440    @Override
4441    public void syncPageItems(int page, boolean immediate) {
4442    }
4443
4444    protected String getPageIndicatorDescription() {
4445        String settings = getResources().getString(R.string.settings_button_text);
4446        return getCurrentPageDescription() + ", " + settings;
4447    }
4448
4449    protected String getCurrentPageDescription() {
4450        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
4451        int delta = numCustomPages();
4452        if (hasCustomContent() && getNextPage() == 0) {
4453            return mCustomContentDescription;
4454        }
4455        return String.format(getContext().getString(R.string.workspace_scroll_format),
4456                page + 1 - delta, getChildCount() - delta);
4457    }
4458
4459    public void getLocationInDragLayer(int[] loc) {
4460        mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
4461    }
4462}
4463