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