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