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