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