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