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