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