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