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