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