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