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