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