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