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