| /* | 
 |  * Copyright (C) 2008 The Android Open Source Project | 
 |  * | 
 |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
 |  * you may not use this file except in compliance with the License. | 
 |  * You may obtain a copy of the License at | 
 |  * | 
 |  *      http://www.apache.org/licenses/LICENSE-2.0 | 
 |  * | 
 |  * Unless required by applicable law or agreed to in writing, software | 
 |  * distributed under the License is distributed on an "AS IS" BASIS, | 
 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |  * See the License for the specific language governing permissions and | 
 |  * limitations under the License. | 
 |  */ | 
 |  | 
 | package com.android.launcher3; | 
 |  | 
 | import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS; | 
 | import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; | 
 | import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS; | 
 | import static com.android.launcher3.LauncherState.ALL_APPS; | 
 | import static com.android.launcher3.LauncherState.NORMAL; | 
 | import static com.android.launcher3.LauncherState.SPRING_LOADED; | 
 | import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_OVERLAY; | 
 |  | 
 | import android.animation.Animator; | 
 | import android.animation.AnimatorListenerAdapter; | 
 | import android.animation.LayoutTransition; | 
 | import android.animation.ObjectAnimator; | 
 | import android.animation.PropertyValuesHolder; | 
 | import android.animation.ValueAnimator; | 
 | import android.animation.ValueAnimator.AnimatorUpdateListener; | 
 | import android.annotation.SuppressLint; | 
 | import android.app.WallpaperManager; | 
 | import android.appwidget.AppWidgetHostView; | 
 | import android.appwidget.AppWidgetProviderInfo; | 
 | import android.content.Context; | 
 | import android.content.res.Resources; | 
 | import android.graphics.Bitmap; | 
 | import android.graphics.Canvas; | 
 | import android.graphics.Point; | 
 | import android.graphics.Rect; | 
 | import android.graphics.drawable.Drawable; | 
 | import android.os.Handler; | 
 | import android.os.IBinder; | 
 | import android.os.Parcelable; | 
 | import android.os.UserHandle; | 
 | import android.util.AttributeSet; | 
 | import android.util.Log; | 
 | import android.util.SparseArray; | 
 | import android.view.LayoutInflater; | 
 | import android.view.MotionEvent; | 
 | import android.view.View; | 
 | import android.view.ViewGroup; | 
 | import android.view.ViewTreeObserver; | 
 | import android.widget.Toast; | 
 |  | 
 | import com.android.launcher3.Launcher.LauncherOverlay; | 
 | import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener; | 
 | import com.android.launcher3.LauncherStateManager.AnimationConfig; | 
 | import com.android.launcher3.accessibility.AccessibleDragListenerAdapter; | 
 | import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper; | 
 | import com.android.launcher3.anim.AnimatorSetBuilder; | 
 | import com.android.launcher3.anim.Interpolators; | 
 | import com.android.launcher3.badge.FolderBadgeInfo; | 
 | import com.android.launcher3.compat.AppWidgetManagerCompat; | 
 | import com.android.launcher3.config.FeatureFlags; | 
 | import com.android.launcher3.dragndrop.DragController; | 
 | import com.android.launcher3.dragndrop.DragLayer; | 
 | import com.android.launcher3.dragndrop.DragOptions; | 
 | import com.android.launcher3.dragndrop.DragView; | 
 | import com.android.launcher3.dragndrop.SpringLoadedDragController; | 
 | import com.android.launcher3.folder.Folder; | 
 | import com.android.launcher3.folder.FolderIcon; | 
 | import com.android.launcher3.folder.PreviewBackground; | 
 | import com.android.launcher3.graphics.DragPreviewProvider; | 
 | import com.android.launcher3.graphics.PreloadIconDrawable; | 
 | import com.android.launcher3.pageindicators.WorkspacePageIndicator; | 
 | import com.android.launcher3.popup.PopupContainerWithArrow; | 
 | import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider; | 
 | import com.android.launcher3.touch.ItemLongClickListener; | 
 | import com.android.launcher3.touch.WorkspaceTouchListener; | 
 | import com.android.launcher3.userevent.nano.LauncherLogProto.Action; | 
 | import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; | 
 | import com.android.launcher3.userevent.nano.LauncherLogProto.Target; | 
 | import com.android.launcher3.util.ItemInfoMatcher; | 
 | import com.android.launcher3.util.LongArrayMap; | 
 | import com.android.launcher3.util.PackageUserKey; | 
 | import com.android.launcher3.util.Thunk; | 
 | import com.android.launcher3.util.WallpaperOffsetInterpolator; | 
 | import com.android.launcher3.widget.LauncherAppWidgetHostView; | 
 | import com.android.launcher3.widget.PendingAddShortcutInfo; | 
 | import com.android.launcher3.widget.PendingAddWidgetInfo; | 
 | import com.android.launcher3.widget.PendingAppWidgetHostView; | 
 |  | 
 | import java.util.ArrayList; | 
 | import java.util.HashSet; | 
 | import java.util.Set; | 
 |  | 
 | /** | 
 |  * The workspace is a wide area with a wallpaper and a finite number of pages. | 
 |  * Each page contains a number of icons, folders or widgets the user can | 
 |  * interact with. A workspace is meant to be used with a fixed width only. | 
 |  */ | 
 | public class Workspace extends PagedView<WorkspacePageIndicator> | 
 |         implements DropTarget, DragSource, View.OnTouchListener, | 
 |         DragController.DragListener, Insettable, LauncherStateManager.StateHandler { | 
 |     private static final String TAG = "Launcher.Workspace"; | 
 |  | 
 |     /** The value that {@link #mTransitionProgress} must be greater than for | 
 |      * {@link #transitionStateShouldAllowDrop()} to return true. */ | 
 |     private static final float ALLOW_DROP_TRANSITION_PROGRESS = 0.25f; | 
 |  | 
 |     /** The value that {@link #mTransitionProgress} must be greater than for | 
 |      * {@link #isFinishedSwitchingState()} ()} to return true. */ | 
 |     private static final float FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS = 0.5f; | 
 |  | 
 |     private static final boolean ENFORCE_DRAG_EVENT_ORDER = false; | 
 |  | 
 |     private static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400; | 
 |     private static final int FADE_EMPTY_SCREEN_DURATION = 150; | 
 |  | 
 |     private static final int ADJACENT_SCREEN_DROP_DURATION = 300; | 
 |  | 
 |     private static final int DEFAULT_PAGE = 0; | 
 |  | 
 |     private static final boolean MAP_NO_RECURSE = false; | 
 |     private static final boolean MAP_RECURSE = true; | 
 |  | 
 |     // The screen id used for the empty screen always present to the right. | 
 |     public static final long EXTRA_EMPTY_SCREEN_ID = -201; | 
 |     // The is the first screen. It is always present, even if its empty. | 
 |     public static final long FIRST_SCREEN_ID = 0; | 
 |  | 
 |     private LayoutTransition mLayoutTransition; | 
 |     @Thunk final WallpaperManager mWallpaperManager; | 
 |  | 
 |     private ShortcutAndWidgetContainer mDragSourceInternal; | 
 |  | 
 |     @Thunk final LongArrayMap<CellLayout> mWorkspaceScreens = new LongArrayMap<>(); | 
 |     @Thunk final ArrayList<Long> mScreenOrder = new ArrayList<>(); | 
 |  | 
 |     @Thunk Runnable mRemoveEmptyScreenRunnable; | 
 |     @Thunk boolean mDeferRemoveExtraEmptyScreen = false; | 
 |  | 
 |     /** | 
 |      * CellInfo for the cell that is currently being dragged | 
 |      */ | 
 |     private CellLayout.CellInfo mDragInfo; | 
 |  | 
 |     /** | 
 |      * Target drop area calculated during last acceptDrop call. | 
 |      */ | 
 |     @Thunk int[] mTargetCell = new int[2]; | 
 |     private int mDragOverX = -1; | 
 |     private int mDragOverY = -1; | 
 |  | 
 |     /** | 
 |      * The CellLayout that is currently being dragged over | 
 |      */ | 
 |     @Thunk CellLayout mDragTargetLayout = null; | 
 |     /** | 
 |      * The CellLayout that we will show as highlighted | 
 |      */ | 
 |     private CellLayout mDragOverlappingLayout = null; | 
 |  | 
 |     /** | 
 |      * The CellLayout which will be dropped to | 
 |      */ | 
 |     private CellLayout mDropToLayout = null; | 
 |  | 
 |     @Thunk final Launcher mLauncher; | 
 |     @Thunk DragController mDragController; | 
 |  | 
 |     private final int[] mTempXY = new int[2]; | 
 |     @Thunk float[] mDragViewVisualCenter = new float[2]; | 
 |     private final float[] mTempTouchCoordinates = new float[2]; | 
 |  | 
 |     private SpringLoadedDragController mSpringLoadedDragController; | 
 |  | 
 |     private boolean mIsSwitchingState = false; | 
 |  | 
 |     boolean mChildrenLayersEnabled = true; | 
 |  | 
 |     private boolean mStripScreensOnPageStopMoving = false; | 
 |  | 
 |     private DragPreviewProvider mOutlineProvider = null; | 
 |     private boolean mWorkspaceFadeInAdjacentScreens; | 
 |  | 
 |     final WallpaperOffsetInterpolator mWallpaperOffset; | 
 |     private boolean mUnlockWallpaperFromDefaultPageOnLayout; | 
 |  | 
 |     // Variables relating to the creation of user folders by hovering shortcuts over shortcuts | 
 |     private static final int FOLDER_CREATION_TIMEOUT = 0; | 
 |     public static final int REORDER_TIMEOUT = 650; | 
 |     private final Alarm mFolderCreationAlarm = new Alarm(); | 
 |     private final Alarm mReorderAlarm = new Alarm(); | 
 |     private PreviewBackground mFolderCreateBg; | 
 |     private FolderIcon mDragOverFolderIcon = null; | 
 |     private boolean mCreateUserFolderOnDrop = false; | 
 |     private boolean mAddToExistingFolderOnDrop = false; | 
 |     private float mMaxDistanceForFolderCreation; | 
 |  | 
 |     // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget) | 
 |     private float mXDown; | 
 |     private float mYDown; | 
 |     final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6; | 
 |     final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3; | 
 |     final static float TOUCH_SLOP_DAMPING_FACTOR = 4; | 
 |  | 
 |     // Relating to the animation of items being dropped externally | 
 |     public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0; | 
 |     public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1; | 
 |     public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2; | 
 |     public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3; | 
 |     public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4; | 
 |  | 
 |     // Related to dragging, folder creation and reordering | 
 |     private static final int DRAG_MODE_NONE = 0; | 
 |     private static final int DRAG_MODE_CREATE_FOLDER = 1; | 
 |     private static final int DRAG_MODE_ADD_TO_FOLDER = 2; | 
 |     private static final int DRAG_MODE_REORDER = 3; | 
 |     private int mDragMode = DRAG_MODE_NONE; | 
 |     @Thunk int mLastReorderX = -1; | 
 |     @Thunk int mLastReorderY = -1; | 
 |  | 
 |     private SparseArray<Parcelable> mSavedStates; | 
 |     private final ArrayList<Integer> mRestoredPages = new ArrayList<>(); | 
 |  | 
 |     private float mCurrentScale; | 
 |     private float mTransitionProgress; | 
 |  | 
 |     // State related to Launcher Overlay | 
 |     LauncherOverlay mLauncherOverlay; | 
 |     boolean mScrollInteractionBegan; | 
 |     boolean mStartedSendingScrollEvents; | 
 |     float mLastOverlayScroll = 0; | 
 |     boolean mOverlayShown = false; | 
 |     private Runnable mOnOverlayHiddenCallback; | 
 |  | 
 |     private boolean mForceDrawAdjacentPages = false; | 
 |  | 
 |     // Total over scrollX in the overlay direction. | 
 |     private float mOverlayTranslation; | 
 |  | 
 |     // Handles workspace state transitions | 
 |     private final WorkspaceStateTransitionAnimation mStateTransitionAnimation; | 
 |  | 
 |     /** | 
 |      * Used to inflate the Workspace from XML. | 
 |      * | 
 |      * @param context The application's context. | 
 |      * @param attrs The attributes set containing the Workspace's customization values. | 
 |      */ | 
 |     public Workspace(Context context, AttributeSet attrs) { | 
 |         this(context, attrs, 0); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Used to inflate the Workspace from XML. | 
 |      * | 
 |      * @param context The application's context. | 
 |      * @param attrs The attributes set containing the Workspace's customization values. | 
 |      * @param defStyle Unused. | 
 |      */ | 
 |     public Workspace(Context context, AttributeSet attrs, int defStyle) { | 
 |         super(context, attrs, defStyle); | 
 |  | 
 |         mLauncher = Launcher.getLauncher(context); | 
 |         mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this); | 
 |         mWallpaperManager = WallpaperManager.getInstance(context); | 
 |  | 
 |         mWallpaperOffset = new WallpaperOffsetInterpolator(this); | 
 |  | 
 |         setHapticFeedbackEnabled(false); | 
 |         initWorkspace(); | 
 |  | 
 |         // Disable multitouch across the workspace/all apps/customize tray | 
 |         setMotionEventSplittingEnabled(true); | 
 |         setOnTouchListener(new WorkspaceTouchListener(mLauncher, this)); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public void setInsets(Rect insets) { | 
 |         mInsets.set(insets); | 
 |  | 
 |         DeviceProfile grid = mLauncher.getDeviceProfile(); | 
 |         mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx); | 
 |         mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens(); | 
 |  | 
 |         Rect padding = grid.workspacePadding; | 
 |         setPadding(padding.left, padding.top, padding.right, padding.bottom); | 
 |  | 
 |         if (grid.shouldFadeAdjacentWorkspaceScreens()) { | 
 |             // In landscape mode the page spacing is set to the default. | 
 |             setPageSpacing(grid.defaultPageSpacingPx); | 
 |         } else { | 
 |             // In portrait, we want the pages spaced such that there is no | 
 |             // overhang of the previous / next page into the current page viewport. | 
 |             // We assume symmetrical padding in portrait mode. | 
 |             setPageSpacing(Math.max(grid.defaultPageSpacingPx, padding.left + 1)); | 
 |         } | 
 |  | 
 |         int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx; | 
 |         int paddingBottom = grid.cellLayoutBottomPaddingPx; | 
 |         for (int i = mWorkspaceScreens.size() - 1; i >= 0; i--) { | 
 |             mWorkspaceScreens.valueAt(i) | 
 |                     .setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom); | 
 |         } | 
 |     } | 
 |  | 
 |     /** | 
 |      * Estimates the size of an item using spans: hSpan, vSpan. | 
 |      * | 
 |      * @return MAX_VALUE for each dimension if unsuccessful. | 
 |      */ | 
 |     public int[] estimateItemSize(ItemInfo itemInfo) { | 
 |         int[] size = new int[2]; | 
 |         if (getChildCount() > 0) { | 
 |             // Use the first page to estimate the child position | 
 |             CellLayout cl = (CellLayout) getChildAt(0); | 
 |             boolean isWidget = itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; | 
 |  | 
 |             Rect r = estimateItemPosition(cl, 0, 0, itemInfo.spanX, itemInfo.spanY); | 
 |  | 
 |             float scale = 1; | 
 |             if (isWidget) { | 
 |                 DeviceProfile profile = mLauncher.getDeviceProfile(); | 
 |                 scale = Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y); | 
 |             } | 
 |             size[0] = r.width(); | 
 |             size[1] = r.height(); | 
 |  | 
 |             if (isWidget) { | 
 |                 size[0] /= scale; | 
 |                 size[1] /= scale; | 
 |             } | 
 |             return size; | 
 |         } else { | 
 |             size[0] = Integer.MAX_VALUE; | 
 |             size[1] = Integer.MAX_VALUE; | 
 |             return size; | 
 |         } | 
 |     } | 
 |  | 
 |     public float getWallpaperOffsetForCenterPage() { | 
 |         int pageScroll = getScrollForPage(getPageNearestToCenterOfScreen()); | 
 |         return mWallpaperOffset.wallpaperOffsetForScroll(pageScroll); | 
 |     } | 
 |  | 
 |     public Rect estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan) { | 
 |         Rect r = new Rect(); | 
 |         cl.cellToRect(hCell, vCell, hSpan, vSpan, r); | 
 |         return r; | 
 |     } | 
 |  | 
 |     @Override | 
 |     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { | 
 |         if (ENFORCE_DRAG_EVENT_ORDER) { | 
 |             enforceDragParity("onDragStart", 0, 0); | 
 |         } | 
 |  | 
 |         if (mDragInfo != null && mDragInfo.cell != null) { | 
 |             CellLayout layout = (CellLayout) mDragInfo.cell.getParent().getParent(); | 
 |             layout.markCellsAsUnoccupiedForView(mDragInfo.cell); | 
 |         } | 
 |  | 
 |         if (mOutlineProvider != null) { | 
 |             if (dragObject.dragView != null) { | 
 |                 Bitmap preview = dragObject.dragView.getPreviewBitmap(); | 
 |  | 
 |                 // The outline is used to visualize where the item will land if dropped | 
 |                 mOutlineProvider.generateDragOutline(preview); | 
 |             } | 
 |         } | 
 |  | 
 |         updateChildrenLayersEnabled(); | 
 |  | 
 |         // Do not add a new page if it is a accessible drag which was not started by the workspace. | 
 |         // We do not support accessibility drag from other sources and instead provide a direct | 
 |         // action for move/add to homescreen. | 
 |         // When a accessible drag is started by the folder, we only allow rearranging withing the | 
 |         // folder. | 
 |         boolean addNewPage = !(options.isAccessibleDrag && dragObject.dragSource != this); | 
 |  | 
 |         if (addNewPage) { | 
 |             mDeferRemoveExtraEmptyScreen = false; | 
 |             addExtraEmptyScreenOnDrag(); | 
 |  | 
 |             if (dragObject.dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET | 
 |                     && dragObject.dragSource != this) { | 
 |                 // When dragging a widget from different source, move to a page which has | 
 |                 // enough space to place this widget (after rearranging/resizing). We special case | 
 |                 // widgets as they cannot be placed inside a folder. | 
 |                 // Start at the current page and search right (on LTR) until finding a page with | 
 |                 // enough space. Since an empty screen is the furthest right, a page must be found. | 
 |                 int currentPage = getPageNearestToCenterOfScreen(); | 
 |                 for (int pageIndex = currentPage; pageIndex < getPageCount(); pageIndex++) { | 
 |                     CellLayout page = (CellLayout) getPageAt(pageIndex); | 
 |                     if (page.hasReorderSolution(dragObject.dragInfo)) { | 
 |                         setCurrentPage(pageIndex); | 
 |                         break; | 
 |                     } | 
 |                 } | 
 |             } | 
 |         } | 
 |  | 
 |         // Always enter the spring loaded mode | 
 |         mLauncher.getStateManager().goToState(SPRING_LOADED); | 
 |     } | 
 |  | 
 |     public void deferRemoveExtraEmptyScreen() { | 
 |         mDeferRemoveExtraEmptyScreen = true; | 
 |     } | 
 |  | 
 |     @Override | 
 |     public void onDragEnd() { | 
 |         if (ENFORCE_DRAG_EVENT_ORDER) { | 
 |             enforceDragParity("onDragEnd", 0, 0); | 
 |         } | 
 |  | 
 |         if (!mDeferRemoveExtraEmptyScreen) { | 
 |             removeExtraEmptyScreen(true, mDragSourceInternal != null); | 
 |         } | 
 |  | 
 |         updateChildrenLayersEnabled(); | 
 |         mDragInfo = null; | 
 |         mOutlineProvider = null; | 
 |         mDragSourceInternal = null; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Initializes various states for this workspace. | 
 |      */ | 
 |     protected void initWorkspace() { | 
 |         mCurrentPage = DEFAULT_PAGE; | 
 |         setClipToPadding(false); | 
 |  | 
 |         setupLayoutTransition(); | 
 |  | 
 |         // Set the wallpaper dimensions when Launcher starts up | 
 |         setWallpaperDimension(); | 
 |     } | 
 |  | 
 |     private void setupLayoutTransition() { | 
 |         // We want to show layout transitions when pages are deleted, to close the gap. | 
 |         mLayoutTransition = new LayoutTransition(); | 
 |         mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING); | 
 |         mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); | 
 |         mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING); | 
 |         mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING); | 
 |         setLayoutTransition(mLayoutTransition); | 
 |     } | 
 |  | 
 |     void enableLayoutTransitions() { | 
 |         setLayoutTransition(mLayoutTransition); | 
 |     } | 
 |     void disableLayoutTransitions() { | 
 |         setLayoutTransition(null); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public void onViewAdded(View child) { | 
 |         if (!(child instanceof CellLayout)) { | 
 |             throw new IllegalArgumentException("A Workspace can only have CellLayout children."); | 
 |         } | 
 |         CellLayout cl = ((CellLayout) child); | 
 |         cl.setOnInterceptTouchListener(this); | 
 |         cl.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); | 
 |         super.onViewAdded(child); | 
 |     } | 
 |  | 
 |     public boolean isTouchActive() { | 
 |         return mTouchState != TOUCH_STATE_REST; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Initializes and binds the first page | 
 |      * @param qsb an existing qsb to recycle or null. | 
 |      */ | 
 |     public void bindAndInitFirstWorkspaceScreen(View qsb) { | 
 |         if (!FeatureFlags.QSB_ON_FIRST_SCREEN) { | 
 |             return; | 
 |         } | 
 |         // Add the first page | 
 |         CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, 0); | 
 |         // Always add a QSB on the first screen. | 
 |         if (qsb == null) { | 
 |             // In transposed layout, we add the QSB in the Grid. As workspace does not touch the | 
 |             // edges, we do not need a full width QSB. | 
 |             qsb = LayoutInflater.from(getContext()) | 
 |                     .inflate(R.layout.search_container_workspace,firstPage, false); | 
 |         } | 
 |  | 
 |         CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, firstPage.getCountX(), 1); | 
 |         lp.canReorder = false; | 
 |         if (!firstPage.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true)) { | 
 |             Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout"); | 
 |         } | 
 |     } | 
 |  | 
 |     public void removeAllWorkspaceScreens() { | 
 |         // Disable all layout transitions before removing all pages to ensure that we don't get the | 
 |         // transition animations competing with us changing the scroll when we add pages | 
 |         disableLayoutTransitions(); | 
 |  | 
 |         // Recycle the QSB widget | 
 |         View qsb = findViewById(R.id.search_container_workspace); | 
 |         if (qsb != null) { | 
 |             ((ViewGroup) qsb.getParent()).removeView(qsb); | 
 |         } | 
 |  | 
 |         // Remove the pages and clear the screen models | 
 |         removeFolderListeners(); | 
 |         removeAllViews(); | 
 |         mScreenOrder.clear(); | 
 |         mWorkspaceScreens.clear(); | 
 |  | 
 |         // Ensure that the first page is always present | 
 |         bindAndInitFirstWorkspaceScreen(qsb); | 
 |  | 
 |         // Re-enable the layout transitions | 
 |         enableLayoutTransitions(); | 
 |     } | 
 |  | 
 |     public void insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) { | 
 |         // Find the index to insert this view into.  If the empty screen exists, then | 
 |         // insert it before that. | 
 |         int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID); | 
 |         if (insertIndex < 0) { | 
 |             insertIndex = mScreenOrder.size(); | 
 |         } | 
 |         insertNewWorkspaceScreen(screenId, insertIndex); | 
 |     } | 
 |  | 
 |     public void insertNewWorkspaceScreen(long screenId) { | 
 |         insertNewWorkspaceScreen(screenId, getChildCount()); | 
 |     } | 
 |  | 
 |     public CellLayout insertNewWorkspaceScreen(long screenId, int insertIndex) { | 
 |         if (mWorkspaceScreens.containsKey(screenId)) { | 
 |             throw new RuntimeException("Screen id " + screenId + " already exists!"); | 
 |         } | 
 |  | 
 |         // Inflate the cell layout, but do not add it automatically so that we can get the newly | 
 |         // created CellLayout. | 
 |         CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate( | 
 |                         R.layout.workspace_screen, this, false /* attachToRoot */); | 
 |         newScreen.getShortcutsAndWidgets().setId(R.id.workspace_page_container); | 
 |         int paddingLeftRight = mLauncher.getDeviceProfile().cellLayoutPaddingLeftRightPx; | 
 |         int paddingBottom = mLauncher.getDeviceProfile().cellLayoutBottomPaddingPx; | 
 |         newScreen.setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom); | 
 |  | 
 |         mWorkspaceScreens.put(screenId, newScreen); | 
 |         mScreenOrder.add(insertIndex, screenId); | 
 |         addView(newScreen, insertIndex); | 
 |         mStateTransitionAnimation.applyChildState( | 
 |                 mLauncher.getStateManager().getState(), newScreen, insertIndex); | 
 |  | 
 |         if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) { | 
 |             newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG); | 
 |         } | 
 |  | 
 |         return newScreen; | 
 |     } | 
 |  | 
 |     public void addExtraEmptyScreenOnDrag() { | 
 |         boolean lastChildOnScreen = false; | 
 |         boolean childOnFinalScreen = false; | 
 |  | 
 |         // Cancel any pending removal of empty screen | 
 |         mRemoveEmptyScreenRunnable = null; | 
 |  | 
 |         if (mDragSourceInternal != null) { | 
 |             if (mDragSourceInternal.getChildCount() == 1) { | 
 |                 lastChildOnScreen = true; | 
 |             } | 
 |             CellLayout cl = (CellLayout) mDragSourceInternal.getParent(); | 
 |             if (indexOfChild(cl) == getChildCount() - 1) { | 
 |                 childOnFinalScreen = true; | 
 |             } | 
 |         } | 
 |  | 
 |         // If this is the last item on the final screen | 
 |         if (lastChildOnScreen && childOnFinalScreen) { | 
 |             return; | 
 |         } | 
 |         if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) { | 
 |             insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID); | 
 |         } | 
 |     } | 
 |  | 
 |     public boolean addExtraEmptyScreen() { | 
 |         if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) { | 
 |             insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID); | 
 |             return true; | 
 |         } | 
 |         return false; | 
 |     } | 
 |  | 
 |     private void convertFinalScreenToEmptyScreenIfNecessary() { | 
 |         if (mLauncher.isWorkspaceLoading()) { | 
 |             // Invalid and dangerous operation if workspace is loading | 
 |             return; | 
 |         } | 
 |  | 
 |         if (hasExtraEmptyScreen() || mScreenOrder.size() == 0) return; | 
 |         long finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1); | 
 |  | 
 |         CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId); | 
 |  | 
 |         // If the final screen is empty, convert it to the extra empty screen | 
 |         if (finalScreen.getShortcutsAndWidgets().getChildCount() == 0 && | 
 |                 !finalScreen.isDropPending()) { | 
 |             mWorkspaceScreens.remove(finalScreenId); | 
 |             mScreenOrder.remove(finalScreenId); | 
 |  | 
 |             // if this is the last screen, convert it to the empty screen | 
 |             mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen); | 
 |             mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID); | 
 |  | 
 |             // Update the model if we have changed any screens | 
 |             LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder); | 
 |         } | 
 |     } | 
 |  | 
 |     public void removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens) { | 
 |         removeExtraEmptyScreenDelayed(animate, null, 0, stripEmptyScreens); | 
 |     } | 
 |  | 
 |     public void removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete, | 
 |             final int delay, final boolean stripEmptyScreens) { | 
 |         if (mLauncher.isWorkspaceLoading()) { | 
 |             // Don't strip empty screens if the workspace is still loading | 
 |             return; | 
 |         } | 
 |  | 
 |         if (delay > 0) { | 
 |             postDelayed(new Runnable() { | 
 |                 @Override | 
 |                 public void run() { | 
 |                     removeExtraEmptyScreenDelayed(animate, onComplete, 0, stripEmptyScreens); | 
 |                 } | 
 |             }, delay); | 
 |             return; | 
 |         } | 
 |  | 
 |         convertFinalScreenToEmptyScreenIfNecessary(); | 
 |         if (hasExtraEmptyScreen()) { | 
 |             int emptyIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID); | 
 |             if (getNextPage() == emptyIndex) { | 
 |                 snapToPage(getNextPage() - 1, SNAP_OFF_EMPTY_SCREEN_DURATION); | 
 |                 fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION, | 
 |                         onComplete, stripEmptyScreens); | 
 |             } else { | 
 |                 snapToPage(getNextPage(), 0); | 
 |                 fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION, | 
 |                         onComplete, stripEmptyScreens); | 
 |             } | 
 |             return; | 
 |         } else if (stripEmptyScreens) { | 
 |             // If we're not going to strip the empty screens after removing | 
 |             // the extra empty screen, do it right away. | 
 |             stripEmptyScreens(); | 
 |         } | 
 |  | 
 |         if (onComplete != null) { | 
 |             onComplete.run(); | 
 |         } | 
 |     } | 
 |  | 
 |     private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete, | 
 |             final boolean stripEmptyScreens) { | 
 |         // XXX: Do we need to update LM workspace screens below? | 
 |         PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f); | 
 |         PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", 0f); | 
 |  | 
 |         final CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID); | 
 |  | 
 |         mRemoveEmptyScreenRunnable = new Runnable() { | 
 |             @Override | 
 |             public void run() { | 
 |                 if (hasExtraEmptyScreen()) { | 
 |                     mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID); | 
 |                     mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID); | 
 |                     removeView(cl); | 
 |                     if (stripEmptyScreens) { | 
 |                         stripEmptyScreens(); | 
 |                     } | 
 |                     // Update the page indicator to reflect the removed page. | 
 |                     showPageIndicatorAtCurrentScroll(); | 
 |                 } | 
 |             } | 
 |         }; | 
 |  | 
 |         ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(cl, alpha, bgAlpha); | 
 |         oa.setDuration(duration); | 
 |         oa.setStartDelay(delay); | 
 |         oa.addListener(new AnimatorListenerAdapter() { | 
 |             @Override | 
 |             public void onAnimationEnd(Animator animation) { | 
 |                 if (mRemoveEmptyScreenRunnable != null) { | 
 |                     mRemoveEmptyScreenRunnable.run(); | 
 |                 } | 
 |                 if (onComplete != null) { | 
 |                     onComplete.run(); | 
 |                 } | 
 |             } | 
 |         }); | 
 |         oa.start(); | 
 |     } | 
 |  | 
 |     public boolean hasExtraEmptyScreen() { | 
 |         return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && getChildCount() > 1; | 
 |     } | 
 |  | 
 |     public long commitExtraEmptyScreen() { | 
 |         if (mLauncher.isWorkspaceLoading()) { | 
 |             // Invalid and dangerous operation if workspace is loading | 
 |             return -1; | 
 |         } | 
 |  | 
 |         CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID); | 
 |         mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID); | 
 |         mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID); | 
 |  | 
 |         long newId = LauncherSettings.Settings.call(getContext().getContentResolver(), | 
 |                 LauncherSettings.Settings.METHOD_NEW_SCREEN_ID) | 
 |                 .getLong(LauncherSettings.Settings.EXTRA_VALUE); | 
 |         mWorkspaceScreens.put(newId, cl); | 
 |         mScreenOrder.add(newId); | 
 |  | 
 |         // Update the model for the new screen | 
 |         LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder); | 
 |  | 
 |         return newId; | 
 |     } | 
 |  | 
 |     public CellLayout getScreenWithId(long screenId) { | 
 |         return mWorkspaceScreens.get(screenId); | 
 |     } | 
 |  | 
 |     public long getIdForScreen(CellLayout layout) { | 
 |         int index = mWorkspaceScreens.indexOfValue(layout); | 
 |         if (index != -1) { | 
 |             return mWorkspaceScreens.keyAt(index); | 
 |         } | 
 |         return -1; | 
 |     } | 
 |  | 
 |     public int getPageIndexForScreenId(long screenId) { | 
 |         return indexOfChild(mWorkspaceScreens.get(screenId)); | 
 |     } | 
 |  | 
 |     public long getScreenIdForPageIndex(int index) { | 
 |         if (0 <= index && index < mScreenOrder.size()) { | 
 |             return mScreenOrder.get(index); | 
 |         } | 
 |         return -1; | 
 |     } | 
 |  | 
 |     public ArrayList<Long> getScreenOrder() { | 
 |         return mScreenOrder; | 
 |     } | 
 |  | 
 |     public void stripEmptyScreens() { | 
 |         if (mLauncher.isWorkspaceLoading()) { | 
 |             // Don't strip empty screens if the workspace is still loading. | 
 |             // This is dangerous and can result in data loss. | 
 |             return; | 
 |         } | 
 |  | 
 |         if (isPageInTransition()) { | 
 |             mStripScreensOnPageStopMoving = true; | 
 |             return; | 
 |         } | 
 |  | 
 |         int currentPage = getNextPage(); | 
 |         ArrayList<Long> removeScreens = new ArrayList<>(); | 
 |         int total = mWorkspaceScreens.size(); | 
 |         for (int i = 0; i < total; i++) { | 
 |             long id = mWorkspaceScreens.keyAt(i); | 
 |             CellLayout cl = mWorkspaceScreens.valueAt(i); | 
 |             // FIRST_SCREEN_ID can never be removed. | 
 |             if ((!FeatureFlags.QSB_ON_FIRST_SCREEN || id > FIRST_SCREEN_ID) | 
 |                     && cl.getShortcutsAndWidgets().getChildCount() == 0) { | 
 |                 removeScreens.add(id); | 
 |             } | 
 |         } | 
 |  | 
 |         boolean isInAccessibleDrag = mLauncher.getAccessibilityDelegate().isInAccessibleDrag(); | 
 |  | 
 |         // We enforce at least one page to add new items to. In the case that we remove the last | 
 |         // such screen, we convert the last screen to the empty screen | 
 |         int minScreens = 1; | 
 |  | 
 |         int pageShift = 0; | 
 |         for (Long id: removeScreens) { | 
 |             CellLayout cl = mWorkspaceScreens.get(id); | 
 |             mWorkspaceScreens.remove(id); | 
 |             mScreenOrder.remove(id); | 
 |  | 
 |             if (getChildCount() > minScreens) { | 
 |                 if (indexOfChild(cl) < currentPage) { | 
 |                     pageShift++; | 
 |                 } | 
 |  | 
 |                 if (isInAccessibleDrag) { | 
 |                     cl.enableAccessibleDrag(false, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG); | 
 |                 } | 
 |  | 
 |                 removeView(cl); | 
 |             } else { | 
 |                 // if this is the last screen, convert it to the empty screen | 
 |                 mRemoveEmptyScreenRunnable = null; | 
 |                 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl); | 
 |                 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID); | 
 |             } | 
 |         } | 
 |  | 
 |         if (!removeScreens.isEmpty()) { | 
 |             // Update the model if we have changed any screens | 
 |             LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder); | 
 |         } | 
 |  | 
 |         if (pageShift >= 0) { | 
 |             setCurrentPage(currentPage - pageShift); | 
 |         } | 
 |     } | 
 |  | 
 |     /** | 
 |      * At bind time, we use the rank (screenId) to compute x and y for hotseat items. | 
 |      * See {@link #addInScreen}. | 
 |      */ | 
 |     public void addInScreenFromBind(View child, ItemInfo info) { | 
 |         int x = info.cellX; | 
 |         int y = info.cellY; | 
 |         if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { | 
 |             int screenId = (int) info.screenId; | 
 |             x = mLauncher.getHotseat().getCellXFromOrder(screenId); | 
 |             y = mLauncher.getHotseat().getCellYFromOrder(screenId); | 
 |         } | 
 |         addInScreen(child, info.container, info.screenId, x, y, info.spanX, info.spanY); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Adds the specified child in the specified screen based on the {@param info} | 
 |      * See {@link #addInScreen(View, long, long, int, int, int, int)}. | 
 |      */ | 
 |     public void addInScreen(View child, ItemInfo info) { | 
 |         addInScreen(child, info.container, info.screenId, info.cellX, info.cellY, | 
 |                 info.spanX, info.spanY); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Adds the specified child in the specified screen. The position and dimension of | 
 |      * the child are defined by x, y, spanX and spanY. | 
 |      * | 
 |      * @param child The child to add in one of the workspace's screens. | 
 |      * @param screenId The screen in which to add the child. | 
 |      * @param x The X position of the child in the screen's grid. | 
 |      * @param y The Y position of the child in the screen's grid. | 
 |      * @param spanX The number of cells spanned horizontally by the child. | 
 |      * @param spanY The number of cells spanned vertically by the child. | 
 |      */ | 
 |     private void addInScreen(View child, long container, long screenId, int x, int y, | 
 |             int spanX, int spanY) { | 
 |         if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { | 
 |             if (getScreenWithId(screenId) == null) { | 
 |                 Log.e(TAG, "Skipping child, screenId " + screenId + " not found"); | 
 |                 // DEBUGGING - Print out the stack trace to see where we are adding from | 
 |                 new Throwable().printStackTrace(); | 
 |                 return; | 
 |             } | 
 |         } | 
 |         if (screenId == EXTRA_EMPTY_SCREEN_ID) { | 
 |             // This should never happen | 
 |             throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID"); | 
 |         } | 
 |  | 
 |         final CellLayout layout; | 
 |         if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { | 
 |             layout = mLauncher.getHotseat().getLayout(); | 
 |  | 
 |             // Hide folder title in the hotseat | 
 |             if (child instanceof FolderIcon) { | 
 |                 ((FolderIcon) child).setTextVisible(false); | 
 |             } | 
 |         } else { | 
 |             // Show folder title if not in the hotseat | 
 |             if (child instanceof FolderIcon) { | 
 |                 ((FolderIcon) child).setTextVisible(true); | 
 |             } | 
 |             layout = getScreenWithId(screenId); | 
 |         } | 
 |  | 
 |         ViewGroup.LayoutParams genericLp = child.getLayoutParams(); | 
 |         CellLayout.LayoutParams lp; | 
 |         if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) { | 
 |             lp = new CellLayout.LayoutParams(x, y, spanX, spanY); | 
 |         } else { | 
 |             lp = (CellLayout.LayoutParams) genericLp; | 
 |             lp.cellX = x; | 
 |             lp.cellY = y; | 
 |             lp.cellHSpan = spanX; | 
 |             lp.cellVSpan = spanY; | 
 |         } | 
 |  | 
 |         if (spanX < 0 && spanY < 0) { | 
 |             lp.isLockedToGrid = false; | 
 |         } | 
 |  | 
 |         // Get the canonical child id to uniquely represent this view in this screen | 
 |         ItemInfo info = (ItemInfo) child.getTag(); | 
 |         int childId = mLauncher.getViewIdForItem(info); | 
 |  | 
 |         boolean markCellsAsOccupied = !(child instanceof Folder); | 
 |         if (!layout.addViewToCellLayout(child, -1, childId, lp, markCellsAsOccupied)) { | 
 |             // TODO: This branch occurs when the workspace is adding views | 
 |             // outside of the defined grid | 
 |             // maybe we should be deleting these items from the LauncherModel? | 
 |             Log.e(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout"); | 
 |         } | 
 |  | 
 |         child.setHapticFeedbackEnabled(false); | 
 |         child.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE); | 
 |         if (child instanceof DropTarget) { | 
 |             mDragController.addDropTarget((DropTarget) child); | 
 |         } | 
 |     } | 
 |  | 
 |     /** | 
 |      * Called directly from a CellLayout (not by the framework), after we've been added as a | 
 |      * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout | 
 |      * that it should intercept touch events, which is not something that is normally supported. | 
 |      */ | 
 |     @SuppressLint("ClickableViewAccessibility") | 
 |     @Override | 
 |     public boolean onTouch(View v, MotionEvent event) { | 
 |         return shouldConsumeTouch(v); | 
 |     } | 
 |  | 
 |     private boolean shouldConsumeTouch(View v) { | 
 |         return !workspaceIconsCanBeDragged() | 
 |                 || (!workspaceInModalState() && indexOfChild(v) != mCurrentPage); | 
 |     } | 
 |  | 
 |     public boolean isSwitchingState() { | 
 |         return mIsSwitchingState; | 
 |     } | 
 |  | 
 |     /** This differs from isSwitchingState in that we take into account how far the transition | 
 |      *  has completed. */ | 
 |     public boolean isFinishedSwitchingState() { | 
 |         return !mIsSwitchingState | 
 |                 || (mTransitionProgress > FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public boolean dispatchUnhandledMove(View focused, int direction) { | 
 |         if (workspaceInModalState() || !isFinishedSwitchingState()) { | 
 |             // when the home screens are shrunken, shouldn't allow side-scrolling | 
 |             return false; | 
 |         } | 
 |         return super.dispatchUnhandledMove(focused, direction); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public boolean onInterceptTouchEvent(MotionEvent ev) { | 
 |         if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { | 
 |             mXDown = ev.getX(); | 
 |             mYDown = ev.getY(); | 
 |         } | 
 |         return super.onInterceptTouchEvent(ev); | 
 |     } | 
 |  | 
 |     @Override | 
 |     protected void determineScrollingStart(MotionEvent ev) { | 
 |         if (!isFinishedSwitchingState()) return; | 
 |  | 
 |         float deltaX = ev.getX() - mXDown; | 
 |         float absDeltaX = Math.abs(deltaX); | 
 |         float absDeltaY = Math.abs(ev.getY() - mYDown); | 
 |  | 
 |         if (Float.compare(absDeltaX, 0f) == 0) return; | 
 |  | 
 |         float slope = absDeltaY / absDeltaX; | 
 |         float theta = (float) Math.atan(slope); | 
 |  | 
 |         if (absDeltaX > mTouchSlop || absDeltaY > mTouchSlop) { | 
 |             cancelCurrentPageLongPress(); | 
 |         } | 
 |  | 
 |         if (theta > MAX_SWIPE_ANGLE) { | 
 |             // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace | 
 |             return; | 
 |         } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) { | 
 |             // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to | 
 |             // increase the touch slop to make it harder to begin scrolling the workspace. This | 
 |             // results in vertically scrolling widgets to more easily. The higher the angle, the | 
 |             // more we increase touch slop. | 
 |             theta -= START_DAMPING_TOUCH_SLOP_ANGLE; | 
 |             float extraRatio = (float) | 
 |                     Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE))); | 
 |             super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio); | 
 |         } else { | 
 |             // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special | 
 |             super.determineScrollingStart(ev); | 
 |         } | 
 |     } | 
 |  | 
 |     protected void onPageBeginTransition() { | 
 |         super.onPageBeginTransition(); | 
 |         updateChildrenLayersEnabled(); | 
 |     } | 
 |  | 
 |     protected void onPageEndTransition() { | 
 |         super.onPageEndTransition(); | 
 |         updateChildrenLayersEnabled(); | 
 |  | 
 |         if (mDragController.isDragging()) { | 
 |             if (workspaceInModalState()) { | 
 |                 // If we are in springloaded mode, then force an event to check if the current touch | 
 |                 // is under a new page (to scroll to) | 
 |                 mDragController.forceTouchMove(); | 
 |             } | 
 |         } | 
 |  | 
 |         if (mStripScreensOnPageStopMoving) { | 
 |             stripEmptyScreens(); | 
 |             mStripScreensOnPageStopMoving = false; | 
 |         } | 
 |     } | 
 |  | 
 |     protected void onScrollInteractionBegin() { | 
 |         super.onScrollInteractionEnd(); | 
 |         mScrollInteractionBegan = true; | 
 |     } | 
 |  | 
 |     protected void onScrollInteractionEnd() { | 
 |         super.onScrollInteractionEnd(); | 
 |         mScrollInteractionBegan = false; | 
 |         if (mStartedSendingScrollEvents) { | 
 |             mStartedSendingScrollEvents = false; | 
 |             mLauncherOverlay.onScrollInteractionEnd(); | 
 |         } | 
 |     } | 
 |  | 
 |     public void setLauncherOverlay(LauncherOverlay overlay) { | 
 |         mLauncherOverlay = overlay; | 
 |         // A new overlay has been set. Reset event tracking | 
 |         mStartedSendingScrollEvents = false; | 
 |         onOverlayScrollChanged(0); | 
 |     } | 
 |  | 
 |  | 
 |     private boolean isScrollingOverlay() { | 
 |         return mLauncherOverlay != null && | 
 |                 ((mIsRtl && getUnboundedScrollX() > mMaxScrollX) || (!mIsRtl && getUnboundedScrollX() < 0)); | 
 |     } | 
 |  | 
 |     @Override | 
 |     protected void snapToDestination() { | 
 |         // If we're overscrolling the overlay, we make sure to immediately reset the PagedView | 
 |         // to it's baseline position instead of letting the overscroll settle. The overlay handles | 
 |         // it's own settling, and every gesture to the overlay should be self-contained and start | 
 |         // from 0, so we zero it out here. | 
 |         if (isScrollingOverlay()) { | 
 |             // We reset mWasInOverscroll so that PagedView doesn't zero out the overscroll | 
 |             // interaction when we call snapToPageImmediately. | 
 |             mWasInOverscroll = false; | 
 |             snapToPageImmediately(0); | 
 |         } else { | 
 |             super.snapToDestination(); | 
 |         } | 
 |     } | 
 |  | 
 |     @Override | 
 |     protected void onScrollChanged(int l, int t, int oldl, int oldt) { | 
 |         super.onScrollChanged(l, t, oldl, oldt); | 
 |  | 
 |         // Update the page indicator progress. | 
 |         boolean isTransitioning = mIsSwitchingState | 
 |                 || (getLayoutTransition() != null && getLayoutTransition().isRunning()); | 
 |         if (!isTransitioning) { | 
 |             showPageIndicatorAtCurrentScroll(); | 
 |         } | 
 |  | 
 |         updatePageAlphaValues(); | 
 |         enableHwLayersOnVisiblePages(); | 
 |     } | 
 |  | 
 |     public void showPageIndicatorAtCurrentScroll() { | 
 |         if (mPageIndicator != null) { | 
 |             mPageIndicator.setScroll(getScrollX(), computeMaxScrollX()); | 
 |         } | 
 |     } | 
 |  | 
 |     @Override | 
 |     protected void overScroll(float amount) { | 
 |         boolean shouldScrollOverlay = mLauncherOverlay != null && | 
 |                 ((amount <= 0 && !mIsRtl) || (amount >= 0 && mIsRtl)); | 
 |  | 
 |         boolean shouldZeroOverlay = mLauncherOverlay != null && mLastOverlayScroll != 0 && | 
 |                 ((amount >= 0 && !mIsRtl) || (amount <= 0 && mIsRtl)); | 
 |  | 
 |         if (shouldScrollOverlay) { | 
 |             if (!mStartedSendingScrollEvents && mScrollInteractionBegan) { | 
 |                 mStartedSendingScrollEvents = true; | 
 |                 mLauncherOverlay.onScrollInteractionBegin(); | 
 |             } | 
 |  | 
 |             mLastOverlayScroll = Math.abs(amount / getMeasuredWidth()); | 
 |             mLauncherOverlay.onScrollChange(mLastOverlayScroll, mIsRtl); | 
 |         } else { | 
 |             dampedOverScroll(amount); | 
 |         } | 
 |  | 
 |         if (shouldZeroOverlay) { | 
 |             mLauncherOverlay.onScrollChange(0, mIsRtl); | 
 |         } | 
 |     } | 
 |  | 
 |     @Override | 
 |     protected boolean shouldFlingForVelocity(int velocityX) { | 
 |         // When the overlay is moving, the fling or settle transition is controlled by the overlay. | 
 |         return Float.compare(Math.abs(mOverlayTranslation), 0) == 0 && | 
 |                 super.shouldFlingForVelocity(velocityX); | 
 |     } | 
 |  | 
 |     /** | 
 |      * The overlay scroll is being controlled locally, just update our overlay effect | 
 |      */ | 
 |     public void onOverlayScrollChanged(float scroll) { | 
 |         if (Float.compare(scroll, 1f) == 0) { | 
 |             if (!mOverlayShown) { | 
 |                 mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE, | 
 |                         Action.Direction.LEFT, ContainerType.WORKSPACE, 0); | 
 |             } | 
 |             mOverlayShown = true; | 
 |             // Not announcing the overlay page for accessibility since it announces itself. | 
 |         } else if (Float.compare(scroll, 0f) == 0) { | 
 |             if (mOverlayShown) { | 
 |                 mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE, | 
 |                         Action.Direction.RIGHT, ContainerType.WORKSPACE, -1); | 
 |             } else if (Float.compare(mOverlayTranslation, 0f) != 0) { | 
 |                 // When arriving to 0 overscroll from non-zero overscroll, announce page for | 
 |                 // accessibility since default announcements were disabled while in overscroll | 
 |                 // state. | 
 |                 // Not doing this if mOverlayShown because in that case the accessibility service | 
 |                 // will announce the launcher window description upon regaining focus after | 
 |                 // switching from the overlay screen. | 
 |                 announcePageForAccessibility(); | 
 |             } | 
 |             mOverlayShown = false; | 
 |             tryRunOverlayCallback(); | 
 |         } | 
 |  | 
 |         float offset = 0f; | 
 |  | 
 |         scroll = Math.max(scroll - offset, 0); | 
 |         scroll = Math.min(1, scroll / (1 - offset)); | 
 |  | 
 |         float alpha = 1 - Interpolators.DEACCEL_3.getInterpolation(scroll); | 
 |         float transX = mLauncher.getDragLayer().getMeasuredWidth() * scroll; | 
 |  | 
 |         if (mIsRtl) { | 
 |             transX = -transX; | 
 |         } | 
 |         mOverlayTranslation = transX; | 
 |  | 
 |         // TODO(adamcohen): figure out a final effect here. We may need to recommend | 
 |         // different effects based on device performance. On at least one relatively high-end | 
 |         // device I've tried, translating the launcher causes things to get quite laggy. | 
 |         mLauncher.getDragLayer().setTranslationX(transX); | 
 |         mLauncher.getDragLayer().getAlphaProperty(ALPHA_INDEX_OVERLAY).setValue(alpha); | 
 |     } | 
 |  | 
 |     /** | 
 |      * @return false if the callback is still pending | 
 |      */ | 
 |     private boolean tryRunOverlayCallback() { | 
 |         if (mOnOverlayHiddenCallback == null) { | 
 |             // Return true as no callback is pending. This is used by OnWindowFocusChangeListener | 
 |             // to remove itself if multiple focus handles were added. | 
 |             return true; | 
 |         } | 
 |         if (mOverlayShown || !hasWindowFocus()) { | 
 |             return false; | 
 |         } | 
 |  | 
 |         mOnOverlayHiddenCallback.run(); | 
 |         mOnOverlayHiddenCallback = null; | 
 |         return true; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Runs the given callback when the minus one overlay is hidden. Specifically, it is run | 
 |      * when launcher's window has focus and the overlay is no longer being shown. If a callback | 
 |      * is already present, the new callback will chain off it so both are run. | 
 |      * | 
 |      * @return Whether the callback was deferred. | 
 |      */ | 
 |     public boolean runOnOverlayHidden(Runnable callback) { | 
 |         if (mOnOverlayHiddenCallback == null) { | 
 |             mOnOverlayHiddenCallback = callback; | 
 |         } else { | 
 |             // Chain the new callback onto the previous callback(s). | 
 |             Runnable oldCallback = mOnOverlayHiddenCallback; | 
 |             mOnOverlayHiddenCallback = () -> { | 
 |                 oldCallback.run(); | 
 |                 callback.run(); | 
 |             }; | 
 |         } | 
 |         if (!tryRunOverlayCallback()) { | 
 |             ViewTreeObserver observer = getViewTreeObserver(); | 
 |             if (observer != null && observer.isAlive()) { | 
 |                 observer.addOnWindowFocusChangeListener( | 
 |                         new ViewTreeObserver.OnWindowFocusChangeListener() { | 
 |                             @Override | 
 |                             public void onWindowFocusChanged(boolean hasFocus) { | 
 |                                 if (tryRunOverlayCallback() && observer.isAlive()) { | 
 |                                     observer.removeOnWindowFocusChangeListener(this); | 
 |                                 } | 
 |                             }}); | 
 |             } | 
 |             return true; | 
 |         } | 
 |         return false; | 
 |     } | 
 |  | 
 |     @Override | 
 |     protected void notifyPageSwitchListener(int prevPage) { | 
 |         super.notifyPageSwitchListener(prevPage); | 
 |         if (prevPage != mCurrentPage) { | 
 |             int swipeDirection = (prevPage < mCurrentPage) ? Action.Direction.RIGHT : Action.Direction.LEFT; | 
 |             mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE, | 
 |                     swipeDirection, ContainerType.WORKSPACE, prevPage); | 
 |         } | 
 |     } | 
 |  | 
 |     protected void setWallpaperDimension() { | 
 |         Utilities.THREAD_POOL_EXECUTOR.execute(new Runnable() { | 
 |             @Override | 
 |             public void run() { | 
 |                 final Point size = LauncherAppState.getIDP(getContext()).defaultWallpaperSize; | 
 |                 if (size.x != mWallpaperManager.getDesiredMinimumWidth() | 
 |                         || size.y != mWallpaperManager.getDesiredMinimumHeight()) { | 
 |                     mWallpaperManager.suggestDesiredDimensions(size.x, size.y); | 
 |                 } | 
 |             } | 
 |         }); | 
 |     } | 
 |  | 
 |     public void lockWallpaperToDefaultPage() { | 
 |         mWallpaperOffset.setLockToDefaultPage(true); | 
 |     } | 
 |  | 
 |     public void unlockWallpaperFromDefaultPageOnNextLayout() { | 
 |         if (mWallpaperOffset.isLockedToDefaultPage()) { | 
 |             mUnlockWallpaperFromDefaultPageOnLayout = true; | 
 |             requestLayout(); | 
 |         } | 
 |     } | 
 |  | 
 |     @Override | 
 |     public void computeScroll() { | 
 |         super.computeScroll(); | 
 |         mWallpaperOffset.syncWithScroll(); | 
 |     } | 
 |  | 
 |     public void computeScrollWithoutInvalidation() { | 
 |         computeScrollHelper(false); | 
 |     } | 
 |  | 
 |     @Override | 
 |     protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) { | 
 |         if (!isSwitchingState()) { | 
 |             super.determineScrollingStart(ev, touchSlopScale); | 
 |         } | 
 |     } | 
 |  | 
 |     @Override | 
 |     public void announceForAccessibility(CharSequence text) { | 
 |         // Don't announce if apps is on top of us. | 
 |         if (!mLauncher.isInState(ALL_APPS)) { | 
 |             super.announceForAccessibility(text); | 
 |         } | 
 |     } | 
 |  | 
 |     public void showOutlinesTemporarily() { | 
 |         if (!mIsPageInTransition && !isTouchActive()) { | 
 |             snapToPage(mCurrentPage); | 
 |         } | 
 |     } | 
 |  | 
 |     private void updatePageAlphaValues() { | 
 |         // We need to check the isDragging case because updatePageAlphaValues is called between | 
 |         // goToState(SPRING_LOADED) and onStartStateTransition. | 
 |         if (!workspaceInModalState() && !mIsSwitchingState && !mDragController.isDragging()) { | 
 |             int screenCenter = getScrollX() + getMeasuredWidth() / 2; | 
 |             for (int i = 0; i < getChildCount(); i++) { | 
 |                 CellLayout child = (CellLayout) getChildAt(i); | 
 |                 if (child != null) { | 
 |                     float scrollProgress = getScrollProgress(screenCenter, child, i); | 
 |                     float alpha = 1 - Math.abs(scrollProgress); | 
 |                     if (mWorkspaceFadeInAdjacentScreens) { | 
 |                         child.getShortcutsAndWidgets().setAlpha(alpha); | 
 |                     } else { | 
 |                         // Pages that are off-screen aren't important for accessibility. | 
 |                         child.getShortcutsAndWidgets().setImportantForAccessibility( | 
 |                                 alpha > 0 ? IMPORTANT_FOR_ACCESSIBILITY_AUTO | 
 |                                         : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); | 
 |                     } | 
 |                 } | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     protected void onAttachedToWindow() { | 
 |         super.onAttachedToWindow(); | 
 |         IBinder windowToken = getWindowToken(); | 
 |         mWallpaperOffset.setWindowToken(windowToken); | 
 |         computeScroll(); | 
 |         mDragController.setWindowToken(windowToken); | 
 |     } | 
 |  | 
 |     protected void onDetachedFromWindow() { | 
 |         super.onDetachedFromWindow(); | 
 |         mWallpaperOffset.setWindowToken(null); | 
 |     } | 
 |  | 
 |     @Override | 
 |     protected void onLayout(boolean changed, int left, int top, int right, int bottom) { | 
 |         if (mUnlockWallpaperFromDefaultPageOnLayout) { | 
 |             mWallpaperOffset.setLockToDefaultPage(false); | 
 |             mUnlockWallpaperFromDefaultPageOnLayout = false; | 
 |         } | 
 |         if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { | 
 |             mWallpaperOffset.syncWithScroll(); | 
 |             mWallpaperOffset.jumpToFinal(); | 
 |         } | 
 |         super.onLayout(changed, left, top, right, bottom); | 
 |         updatePageAlphaValues(); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public int getDescendantFocusability() { | 
 |         if (workspaceInModalState()) { | 
 |             return ViewGroup.FOCUS_BLOCK_DESCENDANTS; | 
 |         } | 
 |         return super.getDescendantFocusability(); | 
 |     } | 
 |  | 
 |     private boolean workspaceInModalState() { | 
 |         return !mLauncher.isInState(NORMAL); | 
 |     } | 
 |  | 
 |     /** Returns whether a drag should be allowed to be started from the current workspace state. */ | 
 |     public boolean workspaceIconsCanBeDragged() { | 
 |         return mLauncher.getStateManager().getState().workspaceIconsCanBeDragged; | 
 |     } | 
 |  | 
 |     private void updateChildrenLayersEnabled() { | 
 |         boolean enableChildrenLayers = mIsSwitchingState || isPageInTransition(); | 
 |  | 
 |         if (enableChildrenLayers != mChildrenLayersEnabled) { | 
 |             mChildrenLayersEnabled = enableChildrenLayers; | 
 |             if (mChildrenLayersEnabled) { | 
 |                 enableHwLayersOnVisiblePages(); | 
 |             } else { | 
 |                 for (int i = 0; i < getPageCount(); i++) { | 
 |                     final CellLayout cl = (CellLayout) getChildAt(i); | 
 |                     cl.enableHardwareLayer(false); | 
 |                 } | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     private void enableHwLayersOnVisiblePages() { | 
 |         if (mChildrenLayersEnabled) { | 
 |             final int screenCount = getChildCount(); | 
 |  | 
 |             final int[] visibleScreens = getVisibleChildrenRange(); | 
 |             int leftScreen = visibleScreens[0]; | 
 |             int rightScreen = visibleScreens[1]; | 
 |             if (mForceDrawAdjacentPages) { | 
 |                 // In overview mode, make sure that the two side pages are visible. | 
 |                 leftScreen = Utilities.boundToRange(getCurrentPage() - 1, 0, rightScreen); | 
 |                 rightScreen = Utilities.boundToRange(getCurrentPage() + 1, | 
 |                     leftScreen, getPageCount() - 1); | 
 |             } | 
 |  | 
 |             if (leftScreen == rightScreen) { | 
 |                 // make sure we're caching at least two pages always | 
 |                 if (rightScreen < screenCount - 1) { | 
 |                     rightScreen++; | 
 |                 } else if (leftScreen > 0) { | 
 |                     leftScreen--; | 
 |                 } | 
 |             } | 
 |  | 
 |             for (int i = 0; i < screenCount; i++) { | 
 |                 final CellLayout layout = (CellLayout) getPageAt(i); | 
 |                 // enable layers between left and right screen inclusive. | 
 |                 boolean enableLayer = leftScreen <= i && i <= rightScreen; | 
 |                 layout.enableHardwareLayer(enableLayer); | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     public void onWallpaperTap(MotionEvent ev) { | 
 |         final int[] position = mTempXY; | 
 |         getLocationOnScreen(position); | 
 |  | 
 |         int pointerIndex = ev.getActionIndex(); | 
 |         position[0] += (int) ev.getX(pointerIndex); | 
 |         position[1] += (int) ev.getY(pointerIndex); | 
 |  | 
 |         mWallpaperManager.sendWallpaperCommand(getWindowToken(), | 
 |                 ev.getAction() == MotionEvent.ACTION_UP | 
 |                         ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP, | 
 |                 position[0], position[1], 0, null); | 
 |     } | 
 |  | 
 |     public void prepareDragWithProvider(DragPreviewProvider outlineProvider) { | 
 |         mOutlineProvider = outlineProvider; | 
 |     } | 
 |  | 
 |     public void snapToPageFromOverView(int whichPage) { | 
 |         snapToPage(whichPage, OVERVIEW_TRANSITION_MS, Interpolators.ZOOM_IN); | 
 |     } | 
 |  | 
 |     private void onStartStateTransition(LauncherState state) { | 
 |         mIsSwitchingState = true; | 
 |         mTransitionProgress = 0; | 
 |  | 
 |         updateChildrenLayersEnabled(); | 
 |     } | 
 |  | 
 |     private void onEndStateTransition() { | 
 |         mIsSwitchingState = false; | 
 |         mForceDrawAdjacentPages = false; | 
 |         mTransitionProgress = 1; | 
 |  | 
 |         updateChildrenLayersEnabled(); | 
 |         updateAccessibilityFlags(); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Sets the current workspace {@link LauncherState} and updates the UI without any animations | 
 |      */ | 
 |     @Override | 
 |     public void setState(LauncherState toState) { | 
 |         onStartStateTransition(toState); | 
 |         mStateTransitionAnimation.setState(toState); | 
 |         onEndStateTransition(); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Sets the current workspace {@link LauncherState}, then animates the UI | 
 |      */ | 
 |     @Override | 
 |     public void setStateWithAnimation(LauncherState toState, | 
 |             AnimatorSetBuilder builder, AnimationConfig config) { | 
 |         StateTransitionListener listener = new StateTransitionListener(toState); | 
 |         mStateTransitionAnimation.setStateWithAnimation(toState, builder, config); | 
 |  | 
 |         // Invalidate the pages now, so that we have the visible pages before the | 
 |         // animation is started | 
 |         if (toState.hasMultipleVisiblePages) { | 
 |             mForceDrawAdjacentPages = true; | 
 |         } | 
 |         invalidate(); // This will call dispatchDraw(), which calls getVisiblePages(). | 
 |  | 
 |         ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1); | 
 |         stepAnimator.addUpdateListener(listener); | 
 |         stepAnimator.setDuration(config.duration); | 
 |         stepAnimator.addListener(listener); | 
 |         builder.play(stepAnimator); | 
 |     } | 
 |  | 
 |     public void updateAccessibilityFlags() { | 
 |         // TODO: Update the accessibility flags appropriately when dragging. | 
 |         int accessibilityFlag = mLauncher.getStateManager().getState().workspaceAccessibilityFlag; | 
 |         if (!mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) { | 
 |             int total = getPageCount(); | 
 |             for (int i = 0; i < total; i++) { | 
 |                 updateAccessibilityFlags(accessibilityFlag, (CellLayout) getPageAt(i)); | 
 |             } | 
 |             setImportantForAccessibility(accessibilityFlag); | 
 |         } | 
 |     } | 
 |  | 
 |     private void updateAccessibilityFlags(int accessibilityFlag, CellLayout page) { | 
 |         page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); | 
 |         page.getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag); | 
 |         page.setContentDescription(null); | 
 |         page.setAccessibilityDelegate(null); | 
 |     } | 
 |  | 
 |     public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) { | 
 |         View child = cellInfo.cell; | 
 |  | 
 |         mDragInfo = cellInfo; | 
 |         child.setVisibility(INVISIBLE); | 
 |  | 
 |         if (options.isAccessibleDrag) { | 
 |             mDragController.addDragListener(new AccessibleDragListenerAdapter( | 
 |                     this, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG) { | 
 |                 @Override | 
 |                 protected void enableAccessibleDrag(boolean enable) { | 
 |                     super.enableAccessibleDrag(enable); | 
 |                     setEnableForLayout(mLauncher.getHotseat().getLayout(),enable); | 
 |                 } | 
 |             }); | 
 |         } | 
 |  | 
 |         beginDragShared(child, this, options); | 
 |     } | 
 |  | 
 |     public void beginDragShared(View child, DragSource source, DragOptions options) { | 
 |         Object dragObject = child.getTag(); | 
 |         if (!(dragObject instanceof ItemInfo)) { | 
 |             String msg = "Drag started with a view that has no tag set. This " | 
 |                     + "will cause a crash (issue 11627249) down the line. " | 
 |                     + "View: " + child + "  tag: " + child.getTag(); | 
 |             throw new IllegalStateException(msg); | 
 |         } | 
 |         beginDragShared(child, source, (ItemInfo) dragObject, | 
 |                 new DragPreviewProvider(child), options); | 
 |     } | 
 |  | 
 |     public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject, | 
 |             DragPreviewProvider previewProvider, DragOptions dragOptions) { | 
 |         float iconScale = 1f; | 
 |         if (child instanceof BubbleTextView) { | 
 |             Drawable icon = ((BubbleTextView) child).getIcon(); | 
 |             if (icon instanceof FastBitmapDrawable) { | 
 |                 iconScale = ((FastBitmapDrawable) icon).getAnimatedScale(); | 
 |             } | 
 |         } | 
 |  | 
 |         child.clearFocus(); | 
 |         child.setPressed(false); | 
 |         mOutlineProvider = previewProvider; | 
 |  | 
 |         // The drag bitmap follows the touch point around on the screen | 
 |         final Bitmap b = previewProvider.createDragBitmap(); | 
 |         int halfPadding = previewProvider.previewPadding / 2; | 
 |  | 
 |         float scale = previewProvider.getScaleAndPosition(b, mTempXY); | 
 |         int dragLayerX = mTempXY[0]; | 
 |         int dragLayerY = mTempXY[1]; | 
 |  | 
 |         DeviceProfile grid = mLauncher.getDeviceProfile(); | 
 |         Point dragVisualizeOffset = null; | 
 |         Rect dragRect = null; | 
 |         if (child instanceof BubbleTextView) { | 
 |             dragRect = new Rect(); | 
 |             ((BubbleTextView) child).getIconBounds(dragRect); | 
 |             dragLayerY += dragRect.top; | 
 |             // Note: The dragRect is used to calculate drag layer offsets, but the | 
 |             // dragVisualizeOffset in addition to the dragRect (the size) to position the outline. | 
 |             dragVisualizeOffset = new Point(- halfPadding, halfPadding); | 
 |         } else if (child instanceof FolderIcon) { | 
 |             int previewSize = grid.folderIconSizePx; | 
 |             dragVisualizeOffset = new Point(- halfPadding, halfPadding - child.getPaddingTop()); | 
 |             dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize); | 
 |         } else if (previewProvider instanceof ShortcutDragPreviewProvider) { | 
 |             dragVisualizeOffset = new Point(- halfPadding, halfPadding); | 
 |         } | 
 |  | 
 |         // Clear the pressed state if necessary | 
 |         if (child instanceof BubbleTextView) { | 
 |             BubbleTextView icon = (BubbleTextView) child; | 
 |             icon.clearPressedBackground(); | 
 |         } | 
 |  | 
 |         if (child.getParent() instanceof ShortcutAndWidgetContainer) { | 
 |             mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent(); | 
 |         } | 
 |  | 
 |         if (child instanceof BubbleTextView && !dragOptions.isAccessibleDrag) { | 
 |             PopupContainerWithArrow popupContainer = PopupContainerWithArrow | 
 |                     .showForIcon((BubbleTextView) child); | 
 |             if (popupContainer != null) { | 
 |                 dragOptions.preDragCondition = popupContainer.createPreDragCondition(); | 
 |  | 
 |                 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis("dragging started"); | 
 |             } | 
 |         } | 
 |  | 
 |         DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, | 
 |                 dragObject, dragVisualizeOffset, dragRect, scale * iconScale, scale, dragOptions); | 
 |         dv.setIntrinsicIconScaleFactor(dragOptions.intrinsicIconScaleFactor); | 
 |         return dv; | 
 |     } | 
 |  | 
 |     private boolean transitionStateShouldAllowDrop() { | 
 |         return (!isSwitchingState() || mTransitionProgress > ALLOW_DROP_TRANSITION_PROGRESS) && | 
 |                 workspaceIconsCanBeDragged(); | 
 |     } | 
 |  | 
 |     /** | 
 |      * {@inheritDoc} | 
 |      */ | 
 |     @Override | 
 |     public boolean acceptDrop(DragObject d) { | 
 |         // If it's an external drop (e.g. from All Apps), check if it should be accepted | 
 |         CellLayout dropTargetLayout = mDropToLayout; | 
 |         if (d.dragSource != this) { | 
 |             // Don't accept the drop if we're not over a screen at time of drop | 
 |             if (dropTargetLayout == null) { | 
 |                 return false; | 
 |             } | 
 |             if (!transitionStateShouldAllowDrop()) return false; | 
 |  | 
 |             mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter); | 
 |  | 
 |             // We want the point to be mapped to the dragTarget. | 
 |             if (mLauncher.isHotseatLayout(dropTargetLayout)) { | 
 |                 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); | 
 |             } else { | 
 |                 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter); | 
 |             } | 
 |  | 
 |             int spanX; | 
 |             int spanY; | 
 |             if (mDragInfo != null) { | 
 |                 final CellLayout.CellInfo dragCellInfo = mDragInfo; | 
 |                 spanX = dragCellInfo.spanX; | 
 |                 spanY = dragCellInfo.spanY; | 
 |             } else { | 
 |                 spanX = d.dragInfo.spanX; | 
 |                 spanY = d.dragInfo.spanY; | 
 |             } | 
 |  | 
 |             int minSpanX = spanX; | 
 |             int minSpanY = spanY; | 
 |             if (d.dragInfo instanceof PendingAddWidgetInfo) { | 
 |                 minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX; | 
 |                 minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY; | 
 |             } | 
 |  | 
 |             mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], | 
 |                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout, | 
 |                     mTargetCell); | 
 |             float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], | 
 |                     mDragViewVisualCenter[1], mTargetCell); | 
 |             if (mCreateUserFolderOnDrop && willCreateUserFolder(d.dragInfo, | 
 |                     dropTargetLayout, mTargetCell, distance, true)) { | 
 |                 return true; | 
 |             } | 
 |  | 
 |             if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder(d.dragInfo, | 
 |                     dropTargetLayout, mTargetCell, distance)) { | 
 |                 return true; | 
 |             } | 
 |  | 
 |             int[] resultSpan = new int[2]; | 
 |             mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0], | 
 |                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, | 
 |                     null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP); | 
 |             boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0; | 
 |  | 
 |             // Don't accept the drop if there's no room for the item | 
 |             if (!foundCell) { | 
 |                 onNoCellFound(dropTargetLayout); | 
 |                 return false; | 
 |             } | 
 |         } | 
 |  | 
 |         long screenId = getIdForScreen(dropTargetLayout); | 
 |         if (screenId == EXTRA_EMPTY_SCREEN_ID) { | 
 |             commitExtraEmptyScreen(); | 
 |         } | 
 |  | 
 |         return true; | 
 |     } | 
 |  | 
 |     boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, | 
 |             float distance, boolean considerTimeout) { | 
 |         if (distance > mMaxDistanceForFolderCreation) return false; | 
 |         View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); | 
 |         return willCreateUserFolder(info, dropOverView, considerTimeout); | 
 |     } | 
 |  | 
 |     boolean willCreateUserFolder(ItemInfo info, View dropOverView, boolean considerTimeout) { | 
 |         if (dropOverView != null) { | 
 |             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams(); | 
 |             if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) { | 
 |                 return false; | 
 |             } | 
 |         } | 
 |  | 
 |         boolean hasntMoved = false; | 
 |         if (mDragInfo != null) { | 
 |             hasntMoved = dropOverView == mDragInfo.cell; | 
 |         } | 
 |  | 
 |         if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) { | 
 |             return false; | 
 |         } | 
 |  | 
 |         boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo); | 
 |         boolean willBecomeShortcut = | 
 |                 (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || | 
 |                         info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT || | 
 |                         info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT); | 
 |  | 
 |         return (aboveShortcut && willBecomeShortcut); | 
 |     } | 
 |  | 
 |     boolean willAddToExistingUserFolder(ItemInfo dragInfo, CellLayout target, int[] targetCell, | 
 |             float distance) { | 
 |         if (distance > mMaxDistanceForFolderCreation) return false; | 
 |         View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); | 
 |         return willAddToExistingUserFolder(dragInfo, dropOverView); | 
 |  | 
 |     } | 
 |     boolean willAddToExistingUserFolder(ItemInfo dragInfo, View dropOverView) { | 
 |         if (dropOverView != null) { | 
 |             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams(); | 
 |             if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) { | 
 |                 return false; | 
 |             } | 
 |         } | 
 |  | 
 |         if (dropOverView instanceof FolderIcon) { | 
 |             FolderIcon fi = (FolderIcon) dropOverView; | 
 |             if (fi.acceptDrop(dragInfo)) { | 
 |                 return true; | 
 |             } | 
 |         } | 
 |         return false; | 
 |     } | 
 |  | 
 |     boolean createUserFolderIfNecessary(View newView, long container, CellLayout target, | 
 |             int[] targetCell, float distance, boolean external, DragView dragView) { | 
 |         if (distance > mMaxDistanceForFolderCreation) return false; | 
 |         View v = target.getChildAt(targetCell[0], targetCell[1]); | 
 |  | 
 |         boolean hasntMoved = false; | 
 |         if (mDragInfo != null) { | 
 |             CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell); | 
 |             hasntMoved = (mDragInfo.cellX == targetCell[0] && | 
 |                     mDragInfo.cellY == targetCell[1]) && (cellParent == target); | 
 |         } | 
 |  | 
 |         if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false; | 
 |         mCreateUserFolderOnDrop = false; | 
 |         final long screenId = getIdForScreen(target); | 
 |  | 
 |         boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo); | 
 |         boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo); | 
 |  | 
 |         if (aboveShortcut && willBecomeShortcut) { | 
 |             ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag(); | 
 |             ShortcutInfo destInfo = (ShortcutInfo) v.getTag(); | 
 |             // if the drag started here, we need to remove it from the workspace | 
 |             if (!external) { | 
 |                 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); | 
 |             } | 
 |  | 
 |             Rect folderLocation = new Rect(); | 
 |             float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation); | 
 |             target.removeView(v); | 
 |  | 
 |             FolderIcon fi = | 
 |                 mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]); | 
 |             destInfo.cellX = -1; | 
 |             destInfo.cellY = -1; | 
 |             sourceInfo.cellX = -1; | 
 |             sourceInfo.cellY = -1; | 
 |  | 
 |             // If the dragView is null, we can't animate | 
 |             boolean animate = dragView != null; | 
 |             if (animate) { | 
 |                 // In order to keep everything continuous, we hand off the currently rendered | 
 |                 // folder background to the newly created icon. This preserves animation state. | 
 |                 fi.setFolderBackground(mFolderCreateBg); | 
 |                 mFolderCreateBg = new PreviewBackground(); | 
 |                 fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale | 
 |                 ); | 
 |             } else { | 
 |                 fi.prepareCreateAnimation(v); | 
 |                 fi.addItem(destInfo); | 
 |                 fi.addItem(sourceInfo); | 
 |             } | 
 |             return true; | 
 |         } | 
 |         return false; | 
 |     } | 
 |  | 
 |     boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell, | 
 |             float distance, DragObject d, boolean external) { | 
 |         if (distance > mMaxDistanceForFolderCreation) return false; | 
 |  | 
 |         View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); | 
 |         if (!mAddToExistingFolderOnDrop) return false; | 
 |         mAddToExistingFolderOnDrop = false; | 
 |  | 
 |         if (dropOverView instanceof FolderIcon) { | 
 |             FolderIcon fi = (FolderIcon) dropOverView; | 
 |             if (fi.acceptDrop(d.dragInfo)) { | 
 |                 fi.onDrop(d, false /* itemReturnedOnFailedDrop */); | 
 |  | 
 |                 // if the drag started here, we need to remove it from the workspace | 
 |                 if (!external) { | 
 |                     getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); | 
 |                 } | 
 |                 return true; | 
 |             } | 
 |         } | 
 |         return false; | 
 |     } | 
 |  | 
 |     @Override | 
 |     public void prepareAccessibilityDrop() { } | 
 |  | 
 |     public void onDrop(final DragObject d, DragOptions options) { | 
 |         mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter); | 
 |         CellLayout dropTargetLayout = mDropToLayout; | 
 |  | 
 |         // We want the point to be mapped to the dragTarget. | 
 |         if (dropTargetLayout != null) { | 
 |             if (mLauncher.isHotseatLayout(dropTargetLayout)) { | 
 |                 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); | 
 |             } else { | 
 |                 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter); | 
 |             } | 
 |         } | 
 |  | 
 |         boolean droppedOnOriginalCell = false; | 
 |  | 
 |         int snapScreen = -1; | 
 |         boolean resizeOnDrop = false; | 
 |         if (d.dragSource != this || mDragInfo == null) { | 
 |             final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0], | 
 |                     (int) mDragViewVisualCenter[1] }; | 
 |             onDropExternal(touchXY, dropTargetLayout, d); | 
 |         } else { | 
 |             final View cell = mDragInfo.cell; | 
 |             boolean droppedOnOriginalCellDuringTransition = false; | 
 |             Runnable onCompleteRunnable = null; | 
 |  | 
 |             if (dropTargetLayout != null && !d.cancelled) { | 
 |                 // Move internally | 
 |                 boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout); | 
 |                 boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout); | 
 |                 long container = hasMovedIntoHotseat ? | 
 |                         LauncherSettings.Favorites.CONTAINER_HOTSEAT : | 
 |                         LauncherSettings.Favorites.CONTAINER_DESKTOP; | 
 |                 long screenId = (mTargetCell[0] < 0) ? | 
 |                         mDragInfo.screenId : getIdForScreen(dropTargetLayout); | 
 |                 int spanX = mDragInfo != null ? mDragInfo.spanX : 1; | 
 |                 int spanY = mDragInfo != null ? mDragInfo.spanY : 1; | 
 |                 // First we find the cell nearest to point at which the item is | 
 |                 // dropped, without any consideration to whether there is an item there. | 
 |  | 
 |                 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int) | 
 |                         mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell); | 
 |                 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], | 
 |                         mDragViewVisualCenter[1], mTargetCell); | 
 |  | 
 |                 // If the item being dropped is a shortcut and the nearest drop | 
 |                 // cell also contains a shortcut, then create a folder with the two shortcuts. | 
 |                 if (createUserFolderIfNecessary(cell, container, | 
 |                         dropTargetLayout, mTargetCell, distance, false, d.dragView) || | 
 |                         addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, | 
 |                                 distance, d, false)) { | 
 |                     mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); | 
 |                     return; | 
 |                 } | 
 |  | 
 |                 // Aside from the special case where we're dropping a shortcut onto a shortcut, | 
 |                 // we need to find the nearest cell location that is vacant | 
 |                 ItemInfo item = d.dragInfo; | 
 |                 int minSpanX = item.spanX; | 
 |                 int minSpanY = item.spanY; | 
 |                 if (item.minSpanX > 0 && item.minSpanY > 0) { | 
 |                     minSpanX = item.minSpanX; | 
 |                     minSpanY = item.minSpanY; | 
 |                 } | 
 |  | 
 |                 droppedOnOriginalCell = item.screenId == screenId && item.container == container | 
 |                         && item.cellX == mTargetCell[0] && item.cellY == mTargetCell[1]; | 
 |                 droppedOnOriginalCellDuringTransition = droppedOnOriginalCell && mIsSwitchingState; | 
 |  | 
 |                 // When quickly moving an item, a user may accidentally rearrange their | 
 |                 // workspace. So instead we move the icon back safely to its original position. | 
 |                 boolean returnToOriginalCellToPreventShuffling = !isFinishedSwitchingState() | 
 |                         && !droppedOnOriginalCellDuringTransition && !dropTargetLayout | 
 |                         .isRegionVacant(mTargetCell[0], mTargetCell[1], spanX, spanY); | 
 |                 int[] resultSpan = new int[2]; | 
 |                 if (returnToOriginalCellToPreventShuffling) { | 
 |                     mTargetCell[0] = mTargetCell[1] = -1; | 
 |                 } else { | 
 |                     mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0], | 
 |                             (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell, | 
 |                             mTargetCell, resultSpan, CellLayout.MODE_ON_DROP); | 
 |                 } | 
 |  | 
 |                 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0; | 
 |  | 
 |                 // if the widget resizes on drop | 
 |                 if (foundCell && (cell instanceof AppWidgetHostView) && | 
 |                         (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) { | 
 |                     resizeOnDrop = true; | 
 |                     item.spanX = resultSpan[0]; | 
 |                     item.spanY = resultSpan[1]; | 
 |                     AppWidgetHostView awhv = (AppWidgetHostView) cell; | 
 |                     AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0], | 
 |                             resultSpan[1]); | 
 |                 } | 
 |  | 
 |                 if (foundCell) { | 
 |                     if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) { | 
 |                         snapScreen = getPageIndexForScreenId(screenId); | 
 |                         snapToPage(snapScreen); | 
 |                     } | 
 |  | 
 |                     final ItemInfo info = (ItemInfo) cell.getTag(); | 
 |                     if (hasMovedLayouts) { | 
 |                         // Reparent the view | 
 |                         CellLayout parentCell = getParentCellLayoutForView(cell); | 
 |                         if (parentCell != null) { | 
 |                             parentCell.removeView(cell); | 
 |                         } else if (FeatureFlags.IS_DOGFOOD_BUILD) { | 
 |                             throw new NullPointerException("mDragInfo.cell has null parent"); | 
 |                         } | 
 |                         addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1], | 
 |                                 info.spanX, info.spanY); | 
 |                     } | 
 |  | 
 |                     // update the item's position after drop | 
 |                     CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); | 
 |                     lp.cellX = lp.tmpCellX = mTargetCell[0]; | 
 |                     lp.cellY = lp.tmpCellY = mTargetCell[1]; | 
 |                     lp.cellHSpan = item.spanX; | 
 |                     lp.cellVSpan = item.spanY; | 
 |                     lp.isLockedToGrid = true; | 
 |  | 
 |                     if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT && | 
 |                             cell instanceof LauncherAppWidgetHostView) { | 
 |                         final CellLayout cellLayout = dropTargetLayout; | 
 |                         // We post this call so that the widget has a chance to be placed | 
 |                         // in its final location | 
 |  | 
 |                         final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell; | 
 |                         AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo(); | 
 |                         if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE | 
 |                                 && !d.accessibleDrag) { | 
 |                             onCompleteRunnable = new Runnable() { | 
 |                                 public void run() { | 
 |                                     if (!isPageInTransition()) { | 
 |                                         AppWidgetResizeFrame.showForWidget(hostView, cellLayout); | 
 |                                     } | 
 |                                 } | 
 |                             }; | 
 |                         } | 
 |                     } | 
 |  | 
 |                     mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId, | 
 |                             lp.cellX, lp.cellY, item.spanX, item.spanY); | 
 |                 } else { | 
 |                     if (!returnToOriginalCellToPreventShuffling) { | 
 |                         onNoCellFound(dropTargetLayout); | 
 |                     } | 
 |  | 
 |                     // If we can't find a drop location, we return the item to its original position | 
 |                     CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); | 
 |                     mTargetCell[0] = lp.cellX; | 
 |                     mTargetCell[1] = lp.cellY; | 
 |                     CellLayout layout = (CellLayout) cell.getParent().getParent(); | 
 |                     layout.markCellsAsOccupiedForView(cell); | 
 |                 } | 
 |             } | 
 |  | 
 |             final CellLayout parent = (CellLayout) cell.getParent().getParent(); | 
 |             if (d.dragView.hasDrawn()) { | 
 |                 if (droppedOnOriginalCellDuringTransition) { | 
 |                     // Animate the item to its original position, while simultaneously exiting | 
 |                     // spring-loaded mode so the page meets the icon where it was picked up. | 
 |                     mLauncher.getDragController().animateDragViewToOriginalPosition( | 
 |                             onCompleteRunnable, cell, SPRING_LOADED_TRANSITION_MS); | 
 |                     mLauncher.getStateManager().goToState(NORMAL); | 
 |                     mLauncher.getDropTargetBar().onDragEnd(); | 
 |                     parent.onDropChild(cell); | 
 |                     return; | 
 |                 } | 
 |                 final ItemInfo info = (ItemInfo) cell.getTag(); | 
 |                 boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET | 
 |                         || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; | 
 |                 if (isWidget) { | 
 |                     int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE : | 
 |                             ANIMATE_INTO_POSITION_AND_DISAPPEAR; | 
 |                     animateWidgetDrop(info, parent, d.dragView, null, animationType, cell, false); | 
 |                 } else { | 
 |                     int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION; | 
 |                     mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration, | 
 |                             this); | 
 |                 } | 
 |             } else { | 
 |                 d.deferDragViewCleanupPostAnimation = false; | 
 |                 cell.setVisibility(VISIBLE); | 
 |             } | 
 |             parent.onDropChild(cell); | 
 |  | 
 |             mLauncher.getStateManager().goToState( | 
 |                     NORMAL, SPRING_LOADED_EXIT_DELAY, onCompleteRunnable); | 
 |         } | 
 |  | 
 |         if (d.stateAnnouncer != null && !droppedOnOriginalCell) { | 
 |             d.stateAnnouncer.completeAction(R.string.item_moved); | 
 |         } | 
 |     } | 
 |  | 
 |     public void onNoCellFound(View dropTargetLayout) { | 
 |         if (mLauncher.isHotseatLayout(dropTargetLayout)) { | 
 |             Hotseat hotseat = mLauncher.getHotseat(); | 
 |             boolean droppedOnAllAppsIcon = !FeatureFlags.NO_ALL_APPS_ICON | 
 |                     && mTargetCell != null && !mLauncher.getDeviceProfile().inv.isAllAppsButtonRank( | 
 |                     hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1])); | 
 |             if (!droppedOnAllAppsIcon) { | 
 |                 // Only show message when hotseat is full and drop target was not AllApps button | 
 |                 showOutOfSpaceMessage(true); | 
 |             } | 
 |         } else { | 
 |             showOutOfSpaceMessage(false); | 
 |         } | 
 |     } | 
 |  | 
 |     private void showOutOfSpaceMessage(boolean isHotseatLayout) { | 
 |         int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space); | 
 |         Toast.makeText(mLauncher, mLauncher.getString(strId), Toast.LENGTH_SHORT).show(); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Computes the area relative to dragLayer which is used to display a page. | 
 |      */ | 
 |     public void getPageAreaRelativeToDragLayer(Rect outArea) { | 
 |         CellLayout child = (CellLayout) getChildAt(getNextPage()); | 
 |         if (child == null) { | 
 |             return; | 
 |         } | 
 |         ShortcutAndWidgetContainer boundingLayout = child.getShortcutsAndWidgets(); | 
 |  | 
 |         // Use the absolute left instead of the child left, as we want the visible area | 
 |         // irrespective of the visible child. Since the view can only scroll horizontally, the | 
 |         // top position is not affected. | 
 |         mTempXY[0] = getPaddingLeft() + boundingLayout.getLeft(); | 
 |         mTempXY[1] = child.getTop() + boundingLayout.getTop(); | 
 |  | 
 |         float scale = mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY); | 
 |         outArea.set(mTempXY[0], mTempXY[1], | 
 |                 (int) (mTempXY[0] + scale * boundingLayout.getMeasuredWidth()), | 
 |                 (int) (mTempXY[1] + scale * boundingLayout.getMeasuredHeight())); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public void onDragEnter(DragObject d) { | 
 |         if (ENFORCE_DRAG_EVENT_ORDER) { | 
 |             enforceDragParity("onDragEnter", 1, 1); | 
 |         } | 
 |  | 
 |         mCreateUserFolderOnDrop = false; | 
 |         mAddToExistingFolderOnDrop = false; | 
 |  | 
 |         mDropToLayout = null; | 
 |         mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter); | 
 |         setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1]); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public void onDragExit(DragObject d) { | 
 |         if (ENFORCE_DRAG_EVENT_ORDER) { | 
 |             enforceDragParity("onDragExit", -1, 0); | 
 |         } | 
 |  | 
 |         // Here we store the final page that will be dropped to, if the workspace in fact | 
 |         // receives the drop | 
 |         mDropToLayout = mDragTargetLayout; | 
 |         if (mDragMode == DRAG_MODE_CREATE_FOLDER) { | 
 |             mCreateUserFolderOnDrop = true; | 
 |         } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) { | 
 |             mAddToExistingFolderOnDrop = true; | 
 |         } | 
 |  | 
 |         // Reset the previous drag target | 
 |         setCurrentDropLayout(null); | 
 |         setCurrentDragOverlappingLayout(null); | 
 |  | 
 |         mSpringLoadedDragController.cancel(); | 
 |     } | 
 |  | 
 |     private void enforceDragParity(String event, int update, int expectedValue) { | 
 |         enforceDragParity(this, event, update, expectedValue); | 
 |         for (int i = 0; i < getChildCount(); i++) { | 
 |             enforceDragParity(getChildAt(i), event, update, expectedValue); | 
 |         } | 
 |     } | 
 |  | 
 |     private void enforceDragParity(View v, String event, int update, int expectedValue) { | 
 |         Object tag = v.getTag(R.id.drag_event_parity); | 
 |         int value = tag == null ? 0 : (Integer) tag; | 
 |         value += update; | 
 |         v.setTag(R.id.drag_event_parity, value); | 
 |  | 
 |         if (value != expectedValue) { | 
 |             Log.e(TAG, event + ": Drag contract violated: " + value); | 
 |         } | 
 |     } | 
 |  | 
 |     void setCurrentDropLayout(CellLayout layout) { | 
 |         if (mDragTargetLayout != null) { | 
 |             mDragTargetLayout.revertTempState(); | 
 |             mDragTargetLayout.onDragExit(); | 
 |         } | 
 |         mDragTargetLayout = layout; | 
 |         if (mDragTargetLayout != null) { | 
 |             mDragTargetLayout.onDragEnter(); | 
 |         } | 
 |         cleanupReorder(true); | 
 |         cleanupFolderCreation(); | 
 |         setCurrentDropOverCell(-1, -1); | 
 |     } | 
 |  | 
 |     void setCurrentDragOverlappingLayout(CellLayout layout) { | 
 |         if (mDragOverlappingLayout != null) { | 
 |             mDragOverlappingLayout.setIsDragOverlapping(false); | 
 |         } | 
 |         mDragOverlappingLayout = layout; | 
 |         if (mDragOverlappingLayout != null) { | 
 |             mDragOverlappingLayout.setIsDragOverlapping(true); | 
 |         } | 
 |         // Invalidating the scrim will also force this CellLayout | 
 |         // to be invalidated so that it is highlighted if necessary. | 
 |         mLauncher.getDragLayer().getScrim().invalidate(); | 
 |     } | 
 |  | 
 |     public CellLayout getCurrentDragOverlappingLayout() { | 
 |         return mDragOverlappingLayout; | 
 |     } | 
 |  | 
 |     void setCurrentDropOverCell(int x, int y) { | 
 |         if (x != mDragOverX || y != mDragOverY) { | 
 |             mDragOverX = x; | 
 |             mDragOverY = y; | 
 |             setDragMode(DRAG_MODE_NONE); | 
 |         } | 
 |     } | 
 |  | 
 |     void setDragMode(int dragMode) { | 
 |         if (dragMode != mDragMode) { | 
 |             if (dragMode == DRAG_MODE_NONE) { | 
 |                 cleanupAddToFolder(); | 
 |                 // We don't want to cancel the re-order alarm every time the target cell changes | 
 |                 // as this feels to slow / unresponsive. | 
 |                 cleanupReorder(false); | 
 |                 cleanupFolderCreation(); | 
 |             } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) { | 
 |                 cleanupReorder(true); | 
 |                 cleanupFolderCreation(); | 
 |             } else if (dragMode == DRAG_MODE_CREATE_FOLDER) { | 
 |                 cleanupAddToFolder(); | 
 |                 cleanupReorder(true); | 
 |             } else if (dragMode == DRAG_MODE_REORDER) { | 
 |                 cleanupAddToFolder(); | 
 |                 cleanupFolderCreation(); | 
 |             } | 
 |             mDragMode = dragMode; | 
 |         } | 
 |     } | 
 |  | 
 |     private void cleanupFolderCreation() { | 
 |         if (mFolderCreateBg != null) { | 
 |             mFolderCreateBg.animateToRest(); | 
 |         } | 
 |         mFolderCreationAlarm.setOnAlarmListener(null); | 
 |         mFolderCreationAlarm.cancelAlarm(); | 
 |     } | 
 |  | 
 |     private void cleanupAddToFolder() { | 
 |         if (mDragOverFolderIcon != null) { | 
 |             mDragOverFolderIcon.onDragExit(); | 
 |             mDragOverFolderIcon = null; | 
 |         } | 
 |     } | 
 |  | 
 |     private void cleanupReorder(boolean cancelAlarm) { | 
 |         // Any pending reorders are canceled | 
 |         if (cancelAlarm) { | 
 |             mReorderAlarm.cancelAlarm(); | 
 |         } | 
 |         mLastReorderX = -1; | 
 |         mLastReorderY = -1; | 
 |     } | 
 |  | 
 |    /* | 
 |     * | 
 |     * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's | 
 |     * coordinate space. The argument xy is modified with the return result. | 
 |     */ | 
 |    void mapPointFromSelfToChild(View v, float[] xy) { | 
 |        xy[0] = xy[0] - v.getLeft(); | 
 |        xy[1] = xy[1] - v.getTop(); | 
 |    } | 
 |  | 
 |    boolean isPointInSelfOverHotseat(int x, int y) { | 
 |        mTempXY[0] = x; | 
 |        mTempXY[1] = y; | 
 |        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true); | 
 |        View hotseat = mLauncher.getHotseat(); | 
 |        return mTempXY[0] >= hotseat.getLeft() && | 
 |                mTempXY[0] <= hotseat.getRight() && | 
 |                mTempXY[1] >= hotseat.getTop() && | 
 |                mTempXY[1] <= hotseat.getBottom(); | 
 |    } | 
 |  | 
 |    void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) { | 
 |        mTempXY[0] = (int) xy[0]; | 
 |        mTempXY[1] = (int) xy[1]; | 
 |        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true); | 
 |        mLauncher.getDragLayer().mapCoordInSelfToDescendant(hotseat.getLayout(), mTempXY); | 
 |  | 
 |        xy[0] = mTempXY[0]; | 
 |        xy[1] = mTempXY[1]; | 
 |    } | 
 |  | 
 |     private boolean isDragWidget(DragObject d) { | 
 |         return (d.dragInfo instanceof LauncherAppWidgetInfo || | 
 |                 d.dragInfo instanceof PendingAddWidgetInfo); | 
 |     } | 
 |  | 
 |     public void onDragOver(DragObject d) { | 
 |         // Skip drag over events while we are dragging over side pages | 
 |         if (!transitionStateShouldAllowDrop()) return; | 
 |  | 
 |         ItemInfo item = d.dragInfo; | 
 |         if (item == null) { | 
 |             if (FeatureFlags.IS_DOGFOOD_BUILD) { | 
 |                 throw new NullPointerException("DragObject has null info"); | 
 |             } | 
 |             return; | 
 |         } | 
 |  | 
 |         // Ensure that we have proper spans for the item that we are dropping | 
 |         if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found"); | 
 |         mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter); | 
 |  | 
 |         final View child = (mDragInfo == null) ? null : mDragInfo.cell; | 
 |         if (setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1])) { | 
 |             if (mLauncher.isHotseatLayout(mDragTargetLayout)) { | 
 |                 mSpringLoadedDragController.cancel(); | 
 |             } else { | 
 |                 mSpringLoadedDragController.setAlarm(mDragTargetLayout); | 
 |             } | 
 |         } | 
 |  | 
 |         // Handle the drag over | 
 |         if (mDragTargetLayout != null) { | 
 |             // We want the point to be mapped to the dragTarget. | 
 |             if (mLauncher.isHotseatLayout(mDragTargetLayout)) { | 
 |                 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); | 
 |             } else { | 
 |                 mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter); | 
 |             } | 
 |  | 
 |             int minSpanX = item.spanX; | 
 |             int minSpanY = item.spanY; | 
 |             if (item.minSpanX > 0 && item.minSpanY > 0) { | 
 |                 minSpanX = item.minSpanX; | 
 |                 minSpanY = item.minSpanY; | 
 |             } | 
 |  | 
 |             mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], | 
 |                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, | 
 |                     mDragTargetLayout, mTargetCell); | 
 |             int reorderX = mTargetCell[0]; | 
 |             int reorderY = mTargetCell[1]; | 
 |  | 
 |             setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]); | 
 |  | 
 |             float targetCellDistance = mDragTargetLayout.getDistanceFromCell( | 
 |                     mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell); | 
 |  | 
 |             manageFolderFeedback(mDragTargetLayout, mTargetCell, targetCellDistance, d); | 
 |  | 
 |             boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int) | 
 |                     mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX, | 
 |                     item.spanY, child, mTargetCell); | 
 |  | 
 |             if (!nearestDropOccupied) { | 
 |                 mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider, | 
 |                         mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, d); | 
 |             } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER) | 
 |                     && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX || | 
 |                     mLastReorderY != reorderY)) { | 
 |  | 
 |                 int[] resultSpan = new int[2]; | 
 |                 mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0], | 
 |                         (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY, | 
 |                         child, mTargetCell, resultSpan, CellLayout.MODE_SHOW_REORDER_HINT); | 
 |  | 
 |                 // Otherwise, if we aren't adding to or creating a folder and there's no pending | 
 |                 // reorder, then we schedule a reorder | 
 |                 ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter, | 
 |                         minSpanX, minSpanY, item.spanX, item.spanY, d, child); | 
 |                 mReorderAlarm.setOnAlarmListener(listener); | 
 |                 mReorderAlarm.setAlarm(REORDER_TIMEOUT); | 
 |             } | 
 |  | 
 |             if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER || | 
 |                     !nearestDropOccupied) { | 
 |                 if (mDragTargetLayout != null) { | 
 |                     mDragTargetLayout.revertTempState(); | 
 |                 } | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     /** | 
 |      * Updates {@link #mDragTargetLayout} and {@link #mDragOverlappingLayout} | 
 |      * based on the DragObject's position. | 
 |      * | 
 |      * The layout will be: | 
 |      * - The Hotseat if the drag object is over it | 
 |      * - A side page if we are in spring-loaded mode and the drag object is over it | 
 |      * - The current page otherwise | 
 |      * | 
 |      * @return whether the layout is different from the current {@link #mDragTargetLayout}. | 
 |      */ | 
 |     private boolean setDropLayoutForDragObject(DragObject d, float centerX, float centerY) { | 
 |         CellLayout layout = null; | 
 |         // Test to see if we are over the hotseat first | 
 |         if (mLauncher.getHotseat() != null && !isDragWidget(d)) { | 
 |             if (isPointInSelfOverHotseat(d.x, d.y)) { | 
 |                 layout = mLauncher.getHotseat().getLayout(); | 
 |             } | 
 |         } | 
 |  | 
 |         int nextPage = getNextPage(); | 
 |         if (layout == null && !isPageInTransition()) { | 
 |             // Check if the item is dragged over left page | 
 |             mTempTouchCoordinates[0] = Math.min(centerX, d.x); | 
 |             mTempTouchCoordinates[1] = d.y; | 
 |             layout = verifyInsidePage(nextPage + (mIsRtl ? 1 : -1), mTempTouchCoordinates); | 
 |         } | 
 |  | 
 |         if (layout == null && !isPageInTransition()) { | 
 |             // Check if the item is dragged over right page | 
 |             mTempTouchCoordinates[0] = Math.max(centerX, d.x); | 
 |             mTempTouchCoordinates[1] = d.y; | 
 |             layout = verifyInsidePage(nextPage + (mIsRtl ? -1 : 1), mTempTouchCoordinates); | 
 |         } | 
 |  | 
 |         // Always pick the current page. | 
 |         if (layout == null && nextPage >= 0 && nextPage < getPageCount()) { | 
 |             layout = (CellLayout) getChildAt(nextPage); | 
 |         } | 
 |         if (layout != mDragTargetLayout) { | 
 |             setCurrentDropLayout(layout); | 
 |             setCurrentDragOverlappingLayout(layout); | 
 |             return true; | 
 |         } | 
 |         return false; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Returns the child CellLayout if the point is inside the page coordinates, null otherwise. | 
 |      */ | 
 |     private CellLayout verifyInsidePage(int pageNo, float[] touchXy)  { | 
 |         if (pageNo >= 0 && pageNo < getPageCount()) { | 
 |             CellLayout cl = (CellLayout) getChildAt(pageNo); | 
 |             mapPointFromSelfToChild(cl, touchXy); | 
 |             if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() && | 
 |                     touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) { | 
 |                 // This point is inside the cell layout | 
 |                 return cl; | 
 |             } | 
 |         } | 
 |         return null; | 
 |     } | 
 |  | 
 |     private void manageFolderFeedback(CellLayout targetLayout, | 
 |             int[] targetCell, float distance, DragObject dragObject) { | 
 |         if (distance > mMaxDistanceForFolderCreation) { | 
 |             if (mDragMode != DRAG_MODE_NONE) { | 
 |                 setDragMode(DRAG_MODE_NONE); | 
 |             } | 
 |             return; | 
 |         } | 
 |  | 
 |         final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]); | 
 |         ItemInfo info = dragObject.dragInfo; | 
 |         boolean userFolderPending = willCreateUserFolder(info, dragOverView, false); | 
 |         if (mDragMode == DRAG_MODE_NONE && userFolderPending && | 
 |                 !mFolderCreationAlarm.alarmPending()) { | 
 |  | 
 |             FolderCreationAlarmListener listener = new | 
 |                     FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]); | 
 |  | 
 |             if (!dragObject.accessibleDrag) { | 
 |                 mFolderCreationAlarm.setOnAlarmListener(listener); | 
 |                 mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT); | 
 |             } else { | 
 |                 listener.onAlarm(mFolderCreationAlarm); | 
 |             } | 
 |  | 
 |             if (dragObject.stateAnnouncer != null) { | 
 |                 dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper | 
 |                         .getDescriptionForDropOver(dragOverView, getContext())); | 
 |             } | 
 |             return; | 
 |         } | 
 |  | 
 |         boolean willAddToFolder = willAddToExistingUserFolder(info, dragOverView); | 
 |         if (willAddToFolder && mDragMode == DRAG_MODE_NONE) { | 
 |             mDragOverFolderIcon = ((FolderIcon) dragOverView); | 
 |             mDragOverFolderIcon.onDragEnter(info); | 
 |             if (targetLayout != null) { | 
 |                 targetLayout.clearDragOutlines(); | 
 |             } | 
 |             setDragMode(DRAG_MODE_ADD_TO_FOLDER); | 
 |  | 
 |             if (dragObject.stateAnnouncer != null) { | 
 |                 dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper | 
 |                         .getDescriptionForDropOver(dragOverView, getContext())); | 
 |             } | 
 |             return; | 
 |         } | 
 |  | 
 |         if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) { | 
 |             setDragMode(DRAG_MODE_NONE); | 
 |         } | 
 |         if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) { | 
 |             setDragMode(DRAG_MODE_NONE); | 
 |         } | 
 |     } | 
 |  | 
 |     class FolderCreationAlarmListener implements OnAlarmListener { | 
 |         final CellLayout layout; | 
 |         final int cellX; | 
 |         final int cellY; | 
 |  | 
 |         final PreviewBackground bg = new PreviewBackground(); | 
 |  | 
 |         public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) { | 
 |             this.layout = layout; | 
 |             this.cellX = cellX; | 
 |             this.cellY = cellY; | 
 |  | 
 |             BubbleTextView cell = (BubbleTextView) layout.getChildAt(cellX, cellY); | 
 |             bg.setup(mLauncher, null, cell.getMeasuredWidth(), cell.getPaddingTop()); | 
 |  | 
 |             // The full preview background should appear behind the icon | 
 |             bg.isClipping = false; | 
 |         } | 
 |  | 
 |         public void onAlarm(Alarm alarm) { | 
 |             mFolderCreateBg = bg; | 
 |             mFolderCreateBg.animateToAccept(layout, cellX, cellY); | 
 |             layout.clearDragOutlines(); | 
 |             setDragMode(DRAG_MODE_CREATE_FOLDER); | 
 |         } | 
 |     } | 
 |  | 
 |     class ReorderAlarmListener implements OnAlarmListener { | 
 |         final float[] dragViewCenter; | 
 |         final int minSpanX, minSpanY, spanX, spanY; | 
 |         final DragObject dragObject; | 
 |         final View child; | 
 |  | 
 |         public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX, | 
 |                 int spanY, DragObject dragObject, View child) { | 
 |             this.dragViewCenter = dragViewCenter; | 
 |             this.minSpanX = minSpanX; | 
 |             this.minSpanY = minSpanY; | 
 |             this.spanX = spanX; | 
 |             this.spanY = spanY; | 
 |             this.child = child; | 
 |             this.dragObject = dragObject; | 
 |         } | 
 |  | 
 |         public void onAlarm(Alarm alarm) { | 
 |             int[] resultSpan = new int[2]; | 
 |             mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], | 
 |                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout, | 
 |                     mTargetCell); | 
 |             mLastReorderX = mTargetCell[0]; | 
 |             mLastReorderY = mTargetCell[1]; | 
 |  | 
 |             mTargetCell = mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0], | 
 |                 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, | 
 |                 child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER); | 
 |  | 
 |             if (mTargetCell[0] < 0 || mTargetCell[1] < 0) { | 
 |                 mDragTargetLayout.revertTempState(); | 
 |             } else { | 
 |                 setDragMode(DRAG_MODE_REORDER); | 
 |             } | 
 |  | 
 |             boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY; | 
 |             mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider, | 
 |                 mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize, dragObject); | 
 |         } | 
 |     } | 
 |  | 
 |     @Override | 
 |     public void getHitRectRelativeToDragLayer(Rect outRect) { | 
 |         // We want the workspace to have the whole area of the display (it will find the correct | 
 |         // cell layout to drop to in the existing drag/drop logic. | 
 |         mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Drop an item that didn't originate on one of the workspace screens. | 
 |      * It may have come from Launcher (e.g. from all apps or customize), or it may have | 
 |      * come from another app altogether. | 
 |      * | 
 |      * NOTE: This can also be called when we are outside of a drag event, when we want | 
 |      * to add an item to one of the workspace screens. | 
 |      */ | 
 |     private void onDropExternal(final int[] touchXY, final CellLayout cellLayout, DragObject d) { | 
 |         if (d.dragInfo instanceof PendingAddShortcutInfo) { | 
 |             ShortcutInfo si = ((PendingAddShortcutInfo) d.dragInfo) | 
 |                     .activityInfo.createShortcutInfo(); | 
 |             if (si != null) { | 
 |                 d.dragInfo = si; | 
 |             } | 
 |         } | 
 |  | 
 |         ItemInfo info = d.dragInfo; | 
 |         int spanX = info.spanX; | 
 |         int spanY = info.spanY; | 
 |         if (mDragInfo != null) { | 
 |             spanX = mDragInfo.spanX; | 
 |             spanY = mDragInfo.spanY; | 
 |         } | 
 |  | 
 |         final long container = mLauncher.isHotseatLayout(cellLayout) ? | 
 |                 LauncherSettings.Favorites.CONTAINER_HOTSEAT : | 
 |                     LauncherSettings.Favorites.CONTAINER_DESKTOP; | 
 |         final long screenId = getIdForScreen(cellLayout); | 
 |         if (!mLauncher.isHotseatLayout(cellLayout) | 
 |                 && screenId != getScreenIdForPageIndex(mCurrentPage) | 
 |                 && !mLauncher.isInState(SPRING_LOADED)) { | 
 |             snapToPage(getPageIndexForScreenId(screenId)); | 
 |         } | 
 |  | 
 |         if (info instanceof PendingAddItemInfo) { | 
 |             final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) info; | 
 |  | 
 |             boolean findNearestVacantCell = true; | 
 |             if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { | 
 |                 mTargetCell = findNearestArea(touchXY[0], touchXY[1], spanX, spanY, | 
 |                         cellLayout, mTargetCell); | 
 |                 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], | 
 |                         mDragViewVisualCenter[1], mTargetCell); | 
 |                 if (willCreateUserFolder(d.dragInfo, cellLayout, mTargetCell, distance, true) | 
 |                         || willAddToExistingUserFolder( | 
 |                                 d.dragInfo, cellLayout, mTargetCell, distance)) { | 
 |                     findNearestVacantCell = false; | 
 |                 } | 
 |             } | 
 |  | 
 |             final ItemInfo item = d.dragInfo; | 
 |             boolean updateWidgetSize = false; | 
 |             if (findNearestVacantCell) { | 
 |                 int minSpanX = item.spanX; | 
 |                 int minSpanY = item.spanY; | 
 |                 if (item.minSpanX > 0 && item.minSpanY > 0) { | 
 |                     minSpanX = item.minSpanX; | 
 |                     minSpanY = item.minSpanY; | 
 |                 } | 
 |                 int[] resultSpan = new int[2]; | 
 |                 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0], | 
 |                         (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY, | 
 |                         null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL); | 
 |  | 
 |                 if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) { | 
 |                     updateWidgetSize = true; | 
 |                 } | 
 |                 item.spanX = resultSpan[0]; | 
 |                 item.spanY = resultSpan[1]; | 
 |             } | 
 |  | 
 |             Runnable onAnimationCompleteRunnable = new Runnable() { | 
 |                 @Override | 
 |                 public void run() { | 
 |                     // Normally removeExtraEmptyScreen is called in Workspace#onDragEnd, but when | 
 |                     // adding an item that may not be dropped right away (due to a config activity) | 
 |                     // we defer the removal until the activity returns. | 
 |                     deferRemoveExtraEmptyScreen(); | 
 |  | 
 |                     // When dragging and dropping from customization tray, we deal with creating | 
 |                     // widgets/shortcuts/folders in a slightly different way | 
 |                     mLauncher.addPendingItem(pendingInfo, container, screenId, mTargetCell, | 
 |                             item.spanX, item.spanY); | 
 |                 } | 
 |             }; | 
 |             boolean isWidget = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET | 
 |                     || pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; | 
 |  | 
 |             AppWidgetHostView finalView = isWidget ? | 
 |                     ((PendingAddWidgetInfo) pendingInfo).boundWidget : null; | 
 |  | 
 |             if (finalView != null && updateWidgetSize) { | 
 |                 AppWidgetResizeFrame.updateWidgetSizeRanges(finalView, mLauncher, item.spanX, | 
 |                         item.spanY); | 
 |             } | 
 |  | 
 |             int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR; | 
 |             if (isWidget && ((PendingAddWidgetInfo) pendingInfo).info != null && | 
 |                     ((PendingAddWidgetInfo) pendingInfo).getHandler().needsConfigure()) { | 
 |                 animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN; | 
 |             } | 
 |             animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable, | 
 |                     animationStyle, finalView, true); | 
 |         } else { | 
 |             // This is for other drag/drop cases, like dragging from All Apps | 
 |             mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); | 
 |  | 
 |             View view; | 
 |  | 
 |             switch (info.itemType) { | 
 |             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: | 
 |             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: | 
 |             case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: | 
 |                 if (info.container == NO_ID) { | 
 |                     // Came from all apps -- make a copy | 
 |                     if (info instanceof AppInfo) { | 
 |                         info = ((AppInfo) info).makeShortcut(); | 
 |                         d.dragInfo = info; | 
 |                     } else if (info instanceof ShortcutInfo) { | 
 |                         info = new ShortcutInfo((ShortcutInfo) info); | 
 |                         d.dragInfo = info; | 
 |                     } | 
 |  | 
 |                 } | 
 |                 view = mLauncher.createShortcut(cellLayout, (ShortcutInfo) info); | 
 |                 break; | 
 |             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: | 
 |                 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout, | 
 |                         (FolderInfo) info); | 
 |                 break; | 
 |             default: | 
 |                 throw new IllegalStateException("Unknown item type: " + info.itemType); | 
 |             } | 
 |  | 
 |             // First we find the cell nearest to point at which the item is | 
 |             // dropped, without any consideration to whether there is an item there. | 
 |             if (touchXY != null) { | 
 |                 mTargetCell = findNearestArea(touchXY[0], touchXY[1], spanX, spanY, | 
 |                         cellLayout, mTargetCell); | 
 |                 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], | 
 |                         mDragViewVisualCenter[1], mTargetCell); | 
 |                 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance, | 
 |                         true, d.dragView)) { | 
 |                     return; | 
 |                 } | 
 |                 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d, | 
 |                         true)) { | 
 |                     return; | 
 |                 } | 
 |             } | 
 |  | 
 |             if (touchXY != null) { | 
 |                 // when dragging and dropping, just find the closest free spot | 
 |                 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0], | 
 |                         (int) mDragViewVisualCenter[1], 1, 1, 1, 1, | 
 |                         null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL); | 
 |             } else { | 
 |                 cellLayout.findCellForSpan(mTargetCell, 1, 1); | 
 |             } | 
 |             // Add the item to DB before adding to screen ensures that the container and other | 
 |             // values of the info is properly updated. | 
 |             mLauncher.getModelWriter().addOrMoveItemInDatabase(info, container, screenId, | 
 |                     mTargetCell[0], mTargetCell[1]); | 
 |  | 
 |             addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], | 
 |                     info.spanX, info.spanY); | 
 |             cellLayout.onDropChild(view); | 
 |             cellLayout.getShortcutsAndWidgets().measureChild(view); | 
 |  | 
 |             if (d.dragView != null) { | 
 |                 // We wrap the animation call in the temporary set and reset of the current | 
 |                 // cellLayout to its final transform -- this means we animate the drag view to | 
 |                 // the correct final location. | 
 |                 setFinalTransitionTransform(); | 
 |                 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, this); | 
 |                 resetTransitionTransform(); | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) { | 
 |         int[] unScaledSize = estimateItemSize(widgetInfo); | 
 |         int visibility = layout.getVisibility(); | 
 |         layout.setVisibility(VISIBLE); | 
 |  | 
 |         int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY); | 
 |         int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY); | 
 |         Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1], | 
 |                 Bitmap.Config.ARGB_8888); | 
 |         layout.measure(width, height); | 
 |         layout.layout(0, 0, unScaledSize[0], unScaledSize[1]); | 
 |         layout.draw(new Canvas(b)); | 
 |         layout.setVisibility(visibility); | 
 |         return b; | 
 |     } | 
 |  | 
 |     private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY, | 
 |             DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, boolean scale) { | 
 |         // Now we animate the dragView, (ie. the widget or shortcut preview) into its final | 
 |         // location and size on the home screen. | 
 |         int spanX = info.spanX; | 
 |         int spanY = info.spanY; | 
 |  | 
 |         Rect r = estimateItemPosition(layout, targetCell[0], targetCell[1], spanX, spanY); | 
 |         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) { | 
 |             DeviceProfile profile = mLauncher.getDeviceProfile(); | 
 |             Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y); | 
 |         } | 
 |         loc[0] = r.left; | 
 |         loc[1] = r.top; | 
 |  | 
 |         setFinalTransitionTransform(); | 
 |         float cellLayoutScale = | 
 |                 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true); | 
 |         resetTransitionTransform(); | 
 |  | 
 |         if (scale) { | 
 |             float dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth(); | 
 |             float dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight(); | 
 |  | 
 |             // The animation will scale the dragView about its center, so we need to center about | 
 |             // the final location. | 
 |             loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2 | 
 |                     - Math.ceil(layout.getUnusedHorizontalSpace() / 2f); | 
 |             loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2; | 
 |             scaleXY[0] = dragViewScaleX * cellLayoutScale; | 
 |             scaleXY[1] = dragViewScaleY * cellLayoutScale; | 
 |         } else { | 
 |             // Since we are not cross-fading the dragView, align the drag view to the | 
 |             // final cell position. | 
 |             float dragScale = dragView.getInitialScale() * cellLayoutScale; | 
 |             loc[0] += (dragScale - 1) * dragView.getWidth() / 2; | 
 |             loc[1] += (dragScale - 1) * dragView.getHeight() / 2; | 
 |             scaleXY[0] = scaleXY[1] = dragScale; | 
 |  | 
 |             // If a dragRegion was provided, offset the final position accordingly. | 
 |             Rect dragRegion = dragView.getDragRegion(); | 
 |             if (dragRegion != null) { | 
 |                 loc[0] += cellLayoutScale * dragRegion.left; | 
 |                 loc[1] += cellLayoutScale * dragRegion.top; | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, final DragView dragView, | 
 |             final Runnable onCompleteRunnable, int animationType, final View finalView, | 
 |             boolean external) { | 
 |         Rect from = new Rect(); | 
 |         mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from); | 
 |  | 
 |         int[] finalPos = new int[2]; | 
 |         float scaleXY[] = new float[2]; | 
 |         boolean scalePreview = !(info instanceof PendingAddShortcutInfo); | 
 |         getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell, | 
 |                 scalePreview); | 
 |  | 
 |         Resources res = mLauncher.getResources(); | 
 |         final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200; | 
 |  | 
 |         boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET || | 
 |                 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; | 
 |         if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) { | 
 |             Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView); | 
 |             dragView.setCrossFadeBitmap(crossFadeBitmap); | 
 |             dragView.crossFade((int) (duration * 0.8f)); | 
 |         } else if (isWidget && external) { | 
 |             scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0],  scaleXY[1]); | 
 |         } | 
 |  | 
 |         DragLayer dragLayer = mLauncher.getDragLayer(); | 
 |         if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) { | 
 |             mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f, | 
 |                     DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration); | 
 |         } else { | 
 |             int endStyle; | 
 |             if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) { | 
 |                 endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE; | 
 |             } else { | 
 |                 endStyle = DragLayer.ANIMATION_END_DISAPPEAR; | 
 |             } | 
 |  | 
 |             Runnable onComplete = new Runnable() { | 
 |                 @Override | 
 |                 public void run() { | 
 |                     if (finalView != null) { | 
 |                         finalView.setVisibility(VISIBLE); | 
 |                     } | 
 |                     if (onCompleteRunnable != null) { | 
 |                         onCompleteRunnable.run(); | 
 |                     } | 
 |                 } | 
 |             }; | 
 |             dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0], | 
 |                     finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle, | 
 |                     duration, this); | 
 |         } | 
 |     } | 
 |  | 
 |     public void setFinalTransitionTransform() { | 
 |         if (isSwitchingState()) { | 
 |             mCurrentScale = getScaleX(); | 
 |             setScaleX(mStateTransitionAnimation.getFinalScale()); | 
 |             setScaleY(mStateTransitionAnimation.getFinalScale()); | 
 |         } | 
 |     } | 
 |     public void resetTransitionTransform() { | 
 |         if (isSwitchingState()) { | 
 |             setScaleX(mCurrentScale); | 
 |             setScaleY(mCurrentScale); | 
 |         } | 
 |     } | 
 |  | 
 |     /** | 
 |      * Return the current CellInfo describing our current drag; this method exists | 
 |      * so that Launcher can sync this object with the correct info when the activity is created/ | 
 |      * destroyed | 
 |      * | 
 |      */ | 
 |     public CellLayout.CellInfo getDragInfo() { | 
 |         return mDragInfo; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Calculate the nearest cell where the given object would be dropped. | 
 |      * | 
 |      * pixelX and pixelY should be in the coordinate system of layout | 
 |      */ | 
 |     @Thunk int[] findNearestArea(int pixelX, int pixelY, | 
 |             int spanX, int spanY, CellLayout layout, int[] recycle) { | 
 |         return layout.findNearestArea( | 
 |                 pixelX, pixelY, spanX, spanY, recycle); | 
 |     } | 
 |  | 
 |     void setup(DragController dragController) { | 
 |         mSpringLoadedDragController = new SpringLoadedDragController(mLauncher); | 
 |         mDragController = dragController; | 
 |  | 
 |         // hardware layers on children are enabled on startup, but should be disabled until | 
 |         // needed | 
 |         updateChildrenLayersEnabled(); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Called at the end of a drag which originated on the workspace. | 
 |      */ | 
 |     public void onDropCompleted(final View target, final DragObject d, | 
 |             final boolean success) { | 
 |  | 
 |         if (success) { | 
 |             if (target != this && mDragInfo != null) { | 
 |                 removeWorkspaceItem(mDragInfo.cell); | 
 |             } | 
 |         } else if (mDragInfo != null) { | 
 |             final CellLayout cellLayout = mLauncher.getCellLayout( | 
 |                     mDragInfo.container, mDragInfo.screenId); | 
 |             if (cellLayout != null) { | 
 |                 cellLayout.onDropChild(mDragInfo.cell); | 
 |             } else if (FeatureFlags.IS_DOGFOOD_BUILD) { | 
 |                 throw new RuntimeException("Invalid state: cellLayout == null in " | 
 |                         + "Workspace#onDropCompleted. Please file a bug. "); | 
 |             } | 
 |         } | 
 |         View cell = getHomescreenIconByItemId(d.originalDragInfo.id); | 
 |         if (d.cancelled && cell != null) { | 
 |             cell.setVisibility(VISIBLE); | 
 |         } | 
 |         mDragInfo = null; | 
 |     } | 
 |  | 
 |     /** | 
 |      * For opposite operation. See {@link #addInScreen}. | 
 |      */ | 
 |     public void removeWorkspaceItem(View v) { | 
 |         CellLayout parentCell = getParentCellLayoutForView(v); | 
 |         if (parentCell != null) { | 
 |             parentCell.removeView(v); | 
 |         } else if (FeatureFlags.IS_DOGFOOD_BUILD) { | 
 |             // When an app is uninstalled using the drop target, we wait until resume to remove | 
 |             // the icon. We also remove all the corresponding items from the workspace at | 
 |             // {@link Launcher#bindComponentsRemoved}. That call can come before or after | 
 |             // {@link Launcher#mOnResumeCallbacks} depending on how busy the worker thread is. | 
 |             Log.e(TAG, "mDragInfo.cell has null parent"); | 
 |         } | 
 |         if (v instanceof DropTarget) { | 
 |             mDragController.removeDropTarget((DropTarget) v); | 
 |         } | 
 |     } | 
 |  | 
 |     /** | 
 |      * Removes all folder listeners | 
 |      */ | 
 |     public void removeFolderListeners() { | 
 |         mapOverItems(false, new ItemOperator() { | 
 |             @Override | 
 |             public boolean evaluate(ItemInfo info, View view) { | 
 |                 if (view instanceof FolderIcon) { | 
 |                     ((FolderIcon) view).removeListeners(); | 
 |                 } | 
 |                 return false; | 
 |             } | 
 |         }); | 
 |     } | 
 |  | 
 |     public boolean isDropEnabled() { | 
 |         return true; | 
 |     } | 
 |  | 
 |     @Override | 
 |     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { | 
 |         // We don't dispatch restoreInstanceState to our children using this code path. | 
 |         // Some pages will be restored immediately as their items are bound immediately, and | 
 |         // others we will need to wait until after their items are bound. | 
 |         mSavedStates = container; | 
 |     } | 
 |  | 
 |     public void restoreInstanceStateForChild(int child) { | 
 |         if (mSavedStates != null) { | 
 |             mRestoredPages.add(child); | 
 |             CellLayout cl = (CellLayout) getChildAt(child); | 
 |             if (cl != null) { | 
 |                 cl.restoreInstanceState(mSavedStates); | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     public void restoreInstanceStateForRemainingPages() { | 
 |         int count = getChildCount(); | 
 |         for (int i = 0; i < count; i++) { | 
 |             if (!mRestoredPages.contains(i)) { | 
 |                 restoreInstanceStateForChild(i); | 
 |             } | 
 |         } | 
 |         mRestoredPages.clear(); | 
 |         mSavedStates = null; | 
 |     } | 
 |  | 
 |     @Override | 
 |     public boolean scrollLeft() { | 
 |         boolean result = false; | 
 |         if (!workspaceInModalState() && !mIsSwitchingState) { | 
 |             result = super.scrollLeft(); | 
 |         } | 
 |         Folder openFolder = Folder.getOpen(mLauncher); | 
 |         if (openFolder != null) { | 
 |             openFolder.completeDragExit(); | 
 |         } | 
 |         return result; | 
 |     } | 
 |  | 
 |     @Override | 
 |     public boolean scrollRight() { | 
 |         boolean result = false; | 
 |         if (!workspaceInModalState() && !mIsSwitchingState) { | 
 |             result = super.scrollRight(); | 
 |         } | 
 |         Folder openFolder = Folder.getOpen(mLauncher); | 
 |         if (openFolder != null) { | 
 |             openFolder.completeDragExit(); | 
 |         } | 
 |         return result; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Returns a specific CellLayout | 
 |      */ | 
 |     CellLayout getParentCellLayoutForView(View v) { | 
 |         ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts(); | 
 |         for (CellLayout layout : layouts) { | 
 |             if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) { | 
 |                 return layout; | 
 |             } | 
 |         } | 
 |         return null; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Returns a list of all the CellLayouts in the workspace. | 
 |      */ | 
 |     ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() { | 
 |         ArrayList<CellLayout> layouts = new ArrayList<>(); | 
 |         int screenCount = getChildCount(); | 
 |         for (int screen = 0; screen < screenCount; screen++) { | 
 |             layouts.add(((CellLayout) getChildAt(screen))); | 
 |         } | 
 |         if (mLauncher.getHotseat() != null) { | 
 |             layouts.add(mLauncher.getHotseat().getLayout()); | 
 |         } | 
 |         return layouts; | 
 |     } | 
 |  | 
 |     /** | 
 |      * We should only use this to search for specific children.  Do not use this method to modify | 
 |      * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from | 
 |      * the hotseat and workspace pages | 
 |      */ | 
 |     ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() { | 
 |         ArrayList<ShortcutAndWidgetContainer> childrenLayouts = new ArrayList<>(); | 
 |         int screenCount = getChildCount(); | 
 |         for (int screen = 0; screen < screenCount; screen++) { | 
 |             childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets()); | 
 |         } | 
 |         if (mLauncher.getHotseat() != null) { | 
 |             childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets()); | 
 |         } | 
 |         return childrenLayouts; | 
 |     } | 
 |  | 
 |     public View getHomescreenIconByItemId(final long id) { | 
 |         return getFirstMatch(new ItemOperator() { | 
 |  | 
 |             @Override | 
 |             public boolean evaluate(ItemInfo info, View v) { | 
 |                 return info != null && info.id == id; | 
 |             } | 
 |         }); | 
 |     } | 
 |  | 
 |     public View getViewForTag(final Object tag) { | 
 |         return getFirstMatch(new ItemOperator() { | 
 |  | 
 |             @Override | 
 |             public boolean evaluate(ItemInfo info, View v) { | 
 |                 return info == tag; | 
 |             } | 
 |         }); | 
 |     } | 
 |  | 
 |     public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) { | 
 |         return (LauncherAppWidgetHostView) getFirstMatch(new ItemOperator() { | 
 |  | 
 |             @Override | 
 |             public boolean evaluate(ItemInfo info, View v) { | 
 |                 return (info instanceof LauncherAppWidgetInfo) && | 
 |                         ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId; | 
 |             } | 
 |         }); | 
 |     } | 
 |  | 
 |     public View getFirstMatch(final ItemOperator operator) { | 
 |         final View[] value = new View[1]; | 
 |         mapOverItems(MAP_NO_RECURSE, new ItemOperator() { | 
 |             @Override | 
 |             public boolean evaluate(ItemInfo info, View v) { | 
 |                 if (operator.evaluate(info, v)) { | 
 |                     value[0] = v; | 
 |                     return true; | 
 |                 } | 
 |                 return false; | 
 |             } | 
 |         }); | 
 |         return value[0]; | 
 |     } | 
 |  | 
 |     void clearDropTargets() { | 
 |         mapOverItems(MAP_NO_RECURSE, new ItemOperator() { | 
 |             @Override | 
 |             public boolean evaluate(ItemInfo info, View v) { | 
 |                 if (v instanceof DropTarget) { | 
 |                     mDragController.removeDropTarget((DropTarget) v); | 
 |                 } | 
 |                 // not done, process all the shortcuts | 
 |                 return false; | 
 |             } | 
 |         }); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Removes items that match the {@param matcher}. When applications are removed | 
 |      * as a part of an update, this is called to ensure that other widgets and application | 
 |      * shortcuts are not removed. | 
 |      */ | 
 |     public void removeItemsByMatcher(final ItemInfoMatcher matcher) { | 
 |         ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts(); | 
 |         for (final CellLayout layoutParent: cellLayouts) { | 
 |             final ViewGroup layout = layoutParent.getShortcutsAndWidgets(); | 
 |  | 
 |             LongArrayMap<View> idToViewMap = new LongArrayMap<>(); | 
 |             ArrayList<ItemInfo> items = new ArrayList<>(); | 
 |             for (int j = 0; j < layout.getChildCount(); j++) { | 
 |                 final View view = layout.getChildAt(j); | 
 |                 if (view.getTag() instanceof ItemInfo) { | 
 |                     ItemInfo item = (ItemInfo) view.getTag(); | 
 |                     items.add(item); | 
 |                     idToViewMap.put(item.id, view); | 
 |                 } | 
 |             } | 
 |  | 
 |             for (ItemInfo itemToRemove : matcher.filterItemInfos(items)) { | 
 |                 View child = idToViewMap.get(itemToRemove.id); | 
 |  | 
 |                 if (child != null) { | 
 |                     // Note: We can not remove the view directly from CellLayoutChildren as this | 
 |                     // does not re-mark the spaces as unoccupied. | 
 |                     layoutParent.removeViewInLayout(child); | 
 |                     if (child instanceof DropTarget) { | 
 |                         mDragController.removeDropTarget((DropTarget) child); | 
 |                     } | 
 |                 } else if (itemToRemove.container >= 0) { | 
 |                     // The item may belong to a folder. | 
 |                     View parent = idToViewMap.get(itemToRemove.container); | 
 |                     if (parent != null) { | 
 |                         FolderInfo folderInfo = (FolderInfo) parent.getTag(); | 
 |                         folderInfo.prepareAutoUpdate(); | 
 |                         folderInfo.remove((ShortcutInfo) itemToRemove, false); | 
 |                     } | 
 |                 } | 
 |             } | 
 |         } | 
 |  | 
 |         // Strip all the empty screens | 
 |         stripEmptyScreens(); | 
 |     } | 
 |  | 
 |     public interface ItemOperator { | 
 |         /** | 
 |          * Process the next itemInfo, possibly with side-effect on the next item. | 
 |          * | 
 |          * @param info info for the shortcut | 
 |          * @param view view for the shortcut | 
 |          * @return true if done, false to continue the map | 
 |          */ | 
 |         boolean evaluate(ItemInfo info, View view); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Map the operator over the shortcuts and widgets, return the first-non-null value. | 
 |      * | 
 |      * @param recurse true: iterate over folder children. false: op get the folders themselves. | 
 |      * @param op the operator to map over the shortcuts | 
 |      */ | 
 |     void mapOverItems(boolean recurse, ItemOperator op) { | 
 |         ArrayList<ShortcutAndWidgetContainer> containers = getAllShortcutAndWidgetContainers(); | 
 |         final int containerCount = containers.size(); | 
 |         for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) { | 
 |             ShortcutAndWidgetContainer container = containers.get(containerIdx); | 
 |             // map over all the shortcuts on the workspace | 
 |             final int itemCount = container.getChildCount(); | 
 |             for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) { | 
 |                 View item = container.getChildAt(itemIdx); | 
 |                 ItemInfo info = (ItemInfo) item.getTag(); | 
 |                 if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) { | 
 |                     FolderIcon folder = (FolderIcon) item; | 
 |                     ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder(); | 
 |                     // map over all the children in the folder | 
 |                     final int childCount = folderChildren.size(); | 
 |                     for (int childIdx = 0; childIdx < childCount; childIdx++) { | 
 |                         View child = folderChildren.get(childIdx); | 
 |                         info = (ItemInfo) child.getTag(); | 
 |                         if (op.evaluate(info, child)) { | 
 |                             return; | 
 |                         } | 
 |                     } | 
 |                 } else { | 
 |                     if (op.evaluate(info, item)) { | 
 |                         return; | 
 |                     } | 
 |                 } | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     void updateShortcuts(ArrayList<ShortcutInfo> shortcuts) { | 
 |         int total  = shortcuts.size(); | 
 |         final HashSet<ShortcutInfo> updates = new HashSet<>(total); | 
 |         final HashSet<Long> folderIds = new HashSet<>(); | 
 |  | 
 |         for (int i = 0; i < total; i++) { | 
 |             ShortcutInfo s = shortcuts.get(i); | 
 |             updates.add(s); | 
 |             folderIds.add(s.container); | 
 |         } | 
 |  | 
 |         mapOverItems(MAP_RECURSE, new ItemOperator() { | 
 |             @Override | 
 |             public boolean evaluate(ItemInfo info, View v) { | 
 |                 if (info instanceof ShortcutInfo && v instanceof BubbleTextView && | 
 |                         updates.contains(info)) { | 
 |                     ShortcutInfo si = (ShortcutInfo) info; | 
 |                     BubbleTextView shortcut = (BubbleTextView) v; | 
 |                     Drawable oldIcon = shortcut.getIcon(); | 
 |                     boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable) | 
 |                             && ((PreloadIconDrawable) oldIcon).hasNotCompleted(); | 
 |                     shortcut.applyFromShortcutInfo(si, si.isPromise() != oldPromiseState); | 
 |                 } | 
 |                 // process all the shortcuts | 
 |                 return false; | 
 |             } | 
 |         }); | 
 |  | 
 |         // Update folder icons | 
 |         mapOverItems(MAP_NO_RECURSE, new ItemOperator() { | 
 |             @Override | 
 |             public boolean evaluate(ItemInfo info, View v) { | 
 |                 if (info instanceof FolderInfo && folderIds.contains(info.id)) { | 
 |                     ((FolderInfo) info).itemsChanged(false); | 
 |                 } | 
 |                 // process all the shortcuts | 
 |                 return false; | 
 |             } | 
 |         }); | 
 |     } | 
 |  | 
 |     public void updateIconBadges(final Set<PackageUserKey> updatedBadges) { | 
 |         final PackageUserKey packageUserKey = new PackageUserKey(null, null); | 
 |         final HashSet<Long> folderIds = new HashSet<>(); | 
 |         mapOverItems(MAP_RECURSE, new ItemOperator() { | 
 |             @Override | 
 |             public boolean evaluate(ItemInfo info, View v) { | 
 |                 if (info instanceof ShortcutInfo && v instanceof BubbleTextView | 
 |                         && packageUserKey.updateFromItemInfo(info)) { | 
 |                     if (updatedBadges.contains(packageUserKey)) { | 
 |                         ((BubbleTextView) v).applyBadgeState(info, true /* animate */); | 
 |                         folderIds.add(info.container); | 
 |                     } | 
 |                 } | 
 |                 // process all the shortcuts | 
 |                 return false; | 
 |             } | 
 |         }); | 
 |  | 
 |         // Update folder icons | 
 |         mapOverItems(MAP_NO_RECURSE, new ItemOperator() { | 
 |             @Override | 
 |             public boolean evaluate(ItemInfo info, View v) { | 
 |                 if (info instanceof FolderInfo && folderIds.contains(info.id) | 
 |                         && v instanceof FolderIcon) { | 
 |                     FolderBadgeInfo folderBadgeInfo = new FolderBadgeInfo(); | 
 |                     for (ShortcutInfo si : ((FolderInfo) info).contents) { | 
 |                         folderBadgeInfo.addBadgeInfo(mLauncher.getBadgeInfoForItem(si)); | 
 |                     } | 
 |                     ((FolderIcon) v).setBadgeInfo(folderBadgeInfo); | 
 |                 } | 
 |                 // process all the shortcuts | 
 |                 return false; | 
 |             } | 
 |         }); | 
 |     } | 
 |  | 
 |     public void removeAbandonedPromise(String packageName, UserHandle user) { | 
 |         HashSet<String> packages = new HashSet<>(1); | 
 |         packages.add(packageName); | 
 |         ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packages, user); | 
 |         mLauncher.getModelWriter().deleteItemsFromDatabase(matcher); | 
 |         removeItemsByMatcher(matcher); | 
 |     } | 
 |  | 
 |     public void updateRestoreItems(final HashSet<ItemInfo> updates) { | 
 |         mapOverItems(MAP_RECURSE, new ItemOperator() { | 
 |             @Override | 
 |             public boolean evaluate(ItemInfo info, View v) { | 
 |                 if (info instanceof ShortcutInfo && v instanceof BubbleTextView | 
 |                         && updates.contains(info)) { | 
 |                     ((BubbleTextView) v).applyPromiseState(false /* promiseStateChanged */); | 
 |                 } else if (v instanceof PendingAppWidgetHostView | 
 |                         && info instanceof LauncherAppWidgetInfo | 
 |                         && updates.contains(info)) { | 
 |                     ((PendingAppWidgetHostView) v).applyState(); | 
 |                 } | 
 |                 // process all the shortcuts | 
 |                 return false; | 
 |             } | 
 |         }); | 
 |     } | 
 |  | 
 |     public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) { | 
 |         if (!changedInfo.isEmpty()) { | 
 |             DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo, | 
 |                     mLauncher.getAppWidgetHost()); | 
 |  | 
 |             LauncherAppWidgetInfo item = changedInfo.get(0); | 
 |             final AppWidgetProviderInfo widgetInfo; | 
 |             if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) { | 
 |                 widgetInfo = AppWidgetManagerCompat | 
 |                         .getInstance(mLauncher).findProvider(item.providerName, item.user); | 
 |             } else { | 
 |                 widgetInfo = AppWidgetManagerCompat.getInstance(mLauncher) | 
 |                         .getLauncherAppWidgetInfo(item.appWidgetId); | 
 |             } | 
 |  | 
 |             if (widgetInfo != null) { | 
 |                 // Re-inflate the widgets which have changed status | 
 |                 widgetRefresh.run(); | 
 |             } else { | 
 |                 // widgetRefresh will automatically run when the packages are updated. | 
 |                 // For now just update the progress bars | 
 |                 mapOverItems(MAP_NO_RECURSE, new ItemOperator() { | 
 |                     @Override | 
 |                     public boolean evaluate(ItemInfo info, View view) { | 
 |                         if (view instanceof PendingAppWidgetHostView | 
 |                                 && changedInfo.contains(info)) { | 
 |                             ((LauncherAppWidgetInfo) info).installProgress = 100; | 
 |                             ((PendingAppWidgetHostView) view).applyState(); | 
 |                         } | 
 |                         // process all the shortcuts | 
 |                         return false; | 
 |                     } | 
 |                 }); | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     void moveToDefaultScreen() { | 
 |         int page = DEFAULT_PAGE; | 
 |         if (!workspaceInModalState() && getNextPage() != page) { | 
 |             snapToPage(page); | 
 |         } | 
 |         View child = getChildAt(page); | 
 |         if (child != null) { | 
 |             child.requestFocus(); | 
 |         } | 
 |     } | 
 |  | 
 |     @Override | 
 |     public int getExpectedHeight() { | 
 |         return getMeasuredHeight() <= 0 || !mIsLayoutValid | 
 |                 ? mLauncher.getDeviceProfile().heightPx : getMeasuredHeight(); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public int getExpectedWidth() { | 
 |         return getMeasuredWidth() <= 0 || !mIsLayoutValid | 
 |                 ? mLauncher.getDeviceProfile().widthPx : getMeasuredWidth(); | 
 |     } | 
 |  | 
 |     @Override | 
 |     protected boolean canAnnouncePageDescription() { | 
 |         // Disable announcements while overscrolling potentially to overlay screen because if we end | 
 |         // up on the overlay screen, it will take care of announcing itself. | 
 |         return Float.compare(mOverlayTranslation, 0f) == 0; | 
 |     } | 
 |  | 
 |     @Override | 
 |     protected String getCurrentPageDescription() { | 
 |         int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; | 
 |         return getPageDescription(page); | 
 |     } | 
 |  | 
 |     private String getPageDescription(int page) { | 
 |         int nScreens = getChildCount(); | 
 |         int extraScreenId = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID); | 
 |         if (extraScreenId >= 0 && nScreens > 1) { | 
 |             if (page == extraScreenId) { | 
 |                 return getContext().getString(R.string.workspace_new_page); | 
 |             } | 
 |             nScreens--; | 
 |         } | 
 |         if (nScreens == 0) { | 
 |             // When the workspace is not loaded, we do not know how many screen will be bound. | 
 |             return getContext().getString(R.string.all_apps_home_button_label); | 
 |         } | 
 |         return getContext().getString(R.string.workspace_scroll_format, page + 1, nScreens); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) { | 
 |         target.gridX = info.cellX; | 
 |         target.gridY = info.cellY; | 
 |         target.pageIndex = getCurrentPage(); | 
 |         targetParent.containerType = ContainerType.WORKSPACE; | 
 |         if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { | 
 |             target.rank = info.rank; | 
 |             targetParent.containerType = ContainerType.HOTSEAT; | 
 |         } else if (info.container >= 0) { | 
 |             targetParent.containerType = ContainerType.FOLDER; | 
 |         } | 
 |     } | 
 |  | 
 |     /** | 
 |      * Used as a workaround to ensure that the AppWidgetService receives the | 
 |      * PACKAGE_ADDED broadcast before updating widgets. | 
 |      */ | 
 |     private class DeferredWidgetRefresh implements Runnable, ProviderChangedListener { | 
 |         private final ArrayList<LauncherAppWidgetInfo> mInfos; | 
 |         private final LauncherAppWidgetHost mHost; | 
 |         private final Handler mHandler; | 
 |  | 
 |         private boolean mRefreshPending; | 
 |  | 
 |         DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos, | 
 |             LauncherAppWidgetHost host) { | 
 |             mInfos = infos; | 
 |             mHost = host; | 
 |             mHandler = new Handler(); | 
 |             mRefreshPending = true; | 
 |  | 
 |             mHost.addProviderChangeListener(this); | 
 |             // Force refresh after 10 seconds, if we don't get the provider changed event. | 
 |             // This could happen when the provider is no longer available in the app. | 
 |             mHandler.postDelayed(this, 10000); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void run() { | 
 |             mHost.removeProviderChangeListener(this); | 
 |             mHandler.removeCallbacks(this); | 
 |  | 
 |             if (!mRefreshPending) { | 
 |                 return; | 
 |             } | 
 |  | 
 |             mRefreshPending = false; | 
 |  | 
 |             ArrayList<PendingAppWidgetHostView> views = new ArrayList<>(mInfos.size()); | 
 |             mapOverItems(MAP_NO_RECURSE, (info, view) -> { | 
 |                 if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) { | 
 |                     views.add((PendingAppWidgetHostView) view); | 
 |                 } | 
 |                 // process all children | 
 |                 return false; | 
 |             }); | 
 |             for (PendingAppWidgetHostView view : views) { | 
 |                 view.reInflate(); | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void notifyWidgetProvidersChanged() { | 
 |             run(); | 
 |         } | 
 |     } | 
 |  | 
 |     private class StateTransitionListener extends AnimatorListenerAdapter | 
 |             implements AnimatorUpdateListener { | 
 |  | 
 |         private final LauncherState mToState; | 
 |  | 
 |         StateTransitionListener(LauncherState toState) { | 
 |             mToState = toState; | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void onAnimationUpdate(ValueAnimator anim) { | 
 |             mTransitionProgress = anim.getAnimatedFraction(); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void onAnimationStart(Animator animation) { | 
 |             onStartStateTransition(mToState); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void onAnimationEnd(Animator animation) { | 
 |             onEndStateTransition(); | 
 |         } | 
 |     } | 
 | } |