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 static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
     20 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
     21 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
     22 import static com.android.launcher3.LauncherState.ALL_APPS;
     23 import static com.android.launcher3.LauncherState.NORMAL;
     24 import static com.android.launcher3.LauncherState.SPRING_LOADED;
     25 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_OVERLAY;
     26 
     27 import android.animation.Animator;
     28 import android.animation.AnimatorListenerAdapter;
     29 import android.animation.LayoutTransition;
     30 import android.animation.ObjectAnimator;
     31 import android.animation.PropertyValuesHolder;
     32 import android.animation.ValueAnimator;
     33 import android.animation.ValueAnimator.AnimatorUpdateListener;
     34 import android.annotation.SuppressLint;
     35 import android.app.WallpaperManager;
     36 import android.appwidget.AppWidgetHostView;
     37 import android.appwidget.AppWidgetProviderInfo;
     38 import android.content.Context;
     39 import android.content.res.Resources;
     40 import android.graphics.Bitmap;
     41 import android.graphics.Canvas;
     42 import android.graphics.Point;
     43 import android.graphics.Rect;
     44 import android.graphics.drawable.Drawable;
     45 import android.os.Handler;
     46 import android.os.IBinder;
     47 import android.os.Parcelable;
     48 import android.os.UserHandle;
     49 import android.util.AttributeSet;
     50 import android.util.Log;
     51 import android.util.SparseArray;
     52 import android.view.LayoutInflater;
     53 import android.view.MotionEvent;
     54 import android.view.View;
     55 import android.view.ViewGroup;
     56 import android.view.ViewTreeObserver;
     57 import android.widget.Toast;
     58 
     59 import com.android.launcher3.Launcher.LauncherOverlay;
     60 import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
     61 import com.android.launcher3.LauncherStateManager.AnimationConfig;
     62 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
     63 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
     64 import com.android.launcher3.anim.AnimatorSetBuilder;
     65 import com.android.launcher3.anim.Interpolators;
     66 import com.android.launcher3.badge.FolderBadgeInfo;
     67 import com.android.launcher3.compat.AppWidgetManagerCompat;
     68 import com.android.launcher3.config.FeatureFlags;
     69 import com.android.launcher3.dragndrop.DragController;
     70 import com.android.launcher3.dragndrop.DragLayer;
     71 import com.android.launcher3.dragndrop.DragOptions;
     72 import com.android.launcher3.dragndrop.DragView;
     73 import com.android.launcher3.dragndrop.SpringLoadedDragController;
     74 import com.android.launcher3.folder.Folder;
     75 import com.android.launcher3.folder.FolderIcon;
     76 import com.android.launcher3.folder.PreviewBackground;
     77 import com.android.launcher3.graphics.DragPreviewProvider;
     78 import com.android.launcher3.graphics.PreloadIconDrawable;
     79 import com.android.launcher3.pageindicators.WorkspacePageIndicator;
     80 import com.android.launcher3.popup.PopupContainerWithArrow;
     81 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
     82 import com.android.launcher3.touch.ItemLongClickListener;
     83 import com.android.launcher3.touch.WorkspaceTouchListener;
     84 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
     85 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
     86 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
     87 import com.android.launcher3.util.ItemInfoMatcher;
     88 import com.android.launcher3.util.LongArrayMap;
     89 import com.android.launcher3.util.PackageUserKey;
     90 import com.android.launcher3.util.Thunk;
     91 import com.android.launcher3.util.WallpaperOffsetInterpolator;
     92 import com.android.launcher3.widget.LauncherAppWidgetHostView;
     93 import com.android.launcher3.widget.PendingAddShortcutInfo;
     94 import com.android.launcher3.widget.PendingAddWidgetInfo;
     95 import com.android.launcher3.widget.PendingAppWidgetHostView;
     96 
     97 import java.util.ArrayList;
     98 import java.util.HashSet;
     99 import java.util.Set;
    100 
    101 /**
    102  * The workspace is a wide area with a wallpaper and a finite number of pages.
    103  * Each page contains a number of icons, folders or widgets the user can
    104  * interact with. A workspace is meant to be used with a fixed width only.
    105  */
    106 public class Workspace extends PagedView<WorkspacePageIndicator>
    107         implements DropTarget, DragSource, View.OnTouchListener,
    108         DragController.DragListener, Insettable, LauncherStateManager.StateHandler {
    109     private static final String TAG = "Launcher.Workspace";
    110 
    111     /** The value that {@link #mTransitionProgress} must be greater than for
    112      * {@link #transitionStateShouldAllowDrop()} to return true. */
    113     private static final float ALLOW_DROP_TRANSITION_PROGRESS = 0.25f;
    114 
    115     /** The value that {@link #mTransitionProgress} must be greater than for
    116      * {@link #isFinishedSwitchingState()} ()} to return true. */
    117     private static final float FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS = 0.5f;
    118 
    119     private static final boolean ENFORCE_DRAG_EVENT_ORDER = false;
    120 
    121     private static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
    122     private static final int FADE_EMPTY_SCREEN_DURATION = 150;
    123 
    124     private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
    125 
    126     private static final int DEFAULT_PAGE = 0;
    127 
    128     private static final boolean MAP_NO_RECURSE = false;
    129     private static final boolean MAP_RECURSE = true;
    130 
    131     // The screen id used for the empty screen always present to the right.
    132     public static final long EXTRA_EMPTY_SCREEN_ID = -201;
    133     // The is the first screen. It is always present, even if its empty.
    134     public static final long FIRST_SCREEN_ID = 0;
    135 
    136     private LayoutTransition mLayoutTransition;
    137     @Thunk final WallpaperManager mWallpaperManager;
    138 
    139     private ShortcutAndWidgetContainer mDragSourceInternal;
    140 
    141     @Thunk final LongArrayMap<CellLayout> mWorkspaceScreens = new LongArrayMap<>();
    142     @Thunk final ArrayList<Long> mScreenOrder = new ArrayList<>();
    143 
    144     @Thunk Runnable mRemoveEmptyScreenRunnable;
    145     @Thunk boolean mDeferRemoveExtraEmptyScreen = false;
    146 
    147     /**
    148      * CellInfo for the cell that is currently being dragged
    149      */
    150     private CellLayout.CellInfo mDragInfo;
    151 
    152     /**
    153      * Target drop area calculated during last acceptDrop call.
    154      */
    155     @Thunk int[] mTargetCell = new int[2];
    156     private int mDragOverX = -1;
    157     private int mDragOverY = -1;
    158 
    159     /**
    160      * The CellLayout that is currently being dragged over
    161      */
    162     @Thunk CellLayout mDragTargetLayout = null;
    163     /**
    164      * The CellLayout that we will show as highlighted
    165      */
    166     private CellLayout mDragOverlappingLayout = null;
    167 
    168     /**
    169      * The CellLayout which will be dropped to
    170      */
    171     private CellLayout mDropToLayout = null;
    172 
    173     @Thunk final Launcher mLauncher;
    174     @Thunk DragController mDragController;
    175 
    176     private final int[] mTempXY = new int[2];
    177     @Thunk float[] mDragViewVisualCenter = new float[2];
    178     private final float[] mTempTouchCoordinates = new float[2];
    179 
    180     private SpringLoadedDragController mSpringLoadedDragController;
    181 
    182     private boolean mIsSwitchingState = false;
    183 
    184     boolean mChildrenLayersEnabled = true;
    185 
    186     private boolean mStripScreensOnPageStopMoving = false;
    187 
    188     private DragPreviewProvider mOutlineProvider = null;
    189     private boolean mWorkspaceFadeInAdjacentScreens;
    190 
    191     final WallpaperOffsetInterpolator mWallpaperOffset;
    192     private boolean mUnlockWallpaperFromDefaultPageOnLayout;
    193 
    194     // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
    195     private static final int FOLDER_CREATION_TIMEOUT = 0;
    196     public static final int REORDER_TIMEOUT = 650;
    197     private final Alarm mFolderCreationAlarm = new Alarm();
    198     private final Alarm mReorderAlarm = new Alarm();
    199     private PreviewBackground mFolderCreateBg;
    200     private FolderIcon mDragOverFolderIcon = null;
    201     private boolean mCreateUserFolderOnDrop = false;
    202     private boolean mAddToExistingFolderOnDrop = false;
    203     private float mMaxDistanceForFolderCreation;
    204 
    205     // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
    206     private float mXDown;
    207     private float mYDown;
    208     final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
    209     final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
    210     final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
    211 
    212     // Relating to the animation of items being dropped externally
    213     public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0;
    214     public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1;
    215     public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2;
    216     public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3;
    217     public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4;
    218 
    219     // Related to dragging, folder creation and reordering
    220     private static final int DRAG_MODE_NONE = 0;
    221     private static final int DRAG_MODE_CREATE_FOLDER = 1;
    222     private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
    223     private static final int DRAG_MODE_REORDER = 3;
    224     private int mDragMode = DRAG_MODE_NONE;
    225     @Thunk int mLastReorderX = -1;
    226     @Thunk int mLastReorderY = -1;
    227 
    228     private SparseArray<Parcelable> mSavedStates;
    229     private final ArrayList<Integer> mRestoredPages = new ArrayList<>();
    230 
    231     private float mCurrentScale;
    232     private float mTransitionProgress;
    233 
    234     // State related to Launcher Overlay
    235     LauncherOverlay mLauncherOverlay;
    236     boolean mScrollInteractionBegan;
    237     boolean mStartedSendingScrollEvents;
    238     float mLastOverlayScroll = 0;
    239     boolean mOverlayShown = false;
    240     private Runnable mOnOverlayHiddenCallback;
    241 
    242     private boolean mForceDrawAdjacentPages = false;
    243 
    244     // Total over scrollX in the overlay direction.
    245     private float mOverlayTranslation;
    246 
    247     // Handles workspace state transitions
    248     private final WorkspaceStateTransitionAnimation mStateTransitionAnimation;
    249 
    250     /**
    251      * Used to inflate the Workspace from XML.
    252      *
    253      * @param context The application's context.
    254      * @param attrs The attributes set containing the Workspace's customization values.
    255      */
    256     public Workspace(Context context, AttributeSet attrs) {
    257         this(context, attrs, 0);
    258     }
    259 
    260     /**
    261      * Used to inflate the Workspace from XML.
    262      *
    263      * @param context The application's context.
    264      * @param attrs The attributes set containing the Workspace's customization values.
    265      * @param defStyle Unused.
    266      */
    267     public Workspace(Context context, AttributeSet attrs, int defStyle) {
    268         super(context, attrs, defStyle);
    269 
    270         mLauncher = Launcher.getLauncher(context);
    271         mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this);
    272         mWallpaperManager = WallpaperManager.getInstance(context);
    273 
    274         mWallpaperOffset = new WallpaperOffsetInterpolator(this);
    275 
    276         setHapticFeedbackEnabled(false);
    277         initWorkspace();
    278 
    279         // Disable multitouch across the workspace/all apps/customize tray
    280         setMotionEventSplittingEnabled(true);
    281         setOnTouchListener(new WorkspaceTouchListener(mLauncher, this));
    282     }
    283 
    284     @Override
    285     public void setInsets(Rect insets) {
    286         mInsets.set(insets);
    287 
    288         DeviceProfile grid = mLauncher.getDeviceProfile();
    289         mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
    290         mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
    291 
    292         Rect padding = grid.workspacePadding;
    293         setPadding(padding.left, padding.top, padding.right, padding.bottom);
    294 
    295         if (grid.shouldFadeAdjacentWorkspaceScreens()) {
    296             // In landscape mode the page spacing is set to the default.
    297             setPageSpacing(grid.defaultPageSpacingPx);
    298         } else {
    299             // In portrait, we want the pages spaced such that there is no
    300             // overhang of the previous / next page into the current page viewport.
    301             // We assume symmetrical padding in portrait mode.
    302             setPageSpacing(Math.max(grid.defaultPageSpacingPx, padding.left + 1));
    303         }
    304 
    305         int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx;
    306         int paddingBottom = grid.cellLayoutBottomPaddingPx;
    307         for (int i = mWorkspaceScreens.size() - 1; i >= 0; i--) {
    308             mWorkspaceScreens.valueAt(i)
    309                     .setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
    310         }
    311     }
    312 
    313     /**
    314      * Estimates the size of an item using spans: hSpan, vSpan.
    315      *
    316      * @return MAX_VALUE for each dimension if unsuccessful.
    317      */
    318     public int[] estimateItemSize(ItemInfo itemInfo) {
    319         int[] size = new int[2];
    320         if (getChildCount() > 0) {
    321             // Use the first page to estimate the child position
    322             CellLayout cl = (CellLayout) getChildAt(0);
    323             boolean isWidget = itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
    324 
    325             Rect r = estimateItemPosition(cl, 0, 0, itemInfo.spanX, itemInfo.spanY);
    326 
    327             float scale = 1;
    328             if (isWidget) {
    329                 DeviceProfile profile = mLauncher.getDeviceProfile();
    330                 scale = Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
    331             }
    332             size[0] = r.width();
    333             size[1] = r.height();
    334 
    335             if (isWidget) {
    336                 size[0] /= scale;
    337                 size[1] /= scale;
    338             }
    339             return size;
    340         } else {
    341             size[0] = Integer.MAX_VALUE;
    342             size[1] = Integer.MAX_VALUE;
    343             return size;
    344         }
    345     }
    346 
    347     public float getWallpaperOffsetForCenterPage() {
    348         int pageScroll = getScrollForPage(getPageNearestToCenterOfScreen());
    349         return mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
    350     }
    351 
    352     public Rect estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan) {
    353         Rect r = new Rect();
    354         cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
    355         return r;
    356     }
    357 
    358     @Override
    359     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
    360         if (ENFORCE_DRAG_EVENT_ORDER) {
    361             enforceDragParity("onDragStart", 0, 0);
    362         }
    363 
    364         if (mDragInfo != null && mDragInfo.cell != null) {
    365             CellLayout layout = (CellLayout) mDragInfo.cell.getParent().getParent();
    366             layout.markCellsAsUnoccupiedForView(mDragInfo.cell);
    367         }
    368 
    369         if (mOutlineProvider != null) {
    370             if (dragObject.dragView != null) {
    371                 Bitmap preview = dragObject.dragView.getPreviewBitmap();
    372 
    373                 // The outline is used to visualize where the item will land if dropped
    374                 mOutlineProvider.generateDragOutline(preview);
    375             }
    376         }
    377 
    378         updateChildrenLayersEnabled();
    379 
    380         // Do not add a new page if it is a accessible drag which was not started by the workspace.
    381         // We do not support accessibility drag from other sources and instead provide a direct
    382         // action for move/add to homescreen.
    383         // When a accessible drag is started by the folder, we only allow rearranging withing the
    384         // folder.
    385         boolean addNewPage = !(options.isAccessibleDrag && dragObject.dragSource != this);
    386 
    387         if (addNewPage) {
    388             mDeferRemoveExtraEmptyScreen = false;
    389             addExtraEmptyScreenOnDrag();
    390 
    391             if (dragObject.dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
    392                     && dragObject.dragSource != this) {
    393                 // When dragging a widget from different source, move to a page which has
    394                 // enough space to place this widget (after rearranging/resizing). We special case
    395                 // widgets as they cannot be placed inside a folder.
    396                 // Start at the current page and search right (on LTR) until finding a page with
    397                 // enough space. Since an empty screen is the furthest right, a page must be found.
    398                 int currentPage = getPageNearestToCenterOfScreen();
    399                 for (int pageIndex = currentPage; pageIndex < getPageCount(); pageIndex++) {
    400                     CellLayout page = (CellLayout) getPageAt(pageIndex);
    401                     if (page.hasReorderSolution(dragObject.dragInfo)) {
    402                         setCurrentPage(pageIndex);
    403                         break;
    404                     }
    405                 }
    406             }
    407         }
    408 
    409         // Always enter the spring loaded mode
    410         mLauncher.getStateManager().goToState(SPRING_LOADED);
    411     }
    412 
    413     public void deferRemoveExtraEmptyScreen() {
    414         mDeferRemoveExtraEmptyScreen = true;
    415     }
    416 
    417     @Override
    418     public void onDragEnd() {
    419         if (ENFORCE_DRAG_EVENT_ORDER) {
    420             enforceDragParity("onDragEnd", 0, 0);
    421         }
    422 
    423         if (!mDeferRemoveExtraEmptyScreen) {
    424             removeExtraEmptyScreen(true, mDragSourceInternal != null);
    425         }
    426 
    427         updateChildrenLayersEnabled();
    428         mDragInfo = null;
    429         mOutlineProvider = null;
    430         mDragSourceInternal = null;
    431     }
    432 
    433     /**
    434      * Initializes various states for this workspace.
    435      */
    436     protected void initWorkspace() {
    437         mCurrentPage = DEFAULT_PAGE;
    438         setClipToPadding(false);
    439 
    440         setupLayoutTransition();
    441 
    442         // Set the wallpaper dimensions when Launcher starts up
    443         setWallpaperDimension();
    444     }
    445 
    446     private void setupLayoutTransition() {
    447         // We want to show layout transitions when pages are deleted, to close the gap.
    448         mLayoutTransition = new LayoutTransition();
    449         mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
    450         mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
    451         mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
    452         mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
    453         setLayoutTransition(mLayoutTransition);
    454     }
    455 
    456     void enableLayoutTransitions() {
    457         setLayoutTransition(mLayoutTransition);
    458     }
    459     void disableLayoutTransitions() {
    460         setLayoutTransition(null);
    461     }
    462 
    463     @Override
    464     public void onViewAdded(View child) {
    465         if (!(child instanceof CellLayout)) {
    466             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
    467         }
    468         CellLayout cl = ((CellLayout) child);
    469         cl.setOnInterceptTouchListener(this);
    470         cl.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
    471         super.onViewAdded(child);
    472     }
    473 
    474     public boolean isTouchActive() {
    475         return mTouchState != TOUCH_STATE_REST;
    476     }
    477 
    478     /**
    479      * Initializes and binds the first page
    480      * @param qsb an existing qsb to recycle or null.
    481      */
    482     public void bindAndInitFirstWorkspaceScreen(View qsb) {
    483         if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
    484             return;
    485         }
    486         // Add the first page
    487         CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, 0);
    488         // Always add a QSB on the first screen.
    489         if (qsb == null) {
    490             // In transposed layout, we add the QSB in the Grid. As workspace does not touch the
    491             // edges, we do not need a full width QSB.
    492             qsb = LayoutInflater.from(getContext())
    493                     .inflate(R.layout.search_container_workspace,firstPage, false);
    494         }
    495 
    496         CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, firstPage.getCountX(), 1);
    497         lp.canReorder = false;
    498         if (!firstPage.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true)) {
    499             Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
    500         }
    501     }
    502 
    503     public void removeAllWorkspaceScreens() {
    504         // Disable all layout transitions before removing all pages to ensure that we don't get the
    505         // transition animations competing with us changing the scroll when we add pages
    506         disableLayoutTransitions();
    507 
    508         // Recycle the QSB widget
    509         View qsb = findViewById(R.id.search_container_workspace);
    510         if (qsb != null) {
    511             ((ViewGroup) qsb.getParent()).removeView(qsb);
    512         }
    513 
    514         // Remove the pages and clear the screen models
    515         removeFolderListeners();
    516         removeAllViews();
    517         mScreenOrder.clear();
    518         mWorkspaceScreens.clear();
    519 
    520         // Ensure that the first page is always present
    521         bindAndInitFirstWorkspaceScreen(qsb);
    522 
    523         // Re-enable the layout transitions
    524         enableLayoutTransitions();
    525     }
    526 
    527     public void insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
    528         // Find the index to insert this view into.  If the empty screen exists, then
    529         // insert it before that.
    530         int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
    531         if (insertIndex < 0) {
    532             insertIndex = mScreenOrder.size();
    533         }
    534         insertNewWorkspaceScreen(screenId, insertIndex);
    535     }
    536 
    537     public void insertNewWorkspaceScreen(long screenId) {
    538         insertNewWorkspaceScreen(screenId, getChildCount());
    539     }
    540 
    541     public CellLayout insertNewWorkspaceScreen(long screenId, int insertIndex) {
    542         if (mWorkspaceScreens.containsKey(screenId)) {
    543             throw new RuntimeException("Screen id " + screenId + " already exists!");
    544         }
    545 
    546         // Inflate the cell layout, but do not add it automatically so that we can get the newly
    547         // created CellLayout.
    548         CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
    549                         R.layout.workspace_screen, this, false /* attachToRoot */);
    550         int paddingLeftRight = mLauncher.getDeviceProfile().cellLayoutPaddingLeftRightPx;
    551         int paddingBottom = mLauncher.getDeviceProfile().cellLayoutBottomPaddingPx;
    552         newScreen.setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
    553 
    554         mWorkspaceScreens.put(screenId, newScreen);
    555         mScreenOrder.add(insertIndex, screenId);
    556         addView(newScreen, insertIndex);
    557         mStateTransitionAnimation.applyChildState(
    558                 mLauncher.getStateManager().getState(), newScreen, insertIndex);
    559 
    560         if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
    561             newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
    562         }
    563 
    564         return newScreen;
    565     }
    566 
    567     public void addExtraEmptyScreenOnDrag() {
    568         boolean lastChildOnScreen = false;
    569         boolean childOnFinalScreen = false;
    570 
    571         // Cancel any pending removal of empty screen
    572         mRemoveEmptyScreenRunnable = null;
    573 
    574         if (mDragSourceInternal != null) {
    575             if (mDragSourceInternal.getChildCount() == 1) {
    576                 lastChildOnScreen = true;
    577             }
    578             CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
    579             if (indexOfChild(cl) == getChildCount() - 1) {
    580                 childOnFinalScreen = true;
    581             }
    582         }
    583 
    584         // If this is the last item on the final screen
    585         if (lastChildOnScreen && childOnFinalScreen) {
    586             return;
    587         }
    588         if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
    589             insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
    590         }
    591     }
    592 
    593     public boolean addExtraEmptyScreen() {
    594         if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
    595             insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
    596             return true;
    597         }
    598         return false;
    599     }
    600 
    601     private void convertFinalScreenToEmptyScreenIfNecessary() {
    602         if (mLauncher.isWorkspaceLoading()) {
    603             // Invalid and dangerous operation if workspace is loading
    604             return;
    605         }
    606 
    607         if (hasExtraEmptyScreen() || mScreenOrder.size() == 0) return;
    608         long finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1);
    609 
    610         CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId);
    611 
    612         // If the final screen is empty, convert it to the extra empty screen
    613         if (finalScreen.getShortcutsAndWidgets().getChildCount() == 0 &&
    614                 !finalScreen.isDropPending()) {
    615             mWorkspaceScreens.remove(finalScreenId);
    616             mScreenOrder.remove(finalScreenId);
    617 
    618             // if this is the last screen, convert it to the empty screen
    619             mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen);
    620             mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
    621 
    622             // Update the model if we have changed any screens
    623             LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
    624         }
    625     }
    626 
    627     public void removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens) {
    628         removeExtraEmptyScreenDelayed(animate, null, 0, stripEmptyScreens);
    629     }
    630 
    631     public void removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete,
    632             final int delay, final boolean stripEmptyScreens) {
    633         if (mLauncher.isWorkspaceLoading()) {
    634             // Don't strip empty screens if the workspace is still loading
    635             return;
    636         }
    637 
    638         if (delay > 0) {
    639             postDelayed(new Runnable() {
    640                 @Override
    641                 public void run() {
    642                     removeExtraEmptyScreenDelayed(animate, onComplete, 0, stripEmptyScreens);
    643                 }
    644             }, delay);
    645             return;
    646         }
    647 
    648         convertFinalScreenToEmptyScreenIfNecessary();
    649         if (hasExtraEmptyScreen()) {
    650             int emptyIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
    651             if (getNextPage() == emptyIndex) {
    652                 snapToPage(getNextPage() - 1, SNAP_OFF_EMPTY_SCREEN_DURATION);
    653                 fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION,
    654                         onComplete, stripEmptyScreens);
    655             } else {
    656                 snapToPage(getNextPage(), 0);
    657                 fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION,
    658                         onComplete, stripEmptyScreens);
    659             }
    660             return;
    661         } else if (stripEmptyScreens) {
    662             // If we're not going to strip the empty screens after removing
    663             // the extra empty screen, do it right away.
    664             stripEmptyScreens();
    665         }
    666 
    667         if (onComplete != null) {
    668             onComplete.run();
    669         }
    670     }
    671 
    672     private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete,
    673             final boolean stripEmptyScreens) {
    674         // XXX: Do we need to update LM workspace screens below?
    675         PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
    676         PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", 0f);
    677 
    678         final CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
    679 
    680         mRemoveEmptyScreenRunnable = new Runnable() {
    681             @Override
    682             public void run() {
    683                 if (hasExtraEmptyScreen()) {
    684                     mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
    685                     mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
    686                     removeView(cl);
    687                     if (stripEmptyScreens) {
    688                         stripEmptyScreens();
    689                     }
    690                     // Update the page indicator to reflect the removed page.
    691                     showPageIndicatorAtCurrentScroll();
    692                 }
    693             }
    694         };
    695 
    696         ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(cl, alpha, bgAlpha);
    697         oa.setDuration(duration);
    698         oa.setStartDelay(delay);
    699         oa.addListener(new AnimatorListenerAdapter() {
    700             @Override
    701             public void onAnimationEnd(Animator animation) {
    702                 if (mRemoveEmptyScreenRunnable != null) {
    703                     mRemoveEmptyScreenRunnable.run();
    704                 }
    705                 if (onComplete != null) {
    706                     onComplete.run();
    707                 }
    708             }
    709         });
    710         oa.start();
    711     }
    712 
    713     public boolean hasExtraEmptyScreen() {
    714         return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && getChildCount() > 1;
    715     }
    716 
    717     public long commitExtraEmptyScreen() {
    718         if (mLauncher.isWorkspaceLoading()) {
    719             // Invalid and dangerous operation if workspace is loading
    720             return -1;
    721         }
    722 
    723         CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
    724         mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
    725         mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
    726 
    727         long newId = LauncherSettings.Settings.call(getContext().getContentResolver(),
    728                 LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
    729                 .getLong(LauncherSettings.Settings.EXTRA_VALUE);
    730         mWorkspaceScreens.put(newId, cl);
    731         mScreenOrder.add(newId);
    732 
    733         // Update the model for the new screen
    734         LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
    735 
    736         return newId;
    737     }
    738 
    739     public CellLayout getScreenWithId(long screenId) {
    740         return mWorkspaceScreens.get(screenId);
    741     }
    742 
    743     public long getIdForScreen(CellLayout layout) {
    744         int index = mWorkspaceScreens.indexOfValue(layout);
    745         if (index != -1) {
    746             return mWorkspaceScreens.keyAt(index);
    747         }
    748         return -1;
    749     }
    750 
    751     public int getPageIndexForScreenId(long screenId) {
    752         return indexOfChild(mWorkspaceScreens.get(screenId));
    753     }
    754 
    755     public long getScreenIdForPageIndex(int index) {
    756         if (0 <= index && index < mScreenOrder.size()) {
    757             return mScreenOrder.get(index);
    758         }
    759         return -1;
    760     }
    761 
    762     public ArrayList<Long> getScreenOrder() {
    763         return mScreenOrder;
    764     }
    765 
    766     public void stripEmptyScreens() {
    767         if (mLauncher.isWorkspaceLoading()) {
    768             // Don't strip empty screens if the workspace is still loading.
    769             // This is dangerous and can result in data loss.
    770             return;
    771         }
    772 
    773         if (isPageInTransition()) {
    774             mStripScreensOnPageStopMoving = true;
    775             return;
    776         }
    777 
    778         int currentPage = getNextPage();
    779         ArrayList<Long> removeScreens = new ArrayList<>();
    780         int total = mWorkspaceScreens.size();
    781         for (int i = 0; i < total; i++) {
    782             long id = mWorkspaceScreens.keyAt(i);
    783             CellLayout cl = mWorkspaceScreens.valueAt(i);
    784             // FIRST_SCREEN_ID can never be removed.
    785             if ((!FeatureFlags.QSB_ON_FIRST_SCREEN || id > FIRST_SCREEN_ID)
    786                     && cl.getShortcutsAndWidgets().getChildCount() == 0) {
    787                 removeScreens.add(id);
    788             }
    789         }
    790 
    791         boolean isInAccessibleDrag = mLauncher.getAccessibilityDelegate().isInAccessibleDrag();
    792 
    793         // We enforce at least one page to add new items to. In the case that we remove the last
    794         // such screen, we convert the last screen to the empty screen
    795         int minScreens = 1;
    796 
    797         int pageShift = 0;
    798         for (Long id: removeScreens) {
    799             CellLayout cl = mWorkspaceScreens.get(id);
    800             mWorkspaceScreens.remove(id);
    801             mScreenOrder.remove(id);
    802 
    803             if (getChildCount() > minScreens) {
    804                 if (indexOfChild(cl) < currentPage) {
    805                     pageShift++;
    806                 }
    807 
    808                 if (isInAccessibleDrag) {
    809                     cl.enableAccessibleDrag(false, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
    810                 }
    811 
    812                 removeView(cl);
    813             } else {
    814                 // if this is the last screen, convert it to the empty screen
    815                 mRemoveEmptyScreenRunnable = null;
    816                 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
    817                 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
    818             }
    819         }
    820 
    821         if (!removeScreens.isEmpty()) {
    822             // Update the model if we have changed any screens
    823             LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
    824         }
    825 
    826         if (pageShift >= 0) {
    827             setCurrentPage(currentPage - pageShift);
    828         }
    829     }
    830 
    831     /**
    832      * At bind time, we use the rank (screenId) to compute x and y for hotseat items.
    833      * See {@link #addInScreen}.
    834      */
    835     public void addInScreenFromBind(View child, ItemInfo info) {
    836         int x = info.cellX;
    837         int y = info.cellY;
    838         if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
    839             int screenId = (int) info.screenId;
    840             x = mLauncher.getHotseat().getCellXFromOrder(screenId);
    841             y = mLauncher.getHotseat().getCellYFromOrder(screenId);
    842         }
    843         addInScreen(child, info.container, info.screenId, x, y, info.spanX, info.spanY);
    844     }
    845 
    846     /**
    847      * Adds the specified child in the specified screen based on the {@param info}
    848      * See {@link #addInScreen(View, long, long, int, int, int, int)}.
    849      */
    850     public void addInScreen(View child, ItemInfo info) {
    851         addInScreen(child, info.container, info.screenId, info.cellX, info.cellY,
    852                 info.spanX, info.spanY);
    853     }
    854 
    855     /**
    856      * Adds the specified child in the specified screen. The position and dimension of
    857      * the child are defined by x, y, spanX and spanY.
    858      *
    859      * @param child The child to add in one of the workspace's screens.
    860      * @param screenId The screen in which to add the child.
    861      * @param x The X position of the child in the screen's grid.
    862      * @param y The Y position of the child in the screen's grid.
    863      * @param spanX The number of cells spanned horizontally by the child.
    864      * @param spanY The number of cells spanned vertically by the child.
    865      */
    866     private void addInScreen(View child, long container, long screenId, int x, int y,
    867             int spanX, int spanY) {
    868         if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
    869             if (getScreenWithId(screenId) == null) {
    870                 Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
    871                 // DEBUGGING - Print out the stack trace to see where we are adding from
    872                 new Throwable().printStackTrace();
    873                 return;
    874             }
    875         }
    876         if (screenId == EXTRA_EMPTY_SCREEN_ID) {
    877             // This should never happen
    878             throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
    879         }
    880 
    881         final CellLayout layout;
    882         if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
    883             layout = mLauncher.getHotseat().getLayout();
    884 
    885             // Hide folder title in the hotseat
    886             if (child instanceof FolderIcon) {
    887                 ((FolderIcon) child).setTextVisible(false);
    888             }
    889         } else {
    890             // Show folder title if not in the hotseat
    891             if (child instanceof FolderIcon) {
    892                 ((FolderIcon) child).setTextVisible(true);
    893             }
    894             layout = getScreenWithId(screenId);
    895         }
    896 
    897         ViewGroup.LayoutParams genericLp = child.getLayoutParams();
    898         CellLayout.LayoutParams lp;
    899         if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
    900             lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
    901         } else {
    902             lp = (CellLayout.LayoutParams) genericLp;
    903             lp.cellX = x;
    904             lp.cellY = y;
    905             lp.cellHSpan = spanX;
    906             lp.cellVSpan = spanY;
    907         }
    908 
    909         if (spanX < 0 && spanY < 0) {
    910             lp.isLockedToGrid = false;
    911         }
    912 
    913         // Get the canonical child id to uniquely represent this view in this screen
    914         ItemInfo info = (ItemInfo) child.getTag();
    915         int childId = mLauncher.getViewIdForItem(info);
    916 
    917         boolean markCellsAsOccupied = !(child instanceof Folder);
    918         if (!layout.addViewToCellLayout(child, -1, childId, lp, markCellsAsOccupied)) {
    919             // TODO: This branch occurs when the workspace is adding views
    920             // outside of the defined grid
    921             // maybe we should be deleting these items from the LauncherModel?
    922             Log.e(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
    923         }
    924 
    925         child.setHapticFeedbackEnabled(false);
    926         child.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
    927         if (child instanceof DropTarget) {
    928             mDragController.addDropTarget((DropTarget) child);
    929         }
    930     }
    931 
    932     /**
    933      * Called directly from a CellLayout (not by the framework), after we've been added as a
    934      * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
    935      * that it should intercept touch events, which is not something that is normally supported.
    936      */
    937     @SuppressLint("ClickableViewAccessibility")
    938     @Override
    939     public boolean onTouch(View v, MotionEvent event) {
    940         return shouldConsumeTouch(v);
    941     }
    942 
    943     private boolean shouldConsumeTouch(View v) {
    944         return !workspaceIconsCanBeDragged()
    945                 || (!workspaceInModalState() && indexOfChild(v) != mCurrentPage);
    946     }
    947 
    948     public boolean isSwitchingState() {
    949         return mIsSwitchingState;
    950     }
    951 
    952     /** This differs from isSwitchingState in that we take into account how far the transition
    953      *  has completed. */
    954     public boolean isFinishedSwitchingState() {
    955         return !mIsSwitchingState
    956                 || (mTransitionProgress > FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS);
    957     }
    958 
    959     @Override
    960     public boolean dispatchUnhandledMove(View focused, int direction) {
    961         if (workspaceInModalState() || !isFinishedSwitchingState()) {
    962             // when the home screens are shrunken, shouldn't allow side-scrolling
    963             return false;
    964         }
    965         return super.dispatchUnhandledMove(focused, direction);
    966     }
    967 
    968     @Override
    969     public boolean onInterceptTouchEvent(MotionEvent ev) {
    970         if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
    971             mXDown = ev.getX();
    972             mYDown = ev.getY();
    973         }
    974         return super.onInterceptTouchEvent(ev);
    975     }
    976 
    977     @Override
    978     protected void determineScrollingStart(MotionEvent ev) {
    979         if (!isFinishedSwitchingState()) return;
    980 
    981         float deltaX = ev.getX() - mXDown;
    982         float absDeltaX = Math.abs(deltaX);
    983         float absDeltaY = Math.abs(ev.getY() - mYDown);
    984 
    985         if (Float.compare(absDeltaX, 0f) == 0) return;
    986 
    987         float slope = absDeltaY / absDeltaX;
    988         float theta = (float) Math.atan(slope);
    989 
    990         if (absDeltaX > mTouchSlop || absDeltaY > mTouchSlop) {
    991             cancelCurrentPageLongPress();
    992         }
    993 
    994         if (theta > MAX_SWIPE_ANGLE) {
    995             // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
    996             return;
    997         } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
    998             // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
    999             // increase the touch slop to make it harder to begin scrolling the workspace. This
   1000             // results in vertically scrolling widgets to more easily. The higher the angle, the
   1001             // more we increase touch slop.
   1002             theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
   1003             float extraRatio = (float)
   1004                     Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
   1005             super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
   1006         } else {
   1007             // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
   1008             super.determineScrollingStart(ev);
   1009         }
   1010     }
   1011 
   1012     protected void onPageBeginTransition() {
   1013         super.onPageBeginTransition();
   1014         updateChildrenLayersEnabled();
   1015     }
   1016 
   1017     protected void onPageEndTransition() {
   1018         super.onPageEndTransition();
   1019         updateChildrenLayersEnabled();
   1020 
   1021         if (mDragController.isDragging()) {
   1022             if (workspaceInModalState()) {
   1023                 // If we are in springloaded mode, then force an event to check if the current touch
   1024                 // is under a new page (to scroll to)
   1025                 mDragController.forceTouchMove();
   1026             }
   1027         }
   1028 
   1029         if (mStripScreensOnPageStopMoving) {
   1030             stripEmptyScreens();
   1031             mStripScreensOnPageStopMoving = false;
   1032         }
   1033     }
   1034 
   1035     protected void onScrollInteractionBegin() {
   1036         super.onScrollInteractionEnd();
   1037         mScrollInteractionBegan = true;
   1038     }
   1039 
   1040     protected void onScrollInteractionEnd() {
   1041         super.onScrollInteractionEnd();
   1042         mScrollInteractionBegan = false;
   1043         if (mStartedSendingScrollEvents) {
   1044             mStartedSendingScrollEvents = false;
   1045             mLauncherOverlay.onScrollInteractionEnd();
   1046         }
   1047     }
   1048 
   1049     public void setLauncherOverlay(LauncherOverlay overlay) {
   1050         mLauncherOverlay = overlay;
   1051         // A new overlay has been set. Reset event tracking
   1052         mStartedSendingScrollEvents = false;
   1053         onOverlayScrollChanged(0);
   1054     }
   1055 
   1056 
   1057     private boolean isScrollingOverlay() {
   1058         return mLauncherOverlay != null &&
   1059                 ((mIsRtl && getUnboundedScrollX() > mMaxScrollX) || (!mIsRtl && getUnboundedScrollX() < 0));
   1060     }
   1061 
   1062     @Override
   1063     protected void snapToDestination() {
   1064         // If we're overscrolling the overlay, we make sure to immediately reset the PagedView
   1065         // to it's baseline position instead of letting the overscroll settle. The overlay handles
   1066         // it's own settling, and every gesture to the overlay should be self-contained and start
   1067         // from 0, so we zero it out here.
   1068         if (isScrollingOverlay()) {
   1069             // We reset mWasInOverscroll so that PagedView doesn't zero out the overscroll
   1070             // interaction when we call snapToPageImmediately.
   1071             mWasInOverscroll = false;
   1072             snapToPageImmediately(0);
   1073         } else {
   1074             super.snapToDestination();
   1075         }
   1076     }
   1077 
   1078     @Override
   1079     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
   1080         super.onScrollChanged(l, t, oldl, oldt);
   1081 
   1082         // Update the page indicator progress.
   1083         boolean isTransitioning = mIsSwitchingState
   1084                 || (getLayoutTransition() != null && getLayoutTransition().isRunning());
   1085         if (!isTransitioning) {
   1086             showPageIndicatorAtCurrentScroll();
   1087         }
   1088 
   1089         updatePageAlphaValues();
   1090         enableHwLayersOnVisiblePages();
   1091     }
   1092 
   1093     public void showPageIndicatorAtCurrentScroll() {
   1094         if (mPageIndicator != null) {
   1095             mPageIndicator.setScroll(getScrollX(), computeMaxScrollX());
   1096         }
   1097     }
   1098 
   1099     @Override
   1100     protected void overScroll(float amount) {
   1101         boolean shouldScrollOverlay = mLauncherOverlay != null &&
   1102                 ((amount <= 0 && !mIsRtl) || (amount >= 0 && mIsRtl));
   1103 
   1104         boolean shouldZeroOverlay = mLauncherOverlay != null && mLastOverlayScroll != 0 &&
   1105                 ((amount >= 0 && !mIsRtl) || (amount <= 0 && mIsRtl));
   1106 
   1107         if (shouldScrollOverlay) {
   1108             if (!mStartedSendingScrollEvents && mScrollInteractionBegan) {
   1109                 mStartedSendingScrollEvents = true;
   1110                 mLauncherOverlay.onScrollInteractionBegin();
   1111             }
   1112 
   1113             mLastOverlayScroll = Math.abs(amount / getMeasuredWidth());
   1114             mLauncherOverlay.onScrollChange(mLastOverlayScroll, mIsRtl);
   1115         } else {
   1116             dampedOverScroll(amount);
   1117         }
   1118 
   1119         if (shouldZeroOverlay) {
   1120             mLauncherOverlay.onScrollChange(0, mIsRtl);
   1121         }
   1122     }
   1123 
   1124     @Override
   1125     protected boolean shouldFlingForVelocity(int velocityX) {
   1126         // When the overlay is moving, the fling or settle transition is controlled by the overlay.
   1127         return Float.compare(Math.abs(mOverlayTranslation), 0) == 0 &&
   1128                 super.shouldFlingForVelocity(velocityX);
   1129     }
   1130 
   1131     /**
   1132      * The overlay scroll is being controlled locally, just update our overlay effect
   1133      */
   1134     public void onOverlayScrollChanged(float scroll) {
   1135         if (Float.compare(scroll, 1f) == 0) {
   1136             if (!mOverlayShown) {
   1137                 mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
   1138                         Action.Direction.LEFT, ContainerType.WORKSPACE, 0);
   1139             }
   1140             mOverlayShown = true;
   1141             // Not announcing the overlay page for accessibility since it announces itself.
   1142         } else if (Float.compare(scroll, 0f) == 0) {
   1143             if (mOverlayShown) {
   1144                 mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
   1145                         Action.Direction.RIGHT, ContainerType.WORKSPACE, -1);
   1146             } else if (Float.compare(mOverlayTranslation, 0f) != 0) {
   1147                 // When arriving to 0 overscroll from non-zero overscroll, announce page for
   1148                 // accessibility since default announcements were disabled while in overscroll
   1149                 // state.
   1150                 // Not doing this if mOverlayShown because in that case the accessibility service
   1151                 // will announce the launcher window description upon regaining focus after
   1152                 // switching from the overlay screen.
   1153                 announcePageForAccessibility();
   1154             }
   1155             mOverlayShown = false;
   1156             tryRunOverlayCallback();
   1157         }
   1158 
   1159         float offset = 0f;
   1160 
   1161         scroll = Math.max(scroll - offset, 0);
   1162         scroll = Math.min(1, scroll / (1 - offset));
   1163 
   1164         float alpha = 1 - Interpolators.DEACCEL_3.getInterpolation(scroll);
   1165         float transX = mLauncher.getDragLayer().getMeasuredWidth() * scroll;
   1166 
   1167         if (mIsRtl) {
   1168             transX = -transX;
   1169         }
   1170         mOverlayTranslation = transX;
   1171 
   1172         // TODO(adamcohen): figure out a final effect here. We may need to recommend
   1173         // different effects based on device performance. On at least one relatively high-end
   1174         // device I've tried, translating the launcher causes things to get quite laggy.
   1175         mLauncher.getDragLayer().setTranslationX(transX);
   1176         mLauncher.getDragLayer().getAlphaProperty(ALPHA_INDEX_OVERLAY).setValue(alpha);
   1177     }
   1178 
   1179     /**
   1180      * @return false if the callback is still pending
   1181      */
   1182     private boolean tryRunOverlayCallback() {
   1183         if (mOnOverlayHiddenCallback == null) {
   1184             // Return true as no callback is pending. This is used by OnWindowFocusChangeListener
   1185             // to remove itself if multiple focus handles were added.
   1186             return true;
   1187         }
   1188         if (mOverlayShown || !hasWindowFocus()) {
   1189             return false;
   1190         }
   1191 
   1192         mOnOverlayHiddenCallback.run();
   1193         mOnOverlayHiddenCallback = null;
   1194         return true;
   1195     }
   1196 
   1197     /**
   1198      * Runs the given callback when the minus one overlay is hidden. Specifically, it is run
   1199      * when launcher's window has focus and the overlay is no longer being shown. If a callback
   1200      * is already present, the new callback will chain off it so both are run.
   1201      *
   1202      * @return Whether the callback was deferred.
   1203      */
   1204     public boolean runOnOverlayHidden(Runnable callback) {
   1205         if (mOnOverlayHiddenCallback == null) {
   1206             mOnOverlayHiddenCallback = callback;
   1207         } else {
   1208             // Chain the new callback onto the previous callback(s).
   1209             Runnable oldCallback = mOnOverlayHiddenCallback;
   1210             mOnOverlayHiddenCallback = () -> {
   1211                 oldCallback.run();
   1212                 callback.run();
   1213             };
   1214         }
   1215         if (!tryRunOverlayCallback()) {
   1216             ViewTreeObserver observer = getViewTreeObserver();
   1217             if (observer != null && observer.isAlive()) {
   1218                 observer.addOnWindowFocusChangeListener(
   1219                         new ViewTreeObserver.OnWindowFocusChangeListener() {
   1220                             @Override
   1221                             public void onWindowFocusChanged(boolean hasFocus) {
   1222                                 if (tryRunOverlayCallback() && observer.isAlive()) {
   1223                                     observer.removeOnWindowFocusChangeListener(this);
   1224                                 }
   1225                             }});
   1226             }
   1227             return true;
   1228         }
   1229         return false;
   1230     }
   1231 
   1232     @Override
   1233     protected void notifyPageSwitchListener(int prevPage) {
   1234         super.notifyPageSwitchListener(prevPage);
   1235         if (prevPage != mCurrentPage) {
   1236             int swipeDirection = (prevPage < mCurrentPage) ? Action.Direction.RIGHT : Action.Direction.LEFT;
   1237             mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
   1238                     swipeDirection, ContainerType.WORKSPACE, prevPage);
   1239         }
   1240     }
   1241 
   1242     protected void setWallpaperDimension() {
   1243         Utilities.THREAD_POOL_EXECUTOR.execute(new Runnable() {
   1244             @Override
   1245             public void run() {
   1246                 final Point size = LauncherAppState.getIDP(getContext()).defaultWallpaperSize;
   1247                 if (size.x != mWallpaperManager.getDesiredMinimumWidth()
   1248                         || size.y != mWallpaperManager.getDesiredMinimumHeight()) {
   1249                     mWallpaperManager.suggestDesiredDimensions(size.x, size.y);
   1250                 }
   1251             }
   1252         });
   1253     }
   1254 
   1255     public void lockWallpaperToDefaultPage() {
   1256         mWallpaperOffset.setLockToDefaultPage(true);
   1257     }
   1258 
   1259     public void unlockWallpaperFromDefaultPageOnNextLayout() {
   1260         if (mWallpaperOffset.isLockedToDefaultPage()) {
   1261             mUnlockWallpaperFromDefaultPageOnLayout = true;
   1262             requestLayout();
   1263         }
   1264     }
   1265 
   1266     @Override
   1267     public void computeScroll() {
   1268         super.computeScroll();
   1269         mWallpaperOffset.syncWithScroll();
   1270     }
   1271 
   1272     public void computeScrollWithoutInvalidation() {
   1273         computeScrollHelper(false);
   1274     }
   1275 
   1276     @Override
   1277     protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
   1278         if (!isSwitchingState()) {
   1279             super.determineScrollingStart(ev, touchSlopScale);
   1280         }
   1281     }
   1282 
   1283     @Override
   1284     public void announceForAccessibility(CharSequence text) {
   1285         // Don't announce if apps is on top of us.
   1286         if (!mLauncher.isInState(ALL_APPS)) {
   1287             super.announceForAccessibility(text);
   1288         }
   1289     }
   1290 
   1291     public void showOutlinesTemporarily() {
   1292         if (!mIsPageInTransition && !isTouchActive()) {
   1293             snapToPage(mCurrentPage);
   1294         }
   1295     }
   1296 
   1297     private void updatePageAlphaValues() {
   1298         // We need to check the isDragging case because updatePageAlphaValues is called between
   1299         // goToState(SPRING_LOADED) and onStartStateTransition.
   1300         if (!workspaceInModalState() && !mIsSwitchingState && !mDragController.isDragging()) {
   1301             int screenCenter = getScrollX() + getMeasuredWidth() / 2;
   1302             for (int i = 0; i < getChildCount(); i++) {
   1303                 CellLayout child = (CellLayout) getChildAt(i);
   1304                 if (child != null) {
   1305                     float scrollProgress = getScrollProgress(screenCenter, child, i);
   1306                     float alpha = 1 - Math.abs(scrollProgress);
   1307                     if (mWorkspaceFadeInAdjacentScreens) {
   1308                         child.getShortcutsAndWidgets().setAlpha(alpha);
   1309                     } else {
   1310                         // Pages that are off-screen aren't important for accessibility.
   1311                         child.getShortcutsAndWidgets().setImportantForAccessibility(
   1312                                 alpha > 0 ? IMPORTANT_FOR_ACCESSIBILITY_AUTO
   1313                                         : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
   1314                     }
   1315                 }
   1316             }
   1317         }
   1318     }
   1319 
   1320     protected void onAttachedToWindow() {
   1321         super.onAttachedToWindow();
   1322         IBinder windowToken = getWindowToken();
   1323         mWallpaperOffset.setWindowToken(windowToken);
   1324         computeScroll();
   1325         mDragController.setWindowToken(windowToken);
   1326     }
   1327 
   1328     protected void onDetachedFromWindow() {
   1329         super.onDetachedFromWindow();
   1330         mWallpaperOffset.setWindowToken(null);
   1331     }
   1332 
   1333     @Override
   1334     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
   1335         if (mUnlockWallpaperFromDefaultPageOnLayout) {
   1336             mWallpaperOffset.setLockToDefaultPage(false);
   1337             mUnlockWallpaperFromDefaultPageOnLayout = false;
   1338         }
   1339         if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
   1340             mWallpaperOffset.syncWithScroll();
   1341             mWallpaperOffset.jumpToFinal();
   1342         }
   1343         super.onLayout(changed, left, top, right, bottom);
   1344         updatePageAlphaValues();
   1345     }
   1346 
   1347     @Override
   1348     public int getDescendantFocusability() {
   1349         if (workspaceInModalState()) {
   1350             return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
   1351         }
   1352         return super.getDescendantFocusability();
   1353     }
   1354 
   1355     private boolean workspaceInModalState() {
   1356         return !mLauncher.isInState(NORMAL);
   1357     }
   1358 
   1359     /** Returns whether a drag should be allowed to be started from the current workspace state. */
   1360     public boolean workspaceIconsCanBeDragged() {
   1361         return mLauncher.getStateManager().getState().workspaceIconsCanBeDragged;
   1362     }
   1363 
   1364     private void updateChildrenLayersEnabled() {
   1365         boolean enableChildrenLayers = mIsSwitchingState || isPageInTransition();
   1366 
   1367         if (enableChildrenLayers != mChildrenLayersEnabled) {
   1368             mChildrenLayersEnabled = enableChildrenLayers;
   1369             if (mChildrenLayersEnabled) {
   1370                 enableHwLayersOnVisiblePages();
   1371             } else {
   1372                 for (int i = 0; i < getPageCount(); i++) {
   1373                     final CellLayout cl = (CellLayout) getChildAt(i);
   1374                     cl.enableHardwareLayer(false);
   1375                 }
   1376             }
   1377         }
   1378     }
   1379 
   1380     private void enableHwLayersOnVisiblePages() {
   1381         if (mChildrenLayersEnabled) {
   1382             final int screenCount = getChildCount();
   1383 
   1384             final int[] visibleScreens = getVisibleChildrenRange();
   1385             int leftScreen = visibleScreens[0];
   1386             int rightScreen = visibleScreens[1];
   1387             if (mForceDrawAdjacentPages) {
   1388                 // In overview mode, make sure that the two side pages are visible.
   1389                 leftScreen = Utilities.boundToRange(getCurrentPage() - 1, 0, rightScreen);
   1390                 rightScreen = Utilities.boundToRange(getCurrentPage() + 1,
   1391                     leftScreen, getPageCount() - 1);
   1392             }
   1393 
   1394             if (leftScreen == rightScreen) {
   1395                 // make sure we're caching at least two pages always
   1396                 if (rightScreen < screenCount - 1) {
   1397                     rightScreen++;
   1398                 } else if (leftScreen > 0) {
   1399                     leftScreen--;
   1400                 }
   1401             }
   1402 
   1403             for (int i = 0; i < screenCount; i++) {
   1404                 final CellLayout layout = (CellLayout) getPageAt(i);
   1405                 // enable layers between left and right screen inclusive.
   1406                 boolean enableLayer = leftScreen <= i && i <= rightScreen;
   1407                 layout.enableHardwareLayer(enableLayer);
   1408             }
   1409         }
   1410     }
   1411 
   1412     public void onWallpaperTap(MotionEvent ev) {
   1413         final int[] position = mTempXY;
   1414         getLocationOnScreen(position);
   1415 
   1416         int pointerIndex = ev.getActionIndex();
   1417         position[0] += (int) ev.getX(pointerIndex);
   1418         position[1] += (int) ev.getY(pointerIndex);
   1419 
   1420         mWallpaperManager.sendWallpaperCommand(getWindowToken(),
   1421                 ev.getAction() == MotionEvent.ACTION_UP
   1422                         ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
   1423                 position[0], position[1], 0, null);
   1424     }
   1425 
   1426     public void prepareDragWithProvider(DragPreviewProvider outlineProvider) {
   1427         mOutlineProvider = outlineProvider;
   1428     }
   1429 
   1430     public void snapToPageFromOverView(int whichPage) {
   1431         snapToPage(whichPage, OVERVIEW_TRANSITION_MS, Interpolators.ZOOM_IN);
   1432     }
   1433 
   1434     private void onStartStateTransition(LauncherState state) {
   1435         mIsSwitchingState = true;
   1436         mTransitionProgress = 0;
   1437 
   1438         updateChildrenLayersEnabled();
   1439     }
   1440 
   1441     private void onEndStateTransition() {
   1442         mIsSwitchingState = false;
   1443         mForceDrawAdjacentPages = false;
   1444         mTransitionProgress = 1;
   1445 
   1446         updateChildrenLayersEnabled();
   1447         updateAccessibilityFlags();
   1448     }
   1449 
   1450     /**
   1451      * Sets the current workspace {@link LauncherState} and updates the UI without any animations
   1452      */
   1453     @Override
   1454     public void setState(LauncherState toState) {
   1455         onStartStateTransition(toState);
   1456         mStateTransitionAnimation.setState(toState);
   1457         onEndStateTransition();
   1458     }
   1459 
   1460     /**
   1461      * Sets the current workspace {@link LauncherState}, then animates the UI
   1462      */
   1463     @Override
   1464     public void setStateWithAnimation(LauncherState toState,
   1465             AnimatorSetBuilder builder, AnimationConfig config) {
   1466         StateTransitionListener listener = new StateTransitionListener(toState);
   1467         mStateTransitionAnimation.setStateWithAnimation(toState, builder, config);
   1468 
   1469         // Invalidate the pages now, so that we have the visible pages before the
   1470         // animation is started
   1471         if (toState.hasMultipleVisiblePages) {
   1472             mForceDrawAdjacentPages = true;
   1473         }
   1474         invalidate(); // This will call dispatchDraw(), which calls getVisiblePages().
   1475 
   1476         ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1);
   1477         stepAnimator.addUpdateListener(listener);
   1478         stepAnimator.setDuration(config.duration);
   1479         stepAnimator.addListener(listener);
   1480         builder.play(stepAnimator);
   1481     }
   1482 
   1483     public void updateAccessibilityFlags() {
   1484         // TODO: Update the accessibility flags appropriately when dragging.
   1485         int accessibilityFlag = mLauncher.getStateManager().getState().workspaceAccessibilityFlag;
   1486         if (!mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
   1487             int total = getPageCount();
   1488             for (int i = 0; i < total; i++) {
   1489                 updateAccessibilityFlags(accessibilityFlag, (CellLayout) getPageAt(i));
   1490             }
   1491             setImportantForAccessibility(accessibilityFlag);
   1492         }
   1493     }
   1494 
   1495     private void updateAccessibilityFlags(int accessibilityFlag, CellLayout page) {
   1496         page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
   1497         page.getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
   1498         page.setContentDescription(null);
   1499         page.setAccessibilityDelegate(null);
   1500     }
   1501 
   1502     public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) {
   1503         View child = cellInfo.cell;
   1504 
   1505         mDragInfo = cellInfo;
   1506         child.setVisibility(INVISIBLE);
   1507 
   1508         if (options.isAccessibleDrag) {
   1509             mDragController.addDragListener(new AccessibleDragListenerAdapter(
   1510                     this, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG) {
   1511                 @Override
   1512                 protected void enableAccessibleDrag(boolean enable) {
   1513                     super.enableAccessibleDrag(enable);
   1514                     setEnableForLayout(mLauncher.getHotseat().getLayout(),enable);
   1515                 }
   1516             });
   1517         }
   1518 
   1519         beginDragShared(child, this, options);
   1520     }
   1521 
   1522     public void beginDragShared(View child, DragSource source, DragOptions options) {
   1523         Object dragObject = child.getTag();
   1524         if (!(dragObject instanceof ItemInfo)) {
   1525             String msg = "Drag started with a view that has no tag set. This "
   1526                     + "will cause a crash (issue 11627249) down the line. "
   1527                     + "View: " + child + "  tag: " + child.getTag();
   1528             throw new IllegalStateException(msg);
   1529         }
   1530         beginDragShared(child, source, (ItemInfo) dragObject,
   1531                 new DragPreviewProvider(child), options);
   1532     }
   1533 
   1534     public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject,
   1535             DragPreviewProvider previewProvider, DragOptions dragOptions) {
   1536         float iconScale = 1f;
   1537         if (child instanceof BubbleTextView) {
   1538             Drawable icon = ((BubbleTextView) child).getIcon();
   1539             if (icon instanceof FastBitmapDrawable) {
   1540                 iconScale = ((FastBitmapDrawable) icon).getAnimatedScale();
   1541             }
   1542         }
   1543 
   1544         child.clearFocus();
   1545         child.setPressed(false);
   1546         mOutlineProvider = previewProvider;
   1547 
   1548         // The drag bitmap follows the touch point around on the screen
   1549         final Bitmap b = previewProvider.createDragBitmap();
   1550         int halfPadding = previewProvider.previewPadding / 2;
   1551 
   1552         float scale = previewProvider.getScaleAndPosition(b, mTempXY);
   1553         int dragLayerX = mTempXY[0];
   1554         int dragLayerY = mTempXY[1];
   1555 
   1556         DeviceProfile grid = mLauncher.getDeviceProfile();
   1557         Point dragVisualizeOffset = null;
   1558         Rect dragRect = null;
   1559         if (child instanceof BubbleTextView) {
   1560             dragRect = new Rect();
   1561             ((BubbleTextView) child).getIconBounds(dragRect);
   1562             dragLayerY += dragRect.top;
   1563             // Note: The dragRect is used to calculate drag layer offsets, but the
   1564             // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
   1565             dragVisualizeOffset = new Point(- halfPadding, halfPadding);
   1566         } else if (child instanceof FolderIcon) {
   1567             int previewSize = grid.folderIconSizePx;
   1568             dragVisualizeOffset = new Point(- halfPadding, halfPadding - child.getPaddingTop());
   1569             dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
   1570         } else if (previewProvider instanceof ShortcutDragPreviewProvider) {
   1571             dragVisualizeOffset = new Point(- halfPadding, halfPadding);
   1572         }
   1573 
   1574         // Clear the pressed state if necessary
   1575         if (child instanceof BubbleTextView) {
   1576             BubbleTextView icon = (BubbleTextView) child;
   1577             icon.clearPressedBackground();
   1578         }
   1579 
   1580         if (child.getParent() instanceof ShortcutAndWidgetContainer) {
   1581             mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
   1582         }
   1583 
   1584         if (child instanceof BubbleTextView && !dragOptions.isAccessibleDrag) {
   1585             PopupContainerWithArrow popupContainer = PopupContainerWithArrow
   1586                     .showForIcon((BubbleTextView) child);
   1587             if (popupContainer != null) {
   1588                 dragOptions.preDragCondition = popupContainer.createPreDragCondition();
   1589 
   1590                 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis("dragging started");
   1591             }
   1592         }
   1593 
   1594         DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
   1595                 dragObject, dragVisualizeOffset, dragRect, scale * iconScale, scale, dragOptions);
   1596         dv.setIntrinsicIconScaleFactor(dragOptions.intrinsicIconScaleFactor);
   1597         return dv;
   1598     }
   1599 
   1600     private boolean transitionStateShouldAllowDrop() {
   1601         return (!isSwitchingState() || mTransitionProgress > ALLOW_DROP_TRANSITION_PROGRESS) &&
   1602                 workspaceIconsCanBeDragged();
   1603     }
   1604 
   1605     /**
   1606      * {@inheritDoc}
   1607      */
   1608     @Override
   1609     public boolean acceptDrop(DragObject d) {
   1610         // If it's an external drop (e.g. from All Apps), check if it should be accepted
   1611         CellLayout dropTargetLayout = mDropToLayout;
   1612         if (d.dragSource != this) {
   1613             // Don't accept the drop if we're not over a screen at time of drop
   1614             if (dropTargetLayout == null) {
   1615                 return false;
   1616             }
   1617             if (!transitionStateShouldAllowDrop()) return false;
   1618 
   1619             mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
   1620 
   1621             // We want the point to be mapped to the dragTarget.
   1622             if (mLauncher.isHotseatLayout(dropTargetLayout)) {
   1623                 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
   1624             } else {
   1625                 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter);
   1626             }
   1627 
   1628             int spanX;
   1629             int spanY;
   1630             if (mDragInfo != null) {
   1631                 final CellLayout.CellInfo dragCellInfo = mDragInfo;
   1632                 spanX = dragCellInfo.spanX;
   1633                 spanY = dragCellInfo.spanY;
   1634             } else {
   1635                 spanX = d.dragInfo.spanX;
   1636                 spanY = d.dragInfo.spanY;
   1637             }
   1638 
   1639             int minSpanX = spanX;
   1640             int minSpanY = spanY;
   1641             if (d.dragInfo instanceof PendingAddWidgetInfo) {
   1642                 minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
   1643                 minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
   1644             }
   1645 
   1646             mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
   1647                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
   1648                     mTargetCell);
   1649             float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
   1650                     mDragViewVisualCenter[1], mTargetCell);
   1651             if (mCreateUserFolderOnDrop && willCreateUserFolder(d.dragInfo,
   1652                     dropTargetLayout, mTargetCell, distance, true)) {
   1653                 return true;
   1654             }
   1655 
   1656             if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder(d.dragInfo,
   1657                     dropTargetLayout, mTargetCell, distance)) {
   1658                 return true;
   1659             }
   1660 
   1661             int[] resultSpan = new int[2];
   1662             mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
   1663                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
   1664                     null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
   1665             boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
   1666 
   1667             // Don't accept the drop if there's no room for the item
   1668             if (!foundCell) {
   1669                 onNoCellFound(dropTargetLayout);
   1670                 return false;
   1671             }
   1672         }
   1673 
   1674         long screenId = getIdForScreen(dropTargetLayout);
   1675         if (screenId == EXTRA_EMPTY_SCREEN_ID) {
   1676             commitExtraEmptyScreen();
   1677         }
   1678 
   1679         return true;
   1680     }
   1681 
   1682     boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell,
   1683             float distance, boolean considerTimeout) {
   1684         if (distance > mMaxDistanceForFolderCreation) return false;
   1685         View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
   1686         return willCreateUserFolder(info, dropOverView, considerTimeout);
   1687     }
   1688 
   1689     boolean willCreateUserFolder(ItemInfo info, View dropOverView, boolean considerTimeout) {
   1690         if (dropOverView != null) {
   1691             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
   1692             if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) {
   1693                 return false;
   1694             }
   1695         }
   1696 
   1697         boolean hasntMoved = false;
   1698         if (mDragInfo != null) {
   1699             hasntMoved = dropOverView == mDragInfo.cell;
   1700         }
   1701 
   1702         if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
   1703             return false;
   1704         }
   1705 
   1706         boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
   1707         boolean willBecomeShortcut =
   1708                 (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
   1709                         info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
   1710                         info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT);
   1711 
   1712         return (aboveShortcut && willBecomeShortcut);
   1713     }
   1714 
   1715     boolean willAddToExistingUserFolder(ItemInfo dragInfo, CellLayout target, int[] targetCell,
   1716             float distance) {
   1717         if (distance > mMaxDistanceForFolderCreation) return false;
   1718         View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
   1719         return willAddToExistingUserFolder(dragInfo, dropOverView);
   1720 
   1721     }
   1722     boolean willAddToExistingUserFolder(ItemInfo dragInfo, View dropOverView) {
   1723         if (dropOverView != null) {
   1724             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
   1725             if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) {
   1726                 return false;
   1727             }
   1728         }
   1729 
   1730         if (dropOverView instanceof FolderIcon) {
   1731             FolderIcon fi = (FolderIcon) dropOverView;
   1732             if (fi.acceptDrop(dragInfo)) {
   1733                 return true;
   1734             }
   1735         }
   1736         return false;
   1737     }
   1738 
   1739     boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
   1740             int[] targetCell, float distance, boolean external, DragView dragView) {
   1741         if (distance > mMaxDistanceForFolderCreation) return false;
   1742         View v = target.getChildAt(targetCell[0], targetCell[1]);
   1743 
   1744         boolean hasntMoved = false;
   1745         if (mDragInfo != null) {
   1746             CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
   1747             hasntMoved = (mDragInfo.cellX == targetCell[0] &&
   1748                     mDragInfo.cellY == targetCell[1]) && (cellParent == target);
   1749         }
   1750 
   1751         if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
   1752         mCreateUserFolderOnDrop = false;
   1753         final long screenId = getIdForScreen(target);
   1754 
   1755         boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
   1756         boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
   1757 
   1758         if (aboveShortcut && willBecomeShortcut) {
   1759             ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
   1760             ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
   1761             // if the drag started here, we need to remove it from the workspace
   1762             if (!external) {
   1763                 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
   1764             }
   1765 
   1766             Rect folderLocation = new Rect();
   1767             float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
   1768             target.removeView(v);
   1769 
   1770             FolderIcon fi =
   1771                 mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
   1772             destInfo.cellX = -1;
   1773             destInfo.cellY = -1;
   1774             sourceInfo.cellX = -1;
   1775             sourceInfo.cellY = -1;
   1776 
   1777             // If the dragView is null, we can't animate
   1778             boolean animate = dragView != null;
   1779             if (animate) {
   1780                 // In order to keep everything continuous, we hand off the currently rendered
   1781                 // folder background to the newly created icon. This preserves animation state.
   1782                 fi.setFolderBackground(mFolderCreateBg);
   1783                 mFolderCreateBg = new PreviewBackground();
   1784                 fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale
   1785                 );
   1786             } else {
   1787                 fi.prepareCreateAnimation(v);
   1788                 fi.addItem(destInfo);
   1789                 fi.addItem(sourceInfo);
   1790             }
   1791             return true;
   1792         }
   1793         return false;
   1794     }
   1795 
   1796     boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
   1797             float distance, DragObject d, boolean external) {
   1798         if (distance > mMaxDistanceForFolderCreation) return false;
   1799 
   1800         View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
   1801         if (!mAddToExistingFolderOnDrop) return false;
   1802         mAddToExistingFolderOnDrop = false;
   1803 
   1804         if (dropOverView instanceof FolderIcon) {
   1805             FolderIcon fi = (FolderIcon) dropOverView;
   1806             if (fi.acceptDrop(d.dragInfo)) {
   1807                 fi.onDrop(d, false /* itemReturnedOnFailedDrop */);
   1808 
   1809                 // if the drag started here, we need to remove it from the workspace
   1810                 if (!external) {
   1811                     getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
   1812                 }
   1813                 return true;
   1814             }
   1815         }
   1816         return false;
   1817     }
   1818 
   1819     @Override
   1820     public void prepareAccessibilityDrop() { }
   1821 
   1822     public void onDrop(final DragObject d, DragOptions options) {
   1823         mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
   1824         CellLayout dropTargetLayout = mDropToLayout;
   1825 
   1826         // We want the point to be mapped to the dragTarget.
   1827         if (dropTargetLayout != null) {
   1828             if (mLauncher.isHotseatLayout(dropTargetLayout)) {
   1829                 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
   1830             } else {
   1831                 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter);
   1832             }
   1833         }
   1834 
   1835         boolean droppedOnOriginalCell = false;
   1836 
   1837         int snapScreen = -1;
   1838         boolean resizeOnDrop = false;
   1839         if (d.dragSource != this || mDragInfo == null) {
   1840             final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
   1841                     (int) mDragViewVisualCenter[1] };
   1842             onDropExternal(touchXY, dropTargetLayout, d);
   1843         } else {
   1844             final View cell = mDragInfo.cell;
   1845             boolean droppedOnOriginalCellDuringTransition = false;
   1846             Runnable onCompleteRunnable = null;
   1847 
   1848             if (dropTargetLayout != null && !d.cancelled) {
   1849                 // Move internally
   1850                 boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
   1851                 boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
   1852                 long container = hasMovedIntoHotseat ?
   1853                         LauncherSettings.Favorites.CONTAINER_HOTSEAT :
   1854                         LauncherSettings.Favorites.CONTAINER_DESKTOP;
   1855                 long screenId = (mTargetCell[0] < 0) ?
   1856                         mDragInfo.screenId : getIdForScreen(dropTargetLayout);
   1857                 int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
   1858                 int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
   1859                 // First we find the cell nearest to point at which the item is
   1860                 // dropped, without any consideration to whether there is an item there.
   1861 
   1862                 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
   1863                         mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
   1864                 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
   1865                         mDragViewVisualCenter[1], mTargetCell);
   1866 
   1867                 // If the item being dropped is a shortcut and the nearest drop
   1868                 // cell also contains a shortcut, then create a folder with the two shortcuts.
   1869                 if (createUserFolderIfNecessary(cell, container,
   1870                         dropTargetLayout, mTargetCell, distance, false, d.dragView) ||
   1871                         addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
   1872                                 distance, d, false)) {
   1873                     mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
   1874                     return;
   1875                 }
   1876 
   1877                 // Aside from the special case where we're dropping a shortcut onto a shortcut,
   1878                 // we need to find the nearest cell location that is vacant
   1879                 ItemInfo item = d.dragInfo;
   1880                 int minSpanX = item.spanX;
   1881                 int minSpanY = item.spanY;
   1882                 if (item.minSpanX > 0 && item.minSpanY > 0) {
   1883                     minSpanX = item.minSpanX;
   1884                     minSpanY = item.minSpanY;
   1885                 }
   1886 
   1887                 droppedOnOriginalCell = item.screenId == screenId && item.container == container
   1888                         && item.cellX == mTargetCell[0] && item.cellY == mTargetCell[1];
   1889                 droppedOnOriginalCellDuringTransition = droppedOnOriginalCell && mIsSwitchingState;
   1890 
   1891                 // When quickly moving an item, a user may accidentally rearrange their
   1892                 // workspace. So instead we move the icon back safely to its original position.
   1893                 boolean returnToOriginalCellToPreventShuffling = !isFinishedSwitchingState()
   1894                         && !droppedOnOriginalCellDuringTransition && !dropTargetLayout
   1895                         .isRegionVacant(mTargetCell[0], mTargetCell[1], spanX, spanY);
   1896                 int[] resultSpan = new int[2];
   1897                 if (returnToOriginalCellToPreventShuffling) {
   1898                     mTargetCell[0] = mTargetCell[1] = -1;
   1899                 } else {
   1900                     mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
   1901                             (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
   1902                             mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
   1903                 }
   1904 
   1905                 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
   1906 
   1907                 // if the widget resizes on drop
   1908                 if (foundCell && (cell instanceof AppWidgetHostView) &&
   1909                         (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
   1910                     resizeOnDrop = true;
   1911                     item.spanX = resultSpan[0];
   1912                     item.spanY = resultSpan[1];
   1913                     AppWidgetHostView awhv = (AppWidgetHostView) cell;
   1914                     AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
   1915                             resultSpan[1]);
   1916                 }
   1917 
   1918                 if (foundCell) {
   1919                     if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
   1920                         snapScreen = getPageIndexForScreenId(screenId);
   1921                         snapToPage(snapScreen);
   1922                     }
   1923 
   1924                     final ItemInfo info = (ItemInfo) cell.getTag();
   1925                     if (hasMovedLayouts) {
   1926                         // Reparent the view
   1927                         CellLayout parentCell = getParentCellLayoutForView(cell);
   1928                         if (parentCell != null) {
   1929                             parentCell.removeView(cell);
   1930                         } else if (FeatureFlags.IS_DOGFOOD_BUILD) {
   1931                             throw new NullPointerException("mDragInfo.cell has null parent");
   1932                         }
   1933                         addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
   1934                                 info.spanX, info.spanY);
   1935                     }
   1936 
   1937                     // update the item's position after drop
   1938                     CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
   1939                     lp.cellX = lp.tmpCellX = mTargetCell[0];
   1940                     lp.cellY = lp.tmpCellY = mTargetCell[1];
   1941                     lp.cellHSpan = item.spanX;
   1942                     lp.cellVSpan = item.spanY;
   1943                     lp.isLockedToGrid = true;
   1944 
   1945                     if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
   1946                             cell instanceof LauncherAppWidgetHostView) {
   1947                         final CellLayout cellLayout = dropTargetLayout;
   1948                         // We post this call so that the widget has a chance to be placed
   1949                         // in its final location
   1950 
   1951                         final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
   1952                         AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo();
   1953                         if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE
   1954                                 && !d.accessibleDrag) {
   1955                             onCompleteRunnable = new Runnable() {
   1956                                 public void run() {
   1957                                     if (!isPageInTransition()) {
   1958                                         AppWidgetResizeFrame.showForWidget(hostView, cellLayout);
   1959                                     }
   1960                                 }
   1961                             };
   1962                         }
   1963                     }
   1964 
   1965                     mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId,
   1966                             lp.cellX, lp.cellY, item.spanX, item.spanY);
   1967                 } else {
   1968                     if (!returnToOriginalCellToPreventShuffling) {
   1969                         onNoCellFound(dropTargetLayout);
   1970                     }
   1971 
   1972                     // If we can't find a drop location, we return the item to its original position
   1973                     CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
   1974                     mTargetCell[0] = lp.cellX;
   1975                     mTargetCell[1] = lp.cellY;
   1976                     CellLayout layout = (CellLayout) cell.getParent().getParent();
   1977                     layout.markCellsAsOccupiedForView(cell);
   1978                 }
   1979             }
   1980 
   1981             final CellLayout parent = (CellLayout) cell.getParent().getParent();
   1982             if (d.dragView.hasDrawn()) {
   1983                 if (droppedOnOriginalCellDuringTransition) {
   1984                     // Animate the item to its original position, while simultaneously exiting
   1985                     // spring-loaded mode so the page meets the icon where it was picked up.
   1986                     mLauncher.getDragController().animateDragViewToOriginalPosition(
   1987                             onCompleteRunnable, cell, SPRING_LOADED_TRANSITION_MS);
   1988                     mLauncher.getStateManager().goToState(NORMAL);
   1989                     mLauncher.getDropTargetBar().onDragEnd();
   1990                     parent.onDropChild(cell);
   1991                     return;
   1992                 }
   1993                 final ItemInfo info = (ItemInfo) cell.getTag();
   1994                 boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
   1995                         || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
   1996                 if (isWidget) {
   1997                     int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
   1998                             ANIMATE_INTO_POSITION_AND_DISAPPEAR;
   1999                     animateWidgetDrop(info, parent, d.dragView, null, animationType, cell, false);
   2000                 } else {
   2001                     int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
   2002                     mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
   2003                             this);
   2004                 }
   2005             } else {
   2006                 d.deferDragViewCleanupPostAnimation = false;
   2007                 cell.setVisibility(VISIBLE);
   2008             }
   2009             parent.onDropChild(cell);
   2010 
   2011             mLauncher.getStateManager().goToState(
   2012                     NORMAL, SPRING_LOADED_EXIT_DELAY, onCompleteRunnable);
   2013         }
   2014 
   2015         if (d.stateAnnouncer != null && !droppedOnOriginalCell) {
   2016             d.stateAnnouncer.completeAction(R.string.item_moved);
   2017         }
   2018     }
   2019 
   2020     public void onNoCellFound(View dropTargetLayout) {
   2021         if (mLauncher.isHotseatLayout(dropTargetLayout)) {
   2022             Hotseat hotseat = mLauncher.getHotseat();
   2023             boolean droppedOnAllAppsIcon = !FeatureFlags.NO_ALL_APPS_ICON
   2024                     && mTargetCell != null && !mLauncher.getDeviceProfile().inv.isAllAppsButtonRank(
   2025                     hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]));
   2026             if (!droppedOnAllAppsIcon) {
   2027                 // Only show message when hotseat is full and drop target was not AllApps button
   2028                 showOutOfSpaceMessage(true);
   2029             }
   2030         } else {
   2031             showOutOfSpaceMessage(false);
   2032         }
   2033     }
   2034 
   2035     private void showOutOfSpaceMessage(boolean isHotseatLayout) {
   2036         int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
   2037         Toast.makeText(mLauncher, mLauncher.getString(strId), Toast.LENGTH_SHORT).show();
   2038     }
   2039 
   2040     /**
   2041      * Computes the area relative to dragLayer which is used to display a page.
   2042      */
   2043     public void getPageAreaRelativeToDragLayer(Rect outArea) {
   2044         CellLayout child = (CellLayout) getChildAt(getNextPage());
   2045         if (child == null) {
   2046             return;
   2047         }
   2048         ShortcutAndWidgetContainer boundingLayout = child.getShortcutsAndWidgets();
   2049 
   2050         // Use the absolute left instead of the child left, as we want the visible area
   2051         // irrespective of the visible child. Since the view can only scroll horizontally, the
   2052         // top position is not affected.
   2053         mTempXY[0] = getPaddingLeft() + boundingLayout.getLeft();
   2054         mTempXY[1] = child.getTop() + boundingLayout.getTop();
   2055 
   2056         float scale = mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY);
   2057         outArea.set(mTempXY[0], mTempXY[1],
   2058                 (int) (mTempXY[0] + scale * boundingLayout.getMeasuredWidth()),
   2059                 (int) (mTempXY[1] + scale * boundingLayout.getMeasuredHeight()));
   2060     }
   2061 
   2062     @Override
   2063     public void onDragEnter(DragObject d) {
   2064         if (ENFORCE_DRAG_EVENT_ORDER) {
   2065             enforceDragParity("onDragEnter", 1, 1);
   2066         }
   2067 
   2068         mCreateUserFolderOnDrop = false;
   2069         mAddToExistingFolderOnDrop = false;
   2070 
   2071         mDropToLayout = null;
   2072         mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
   2073         setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1]);
   2074     }
   2075 
   2076     @Override
   2077     public void onDragExit(DragObject d) {
   2078         if (ENFORCE_DRAG_EVENT_ORDER) {
   2079             enforceDragParity("onDragExit", -1, 0);
   2080         }
   2081 
   2082         // Here we store the final page that will be dropped to, if the workspace in fact
   2083         // receives the drop
   2084         mDropToLayout = mDragTargetLayout;
   2085         if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
   2086             mCreateUserFolderOnDrop = true;
   2087         } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
   2088             mAddToExistingFolderOnDrop = true;
   2089         }
   2090 
   2091         // Reset the previous drag target
   2092         setCurrentDropLayout(null);
   2093         setCurrentDragOverlappingLayout(null);
   2094 
   2095         mSpringLoadedDragController.cancel();
   2096     }
   2097 
   2098     private void enforceDragParity(String event, int update, int expectedValue) {
   2099         enforceDragParity(this, event, update, expectedValue);
   2100         for (int i = 0; i < getChildCount(); i++) {
   2101             enforceDragParity(getChildAt(i), event, update, expectedValue);
   2102         }
   2103     }
   2104 
   2105     private void enforceDragParity(View v, String event, int update, int expectedValue) {
   2106         Object tag = v.getTag(R.id.drag_event_parity);
   2107         int value = tag == null ? 0 : (Integer) tag;
   2108         value += update;
   2109         v.setTag(R.id.drag_event_parity, value);
   2110 
   2111         if (value != expectedValue) {
   2112             Log.e(TAG, event + ": Drag contract violated: " + value);
   2113         }
   2114     }
   2115 
   2116     void setCurrentDropLayout(CellLayout layout) {
   2117         if (mDragTargetLayout != null) {
   2118             mDragTargetLayout.revertTempState();
   2119             mDragTargetLayout.onDragExit();
   2120         }
   2121         mDragTargetLayout = layout;
   2122         if (mDragTargetLayout != null) {
   2123             mDragTargetLayout.onDragEnter();
   2124         }
   2125         cleanupReorder(true);
   2126         cleanupFolderCreation();
   2127         setCurrentDropOverCell(-1, -1);
   2128     }
   2129 
   2130     void setCurrentDragOverlappingLayout(CellLayout layout) {
   2131         if (mDragOverlappingLayout != null) {
   2132             mDragOverlappingLayout.setIsDragOverlapping(false);
   2133         }
   2134         mDragOverlappingLayout = layout;
   2135         if (mDragOverlappingLayout != null) {
   2136             mDragOverlappingLayout.setIsDragOverlapping(true);
   2137         }
   2138         // Invalidating the scrim will also force this CellLayout
   2139         // to be invalidated so that it is highlighted if necessary.
   2140         mLauncher.getDragLayer().getScrim().invalidate();
   2141     }
   2142 
   2143     public CellLayout getCurrentDragOverlappingLayout() {
   2144         return mDragOverlappingLayout;
   2145     }
   2146 
   2147     void setCurrentDropOverCell(int x, int y) {
   2148         if (x != mDragOverX || y != mDragOverY) {
   2149             mDragOverX = x;
   2150             mDragOverY = y;
   2151             setDragMode(DRAG_MODE_NONE);
   2152         }
   2153     }
   2154 
   2155     void setDragMode(int dragMode) {
   2156         if (dragMode != mDragMode) {
   2157             if (dragMode == DRAG_MODE_NONE) {
   2158                 cleanupAddToFolder();
   2159                 // We don't want to cancel the re-order alarm every time the target cell changes
   2160                 // as this feels to slow / unresponsive.
   2161                 cleanupReorder(false);
   2162                 cleanupFolderCreation();
   2163             } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
   2164                 cleanupReorder(true);
   2165                 cleanupFolderCreation();
   2166             } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
   2167                 cleanupAddToFolder();
   2168                 cleanupReorder(true);
   2169             } else if (dragMode == DRAG_MODE_REORDER) {
   2170                 cleanupAddToFolder();
   2171                 cleanupFolderCreation();
   2172             }
   2173             mDragMode = dragMode;
   2174         }
   2175     }
   2176 
   2177     private void cleanupFolderCreation() {
   2178         if (mFolderCreateBg != null) {
   2179             mFolderCreateBg.animateToRest();
   2180         }
   2181         mFolderCreationAlarm.setOnAlarmListener(null);
   2182         mFolderCreationAlarm.cancelAlarm();
   2183     }
   2184 
   2185     private void cleanupAddToFolder() {
   2186         if (mDragOverFolderIcon != null) {
   2187             mDragOverFolderIcon.onDragExit();
   2188             mDragOverFolderIcon = null;
   2189         }
   2190     }
   2191 
   2192     private void cleanupReorder(boolean cancelAlarm) {
   2193         // Any pending reorders are canceled
   2194         if (cancelAlarm) {
   2195             mReorderAlarm.cancelAlarm();
   2196         }
   2197         mLastReorderX = -1;
   2198         mLastReorderY = -1;
   2199     }
   2200 
   2201    /*
   2202     *
   2203     * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
   2204     * coordinate space. The argument xy is modified with the return result.
   2205     */
   2206    void mapPointFromSelfToChild(View v, float[] xy) {
   2207        xy[0] = xy[0] - v.getLeft();
   2208        xy[1] = xy[1] - v.getTop();
   2209    }
   2210 
   2211    boolean isPointInSelfOverHotseat(int x, int y) {
   2212        mTempXY[0] = x;
   2213        mTempXY[1] = y;
   2214        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
   2215        View hotseat = mLauncher.getHotseat();
   2216        return mTempXY[0] >= hotseat.getLeft() &&
   2217                mTempXY[0] <= hotseat.getRight() &&
   2218                mTempXY[1] >= hotseat.getTop() &&
   2219                mTempXY[1] <= hotseat.getBottom();
   2220    }
   2221 
   2222    void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
   2223        mTempXY[0] = (int) xy[0];
   2224        mTempXY[1] = (int) xy[1];
   2225        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
   2226        mLauncher.getDragLayer().mapCoordInSelfToDescendant(hotseat.getLayout(), mTempXY);
   2227 
   2228        xy[0] = mTempXY[0];
   2229        xy[1] = mTempXY[1];
   2230    }
   2231 
   2232     private boolean isDragWidget(DragObject d) {
   2233         return (d.dragInfo instanceof LauncherAppWidgetInfo ||
   2234                 d.dragInfo instanceof PendingAddWidgetInfo);
   2235     }
   2236 
   2237     public void onDragOver(DragObject d) {
   2238         // Skip drag over events while we are dragging over side pages
   2239         if (!transitionStateShouldAllowDrop()) return;
   2240 
   2241         ItemInfo item = d.dragInfo;
   2242         if (item == null) {
   2243             if (FeatureFlags.IS_DOGFOOD_BUILD) {
   2244                 throw new NullPointerException("DragObject has null info");
   2245             }
   2246             return;
   2247         }
   2248 
   2249         // Ensure that we have proper spans for the item that we are dropping
   2250         if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
   2251         mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
   2252 
   2253         final View child = (mDragInfo == null) ? null : mDragInfo.cell;
   2254         if (setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1])) {
   2255             if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
   2256                 mSpringLoadedDragController.cancel();
   2257             } else {
   2258                 mSpringLoadedDragController.setAlarm(mDragTargetLayout);
   2259             }
   2260         }
   2261 
   2262         // Handle the drag over
   2263         if (mDragTargetLayout != null) {
   2264             // We want the point to be mapped to the dragTarget.
   2265             if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
   2266                 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
   2267             } else {
   2268                 mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter);
   2269             }
   2270 
   2271             int minSpanX = item.spanX;
   2272             int minSpanY = item.spanY;
   2273             if (item.minSpanX > 0 && item.minSpanY > 0) {
   2274                 minSpanX = item.minSpanX;
   2275                 minSpanY = item.minSpanY;
   2276             }
   2277 
   2278             mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
   2279                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY,
   2280                     mDragTargetLayout, mTargetCell);
   2281             int reorderX = mTargetCell[0];
   2282             int reorderY = mTargetCell[1];
   2283 
   2284             setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
   2285 
   2286             float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
   2287                     mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
   2288 
   2289             manageFolderFeedback(mDragTargetLayout, mTargetCell, targetCellDistance, d);
   2290 
   2291             boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
   2292                     mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
   2293                     item.spanY, child, mTargetCell);
   2294 
   2295             if (!nearestDropOccupied) {
   2296                 mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider,
   2297                         mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, d);
   2298             } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
   2299                     && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
   2300                     mLastReorderY != reorderY)) {
   2301 
   2302                 int[] resultSpan = new int[2];
   2303                 mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
   2304                         (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY,
   2305                         child, mTargetCell, resultSpan, CellLayout.MODE_SHOW_REORDER_HINT);
   2306 
   2307                 // Otherwise, if we aren't adding to or creating a folder and there's no pending
   2308                 // reorder, then we schedule a reorder
   2309                 ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
   2310                         minSpanX, minSpanY, item.spanX, item.spanY, d, child);
   2311                 mReorderAlarm.setOnAlarmListener(listener);
   2312                 mReorderAlarm.setAlarm(REORDER_TIMEOUT);
   2313             }
   2314 
   2315             if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
   2316                     !nearestDropOccupied) {
   2317                 if (mDragTargetLayout != null) {
   2318                     mDragTargetLayout.revertTempState();
   2319                 }
   2320             }
   2321         }
   2322     }
   2323 
   2324     /**
   2325      * Updates {@link #mDragTargetLayout} and {@link #mDragOverlappingLayout}
   2326      * based on the DragObject's position.
   2327      *
   2328      * The layout will be:
   2329      * - The Hotseat if the drag object is over it
   2330      * - A side page if we are in spring-loaded mode and the drag object is over it
   2331      * - The current page otherwise
   2332      *
   2333      * @return whether the layout is different from the current {@link #mDragTargetLayout}.
   2334      */
   2335     private boolean setDropLayoutForDragObject(DragObject d, float centerX, float centerY) {
   2336         CellLayout layout = null;
   2337         // Test to see if we are over the hotseat first
   2338         if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
   2339             if (isPointInSelfOverHotseat(d.x, d.y)) {
   2340                 layout = mLauncher.getHotseat().getLayout();
   2341             }
   2342         }
   2343 
   2344         int nextPage = getNextPage();
   2345         if (layout == null && !isPageInTransition()) {
   2346             // Check if the item is dragged over left page
   2347             mTempTouchCoordinates[0] = Math.min(centerX, d.x);
   2348             mTempTouchCoordinates[1] = d.y;
   2349             layout = verifyInsidePage(nextPage + (mIsRtl ? 1 : -1), mTempTouchCoordinates);
   2350         }
   2351 
   2352         if (layout == null && !isPageInTransition()) {
   2353             // Check if the item is dragged over right page
   2354             mTempTouchCoordinates[0] = Math.max(centerX, d.x);
   2355             mTempTouchCoordinates[1] = d.y;
   2356             layout = verifyInsidePage(nextPage + (mIsRtl ? -1 : 1), mTempTouchCoordinates);
   2357         }
   2358 
   2359         // Always pick the current page.
   2360         if (layout == null && nextPage >= 0 && nextPage < getPageCount()) {
   2361             layout = (CellLayout) getChildAt(nextPage);
   2362         }
   2363         if (layout != mDragTargetLayout) {
   2364             setCurrentDropLayout(layout);
   2365             setCurrentDragOverlappingLayout(layout);
   2366             return true;
   2367         }
   2368         return false;
   2369     }
   2370 
   2371     /**
   2372      * Returns the child CellLayout if the point is inside the page coordinates, null otherwise.
   2373      */
   2374     private CellLayout verifyInsidePage(int pageNo, float[] touchXy)  {
   2375         if (pageNo >= 0 && pageNo < getPageCount()) {
   2376             CellLayout cl = (CellLayout) getChildAt(pageNo);
   2377             mapPointFromSelfToChild(cl, touchXy);
   2378             if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
   2379                     touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
   2380                 // This point is inside the cell layout
   2381                 return cl;
   2382             }
   2383         }
   2384         return null;
   2385     }
   2386 
   2387     private void manageFolderFeedback(CellLayout targetLayout,
   2388             int[] targetCell, float distance, DragObject dragObject) {
   2389         if (distance > mMaxDistanceForFolderCreation) return;
   2390 
   2391         final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]);
   2392         ItemInfo info = dragObject.dragInfo;
   2393         boolean userFolderPending = willCreateUserFolder(info, dragOverView, false);
   2394         if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
   2395                 !mFolderCreationAlarm.alarmPending()) {
   2396 
   2397             FolderCreationAlarmListener listener = new
   2398                     FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]);
   2399 
   2400             if (!dragObject.accessibleDrag) {
   2401                 mFolderCreationAlarm.setOnAlarmListener(listener);
   2402                 mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
   2403             } else {
   2404                 listener.onAlarm(mFolderCreationAlarm);
   2405             }
   2406 
   2407             if (dragObject.stateAnnouncer != null) {
   2408                 dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
   2409                         .getDescriptionForDropOver(dragOverView, getContext()));
   2410             }
   2411             return;
   2412         }
   2413 
   2414         boolean willAddToFolder = willAddToExistingUserFolder(info, dragOverView);
   2415         if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
   2416             mDragOverFolderIcon = ((FolderIcon) dragOverView);
   2417             mDragOverFolderIcon.onDragEnter(info);
   2418             if (targetLayout != null) {
   2419                 targetLayout.clearDragOutlines();
   2420             }
   2421             setDragMode(DRAG_MODE_ADD_TO_FOLDER);
   2422 
   2423             if (dragObject.stateAnnouncer != null) {
   2424                 dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
   2425                         .getDescriptionForDropOver(dragOverView, getContext()));
   2426             }
   2427             return;
   2428         }
   2429 
   2430         if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
   2431             setDragMode(DRAG_MODE_NONE);
   2432         }
   2433         if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
   2434             setDragMode(DRAG_MODE_NONE);
   2435         }
   2436     }
   2437 
   2438     class FolderCreationAlarmListener implements OnAlarmListener {
   2439         final CellLayout layout;
   2440         final int cellX;
   2441         final int cellY;
   2442 
   2443         final PreviewBackground bg = new PreviewBackground();
   2444 
   2445         public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
   2446             this.layout = layout;
   2447             this.cellX = cellX;
   2448             this.cellY = cellY;
   2449 
   2450             BubbleTextView cell = (BubbleTextView) layout.getChildAt(cellX, cellY);
   2451             bg.setup(mLauncher, null, cell.getMeasuredWidth(), cell.getPaddingTop());
   2452 
   2453             // The full preview background should appear behind the icon
   2454             bg.isClipping = false;
   2455         }
   2456 
   2457         public void onAlarm(Alarm alarm) {
   2458             mFolderCreateBg = bg;
   2459             mFolderCreateBg.animateToAccept(layout, cellX, cellY);
   2460             layout.clearDragOutlines();
   2461             setDragMode(DRAG_MODE_CREATE_FOLDER);
   2462         }
   2463     }
   2464 
   2465     class ReorderAlarmListener implements OnAlarmListener {
   2466         final float[] dragViewCenter;
   2467         final int minSpanX, minSpanY, spanX, spanY;
   2468         final DragObject dragObject;
   2469         final View child;
   2470 
   2471         public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
   2472                 int spanY, DragObject dragObject, View child) {
   2473             this.dragViewCenter = dragViewCenter;
   2474             this.minSpanX = minSpanX;
   2475             this.minSpanY = minSpanY;
   2476             this.spanX = spanX;
   2477             this.spanY = spanY;
   2478             this.child = child;
   2479             this.dragObject = dragObject;
   2480         }
   2481 
   2482         public void onAlarm(Alarm alarm) {
   2483             int[] resultSpan = new int[2];
   2484             mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
   2485                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout,
   2486                     mTargetCell);
   2487             mLastReorderX = mTargetCell[0];
   2488             mLastReorderY = mTargetCell[1];
   2489 
   2490             mTargetCell = mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
   2491                 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
   2492                 child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
   2493 
   2494             if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
   2495                 mDragTargetLayout.revertTempState();
   2496             } else {
   2497                 setDragMode(DRAG_MODE_REORDER);
   2498             }
   2499 
   2500             boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
   2501             mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider,
   2502                 mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize, dragObject);
   2503         }
   2504     }
   2505 
   2506     @Override
   2507     public void getHitRectRelativeToDragLayer(Rect outRect) {
   2508         // We want the workspace to have the whole area of the display (it will find the correct
   2509         // cell layout to drop to in the existing drag/drop logic.
   2510         mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect);
   2511     }
   2512 
   2513     /**
   2514      * Drop an item that didn't originate on one of the workspace screens.
   2515      * It may have come from Launcher (e.g. from all apps or customize), or it may have
   2516      * come from another app altogether.
   2517      *
   2518      * NOTE: This can also be called when we are outside of a drag event, when we want
   2519      * to add an item to one of the workspace screens.
   2520      */
   2521     private void onDropExternal(final int[] touchXY, final CellLayout cellLayout, DragObject d) {
   2522         if (d.dragInfo instanceof PendingAddShortcutInfo) {
   2523             ShortcutInfo si = ((PendingAddShortcutInfo) d.dragInfo)
   2524                     .activityInfo.createShortcutInfo();
   2525             if (si != null) {
   2526                 d.dragInfo = si;
   2527             }
   2528         }
   2529 
   2530         ItemInfo info = d.dragInfo;
   2531         int spanX = info.spanX;
   2532         int spanY = info.spanY;
   2533         if (mDragInfo != null) {
   2534             spanX = mDragInfo.spanX;
   2535             spanY = mDragInfo.spanY;
   2536         }
   2537 
   2538         final long container = mLauncher.isHotseatLayout(cellLayout) ?
   2539                 LauncherSettings.Favorites.CONTAINER_HOTSEAT :
   2540                     LauncherSettings.Favorites.CONTAINER_DESKTOP;
   2541         final long screenId = getIdForScreen(cellLayout);
   2542         if (!mLauncher.isHotseatLayout(cellLayout)
   2543                 && screenId != getScreenIdForPageIndex(mCurrentPage)
   2544                 && !mLauncher.isInState(SPRING_LOADED)) {
   2545             snapToPage(getPageIndexForScreenId(screenId));
   2546         }
   2547 
   2548         if (info instanceof PendingAddItemInfo) {
   2549             final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) info;
   2550 
   2551             boolean findNearestVacantCell = true;
   2552             if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
   2553                 mTargetCell = findNearestArea(touchXY[0], touchXY[1], spanX, spanY,
   2554                         cellLayout, mTargetCell);
   2555                 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
   2556                         mDragViewVisualCenter[1], mTargetCell);
   2557                 if (willCreateUserFolder(d.dragInfo, cellLayout, mTargetCell, distance, true)
   2558                         || willAddToExistingUserFolder(
   2559                                 d.dragInfo, cellLayout, mTargetCell, distance)) {
   2560                     findNearestVacantCell = false;
   2561                 }
   2562             }
   2563 
   2564             final ItemInfo item = d.dragInfo;
   2565             boolean updateWidgetSize = false;
   2566             if (findNearestVacantCell) {
   2567                 int minSpanX = item.spanX;
   2568                 int minSpanY = item.spanY;
   2569                 if (item.minSpanX > 0 && item.minSpanY > 0) {
   2570                     minSpanX = item.minSpanX;
   2571                     minSpanY = item.minSpanY;
   2572                 }
   2573                 int[] resultSpan = new int[2];
   2574                 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
   2575                         (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
   2576                         null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
   2577 
   2578                 if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) {
   2579                     updateWidgetSize = true;
   2580                 }
   2581                 item.spanX = resultSpan[0];
   2582                 item.spanY = resultSpan[1];
   2583             }
   2584 
   2585             Runnable onAnimationCompleteRunnable = new Runnable() {
   2586                 @Override
   2587                 public void run() {
   2588                     // Normally removeExtraEmptyScreen is called in Workspace#onDragEnd, but when
   2589                     // adding an item that may not be dropped right away (due to a config activity)
   2590                     // we defer the removal until the activity returns.
   2591                     deferRemoveExtraEmptyScreen();
   2592 
   2593                     // When dragging and dropping from customization tray, we deal with creating
   2594                     // widgets/shortcuts/folders in a slightly different way
   2595                     mLauncher.addPendingItem(pendingInfo, container, screenId, mTargetCell,
   2596                             item.spanX, item.spanY);
   2597                 }
   2598             };
   2599             boolean isWidget = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
   2600                     || pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
   2601 
   2602             AppWidgetHostView finalView = isWidget ?
   2603                     ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
   2604 
   2605             if (finalView != null && updateWidgetSize) {
   2606                 AppWidgetResizeFrame.updateWidgetSizeRanges(finalView, mLauncher, item.spanX,
   2607                         item.spanY);
   2608             }
   2609 
   2610             int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
   2611             if (isWidget && ((PendingAddWidgetInfo) pendingInfo).info != null &&
   2612                     ((PendingAddWidgetInfo) pendingInfo).getHandler().needsConfigure()) {
   2613                 animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
   2614             }
   2615             animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
   2616                     animationStyle, finalView, true);
   2617         } else {
   2618             // This is for other drag/drop cases, like dragging from All Apps
   2619             mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
   2620 
   2621             View view;
   2622 
   2623             switch (info.itemType) {
   2624             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
   2625             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
   2626             case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
   2627                 if (info.container == NO_ID) {
   2628                     // Came from all apps -- make a copy
   2629                     if (info instanceof AppInfo) {
   2630                         info = ((AppInfo) info).makeShortcut();
   2631                         d.dragInfo = info;
   2632                     } else if (info instanceof ShortcutInfo) {
   2633                         info = new ShortcutInfo((ShortcutInfo) info);
   2634                         d.dragInfo = info;
   2635                     }
   2636 
   2637                 }
   2638                 view = mLauncher.createShortcut(cellLayout, (ShortcutInfo) info);
   2639                 break;
   2640             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
   2641                 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
   2642                         (FolderInfo) info);
   2643                 break;
   2644             default:
   2645                 throw new IllegalStateException("Unknown item type: " + info.itemType);
   2646             }
   2647 
   2648             // First we find the cell nearest to point at which the item is
   2649             // dropped, without any consideration to whether there is an item there.
   2650             if (touchXY != null) {
   2651                 mTargetCell = findNearestArea(touchXY[0], touchXY[1], spanX, spanY,
   2652                         cellLayout, mTargetCell);
   2653                 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
   2654                         mDragViewVisualCenter[1], mTargetCell);
   2655                 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
   2656                         true, d.dragView)) {
   2657                     return;
   2658                 }
   2659                 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
   2660                         true)) {
   2661                     return;
   2662                 }
   2663             }
   2664 
   2665             if (touchXY != null) {
   2666                 // when dragging and dropping, just find the closest free spot
   2667                 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
   2668                         (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
   2669                         null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
   2670             } else {
   2671                 cellLayout.findCellForSpan(mTargetCell, 1, 1);
   2672             }
   2673             // Add the item to DB before adding to screen ensures that the container and other
   2674             // values of the info is properly updated.
   2675             mLauncher.getModelWriter().addOrMoveItemInDatabase(info, container, screenId,
   2676                     mTargetCell[0], mTargetCell[1]);
   2677 
   2678             addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1],
   2679                     info.spanX, info.spanY);
   2680             cellLayout.onDropChild(view);
   2681             cellLayout.getShortcutsAndWidgets().measureChild(view);
   2682 
   2683             if (d.dragView != null) {
   2684                 // We wrap the animation call in the temporary set and reset of the current
   2685                 // cellLayout to its final transform -- this means we animate the drag view to
   2686                 // the correct final location.
   2687                 setFinalTransitionTransform();
   2688                 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, this);
   2689                 resetTransitionTransform();
   2690             }
   2691         }
   2692     }
   2693 
   2694     public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
   2695         int[] unScaledSize = estimateItemSize(widgetInfo);
   2696         int visibility = layout.getVisibility();
   2697         layout.setVisibility(VISIBLE);
   2698 
   2699         int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
   2700         int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
   2701         Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
   2702                 Bitmap.Config.ARGB_8888);
   2703         layout.measure(width, height);
   2704         layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
   2705         layout.draw(new Canvas(b));
   2706         layout.setVisibility(visibility);
   2707         return b;
   2708     }
   2709 
   2710     private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
   2711             DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, boolean scale) {
   2712         // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
   2713         // location and size on the home screen.
   2714         int spanX = info.spanX;
   2715         int spanY = info.spanY;
   2716 
   2717         Rect r = estimateItemPosition(layout, targetCell[0], targetCell[1], spanX, spanY);
   2718         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
   2719             DeviceProfile profile = mLauncher.getDeviceProfile();
   2720             Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
   2721         }
   2722         loc[0] = r.left;
   2723         loc[1] = r.top;
   2724 
   2725         setFinalTransitionTransform();
   2726         float cellLayoutScale =
   2727                 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
   2728         resetTransitionTransform();
   2729 
   2730         if (scale) {
   2731             float dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
   2732             float dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
   2733 
   2734             // The animation will scale the dragView about its center, so we need to center about
   2735             // the final location.
   2736             loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2
   2737                     - Math.ceil(layout.getUnusedHorizontalSpace() / 2f);
   2738             loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
   2739             scaleXY[0] = dragViewScaleX * cellLayoutScale;
   2740             scaleXY[1] = dragViewScaleY * cellLayoutScale;
   2741         } else {
   2742             // Since we are not cross-fading the dragView, align the drag view to the
   2743             // final cell position.
   2744             float dragScale = dragView.getInitialScale() * cellLayoutScale;
   2745             loc[0] += (dragScale - 1) * dragView.getWidth() / 2;
   2746             loc[1] += (dragScale - 1) * dragView.getHeight() / 2;
   2747             scaleXY[0] = scaleXY[1] = dragScale;
   2748 
   2749             // If a dragRegion was provided, offset the final position accordingly.
   2750             Rect dragRegion = dragView.getDragRegion();
   2751             if (dragRegion != null) {
   2752                 loc[0] += cellLayoutScale * dragRegion.left;
   2753                 loc[1] += cellLayoutScale * dragRegion.top;
   2754             }
   2755         }
   2756     }
   2757 
   2758     public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, final DragView dragView,
   2759             final Runnable onCompleteRunnable, int animationType, final View finalView,
   2760             boolean external) {
   2761         Rect from = new Rect();
   2762         mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
   2763 
   2764         int[] finalPos = new int[2];
   2765         float scaleXY[] = new float[2];
   2766         boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
   2767         getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
   2768                 scalePreview);
   2769 
   2770         Resources res = mLauncher.getResources();
   2771         final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
   2772 
   2773         boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET ||
   2774                 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
   2775         if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
   2776             Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
   2777             dragView.setCrossFadeBitmap(crossFadeBitmap);
   2778             dragView.crossFade((int) (duration * 0.8f));
   2779         } else if (isWidget && external) {
   2780             scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0],  scaleXY[1]);
   2781         }
   2782 
   2783         DragLayer dragLayer = mLauncher.getDragLayer();
   2784         if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
   2785             mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
   2786                     DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
   2787         } else {
   2788             int endStyle;
   2789             if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
   2790                 endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
   2791             } else {
   2792                 endStyle = DragLayer.ANIMATION_END_DISAPPEAR;
   2793             }
   2794 
   2795             Runnable onComplete = new Runnable() {
   2796                 @Override
   2797                 public void run() {
   2798                     if (finalView != null) {
   2799                         finalView.setVisibility(VISIBLE);
   2800                     }
   2801                     if (onCompleteRunnable != null) {
   2802                         onCompleteRunnable.run();
   2803                     }
   2804                 }
   2805             };
   2806             dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
   2807                     finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
   2808                     duration, this);
   2809         }
   2810     }
   2811 
   2812     public void setFinalTransitionTransform() {
   2813         if (isSwitchingState()) {
   2814             mCurrentScale = getScaleX();
   2815             setScaleX(mStateTransitionAnimation.getFinalScale());
   2816             setScaleY(mStateTransitionAnimation.getFinalScale());
   2817         }
   2818     }
   2819     public void resetTransitionTransform() {
   2820         if (isSwitchingState()) {
   2821             setScaleX(mCurrentScale);
   2822             setScaleY(mCurrentScale);
   2823         }
   2824     }
   2825 
   2826     /**
   2827      * Return the current CellInfo describing our current drag; this method exists
   2828      * so that Launcher can sync this object with the correct info when the activity is created/
   2829      * destroyed
   2830      *
   2831      */
   2832     public CellLayout.CellInfo getDragInfo() {
   2833         return mDragInfo;
   2834     }
   2835 
   2836     /**
   2837      * Calculate the nearest cell where the given object would be dropped.
   2838      *
   2839      * pixelX and pixelY should be in the coordinate system of layout
   2840      */
   2841     @Thunk int[] findNearestArea(int pixelX, int pixelY,
   2842             int spanX, int spanY, CellLayout layout, int[] recycle) {
   2843         return layout.findNearestArea(
   2844                 pixelX, pixelY, spanX, spanY, recycle);
   2845     }
   2846 
   2847     void setup(DragController dragController) {
   2848         mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
   2849         mDragController = dragController;
   2850 
   2851         // hardware layers on children are enabled on startup, but should be disabled until
   2852         // needed
   2853         updateChildrenLayersEnabled();
   2854     }
   2855 
   2856     /**
   2857      * Called at the end of a drag which originated on the workspace.
   2858      */
   2859     public void onDropCompleted(final View target, final DragObject d,
   2860             final boolean success) {
   2861 
   2862         if (success) {
   2863             if (target != this && mDragInfo != null) {
   2864                 removeWorkspaceItem(mDragInfo.cell);
   2865             }
   2866         } else if (mDragInfo != null) {
   2867             final CellLayout cellLayout = mLauncher.getCellLayout(
   2868                     mDragInfo.container, mDragInfo.screenId);
   2869             if (cellLayout != null) {
   2870                 cellLayout.onDropChild(mDragInfo.cell);
   2871             } else if (FeatureFlags.IS_DOGFOOD_BUILD) {
   2872                 throw new RuntimeException("Invalid state: cellLayout == null in "
   2873                         + "Workspace#onDropCompleted. Please file a bug. ");
   2874             }
   2875         }
   2876         View cell = getHomescreenIconByItemId(d.originalDragInfo.id);
   2877         if (d.cancelled && cell != null) {
   2878             cell.setVisibility(VISIBLE);
   2879         }
   2880         mDragInfo = null;
   2881     }
   2882 
   2883     /**
   2884      * For opposite operation. See {@link #addInScreen}.
   2885      */
   2886     public void removeWorkspaceItem(View v) {
   2887         CellLayout parentCell = getParentCellLayoutForView(v);
   2888         if (parentCell != null) {
   2889             parentCell.removeView(v);
   2890         } else if (FeatureFlags.IS_DOGFOOD_BUILD) {
   2891             // When an app is uninstalled using the drop target, we wait until resume to remove
   2892             // the icon. We also remove all the corresponding items from the workspace at
   2893             // {@link Launcher#bindComponentsRemoved}. That call can come before or after
   2894             // {@link Launcher#mOnResumeCallbacks} depending on how busy the worker thread is.
   2895             Log.e(TAG, "mDragInfo.cell has null parent");
   2896         }
   2897         if (v instanceof DropTarget) {
   2898             mDragController.removeDropTarget((DropTarget) v);
   2899         }
   2900     }
   2901 
   2902     /**
   2903      * Removes all folder listeners
   2904      */
   2905     public void removeFolderListeners() {
   2906         mapOverItems(false, new ItemOperator() {
   2907             @Override
   2908             public boolean evaluate(ItemInfo info, View view) {
   2909                 if (view instanceof FolderIcon) {
   2910                     ((FolderIcon) view).removeListeners();
   2911                 }
   2912                 return false;
   2913             }
   2914         });
   2915     }
   2916 
   2917     public boolean isDropEnabled() {
   2918         return true;
   2919     }
   2920 
   2921     @Override
   2922     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
   2923         // We don't dispatch restoreInstanceState to our children using this code path.
   2924         // Some pages will be restored immediately as their items are bound immediately, and
   2925         // others we will need to wait until after their items are bound.
   2926         mSavedStates = container;
   2927     }
   2928 
   2929     public void restoreInstanceStateForChild(int child) {
   2930         if (mSavedStates != null) {
   2931             mRestoredPages.add(child);
   2932             CellLayout cl = (CellLayout) getChildAt(child);
   2933             if (cl != null) {
   2934                 cl.restoreInstanceState(mSavedStates);
   2935             }
   2936         }
   2937     }
   2938 
   2939     public void restoreInstanceStateForRemainingPages() {
   2940         int count = getChildCount();
   2941         for (int i = 0; i < count; i++) {
   2942             if (!mRestoredPages.contains(i)) {
   2943                 restoreInstanceStateForChild(i);
   2944             }
   2945         }
   2946         mRestoredPages.clear();
   2947         mSavedStates = null;
   2948     }
   2949 
   2950     @Override
   2951     public boolean scrollLeft() {
   2952         boolean result = false;
   2953         if (!workspaceInModalState() && !mIsSwitchingState) {
   2954             result = super.scrollLeft();
   2955         }
   2956         Folder openFolder = Folder.getOpen(mLauncher);
   2957         if (openFolder != null) {
   2958             openFolder.completeDragExit();
   2959         }
   2960         return result;
   2961     }
   2962 
   2963     @Override
   2964     public boolean scrollRight() {
   2965         boolean result = false;
   2966         if (!workspaceInModalState() && !mIsSwitchingState) {
   2967             result = super.scrollRight();
   2968         }
   2969         Folder openFolder = Folder.getOpen(mLauncher);
   2970         if (openFolder != null) {
   2971             openFolder.completeDragExit();
   2972         }
   2973         return result;
   2974     }
   2975 
   2976     /**
   2977      * Returns a specific CellLayout
   2978      */
   2979     CellLayout getParentCellLayoutForView(View v) {
   2980         ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
   2981         for (CellLayout layout : layouts) {
   2982             if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
   2983                 return layout;
   2984             }
   2985         }
   2986         return null;
   2987     }
   2988 
   2989     /**
   2990      * Returns a list of all the CellLayouts in the workspace.
   2991      */
   2992     ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
   2993         ArrayList<CellLayout> layouts = new ArrayList<>();
   2994         int screenCount = getChildCount();
   2995         for (int screen = 0; screen < screenCount; screen++) {
   2996             layouts.add(((CellLayout) getChildAt(screen)));
   2997         }
   2998         if (mLauncher.getHotseat() != null) {
   2999             layouts.add(mLauncher.getHotseat().getLayout());
   3000         }
   3001         return layouts;
   3002     }
   3003 
   3004     /**
   3005      * We should only use this to search for specific children.  Do not use this method to modify
   3006      * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
   3007      * the hotseat and workspace pages
   3008      */
   3009     ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
   3010         ArrayList<ShortcutAndWidgetContainer> childrenLayouts = new ArrayList<>();
   3011         int screenCount = getChildCount();
   3012         for (int screen = 0; screen < screenCount; screen++) {
   3013             childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
   3014         }
   3015         if (mLauncher.getHotseat() != null) {
   3016             childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
   3017         }
   3018         return childrenLayouts;
   3019     }
   3020 
   3021     public View getHomescreenIconByItemId(final long id) {
   3022         return getFirstMatch(new ItemOperator() {
   3023 
   3024             @Override
   3025             public boolean evaluate(ItemInfo info, View v) {
   3026                 return info != null && info.id == id;
   3027             }
   3028         });
   3029     }
   3030 
   3031     public View getViewForTag(final Object tag) {
   3032         return getFirstMatch(new ItemOperator() {
   3033 
   3034             @Override
   3035             public boolean evaluate(ItemInfo info, View v) {
   3036                 return info == tag;
   3037             }
   3038         });
   3039     }
   3040 
   3041     public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) {
   3042         return (LauncherAppWidgetHostView) getFirstMatch(new ItemOperator() {
   3043 
   3044             @Override
   3045             public boolean evaluate(ItemInfo info, View v) {
   3046                 return (info instanceof LauncherAppWidgetInfo) &&
   3047                         ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId;
   3048             }
   3049         });
   3050     }
   3051 
   3052     public View getFirstMatch(final ItemOperator operator) {
   3053         final View[] value = new View[1];
   3054         mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
   3055             @Override
   3056             public boolean evaluate(ItemInfo info, View v) {
   3057                 if (operator.evaluate(info, v)) {
   3058                     value[0] = v;
   3059                     return true;
   3060                 }
   3061                 return false;
   3062             }
   3063         });
   3064         return value[0];
   3065     }
   3066 
   3067     void clearDropTargets() {
   3068         mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
   3069             @Override
   3070             public boolean evaluate(ItemInfo info, View v) {
   3071                 if (v instanceof DropTarget) {
   3072                     mDragController.removeDropTarget((DropTarget) v);
   3073                 }
   3074                 // not done, process all the shortcuts
   3075                 return false;
   3076             }
   3077         });
   3078     }
   3079 
   3080     /**
   3081      * Removes items that match the {@param matcher}. When applications are removed
   3082      * as a part of an update, this is called to ensure that other widgets and application
   3083      * shortcuts are not removed.
   3084      */
   3085     public void removeItemsByMatcher(final ItemInfoMatcher matcher) {
   3086         ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
   3087         for (final CellLayout layoutParent: cellLayouts) {
   3088             final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
   3089 
   3090             LongArrayMap<View> idToViewMap = new LongArrayMap<>();
   3091             ArrayList<ItemInfo> items = new ArrayList<>();
   3092             for (int j = 0; j < layout.getChildCount(); j++) {
   3093                 final View view = layout.getChildAt(j);
   3094                 if (view.getTag() instanceof ItemInfo) {
   3095                     ItemInfo item = (ItemInfo) view.getTag();
   3096                     items.add(item);
   3097                     idToViewMap.put(item.id, view);
   3098                 }
   3099             }
   3100 
   3101             for (ItemInfo itemToRemove : matcher.filterItemInfos(items)) {
   3102                 View child = idToViewMap.get(itemToRemove.id);
   3103 
   3104                 if (child != null) {
   3105                     // Note: We can not remove the view directly from CellLayoutChildren as this
   3106                     // does not re-mark the spaces as unoccupied.
   3107                     layoutParent.removeViewInLayout(child);
   3108                     if (child instanceof DropTarget) {
   3109                         mDragController.removeDropTarget((DropTarget) child);
   3110                     }
   3111                 } else if (itemToRemove.container >= 0) {
   3112                     // The item may belong to a folder.
   3113                     View parent = idToViewMap.get(itemToRemove.container);
   3114                     if (parent != null) {
   3115                         FolderInfo folderInfo = (FolderInfo) parent.getTag();
   3116                         folderInfo.prepareAutoUpdate();
   3117                         folderInfo.remove((ShortcutInfo) itemToRemove, false);
   3118                     }
   3119                 }
   3120             }
   3121         }
   3122 
   3123         // Strip all the empty screens
   3124         stripEmptyScreens();
   3125     }
   3126 
   3127     public interface ItemOperator {
   3128         /**
   3129          * Process the next itemInfo, possibly with side-effect on the next item.
   3130          *
   3131          * @param info info for the shortcut
   3132          * @param view view for the shortcut
   3133          * @return true if done, false to continue the map
   3134          */
   3135         boolean evaluate(ItemInfo info, View view);
   3136     }
   3137 
   3138     /**
   3139      * Map the operator over the shortcuts and widgets, return the first-non-null value.
   3140      *
   3141      * @param recurse true: iterate over folder children. false: op get the folders themselves.
   3142      * @param op the operator to map over the shortcuts
   3143      */
   3144     void mapOverItems(boolean recurse, ItemOperator op) {
   3145         ArrayList<ShortcutAndWidgetContainer> containers = getAllShortcutAndWidgetContainers();
   3146         final int containerCount = containers.size();
   3147         for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) {
   3148             ShortcutAndWidgetContainer container = containers.get(containerIdx);
   3149             // map over all the shortcuts on the workspace
   3150             final int itemCount = container.getChildCount();
   3151             for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
   3152                 View item = container.getChildAt(itemIdx);
   3153                 ItemInfo info = (ItemInfo) item.getTag();
   3154                 if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) {
   3155                     FolderIcon folder = (FolderIcon) item;
   3156                     ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder();
   3157                     // map over all the children in the folder
   3158                     final int childCount = folderChildren.size();
   3159                     for (int childIdx = 0; childIdx < childCount; childIdx++) {
   3160                         View child = folderChildren.get(childIdx);
   3161                         info = (ItemInfo) child.getTag();
   3162                         if (op.evaluate(info, child)) {
   3163                             return;
   3164                         }
   3165                     }
   3166                 } else {
   3167                     if (op.evaluate(info, item)) {
   3168                         return;
   3169                     }
   3170                 }
   3171             }
   3172         }
   3173     }
   3174 
   3175     void updateShortcuts(ArrayList<ShortcutInfo> shortcuts) {
   3176         int total  = shortcuts.size();
   3177         final HashSet<ShortcutInfo> updates = new HashSet<>(total);
   3178         final HashSet<Long> folderIds = new HashSet<>();
   3179 
   3180         for (int i = 0; i < total; i++) {
   3181             ShortcutInfo s = shortcuts.get(i);
   3182             updates.add(s);
   3183             folderIds.add(s.container);
   3184         }
   3185 
   3186         mapOverItems(MAP_RECURSE, new ItemOperator() {
   3187             @Override
   3188             public boolean evaluate(ItemInfo info, View v) {
   3189                 if (info instanceof ShortcutInfo && v instanceof BubbleTextView &&
   3190                         updates.contains(info)) {
   3191                     ShortcutInfo si = (ShortcutInfo) info;
   3192                     BubbleTextView shortcut = (BubbleTextView) v;
   3193                     Drawable oldIcon = shortcut.getIcon();
   3194                     boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
   3195                             && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
   3196                     shortcut.applyFromShortcutInfo(si, si.isPromise() != oldPromiseState);
   3197                 }
   3198                 // process all the shortcuts
   3199                 return false;
   3200             }
   3201         });
   3202 
   3203         // Update folder icons
   3204         mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
   3205             @Override
   3206             public boolean evaluate(ItemInfo info, View v) {
   3207                 if (info instanceof FolderInfo && folderIds.contains(info.id)) {
   3208                     ((FolderInfo) info).itemsChanged(false);
   3209                 }
   3210                 // process all the shortcuts
   3211                 return false;
   3212             }
   3213         });
   3214     }
   3215 
   3216     public void updateIconBadges(final Set<PackageUserKey> updatedBadges) {
   3217         final PackageUserKey packageUserKey = new PackageUserKey(null, null);
   3218         final HashSet<Long> folderIds = new HashSet<>();
   3219         mapOverItems(MAP_RECURSE, new ItemOperator() {
   3220             @Override
   3221             public boolean evaluate(ItemInfo info, View v) {
   3222                 if (info instanceof ShortcutInfo && v instanceof BubbleTextView
   3223                         && packageUserKey.updateFromItemInfo(info)) {
   3224                     if (updatedBadges.contains(packageUserKey)) {
   3225                         ((BubbleTextView) v).applyBadgeState(info, true /* animate */);
   3226                         folderIds.add(info.container);
   3227                     }
   3228                 }
   3229                 // process all the shortcuts
   3230                 return false;
   3231             }
   3232         });
   3233 
   3234         // Update folder icons
   3235         mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
   3236             @Override
   3237             public boolean evaluate(ItemInfo info, View v) {
   3238                 if (info instanceof FolderInfo && folderIds.contains(info.id)
   3239                         && v instanceof FolderIcon) {
   3240                     FolderBadgeInfo folderBadgeInfo = new FolderBadgeInfo();
   3241                     for (ShortcutInfo si : ((FolderInfo) info).contents) {
   3242                         folderBadgeInfo.addBadgeInfo(mLauncher.getBadgeInfoForItem(si));
   3243                     }
   3244                     ((FolderIcon) v).setBadgeInfo(folderBadgeInfo);
   3245                 }
   3246                 // process all the shortcuts
   3247                 return false;
   3248             }
   3249         });
   3250     }
   3251 
   3252     public void removeAbandonedPromise(String packageName, UserHandle user) {
   3253         HashSet<String> packages = new HashSet<>(1);
   3254         packages.add(packageName);
   3255         ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packages, user);
   3256         mLauncher.getModelWriter().deleteItemsFromDatabase(matcher);
   3257         removeItemsByMatcher(matcher);
   3258     }
   3259 
   3260     public void updateRestoreItems(final HashSet<ItemInfo> updates) {
   3261         mapOverItems(MAP_RECURSE, new ItemOperator() {
   3262             @Override
   3263             public boolean evaluate(ItemInfo info, View v) {
   3264                 if (info instanceof ShortcutInfo && v instanceof BubbleTextView
   3265                         && updates.contains(info)) {
   3266                     ((BubbleTextView) v).applyPromiseState(false /* promiseStateChanged */);
   3267                 } else if (v instanceof PendingAppWidgetHostView
   3268                         && info instanceof LauncherAppWidgetInfo
   3269                         && updates.contains(info)) {
   3270                     ((PendingAppWidgetHostView) v).applyState();
   3271                 }
   3272                 // process all the shortcuts
   3273                 return false;
   3274             }
   3275         });
   3276     }
   3277 
   3278     public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) {
   3279         if (!changedInfo.isEmpty()) {
   3280             DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
   3281                     mLauncher.getAppWidgetHost());
   3282 
   3283             LauncherAppWidgetInfo item = changedInfo.get(0);
   3284             final AppWidgetProviderInfo widgetInfo;
   3285             if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
   3286                 widgetInfo = AppWidgetManagerCompat
   3287                         .getInstance(mLauncher).findProvider(item.providerName, item.user);
   3288             } else {
   3289                 widgetInfo = AppWidgetManagerCompat.getInstance(mLauncher)
   3290                         .getLauncherAppWidgetInfo(item.appWidgetId);
   3291             }
   3292 
   3293             if (widgetInfo != null) {
   3294                 // Re-inflate the widgets which have changed status
   3295                 widgetRefresh.run();
   3296             } else {
   3297                 // widgetRefresh will automatically run when the packages are updated.
   3298                 // For now just update the progress bars
   3299                 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
   3300                     @Override
   3301                     public boolean evaluate(ItemInfo info, View view) {
   3302                         if (view instanceof PendingAppWidgetHostView
   3303                                 && changedInfo.contains(info)) {
   3304                             ((LauncherAppWidgetInfo) info).installProgress = 100;
   3305                             ((PendingAppWidgetHostView) view).applyState();
   3306                         }
   3307                         // process all the shortcuts
   3308                         return false;
   3309                     }
   3310                 });
   3311             }
   3312         }
   3313     }
   3314 
   3315     void moveToDefaultScreen() {
   3316         int page = DEFAULT_PAGE;
   3317         if (!workspaceInModalState() && getNextPage() != page) {
   3318             snapToPage(page);
   3319         }
   3320         View child = getChildAt(page);
   3321         if (child != null) {
   3322             child.requestFocus();
   3323         }
   3324     }
   3325 
   3326     @Override
   3327     public int getExpectedHeight() {
   3328         return getMeasuredHeight() <= 0 || !mIsLayoutValid
   3329                 ? mLauncher.getDeviceProfile().heightPx : getMeasuredHeight();
   3330     }
   3331 
   3332     @Override
   3333     public int getExpectedWidth() {
   3334         return getMeasuredWidth() <= 0 || !mIsLayoutValid
   3335                 ? mLauncher.getDeviceProfile().widthPx : getMeasuredWidth();
   3336     }
   3337 
   3338     @Override
   3339     protected boolean canAnnouncePageDescription() {
   3340         // Disable announcements while overscrolling potentially to overlay screen because if we end
   3341         // up on the overlay screen, it will take care of announcing itself.
   3342         return Float.compare(mOverlayTranslation, 0f) == 0;
   3343     }
   3344 
   3345     @Override
   3346     protected String getCurrentPageDescription() {
   3347         int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
   3348         return getPageDescription(page);
   3349     }
   3350 
   3351     private String getPageDescription(int page) {
   3352         int nScreens = getChildCount();
   3353         int extraScreenId = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
   3354         if (extraScreenId >= 0 && nScreens > 1) {
   3355             if (page == extraScreenId) {
   3356                 return getContext().getString(R.string.workspace_new_page);
   3357             }
   3358             nScreens--;
   3359         }
   3360         if (nScreens == 0) {
   3361             // When the workspace is not loaded, we do not know how many screen will be bound.
   3362             return getContext().getString(R.string.all_apps_home_button_label);
   3363         }
   3364         return getContext().getString(R.string.workspace_scroll_format, page + 1, nScreens);
   3365     }
   3366 
   3367     @Override
   3368     public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
   3369         target.gridX = info.cellX;
   3370         target.gridY = info.cellY;
   3371         target.pageIndex = getCurrentPage();
   3372         targetParent.containerType = ContainerType.WORKSPACE;
   3373         if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
   3374             target.rank = info.rank;
   3375             targetParent.containerType = ContainerType.HOTSEAT;
   3376         } else if (info.container >= 0) {
   3377             targetParent.containerType = ContainerType.FOLDER;
   3378         }
   3379     }
   3380 
   3381     /**
   3382      * Used as a workaround to ensure that the AppWidgetService receives the
   3383      * PACKAGE_ADDED broadcast before updating widgets.
   3384      */
   3385     private class DeferredWidgetRefresh implements Runnable, ProviderChangedListener {
   3386         private final ArrayList<LauncherAppWidgetInfo> mInfos;
   3387         private final LauncherAppWidgetHost mHost;
   3388         private final Handler mHandler;
   3389 
   3390         private boolean mRefreshPending;
   3391 
   3392         DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos,
   3393             LauncherAppWidgetHost host) {
   3394             mInfos = infos;
   3395             mHost = host;
   3396             mHandler = new Handler();
   3397             mRefreshPending = true;
   3398 
   3399             mHost.addProviderChangeListener(this);
   3400             // Force refresh after 10 seconds, if we don't get the provider changed event.
   3401             // This could happen when the provider is no longer available in the app.
   3402             mHandler.postDelayed(this, 10000);
   3403         }
   3404 
   3405         @Override
   3406         public void run() {
   3407             mHost.removeProviderChangeListener(this);
   3408             mHandler.removeCallbacks(this);
   3409 
   3410             if (!mRefreshPending) {
   3411                 return;
   3412             }
   3413 
   3414             mRefreshPending = false;
   3415 
   3416             ArrayList<PendingAppWidgetHostView> views = new ArrayList<>(mInfos.size());
   3417             mapOverItems(MAP_NO_RECURSE, (info, view) -> {
   3418                 if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
   3419                     views.add((PendingAppWidgetHostView) view);
   3420                 }
   3421                 // process all children
   3422                 return false;
   3423             });
   3424             for (PendingAppWidgetHostView view : views) {
   3425                 view.reInflate();
   3426             }
   3427         }
   3428 
   3429         @Override
   3430         public void notifyWidgetProvidersChanged() {
   3431             run();
   3432         }
   3433     }
   3434 
   3435     private class StateTransitionListener extends AnimatorListenerAdapter
   3436             implements AnimatorUpdateListener {
   3437 
   3438         private final LauncherState mToState;
   3439 
   3440         StateTransitionListener(LauncherState toState) {
   3441             mToState = toState;
   3442         }
   3443 
   3444         @Override
   3445         public void onAnimationUpdate(ValueAnimator anim) {
   3446             mTransitionProgress = anim.getAnimatedFraction();
   3447         }
   3448 
   3449         @Override
   3450         public void onAnimationStart(Animator animation) {
   3451             onStartStateTransition(mToState);
   3452         }
   3453 
   3454         @Override
   3455         public void onAnimationEnd(Animator animation) {
   3456             onEndStateTransition();
   3457         }
   3458     }
   3459 }
   3460