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