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