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