• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher2;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorSet;
21 import android.animation.ObjectAnimator;
22 import android.animation.TimeInterpolator;
23 import android.animation.ValueAnimator;
24 import android.animation.ValueAnimator.AnimatorUpdateListener;
25 import android.app.WallpaperManager;
26 import android.appwidget.AppWidgetHostView;
27 import android.appwidget.AppWidgetProviderInfo;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.SharedPreferences;
32 import android.content.res.Resources;
33 import android.content.res.TypedArray;
34 import android.graphics.Bitmap;
35 import android.graphics.Canvas;
36 import android.graphics.Matrix;
37 import android.graphics.Point;
38 import android.graphics.PointF;
39 import android.graphics.Rect;
40 import android.graphics.Region.Op;
41 import android.graphics.drawable.Drawable;
42 import android.os.IBinder;
43 import android.os.Parcelable;
44 import android.util.AttributeSet;
45 import android.util.Log;
46 import android.util.SparseArray;
47 import android.view.Display;
48 import android.view.MotionEvent;
49 import android.view.View;
50 import android.view.ViewGroup;
51 import android.view.animation.DecelerateInterpolator;
52 import android.widget.ImageView;
53 import android.widget.TextView;
54 
55 import com.android.launcher.R;
56 import com.android.launcher2.FolderIcon.FolderRingAnimator;
57 import com.android.launcher2.LauncherSettings.Favorites;
58 
59 import java.net.URISyntaxException;
60 import java.util.ArrayList;
61 import java.util.HashSet;
62 import java.util.Iterator;
63 import java.util.Set;
64 
65 /**
66  * The workspace is a wide area with a wallpaper and a finite number of pages.
67  * Each page contains a number of icons, folders or widgets the user can
68  * interact with. A workspace is meant to be used with a fixed width only.
69  */
70 public class Workspace extends SmoothPagedView
71         implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
72         DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener {
73     private static final String TAG = "Launcher.Workspace";
74 
75     // Y rotation to apply to the workspace screens
76     private static final float WORKSPACE_OVERSCROLL_ROTATION = 24f;
77 
78     private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
79     private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
80     private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
81 
82     private static final int BACKGROUND_FADE_OUT_DURATION = 350;
83     private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
84     private static final int FLING_THRESHOLD_VELOCITY = 500;
85 
86     // These animators are used to fade the children's outlines
87     private ObjectAnimator mChildrenOutlineFadeInAnimation;
88     private ObjectAnimator mChildrenOutlineFadeOutAnimation;
89     private float mChildrenOutlineAlpha = 0;
90 
91     // These properties refer to the background protection gradient used for AllApps and Customize
92     private ValueAnimator mBackgroundFadeInAnimation;
93     private ValueAnimator mBackgroundFadeOutAnimation;
94     private Drawable mBackground;
95     boolean mDrawBackground = true;
96     private float mBackgroundAlpha = 0;
97     private float mOverScrollMaxBackgroundAlpha = 0.0f;
98 
99     private float mWallpaperScrollRatio = 1.0f;
100     private int mOriginalPageSpacing;
101 
102     private final WallpaperManager mWallpaperManager;
103     private IBinder mWindowToken;
104     private static final float WALLPAPER_SCREENS_SPAN = 2f;
105 
106     private int mDefaultPage;
107 
108     /**
109      * CellInfo for the cell that is currently being dragged
110      */
111     private CellLayout.CellInfo mDragInfo;
112 
113     /**
114      * Target drop area calculated during last acceptDrop call.
115      */
116     private int[] mTargetCell = new int[2];
117     private int mDragOverX = -1;
118     private int mDragOverY = -1;
119 
120     static Rect mLandscapeCellLayoutMetrics = null;
121     static Rect mPortraitCellLayoutMetrics = null;
122 
123     /**
124      * The CellLayout that is currently being dragged over
125      */
126     private CellLayout mDragTargetLayout = null;
127     /**
128      * The CellLayout that we will show as glowing
129      */
130     private CellLayout mDragOverlappingLayout = null;
131 
132     /**
133      * The CellLayout which will be dropped to
134      */
135     private CellLayout mDropToLayout = null;
136 
137     private Launcher mLauncher;
138     private IconCache mIconCache;
139     private DragController mDragController;
140 
141     // These are temporary variables to prevent having to allocate a new object just to
142     // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
143     private int[] mTempCell = new int[2];
144     private int[] mTempEstimate = new int[2];
145     private float[] mDragViewVisualCenter = new float[2];
146     private float[] mTempDragCoordinates = new float[2];
147     private float[] mTempCellLayoutCenterCoordinates = new float[2];
148     private float[] mTempDragBottomRightCoordinates = new float[2];
149     private Matrix mTempInverseMatrix = new Matrix();
150 
151     private SpringLoadedDragController mSpringLoadedDragController;
152     private float mSpringLoadedShrinkFactor;
153 
154     private static final int DEFAULT_CELL_COUNT_X = 4;
155     private static final int DEFAULT_CELL_COUNT_Y = 4;
156 
157     // State variable that indicates whether the pages are small (ie when you're
158     // in all apps or customize mode)
159 
160     enum State { NORMAL, SPRING_LOADED, SMALL };
161     private State mState = State.NORMAL;
162     private boolean mIsSwitchingState = false;
163 
164     boolean mAnimatingViewIntoPlace = false;
165     boolean mIsDragOccuring = false;
166     boolean mChildrenLayersEnabled = true;
167 
168     /** Is the user is dragging an item near the edge of a page? */
169     private boolean mInScrollArea = false;
170 
171     private final HolographicOutlineHelper mOutlineHelper = new HolographicOutlineHelper();
172     private Bitmap mDragOutline = null;
173     private final Rect mTempRect = new Rect();
174     private final int[] mTempXY = new int[2];
175     private int[] mTempVisiblePagesRange = new int[2];
176     private float mOverscrollFade = 0;
177     private boolean mOverscrollTransformsSet;
178     public static final int DRAG_BITMAP_PADDING = 2;
179     private boolean mWorkspaceFadeInAdjacentScreens;
180 
181     enum WallpaperVerticalOffset { TOP, MIDDLE, BOTTOM };
182     int mWallpaperWidth;
183     int mWallpaperHeight;
184     WallpaperOffsetInterpolator mWallpaperOffset;
185     boolean mUpdateWallpaperOffsetImmediately = false;
186     private Runnable mDelayedResizeRunnable;
187     private Runnable mDelayedSnapToPageRunnable;
188     private Point mDisplaySize = new Point();
189     private boolean mIsStaticWallpaper;
190     private int mWallpaperTravelWidth;
191     private int mSpringLoadedPageSpacing;
192     private int mCameraDistance;
193 
194     // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
195     private static final int FOLDER_CREATION_TIMEOUT = 0;
196     private static final int REORDER_TIMEOUT = 250;
197     private final Alarm mFolderCreationAlarm = new Alarm();
198     private final Alarm mReorderAlarm = new Alarm();
199     private FolderRingAnimator mDragFolderRingAnimator = null;
200     private FolderIcon mDragOverFolderIcon = null;
201     private boolean mCreateUserFolderOnDrop = false;
202     private boolean mAddToExistingFolderOnDrop = false;
203     private DropTarget.DragEnforcer mDragEnforcer;
204     private float mMaxDistanceForFolderCreation;
205 
206     // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
207     private float mXDown;
208     private float mYDown;
209     final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
210     final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
211     final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
212 
213     // Relating to the animation of items being dropped externally
214     public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0;
215     public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1;
216     public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2;
217     public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3;
218     public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4;
219 
220     // Related to dragging, folder creation and reordering
221     private static final int DRAG_MODE_NONE = 0;
222     private static final int DRAG_MODE_CREATE_FOLDER = 1;
223     private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
224     private static final int DRAG_MODE_REORDER = 3;
225     private int mDragMode = DRAG_MODE_NONE;
226     private int mLastReorderX = -1;
227     private int mLastReorderY = -1;
228 
229     private SparseArray<Parcelable> mSavedStates;
230     private final ArrayList<Integer> mRestoredPages = new ArrayList<Integer>();
231 
232     // These variables are used for storing the initial and final values during workspace animations
233     private int mSavedScrollX;
234     private float mSavedRotationY;
235     private float mSavedTranslationX;
236     private float mCurrentScaleX;
237     private float mCurrentScaleY;
238     private float mCurrentRotationY;
239     private float mCurrentTranslationX;
240     private float mCurrentTranslationY;
241     private float[] mOldTranslationXs;
242     private float[] mOldTranslationYs;
243     private float[] mOldScaleXs;
244     private float[] mOldScaleYs;
245     private float[] mOldBackgroundAlphas;
246     private float[] mOldAlphas;
247     private float[] mNewTranslationXs;
248     private float[] mNewTranslationYs;
249     private float[] mNewScaleXs;
250     private float[] mNewScaleYs;
251     private float[] mNewBackgroundAlphas;
252     private float[] mNewAlphas;
253     private float[] mNewRotationYs;
254     private float mTransitionProgress;
255 
256     private final Runnable mBindPages = new Runnable() {
257         @Override
258         public void run() {
259             mLauncher.getModel().bindRemainingSynchronousPages();
260         }
261     };
262 
263     /**
264      * Used to inflate the Workspace from XML.
265      *
266      * @param context The application's context.
267      * @param attrs The attributes set containing the Workspace's customization values.
268      */
Workspace(Context context, AttributeSet attrs)269     public Workspace(Context context, AttributeSet attrs) {
270         this(context, attrs, 0);
271     }
272 
273     /**
274      * Used to inflate the Workspace from XML.
275      *
276      * @param context The application's context.
277      * @param attrs The attributes set containing the Workspace's customization values.
278      * @param defStyle Unused.
279      */
Workspace(Context context, AttributeSet attrs, int defStyle)280     public Workspace(Context context, AttributeSet attrs, int defStyle) {
281         super(context, attrs, defStyle);
282         mContentIsRefreshable = false;
283         mOriginalPageSpacing = mPageSpacing;
284 
285         mDragEnforcer = new DropTarget.DragEnforcer(context);
286         // With workspace, data is available straight from the get-go
287         setDataIsReady();
288 
289         mLauncher = (Launcher) context;
290         final Resources res = getResources();
291         mWorkspaceFadeInAdjacentScreens = res.getBoolean(R.bool.config_workspaceFadeAdjacentScreens);
292         mFadeInAdjacentScreens = false;
293         mWallpaperManager = WallpaperManager.getInstance(context);
294 
295         int cellCountX = DEFAULT_CELL_COUNT_X;
296         int cellCountY = DEFAULT_CELL_COUNT_Y;
297 
298         TypedArray a = context.obtainStyledAttributes(attrs,
299                 R.styleable.Workspace, defStyle, 0);
300 
301         if (LauncherApplication.isScreenLarge()) {
302             // Determine number of rows/columns dynamically
303             // TODO: This code currently fails on tablets with an aspect ratio < 1.3.
304             // Around that ratio we should make cells the same size in portrait and
305             // landscape
306             TypedArray actionBarSizeTypedArray =
307                 context.obtainStyledAttributes(new int[] { android.R.attr.actionBarSize });
308             final float actionBarHeight = actionBarSizeTypedArray.getDimension(0, 0f);
309 
310             Point minDims = new Point();
311             Point maxDims = new Point();
312             mLauncher.getWindowManager().getDefaultDisplay().getCurrentSizeRange(minDims, maxDims);
313 
314             cellCountX = 1;
315             while (CellLayout.widthInPortrait(res, cellCountX + 1) <= minDims.x) {
316                 cellCountX++;
317             }
318 
319             cellCountY = 1;
320             while (actionBarHeight + CellLayout.heightInLandscape(res, cellCountY + 1)
321                 <= minDims.y) {
322                 cellCountY++;
323             }
324         }
325 
326         mSpringLoadedShrinkFactor =
327             res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
328         mSpringLoadedPageSpacing =
329                 res.getDimensionPixelSize(R.dimen.workspace_spring_loaded_page_spacing);
330         mCameraDistance = res.getInteger(R.integer.config_cameraDistance);
331 
332         // if the value is manually specified, use that instead
333         cellCountX = a.getInt(R.styleable.Workspace_cellCountX, cellCountX);
334         cellCountY = a.getInt(R.styleable.Workspace_cellCountY, cellCountY);
335         mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
336         a.recycle();
337 
338         setOnHierarchyChangeListener(this);
339 
340         LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY);
341         setHapticFeedbackEnabled(false);
342 
343         initWorkspace();
344 
345         // Disable multitouch across the workspace/all apps/customize tray
346         setMotionEventSplittingEnabled(true);
347 
348         // Unless otherwise specified this view is important for accessibility.
349         if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
350             setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
351         }
352     }
353 
354     // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
355     // dimension if unsuccessful
estimateItemSize(int hSpan, int vSpan, ItemInfo itemInfo, boolean springLoaded)356     public int[] estimateItemSize(int hSpan, int vSpan,
357             ItemInfo itemInfo, boolean springLoaded) {
358         int[] size = new int[2];
359         if (getChildCount() > 0) {
360             CellLayout cl = (CellLayout) mLauncher.getWorkspace().getChildAt(0);
361             Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan);
362             size[0] = r.width();
363             size[1] = r.height();
364             if (springLoaded) {
365                 size[0] *= mSpringLoadedShrinkFactor;
366                 size[1] *= mSpringLoadedShrinkFactor;
367             }
368             return size;
369         } else {
370             size[0] = Integer.MAX_VALUE;
371             size[1] = Integer.MAX_VALUE;
372             return size;
373         }
374     }
estimateItemPosition(CellLayout cl, ItemInfo pendingInfo, int hCell, int vCell, int hSpan, int vSpan)375     public Rect estimateItemPosition(CellLayout cl, ItemInfo pendingInfo,
376             int hCell, int vCell, int hSpan, int vSpan) {
377         Rect r = new Rect();
378         cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
379         return r;
380     }
381 
onDragStart(DragSource source, Object info, int dragAction)382     public void onDragStart(DragSource source, Object info, int dragAction) {
383         mIsDragOccuring = true;
384         updateChildrenLayersEnabled(false);
385         mLauncher.lockScreenOrientation();
386         setChildrenBackgroundAlphaMultipliers(1f);
387         // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging
388         InstallShortcutReceiver.enableInstallQueue();
389         UninstallShortcutReceiver.enableUninstallQueue();
390     }
391 
onDragEnd()392     public void onDragEnd() {
393         mIsDragOccuring = false;
394         updateChildrenLayersEnabled(false);
395         mLauncher.unlockScreenOrientation(false);
396 
397         // Re-enable any Un/InstallShortcutReceiver and now process any queued items
398         InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
399         UninstallShortcutReceiver.disableAndFlushUninstallQueue(getContext());
400     }
401 
402     /**
403      * Initializes various states for this workspace.
404      */
initWorkspace()405     protected void initWorkspace() {
406         Context context = getContext();
407         mCurrentPage = mDefaultPage;
408         Launcher.setScreen(mCurrentPage);
409         LauncherApplication app = (LauncherApplication)context.getApplicationContext();
410         mIconCache = app.getIconCache();
411         setWillNotDraw(false);
412         setChildrenDrawnWithCacheEnabled(true);
413 
414         final Resources res = getResources();
415         try {
416             mBackground = res.getDrawable(R.drawable.apps_customize_bg);
417         } catch (Resources.NotFoundException e) {
418             // In this case, we will skip drawing background protection
419         }
420 
421         mWallpaperOffset = new WallpaperOffsetInterpolator();
422         Display display = mLauncher.getWindowManager().getDefaultDisplay();
423         display.getSize(mDisplaySize);
424         mWallpaperTravelWidth = (int) (mDisplaySize.x *
425                 wallpaperTravelToScreenWidthRatio(mDisplaySize.x, mDisplaySize.y));
426 
427         mMaxDistanceForFolderCreation = (0.55f * res.getDimensionPixelSize(R.dimen.app_icon_size));
428         mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
429     }
430 
431     @Override
getScrollMode()432     protected int getScrollMode() {
433         return SmoothPagedView.X_LARGE_MODE;
434     }
435 
436     @Override
onChildViewAdded(View parent, View child)437     public void onChildViewAdded(View parent, View child) {
438         if (!(child instanceof CellLayout)) {
439             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
440         }
441         CellLayout cl = ((CellLayout) child);
442         cl.setOnInterceptTouchListener(this);
443         cl.setClickable(true);
444         cl.setContentDescription(getContext().getString(
445                 R.string.workspace_description_format, getChildCount()));
446     }
447 
448     @Override
onChildViewRemoved(View parent, View child)449     public void onChildViewRemoved(View parent, View child) {
450     }
451 
shouldDrawChild(View child)452     protected boolean shouldDrawChild(View child) {
453         final CellLayout cl = (CellLayout) child;
454         return super.shouldDrawChild(child) &&
455             (cl.getShortcutsAndWidgets().getAlpha() > 0 ||
456              cl.getBackgroundAlpha() > 0);
457     }
458 
459     /**
460      * @return The open folder on the current screen, or null if there is none
461      */
getOpenFolder()462     Folder getOpenFolder() {
463         DragLayer dragLayer = mLauncher.getDragLayer();
464         int count = dragLayer.getChildCount();
465         for (int i = 0; i < count; i++) {
466             View child = dragLayer.getChildAt(i);
467             if (child instanceof Folder) {
468                 Folder folder = (Folder) child;
469                 if (folder.getInfo().opened)
470                     return folder;
471             }
472         }
473         return null;
474     }
475 
isTouchActive()476     boolean isTouchActive() {
477         return mTouchState != TOUCH_STATE_REST;
478     }
479 
480     /**
481      * Adds the specified child in the specified screen. The position and dimension of
482      * the child are defined by x, y, spanX and spanY.
483      *
484      * @param child The child to add in one of the workspace's screens.
485      * @param screen The screen in which to add the child.
486      * @param x The X position of the child in the screen's grid.
487      * @param y The Y position of the child in the screen's grid.
488      * @param spanX The number of cells spanned horizontally by the child.
489      * @param spanY The number of cells spanned vertically by the child.
490      */
addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY)491     void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY) {
492         addInScreen(child, container, screen, x, y, spanX, spanY, false);
493     }
494 
495     /**
496      * Adds the specified child in the specified screen. The position and dimension of
497      * the child are defined by x, y, spanX and spanY.
498      *
499      * @param child The child to add in one of the workspace's screens.
500      * @param screen The screen in which to add the child.
501      * @param x The X position of the child in the screen's grid.
502      * @param y The Y position of the child in the screen's grid.
503      * @param spanX The number of cells spanned horizontally by the child.
504      * @param spanY The number of cells spanned vertically by the child.
505      * @param insert When true, the child is inserted at the beginning of the children list.
506      */
addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY, boolean insert)507     void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY,
508             boolean insert) {
509         if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
510             if (screen < 0 || screen >= getChildCount()) {
511                 Log.e(TAG, "The screen must be >= 0 and < " + getChildCount()
512                     + " (was " + screen + "); skipping child");
513                 return;
514             }
515         }
516 
517         final CellLayout layout;
518         if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
519             layout = mLauncher.getHotseat().getLayout();
520             child.setOnKeyListener(null);
521 
522             // Hide folder title in the hotseat
523             if (child instanceof FolderIcon) {
524                 ((FolderIcon) child).setTextVisible(false);
525             }
526 
527             if (screen < 0) {
528                 screen = mLauncher.getHotseat().getOrderInHotseat(x, y);
529             } else {
530                 // Note: We do this to ensure that the hotseat is always laid out in the orientation
531                 // of the hotseat in order regardless of which orientation they were added
532                 x = mLauncher.getHotseat().getCellXFromOrder(screen);
533                 y = mLauncher.getHotseat().getCellYFromOrder(screen);
534             }
535         } else {
536             // Show folder title if not in the hotseat
537             if (child instanceof FolderIcon) {
538                 ((FolderIcon) child).setTextVisible(true);
539             }
540 
541             layout = (CellLayout) getChildAt(screen);
542             child.setOnKeyListener(new IconKeyEventListener());
543         }
544 
545         LayoutParams genericLp = child.getLayoutParams();
546         CellLayout.LayoutParams lp;
547         if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
548             lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
549         } else {
550             lp = (CellLayout.LayoutParams) genericLp;
551             lp.cellX = x;
552             lp.cellY = y;
553             lp.cellHSpan = spanX;
554             lp.cellVSpan = spanY;
555         }
556 
557         if (spanX < 0 && spanY < 0) {
558             lp.isLockedToGrid = false;
559         }
560 
561         // Get the canonical child id to uniquely represent this view in this screen
562         int childId = LauncherModel.getCellLayoutChildId(container, screen, x, y, spanX, spanY);
563         boolean markCellsAsOccupied = !(child instanceof Folder);
564         if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
565             // TODO: This branch occurs when the workspace is adding views
566             // outside of the defined grid
567             // maybe we should be deleting these items from the LauncherModel?
568             Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
569         }
570 
571         if (!(child instanceof Folder)) {
572             child.setHapticFeedbackEnabled(false);
573             child.setOnLongClickListener(mLongClickListener);
574         }
575         if (child instanceof DropTarget) {
576             mDragController.addDropTarget((DropTarget) child);
577         }
578     }
579 
580     /**
581      * Check if the point (x, y) hits a given page.
582      */
hitsPage(int index, float x, float y)583     private boolean hitsPage(int index, float x, float y) {
584         final View page = getChildAt(index);
585         if (page != null) {
586             float[] localXY = { x, y };
587             mapPointFromSelfToChild(page, localXY);
588             return (localXY[0] >= 0 && localXY[0] < page.getWidth()
589                     && localXY[1] >= 0 && localXY[1] < page.getHeight());
590         }
591         return false;
592     }
593 
594     @Override
hitsPreviousPage(float x, float y)595     protected boolean hitsPreviousPage(float x, float y) {
596         // mNextPage is set to INVALID_PAGE whenever we are stationary.
597         // Calculating "next page" this way ensures that you scroll to whatever page you tap on
598         final int current = (mNextPage == INVALID_PAGE) ? mCurrentPage : mNextPage;
599 
600         // Only allow tap to next page on large devices, where there's significant margin outside
601         // the active workspace
602         return LauncherApplication.isScreenLarge() && hitsPage(current - 1, x, y);
603     }
604 
605     @Override
hitsNextPage(float x, float y)606     protected boolean hitsNextPage(float x, float y) {
607         // mNextPage is set to INVALID_PAGE whenever we are stationary.
608         // Calculating "next page" this way ensures that you scroll to whatever page you tap on
609         final int current = (mNextPage == INVALID_PAGE) ? mCurrentPage : mNextPage;
610 
611         // Only allow tap to next page on large devices, where there's significant margin outside
612         // the active workspace
613         return LauncherApplication.isScreenLarge() && hitsPage(current + 1, x, y);
614     }
615 
616     /**
617      * Called directly from a CellLayout (not by the framework), after we've been added as a
618      * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
619      * that it should intercept touch events, which is not something that is normally supported.
620      */
621     @Override
onTouch(View v, MotionEvent event)622     public boolean onTouch(View v, MotionEvent event) {
623         return (isSmall() || !isFinishedSwitchingState());
624     }
625 
isSwitchingState()626     public boolean isSwitchingState() {
627         return mIsSwitchingState;
628     }
629 
630     /** This differs from isSwitchingState in that we take into account how far the transition
631      *  has completed. */
isFinishedSwitchingState()632     public boolean isFinishedSwitchingState() {
633         return !mIsSwitchingState || (mTransitionProgress > 0.5f);
634     }
635 
onWindowVisibilityChanged(int visibility)636     protected void onWindowVisibilityChanged (int visibility) {
637         mLauncher.onWindowVisibilityChanged(visibility);
638     }
639 
640     @Override
dispatchUnhandledMove(View focused, int direction)641     public boolean dispatchUnhandledMove(View focused, int direction) {
642         if (isSmall() || !isFinishedSwitchingState()) {
643             // when the home screens are shrunken, shouldn't allow side-scrolling
644             return false;
645         }
646         return super.dispatchUnhandledMove(focused, direction);
647     }
648 
649     @Override
onInterceptTouchEvent(MotionEvent ev)650     public boolean onInterceptTouchEvent(MotionEvent ev) {
651         switch (ev.getAction() & MotionEvent.ACTION_MASK) {
652         case MotionEvent.ACTION_DOWN:
653             mXDown = ev.getX();
654             mYDown = ev.getY();
655             break;
656         case MotionEvent.ACTION_POINTER_UP:
657         case MotionEvent.ACTION_UP:
658             if (mTouchState == TOUCH_STATE_REST) {
659                 final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
660                 if (!currentPage.lastDownOnOccupiedCell()) {
661                     onWallpaperTap(ev);
662                 }
663             }
664         }
665         return super.onInterceptTouchEvent(ev);
666     }
667 
reinflateWidgetsIfNecessary()668     protected void reinflateWidgetsIfNecessary() {
669         final int clCount = getChildCount();
670         for (int i = 0; i < clCount; i++) {
671             CellLayout cl = (CellLayout) getChildAt(i);
672             ShortcutAndWidgetContainer swc = cl.getShortcutsAndWidgets();
673             final int itemCount = swc.getChildCount();
674             for (int j = 0; j < itemCount; j++) {
675                 View v = swc.getChildAt(j);
676 
677                 if (v.getTag() instanceof LauncherAppWidgetInfo) {
678                     LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
679                     LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) info.hostView;
680                     if (lahv != null && lahv.orientationChangedSincedInflation()) {
681                         mLauncher.removeAppWidget(info);
682                         // Remove the current widget which is inflated with the wrong orientation
683                         cl.removeView(lahv);
684                         mLauncher.bindAppWidget(info);
685                     }
686                 }
687             }
688         }
689     }
690 
691     @Override
determineScrollingStart(MotionEvent ev)692     protected void determineScrollingStart(MotionEvent ev) {
693         if (isSmall()) return;
694         if (!isFinishedSwitchingState()) return;
695 
696         float deltaX = Math.abs(ev.getX() - mXDown);
697         float deltaY = Math.abs(ev.getY() - mYDown);
698 
699         if (Float.compare(deltaX, 0f) == 0) return;
700 
701         float slope = deltaY / deltaX;
702         float theta = (float) Math.atan(slope);
703 
704         if (deltaX > mTouchSlop || deltaY > mTouchSlop) {
705             cancelCurrentPageLongPress();
706         }
707 
708         if (theta > MAX_SWIPE_ANGLE) {
709             // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
710             return;
711         } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
712             // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
713             // increase the touch slop to make it harder to begin scrolling the workspace. This
714             // results in vertically scrolling widgets to more easily. The higher the angle, the
715             // more we increase touch slop.
716             theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
717             float extraRatio = (float)
718                     Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
719             super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
720         } else {
721             // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
722             super.determineScrollingStart(ev);
723         }
724     }
725 
onPageBeginMoving()726     protected void onPageBeginMoving() {
727         super.onPageBeginMoving();
728 
729         if (isHardwareAccelerated()) {
730             updateChildrenLayersEnabled(false);
731         } else {
732             if (mNextPage != INVALID_PAGE) {
733                 // we're snapping to a particular screen
734                 enableChildrenCache(mCurrentPage, mNextPage);
735             } else {
736                 // this is when user is actively dragging a particular screen, they might
737                 // swipe it either left or right (but we won't advance by more than one screen)
738                 enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
739             }
740         }
741 
742         // Only show page outlines as we pan if we are on large screen
743         if (LauncherApplication.isScreenLarge()) {
744             showOutlines();
745             mIsStaticWallpaper = mWallpaperManager.getWallpaperInfo() == null;
746         }
747 
748         // If we are not fading in adjacent screens, we still need to restore the alpha in case the
749         // user scrolls while we are transitioning (should not affect dispatchDraw optimizations)
750         if (!mWorkspaceFadeInAdjacentScreens) {
751             for (int i = 0; i < getChildCount(); ++i) {
752                 ((CellLayout) getPageAt(i)).setShortcutAndWidgetAlpha(1f);
753             }
754         }
755 
756         // Show the scroll indicator as you pan the page
757         showScrollingIndicator(false);
758     }
759 
onPageEndMoving()760     protected void onPageEndMoving() {
761         super.onPageEndMoving();
762 
763         if (isHardwareAccelerated()) {
764             updateChildrenLayersEnabled(false);
765         } else {
766             clearChildrenCache();
767         }
768 
769 
770         if (mDragController.isDragging()) {
771             if (isSmall()) {
772                 // If we are in springloaded mode, then force an event to check if the current touch
773                 // is under a new page (to scroll to)
774                 mDragController.forceMoveEvent();
775             }
776         } else {
777             // If we are not mid-dragging, hide the page outlines if we are on a large screen
778             if (LauncherApplication.isScreenLarge()) {
779                 hideOutlines();
780             }
781 
782             // Hide the scroll indicator as you pan the page
783             if (!mDragController.isDragging()) {
784                 hideScrollingIndicator(false);
785             }
786         }
787         mOverScrollMaxBackgroundAlpha = 0.0f;
788 
789         if (mDelayedResizeRunnable != null) {
790             mDelayedResizeRunnable.run();
791             mDelayedResizeRunnable = null;
792         }
793 
794         if (mDelayedSnapToPageRunnable != null) {
795             mDelayedSnapToPageRunnable.run();
796             mDelayedSnapToPageRunnable = null;
797         }
798     }
799 
800     @Override
notifyPageSwitchListener()801     protected void notifyPageSwitchListener() {
802         super.notifyPageSwitchListener();
803         Launcher.setScreen(mCurrentPage);
804     };
805 
806     // As a ratio of screen height, the total distance we want the parallax effect to span
807     // horizontally
wallpaperTravelToScreenWidthRatio(int width, int height)808     private float wallpaperTravelToScreenWidthRatio(int width, int height) {
809         float aspectRatio = width / (float) height;
810 
811         // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
812         // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
813         // We will use these two data points to extrapolate how much the wallpaper parallax effect
814         // to span (ie travel) at any aspect ratio:
815 
816         final float ASPECT_RATIO_LANDSCAPE = 16/10f;
817         final float ASPECT_RATIO_PORTRAIT = 10/16f;
818         final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
819         final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;
820 
821         // To find out the desired width at different aspect ratios, we use the following two
822         // formulas, where the coefficient on x is the aspect ratio (width/height):
823         //   (16/10)x + y = 1.5
824         //   (10/16)x + y = 1.2
825         // We solve for x and y and end up with a final formula:
826         final float x =
827             (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
828             (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
829         final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
830         return x * aspectRatio + y;
831     }
832 
833     // The range of scroll values for Workspace
getScrollRange()834     private int getScrollRange() {
835         return getChildOffset(getChildCount() - 1) - getChildOffset(0);
836     }
837 
setWallpaperDimension()838     protected void setWallpaperDimension() {
839         Point minDims = new Point();
840         Point maxDims = new Point();
841         mLauncher.getWindowManager().getDefaultDisplay().getCurrentSizeRange(minDims, maxDims);
842 
843         final int maxDim = Math.max(maxDims.x, maxDims.y);
844         final int minDim = Math.min(minDims.x, minDims.y);
845 
846         // We need to ensure that there is enough extra space in the wallpaper for the intended
847         // parallax effects
848         if (LauncherApplication.isScreenLarge()) {
849             mWallpaperWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));
850             mWallpaperHeight = maxDim;
851         } else {
852             mWallpaperWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
853             mWallpaperHeight = maxDim;
854         }
855         new Thread("setWallpaperDimension") {
856             public void run() {
857                 mWallpaperManager.suggestDesiredDimensions(mWallpaperWidth, mWallpaperHeight);
858             }
859         }.start();
860     }
861 
wallpaperOffsetForCurrentScroll()862     private float wallpaperOffsetForCurrentScroll() {
863         // Set wallpaper offset steps (1 / (number of screens - 1))
864         mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 1.0f);
865 
866         // For the purposes of computing the scrollRange and overScrollOffset, we assume
867         // that mLayoutScale is 1. This means that when we're in spring-loaded mode,
868         // there's no discrepancy between the wallpaper offset for a given page.
869         float layoutScale = mLayoutScale;
870         mLayoutScale = 1f;
871         int scrollRange = getScrollRange();
872 
873         // Again, we adjust the wallpaper offset to be consistent between values of mLayoutScale
874         float adjustedScrollX = Math.max(0, Math.min(getScrollX(), mMaxScrollX));
875         adjustedScrollX *= mWallpaperScrollRatio;
876         mLayoutScale = layoutScale;
877 
878         float scrollProgress =
879             adjustedScrollX / (float) scrollRange;
880 
881         if (LauncherApplication.isScreenLarge() && mIsStaticWallpaper) {
882             // The wallpaper travel width is how far, from left to right, the wallpaper will move
883             // at this orientation. On tablets in portrait mode we don't move all the way to the
884             // edges of the wallpaper, or otherwise the parallax effect would be too strong.
885             int wallpaperTravelWidth = Math.min(mWallpaperTravelWidth, mWallpaperWidth);
886 
887             float offsetInDips = wallpaperTravelWidth * scrollProgress +
888                 (mWallpaperWidth - wallpaperTravelWidth) / 2; // center it
889             float offset = offsetInDips / (float) mWallpaperWidth;
890             return offset;
891         } else {
892             return scrollProgress;
893         }
894     }
895 
syncWallpaperOffsetWithScroll()896     private void syncWallpaperOffsetWithScroll() {
897         final boolean enableWallpaperEffects = isHardwareAccelerated();
898         if (enableWallpaperEffects) {
899             mWallpaperOffset.setFinalX(wallpaperOffsetForCurrentScroll());
900         }
901     }
902 
updateWallpaperOffsetImmediately()903     public void updateWallpaperOffsetImmediately() {
904         mUpdateWallpaperOffsetImmediately = true;
905     }
906 
updateWallpaperOffsets()907     private void updateWallpaperOffsets() {
908         boolean updateNow = false;
909         boolean keepUpdating = true;
910         if (mUpdateWallpaperOffsetImmediately) {
911             updateNow = true;
912             keepUpdating = false;
913             mWallpaperOffset.jumpToFinal();
914             mUpdateWallpaperOffsetImmediately = false;
915         } else {
916             updateNow = keepUpdating = mWallpaperOffset.computeScrollOffset();
917         }
918         if (updateNow) {
919             if (mWindowToken != null) {
920                 mWallpaperManager.setWallpaperOffsets(mWindowToken,
921                         mWallpaperOffset.getCurrX(), mWallpaperOffset.getCurrY());
922             }
923         }
924         if (keepUpdating) {
925             invalidate();
926         }
927     }
928 
929     @Override
updateCurrentPageScroll()930     protected void updateCurrentPageScroll() {
931         super.updateCurrentPageScroll();
932         computeWallpaperScrollRatio(mCurrentPage);
933     }
934 
935     @Override
snapToPage(int whichPage)936     protected void snapToPage(int whichPage) {
937         super.snapToPage(whichPage);
938         computeWallpaperScrollRatio(whichPage);
939     }
940 
941     @Override
snapToPage(int whichPage, int duration)942     protected void snapToPage(int whichPage, int duration) {
943         super.snapToPage(whichPage, duration);
944         computeWallpaperScrollRatio(whichPage);
945     }
946 
snapToPage(int whichPage, Runnable r)947     protected void snapToPage(int whichPage, Runnable r) {
948         if (mDelayedSnapToPageRunnable != null) {
949             mDelayedSnapToPageRunnable.run();
950         }
951         mDelayedSnapToPageRunnable = r;
952         snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION);
953     }
954 
computeWallpaperScrollRatio(int page)955     private void computeWallpaperScrollRatio(int page) {
956         // Here, we determine what the desired scroll would be with and without a layout scale,
957         // and compute a ratio between the two. This allows us to adjust the wallpaper offset
958         // as though there is no layout scale.
959         float layoutScale = mLayoutScale;
960         int scaled = getChildOffset(page) - getRelativeChildOffset(page);
961         mLayoutScale = 1.0f;
962         float unscaled = getChildOffset(page) - getRelativeChildOffset(page);
963         mLayoutScale = layoutScale;
964         if (scaled > 0) {
965             mWallpaperScrollRatio = (1.0f * unscaled) / scaled;
966         } else {
967             mWallpaperScrollRatio = 1f;
968         }
969     }
970 
971     class WallpaperOffsetInterpolator {
972         float mFinalHorizontalWallpaperOffset = 0.0f;
973         float mFinalVerticalWallpaperOffset = 0.5f;
974         float mHorizontalWallpaperOffset = 0.0f;
975         float mVerticalWallpaperOffset = 0.5f;
976         long mLastWallpaperOffsetUpdateTime;
977         boolean mIsMovingFast;
978         boolean mOverrideHorizontalCatchupConstant;
979         float mHorizontalCatchupConstant = 0.35f;
980         float mVerticalCatchupConstant = 0.35f;
981 
WallpaperOffsetInterpolator()982         public WallpaperOffsetInterpolator() {
983         }
984 
setOverrideHorizontalCatchupConstant(boolean override)985         public void setOverrideHorizontalCatchupConstant(boolean override) {
986             mOverrideHorizontalCatchupConstant = override;
987         }
988 
setHorizontalCatchupConstant(float f)989         public void setHorizontalCatchupConstant(float f) {
990             mHorizontalCatchupConstant = f;
991         }
992 
setVerticalCatchupConstant(float f)993         public void setVerticalCatchupConstant(float f) {
994             mVerticalCatchupConstant = f;
995         }
996 
computeScrollOffset()997         public boolean computeScrollOffset() {
998             if (Float.compare(mHorizontalWallpaperOffset, mFinalHorizontalWallpaperOffset) == 0 &&
999                     Float.compare(mVerticalWallpaperOffset, mFinalVerticalWallpaperOffset) == 0) {
1000                 mIsMovingFast = false;
1001                 return false;
1002             }
1003             boolean isLandscape = mDisplaySize.x > mDisplaySize.y;
1004 
1005             long currentTime = System.currentTimeMillis();
1006             long timeSinceLastUpdate = currentTime - mLastWallpaperOffsetUpdateTime;
1007             timeSinceLastUpdate = Math.min((long) (1000/30f), timeSinceLastUpdate);
1008             timeSinceLastUpdate = Math.max(1L, timeSinceLastUpdate);
1009 
1010             float xdiff = Math.abs(mFinalHorizontalWallpaperOffset - mHorizontalWallpaperOffset);
1011             if (!mIsMovingFast && xdiff > 0.07) {
1012                 mIsMovingFast = true;
1013             }
1014 
1015             float fractionToCatchUpIn1MsHorizontal;
1016             if (mOverrideHorizontalCatchupConstant) {
1017                 fractionToCatchUpIn1MsHorizontal = mHorizontalCatchupConstant;
1018             } else if (mIsMovingFast) {
1019                 fractionToCatchUpIn1MsHorizontal = isLandscape ? 0.5f : 0.75f;
1020             } else {
1021                 // slow
1022                 fractionToCatchUpIn1MsHorizontal = isLandscape ? 0.27f : 0.5f;
1023             }
1024             float fractionToCatchUpIn1MsVertical = mVerticalCatchupConstant;
1025 
1026             fractionToCatchUpIn1MsHorizontal /= 33f;
1027             fractionToCatchUpIn1MsVertical /= 33f;
1028 
1029             final float UPDATE_THRESHOLD = 0.00001f;
1030             float hOffsetDelta = mFinalHorizontalWallpaperOffset - mHorizontalWallpaperOffset;
1031             float vOffsetDelta = mFinalVerticalWallpaperOffset - mVerticalWallpaperOffset;
1032             boolean jumpToFinalValue = Math.abs(hOffsetDelta) < UPDATE_THRESHOLD &&
1033                 Math.abs(vOffsetDelta) < UPDATE_THRESHOLD;
1034 
1035             // Don't have any lag between workspace and wallpaper on non-large devices
1036             if (!LauncherApplication.isScreenLarge() || jumpToFinalValue) {
1037                 mHorizontalWallpaperOffset = mFinalHorizontalWallpaperOffset;
1038                 mVerticalWallpaperOffset = mFinalVerticalWallpaperOffset;
1039             } else {
1040                 float percentToCatchUpVertical =
1041                     Math.min(1.0f, timeSinceLastUpdate * fractionToCatchUpIn1MsVertical);
1042                 float percentToCatchUpHorizontal =
1043                     Math.min(1.0f, timeSinceLastUpdate * fractionToCatchUpIn1MsHorizontal);
1044                 mHorizontalWallpaperOffset += percentToCatchUpHorizontal * hOffsetDelta;
1045                 mVerticalWallpaperOffset += percentToCatchUpVertical * vOffsetDelta;
1046             }
1047 
1048             mLastWallpaperOffsetUpdateTime = System.currentTimeMillis();
1049             return true;
1050         }
1051 
1052         public float getCurrX() {
1053             return mHorizontalWallpaperOffset;
1054         }
1055 
1056         public float getFinalX() {
1057             return mFinalHorizontalWallpaperOffset;
1058         }
1059 
1060         public float getCurrY() {
1061             return mVerticalWallpaperOffset;
1062         }
1063 
1064         public float getFinalY() {
1065             return mFinalVerticalWallpaperOffset;
1066         }
1067 
1068         public void setFinalX(float x) {
1069             mFinalHorizontalWallpaperOffset = Math.max(0f, Math.min(x, 1.0f));
1070         }
1071 
1072         public void setFinalY(float y) {
1073             mFinalVerticalWallpaperOffset = Math.max(0f, Math.min(y, 1.0f));
1074         }
1075 
1076         public void jumpToFinal() {
1077             mHorizontalWallpaperOffset = mFinalHorizontalWallpaperOffset;
1078             mVerticalWallpaperOffset = mFinalVerticalWallpaperOffset;
1079         }
1080     }
1081 
1082     @Override
1083     public void computeScroll() {
1084         super.computeScroll();
1085         syncWallpaperOffsetWithScroll();
1086     }
1087 
1088     void showOutlines() {
1089         if (!isSmall() && !mIsSwitchingState) {
1090             if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
1091             if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
1092             mChildrenOutlineFadeInAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 1.0f);
1093             mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
1094             mChildrenOutlineFadeInAnimation.start();
1095         }
1096     }
1097 
1098     void hideOutlines() {
1099         if (!isSmall() && !mIsSwitchingState) {
1100             if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
1101             if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
1102             mChildrenOutlineFadeOutAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 0.0f);
1103             mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
1104             mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
1105             mChildrenOutlineFadeOutAnimation.start();
1106         }
1107     }
1108 
1109     public void showOutlinesTemporarily() {
1110         if (!mIsPageMoving && !isTouchActive()) {
1111             snapToPage(mCurrentPage);
1112         }
1113     }
1114 
1115     public void setChildrenOutlineAlpha(float alpha) {
1116         mChildrenOutlineAlpha = alpha;
1117         for (int i = 0; i < getChildCount(); i++) {
1118             CellLayout cl = (CellLayout) getChildAt(i);
1119             cl.setBackgroundAlpha(alpha);
1120         }
1121     }
1122 
1123     public float getChildrenOutlineAlpha() {
1124         return mChildrenOutlineAlpha;
1125     }
1126 
1127     void disableBackground() {
1128         mDrawBackground = false;
1129     }
1130     void enableBackground() {
1131         mDrawBackground = true;
1132     }
1133 
1134     private void animateBackgroundGradient(float finalAlpha, boolean animated) {
1135         if (mBackground == null) return;
1136         if (mBackgroundFadeInAnimation != null) {
1137             mBackgroundFadeInAnimation.cancel();
1138             mBackgroundFadeInAnimation = null;
1139         }
1140         if (mBackgroundFadeOutAnimation != null) {
1141             mBackgroundFadeOutAnimation.cancel();
1142             mBackgroundFadeOutAnimation = null;
1143         }
1144         float startAlpha = getBackgroundAlpha();
1145         if (finalAlpha != startAlpha) {
1146             if (animated) {
1147                 mBackgroundFadeOutAnimation = LauncherAnimUtils.ofFloat(startAlpha, finalAlpha);
1148                 mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() {
1149                     public void onAnimationUpdate(ValueAnimator animation) {
1150                         setBackgroundAlpha(((Float) animation.getAnimatedValue()).floatValue());
1151                     }
1152                 });
1153                 mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
1154                 mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
1155                 mBackgroundFadeOutAnimation.start();
1156             } else {
1157                 setBackgroundAlpha(finalAlpha);
1158             }
1159         }
1160     }
1161 
1162     public void setBackgroundAlpha(float alpha) {
1163         if (alpha != mBackgroundAlpha) {
1164             mBackgroundAlpha = alpha;
1165             invalidate();
1166         }
1167     }
1168 
1169     public float getBackgroundAlpha() {
1170         return mBackgroundAlpha;
1171     }
1172 
1173     float backgroundAlphaInterpolator(float r) {
1174         float pivotA = 0.1f;
1175         float pivotB = 0.4f;
1176         if (r < pivotA) {
1177             return 0;
1178         } else if (r > pivotB) {
1179             return 1.0f;
1180         } else {
1181             return (r - pivotA)/(pivotB - pivotA);
1182         }
1183     }
1184 
1185     float overScrollBackgroundAlphaInterpolator(float r) {
1186         float threshold = 0.08f;
1187 
1188         if (r > mOverScrollMaxBackgroundAlpha) {
1189             mOverScrollMaxBackgroundAlpha = r;
1190         } else if (r < mOverScrollMaxBackgroundAlpha) {
1191             r = mOverScrollMaxBackgroundAlpha;
1192         }
1193 
1194         return Math.min(r / threshold, 1.0f);
1195     }
1196 
1197     private void updatePageAlphaValues(int screenCenter) {
1198         boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
1199         if (mWorkspaceFadeInAdjacentScreens &&
1200                 mState == State.NORMAL &&
1201                 !mIsSwitchingState &&
1202                 !isInOverscroll) {
1203             for (int i = 0; i < getChildCount(); i++) {
1204                 CellLayout child = (CellLayout) getChildAt(i);
1205                 if (child != null) {
1206                     float scrollProgress = getScrollProgress(screenCenter, child, i);
1207                     float alpha = 1 - Math.abs(scrollProgress);
1208                     child.getShortcutsAndWidgets().setAlpha(alpha);
1209                     if (!mIsDragOccuring) {
1210                         child.setBackgroundAlphaMultiplier(
1211                                 backgroundAlphaInterpolator(Math.abs(scrollProgress)));
1212                     } else {
1213                         child.setBackgroundAlphaMultiplier(1f);
1214                     }
1215                 }
1216             }
1217         }
1218     }
1219 
1220     private void setChildrenBackgroundAlphaMultipliers(float a) {
1221         for (int i = 0; i < getChildCount(); i++) {
1222             CellLayout child = (CellLayout) getChildAt(i);
1223             child.setBackgroundAlphaMultiplier(a);
1224         }
1225     }
1226 
1227     @Override
1228     protected void screenScrolled(int screenCenter) {
1229         super.screenScrolled(screenCenter);
1230 
1231         updatePageAlphaValues(screenCenter);
1232         enableHwLayersOnVisiblePages();
1233 
1234         if (mOverScrollX < 0 || mOverScrollX > mMaxScrollX) {
1235             int index = mOverScrollX < 0 ? 0 : getChildCount() - 1;
1236             CellLayout cl = (CellLayout) getChildAt(index);
1237             float scrollProgress = getScrollProgress(screenCenter, cl, index);
1238             cl.setOverScrollAmount(Math.abs(scrollProgress), index == 0);
1239             float rotation = - WORKSPACE_OVERSCROLL_ROTATION * scrollProgress;
1240             cl.setRotationY(rotation);
1241             setFadeForOverScroll(Math.abs(scrollProgress));
1242             if (!mOverscrollTransformsSet) {
1243                 mOverscrollTransformsSet = true;
1244                 cl.setCameraDistance(mDensity * mCameraDistance);
1245                 cl.setPivotX(cl.getMeasuredWidth() * (index == 0 ? 0.75f : 0.25f));
1246                 cl.setPivotY(cl.getMeasuredHeight() * 0.5f);
1247                 cl.setOverscrollTransformsDirty(true);
1248             }
1249         } else {
1250             if (mOverscrollFade != 0) {
1251                 setFadeForOverScroll(0);
1252             }
1253             if (mOverscrollTransformsSet) {
1254                 mOverscrollTransformsSet = false;
1255                 ((CellLayout) getChildAt(0)).resetOverscrollTransforms();
1256                 ((CellLayout) getChildAt(getChildCount() - 1)).resetOverscrollTransforms();
1257             }
1258         }
1259     }
1260 
1261     @Override
1262     protected void overScroll(float amount) {
1263         acceleratedOverScroll(amount);
1264     }
1265 
1266     protected void onAttachedToWindow() {
1267         super.onAttachedToWindow();
1268         mWindowToken = getWindowToken();
1269         computeScroll();
1270         mDragController.setWindowToken(mWindowToken);
1271     }
1272 
1273     protected void onDetachedFromWindow() {
1274         mWindowToken = null;
1275     }
1276 
1277     @Override
1278     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1279         if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
1280             mUpdateWallpaperOffsetImmediately = true;
1281         }
1282         super.onLayout(changed, left, top, right, bottom);
1283     }
1284 
1285     @Override
1286     protected void onDraw(Canvas canvas) {
1287         updateWallpaperOffsets();
1288 
1289         // Draw the background gradient if necessary
1290         if (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground) {
1291             int alpha = (int) (mBackgroundAlpha * 255);
1292             mBackground.setAlpha(alpha);
1293             mBackground.setBounds(getScrollX(), 0, getScrollX() + getMeasuredWidth(),
1294                     getMeasuredHeight());
1295             mBackground.draw(canvas);
1296         }
1297 
1298         super.onDraw(canvas);
1299 
1300         // Call back to LauncherModel to finish binding after the first draw
1301         post(mBindPages);
1302     }
1303 
1304     boolean isDrawingBackgroundGradient() {
1305         return (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground);
1306     }
1307 
1308     @Override
1309     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1310         if (!mLauncher.isAllAppsVisible()) {
1311             final Folder openFolder = getOpenFolder();
1312             if (openFolder != null) {
1313                 return openFolder.requestFocus(direction, previouslyFocusedRect);
1314             } else {
1315                 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
1316             }
1317         }
1318         return false;
1319     }
1320 
1321     @Override
1322     public int getDescendantFocusability() {
1323         if (isSmall()) {
1324             return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
1325         }
1326         return super.getDescendantFocusability();
1327     }
1328 
1329     @Override
1330     public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1331         if (!mLauncher.isAllAppsVisible()) {
1332             final Folder openFolder = getOpenFolder();
1333             if (openFolder != null) {
1334                 openFolder.addFocusables(views, direction);
1335             } else {
1336                 super.addFocusables(views, direction, focusableMode);
1337             }
1338         }
1339     }
1340 
1341     public boolean isSmall() {
1342         return mState == State.SMALL || mState == State.SPRING_LOADED;
1343     }
1344 
1345     void enableChildrenCache(int fromPage, int toPage) {
1346         if (fromPage > toPage) {
1347             final int temp = fromPage;
1348             fromPage = toPage;
1349             toPage = temp;
1350         }
1351 
1352         final int screenCount = getChildCount();
1353 
1354         fromPage = Math.max(fromPage, 0);
1355         toPage = Math.min(toPage, screenCount - 1);
1356 
1357         for (int i = fromPage; i <= toPage; i++) {
1358             final CellLayout layout = (CellLayout) getChildAt(i);
1359             layout.setChildrenDrawnWithCacheEnabled(true);
1360             layout.setChildrenDrawingCacheEnabled(true);
1361         }
1362     }
1363 
1364     void clearChildrenCache() {
1365         final int screenCount = getChildCount();
1366         for (int i = 0; i < screenCount; i++) {
1367             final CellLayout layout = (CellLayout) getChildAt(i);
1368             layout.setChildrenDrawnWithCacheEnabled(false);
1369             // In software mode, we don't want the items to continue to be drawn into bitmaps
1370             if (!isHardwareAccelerated()) {
1371                 layout.setChildrenDrawingCacheEnabled(false);
1372             }
1373         }
1374     }
1375 
1376 
1377     private void updateChildrenLayersEnabled(boolean force) {
1378         boolean small = mState == State.SMALL || mIsSwitchingState;
1379         boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving();
1380 
1381         if (enableChildrenLayers != mChildrenLayersEnabled) {
1382             mChildrenLayersEnabled = enableChildrenLayers;
1383             if (mChildrenLayersEnabled) {
1384                 enableHwLayersOnVisiblePages();
1385             } else {
1386                 for (int i = 0; i < getPageCount(); i++) {
1387                     final CellLayout cl = (CellLayout) getChildAt(i);
1388                     cl.disableHardwareLayers();
1389                 }
1390             }
1391         }
1392     }
1393 
1394     private void enableHwLayersOnVisiblePages() {
1395         if (mChildrenLayersEnabled) {
1396             final int screenCount = getChildCount();
1397             getVisiblePages(mTempVisiblePagesRange);
1398             int leftScreen = mTempVisiblePagesRange[0];
1399             int rightScreen = mTempVisiblePagesRange[1];
1400             if (leftScreen == rightScreen) {
1401                 // make sure we're caching at least two pages always
1402                 if (rightScreen < screenCount - 1) {
1403                     rightScreen++;
1404                 } else if (leftScreen > 0) {
1405                     leftScreen--;
1406                 }
1407             }
1408             for (int i = 0; i < screenCount; i++) {
1409                 final CellLayout layout = (CellLayout) getChildAt(i);
1410                 if (!(leftScreen <= i && i <= rightScreen && shouldDrawChild(layout))) {
1411                     layout.disableHardwareLayers();
1412                 }
1413             }
1414             for (int i = 0; i < screenCount; i++) {
1415                 final CellLayout layout = (CellLayout) getChildAt(i);
1416                 if (leftScreen <= i && i <= rightScreen && shouldDrawChild(layout)) {
1417                     layout.enableHardwareLayers();
1418                 }
1419             }
1420         }
1421     }
1422 
1423     public void buildPageHardwareLayers() {
1424         // force layers to be enabled just for the call to buildLayer
1425         updateChildrenLayersEnabled(true);
1426         if (getWindowToken() != null) {
1427             final int childCount = getChildCount();
1428             for (int i = 0; i < childCount; i++) {
1429                 CellLayout cl = (CellLayout) getChildAt(i);
1430                 cl.buildHardwareLayer();
1431             }
1432         }
1433         updateChildrenLayersEnabled(false);
1434     }
1435 
1436     protected void onWallpaperTap(MotionEvent ev) {
1437         final int[] position = mTempCell;
1438         getLocationOnScreen(position);
1439 
1440         int pointerIndex = ev.getActionIndex();
1441         position[0] += (int) ev.getX(pointerIndex);
1442         position[1] += (int) ev.getY(pointerIndex);
1443 
1444         mWallpaperManager.sendWallpaperCommand(getWindowToken(),
1445                 ev.getAction() == MotionEvent.ACTION_UP
1446                         ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
1447                 position[0], position[1], 0, null);
1448     }
1449 
1450     /*
1451      * This interpolator emulates the rate at which the perceived scale of an object changes
1452      * as its distance from a camera increases. When this interpolator is applied to a scale
1453      * animation on a view, it evokes the sense that the object is shrinking due to moving away
1454      * from the camera.
1455      */
1456     static class ZInterpolator implements TimeInterpolator {
1457         private float focalLength;
1458 
1459         public ZInterpolator(float foc) {
1460             focalLength = foc;
1461         }
1462 
1463         public float getInterpolation(float input) {
1464             return (1.0f - focalLength / (focalLength + input)) /
1465                 (1.0f - focalLength / (focalLength + 1.0f));
1466         }
1467     }
1468 
1469     /*
1470      * The exact reverse of ZInterpolator.
1471      */
1472     static class InverseZInterpolator implements TimeInterpolator {
1473         private ZInterpolator zInterpolator;
1474         public InverseZInterpolator(float foc) {
1475             zInterpolator = new ZInterpolator(foc);
1476         }
1477         public float getInterpolation(float input) {
1478             return 1 - zInterpolator.getInterpolation(1 - input);
1479         }
1480     }
1481 
1482     /*
1483      * ZInterpolator compounded with an ease-out.
1484      */
1485     static class ZoomOutInterpolator implements TimeInterpolator {
1486         private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75f);
1487         private final ZInterpolator zInterpolator = new ZInterpolator(0.13f);
1488 
1489         public float getInterpolation(float input) {
1490             return decelerate.getInterpolation(zInterpolator.getInterpolation(input));
1491         }
1492     }
1493 
1494     /*
1495      * InvereZInterpolator compounded with an ease-out.
1496      */
1497     static class ZoomInInterpolator implements TimeInterpolator {
1498         private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
1499         private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
1500 
1501         public float getInterpolation(float input) {
1502             return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
1503         }
1504     }
1505 
1506     private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
1507 
1508     /*
1509     *
1510     * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we
1511     * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
1512     *
1513     * These methods mark the appropriate pages as accepting drops (which alters their visual
1514     * appearance).
1515     *
1516     */
1517     public void onDragStartedWithItem(View v) {
1518         final Canvas canvas = new Canvas();
1519 
1520         // The outline is used to visualize where the item will land if dropped
1521         mDragOutline = createDragOutline(v, canvas, DRAG_BITMAP_PADDING);
1522     }
1523 
1524     public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
1525         final Canvas canvas = new Canvas();
1526 
1527         int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
1528 
1529         // The outline is used to visualize where the item will land if dropped
1530         mDragOutline = createDragOutline(b, canvas, DRAG_BITMAP_PADDING, size[0],
1531                 size[1], clipAlpha);
1532     }
1533 
1534     public void exitWidgetResizeMode() {
1535         DragLayer dragLayer = mLauncher.getDragLayer();
1536         dragLayer.clearAllResizeFrames();
1537     }
1538 
1539     private void initAnimationArrays() {
1540         final int childCount = getChildCount();
1541         if (mOldTranslationXs != null) return;
1542         mOldTranslationXs = new float[childCount];
1543         mOldTranslationYs = new float[childCount];
1544         mOldScaleXs = new float[childCount];
1545         mOldScaleYs = new float[childCount];
1546         mOldBackgroundAlphas = new float[childCount];
1547         mOldAlphas = new float[childCount];
1548         mNewTranslationXs = new float[childCount];
1549         mNewTranslationYs = new float[childCount];
1550         mNewScaleXs = new float[childCount];
1551         mNewScaleYs = new float[childCount];
1552         mNewBackgroundAlphas = new float[childCount];
1553         mNewAlphas = new float[childCount];
1554         mNewRotationYs = new float[childCount];
1555     }
1556 
1557     Animator getChangeStateAnimation(final State state, boolean animated) {
1558         return getChangeStateAnimation(state, animated, 0);
1559     }
1560 
1561     Animator getChangeStateAnimation(final State state, boolean animated, int delay) {
1562         if (mState == state) {
1563             return null;
1564         }
1565 
1566         // Initialize animation arrays for the first time if necessary
1567         initAnimationArrays();
1568 
1569         AnimatorSet anim = animated ? LauncherAnimUtils.createAnimatorSet() : null;
1570 
1571         // Stop any scrolling, move to the current page right away
1572         setCurrentPage(getNextPage());
1573 
1574         final State oldState = mState;
1575         final boolean oldStateIsNormal = (oldState == State.NORMAL);
1576         final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED);
1577         final boolean oldStateIsSmall = (oldState == State.SMALL);
1578         mState = state;
1579         final boolean stateIsNormal = (state == State.NORMAL);
1580         final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED);
1581         final boolean stateIsSmall = (state == State.SMALL);
1582         float finalScaleFactor = 1.0f;
1583         float finalBackgroundAlpha = stateIsSpringLoaded ? 1.0f : 0f;
1584         float translationX = 0;
1585         float translationY = 0;
1586         boolean zoomIn = true;
1587 
1588         if (state != State.NORMAL) {
1589             finalScaleFactor = mSpringLoadedShrinkFactor - (stateIsSmall ? 0.1f : 0);
1590             setPageSpacing(mSpringLoadedPageSpacing);
1591             if (oldStateIsNormal && stateIsSmall) {
1592                 zoomIn = false;
1593                 setLayoutScale(finalScaleFactor);
1594                 updateChildrenLayersEnabled(false);
1595             } else {
1596                 finalBackgroundAlpha = 1.0f;
1597                 setLayoutScale(finalScaleFactor);
1598             }
1599         } else {
1600             setPageSpacing(mOriginalPageSpacing);
1601             setLayoutScale(1.0f);
1602         }
1603 
1604         final int duration = zoomIn ?
1605                 getResources().getInteger(R.integer.config_workspaceUnshrinkTime) :
1606                 getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
1607         for (int i = 0; i < getChildCount(); i++) {
1608             final CellLayout cl = (CellLayout) getChildAt(i);
1609             float finalAlpha = (!mWorkspaceFadeInAdjacentScreens || stateIsSpringLoaded ||
1610                     (i == mCurrentPage)) ? 1f : 0f;
1611             float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
1612             float initialAlpha = currentAlpha;
1613 
1614             // Determine the pages alpha during the state transition
1615             if ((oldStateIsSmall && stateIsNormal) ||
1616                 (oldStateIsNormal && stateIsSmall)) {
1617                 // To/from workspace - only show the current page unless the transition is not
1618                 //                     animated and the animation end callback below doesn't run;
1619                 //                     or, if we're in spring-loaded mode
1620                 if (i == mCurrentPage || !animated || oldStateIsSpringLoaded) {
1621                     finalAlpha = 1f;
1622                 } else {
1623                     initialAlpha = 0f;
1624                     finalAlpha = 0f;
1625                 }
1626             }
1627 
1628             mOldAlphas[i] = initialAlpha;
1629             mNewAlphas[i] = finalAlpha;
1630             if (animated) {
1631                 mOldTranslationXs[i] = cl.getTranslationX();
1632                 mOldTranslationYs[i] = cl.getTranslationY();
1633                 mOldScaleXs[i] = cl.getScaleX();
1634                 mOldScaleYs[i] = cl.getScaleY();
1635                 mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
1636 
1637                 mNewTranslationXs[i] = translationX;
1638                 mNewTranslationYs[i] = translationY;
1639                 mNewScaleXs[i] = finalScaleFactor;
1640                 mNewScaleYs[i] = finalScaleFactor;
1641                 mNewBackgroundAlphas[i] = finalBackgroundAlpha;
1642             } else {
1643                 cl.setTranslationX(translationX);
1644                 cl.setTranslationY(translationY);
1645                 cl.setScaleX(finalScaleFactor);
1646                 cl.setScaleY(finalScaleFactor);
1647                 cl.setBackgroundAlpha(finalBackgroundAlpha);
1648                 cl.setShortcutAndWidgetAlpha(finalAlpha);
1649             }
1650         }
1651 
1652         if (animated) {
1653             for (int index = 0; index < getChildCount(); index++) {
1654                 final int i = index;
1655                 final CellLayout cl = (CellLayout) getChildAt(i);
1656                 float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
1657                 if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) {
1658                     cl.setTranslationX(mNewTranslationXs[i]);
1659                     cl.setTranslationY(mNewTranslationYs[i]);
1660                     cl.setScaleX(mNewScaleXs[i]);
1661                     cl.setScaleY(mNewScaleYs[i]);
1662                     cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
1663                     cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
1664                     cl.setRotationY(mNewRotationYs[i]);
1665                 } else {
1666                     LauncherViewPropertyAnimator a = new LauncherViewPropertyAnimator(cl);
1667                     a.translationX(mNewTranslationXs[i])
1668                         .translationY(mNewTranslationYs[i])
1669                         .scaleX(mNewScaleXs[i])
1670                         .scaleY(mNewScaleYs[i])
1671                         .setDuration(duration)
1672                         .setInterpolator(mZoomInInterpolator);
1673                     anim.play(a);
1674 
1675                     if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
1676                         LauncherViewPropertyAnimator alphaAnim =
1677                             new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
1678                         alphaAnim.alpha(mNewAlphas[i])
1679                             .setDuration(duration)
1680                             .setInterpolator(mZoomInInterpolator);
1681                         anim.play(alphaAnim);
1682                     }
1683                     if (mOldBackgroundAlphas[i] != 0 ||
1684                         mNewBackgroundAlphas[i] != 0) {
1685                         ValueAnimator bgAnim = LauncherAnimUtils.ofFloat(0f, 1f).setDuration(duration);
1686                         bgAnim.setInterpolator(mZoomInInterpolator);
1687                         bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
1688                                 public void onAnimationUpdate(float a, float b) {
1689                                     cl.setBackgroundAlpha(
1690                                             a * mOldBackgroundAlphas[i] +
1691                                             b * mNewBackgroundAlphas[i]);
1692                                 }
1693                             });
1694                         anim.play(bgAnim);
1695                     }
1696                 }
1697             }
1698             buildPageHardwareLayers();
1699             anim.setStartDelay(delay);
1700         }
1701 
1702         if (stateIsSpringLoaded) {
1703             // Right now we're covered by Apps Customize
1704             // Show the background gradient immediately, so the gradient will
1705             // be showing once AppsCustomize disappears
1706             animateBackgroundGradient(getResources().getInteger(
1707                     R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, false);
1708         } else {
1709             // Fade the background gradient away
1710             animateBackgroundGradient(0f, true);
1711         }
1712         return anim;
1713     }
1714 
1715     @Override
1716     public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
1717         mIsSwitchingState = true;
1718         cancelScrollingIndicatorAnimations();
1719     }
1720 
1721     @Override
1722     public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
1723     }
1724 
1725     @Override
1726     public void onLauncherTransitionStep(Launcher l, float t) {
1727         mTransitionProgress = t;
1728     }
1729 
1730     @Override
1731     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
1732         mIsSwitchingState = false;
1733         mWallpaperOffset.setOverrideHorizontalCatchupConstant(false);
1734         updateChildrenLayersEnabled(false);
1735         // The code in getChangeStateAnimation to determine initialAlpha and finalAlpha will ensure
1736         // ensure that only the current page is visible during (and subsequently, after) the
1737         // transition animation.  If fade adjacent pages is disabled, then re-enable the page
1738         // visibility after the transition animation.
1739         if (!mWorkspaceFadeInAdjacentScreens) {
1740             for (int i = 0; i < getChildCount(); i++) {
1741                 final CellLayout cl = (CellLayout) getChildAt(i);
1742                 cl.setShortcutAndWidgetAlpha(1f);
1743             }
1744         }
1745     }
1746 
1747     @Override
1748     public View getContent() {
1749         return this;
1750     }
1751 
1752     /**
1753      * Draw the View v into the given Canvas.
1754      *
1755      * @param v the view to draw
1756      * @param destCanvas the canvas to draw on
1757      * @param padding the horizontal and vertical padding to use when drawing
1758      */
1759     private void drawDragView(View v, Canvas destCanvas, int padding, boolean pruneToDrawable) {
1760         final Rect clipRect = mTempRect;
1761         v.getDrawingRect(clipRect);
1762 
1763         boolean textVisible = false;
1764 
1765         destCanvas.save();
1766         if (v instanceof TextView && pruneToDrawable) {
1767             Drawable d = ((TextView) v).getCompoundDrawables()[1];
1768             clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding);
1769             destCanvas.translate(padding / 2, padding / 2);
1770             d.draw(destCanvas);
1771         } else {
1772             if (v instanceof FolderIcon) {
1773                 // For FolderIcons the text can bleed into the icon area, and so we need to
1774                 // hide the text completely (which can't be achieved by clipping).
1775                 if (((FolderIcon) v).getTextVisible()) {
1776                     ((FolderIcon) v).setTextVisible(false);
1777                     textVisible = true;
1778                 }
1779             } else if (v instanceof BubbleTextView) {
1780                 final BubbleTextView tv = (BubbleTextView) v;
1781                 clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V +
1782                         tv.getLayout().getLineTop(0);
1783             } else if (v instanceof TextView) {
1784                 final TextView tv = (TextView) v;
1785                 clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() +
1786                         tv.getLayout().getLineTop(0);
1787             }
1788             destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
1789             destCanvas.clipRect(clipRect, Op.REPLACE);
1790             v.draw(destCanvas);
1791 
1792             // Restore text visibility of FolderIcon if necessary
1793             if (textVisible) {
1794                 ((FolderIcon) v).setTextVisible(true);
1795             }
1796         }
1797         destCanvas.restore();
1798     }
1799 
1800     /**
1801      * Returns a new bitmap to show when the given View is being dragged around.
1802      * Responsibility for the bitmap is transferred to the caller.
1803      */
1804     public Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
1805         Bitmap b;
1806 
1807         if (v instanceof TextView) {
1808             Drawable d = ((TextView) v).getCompoundDrawables()[1];
1809             b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding,
1810                     d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888);
1811         } else {
1812             b = Bitmap.createBitmap(
1813                     v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
1814         }
1815 
1816         canvas.setBitmap(b);
1817         drawDragView(v, canvas, padding, true);
1818         canvas.setBitmap(null);
1819 
1820         return b;
1821     }
1822 
1823     /**
1824      * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
1825      * Responsibility for the bitmap is transferred to the caller.
1826      */
1827     private Bitmap createDragOutline(View v, Canvas canvas, int padding) {
1828         final int outlineColor = getResources().getColor(android.R.color.holo_blue_light);
1829         final Bitmap b = Bitmap.createBitmap(
1830                 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
1831 
1832         canvas.setBitmap(b);
1833         drawDragView(v, canvas, padding, true);
1834         mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
1835         canvas.setBitmap(null);
1836         return b;
1837     }
1838 
1839     /**
1840      * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
1841      * Responsibility for the bitmap is transferred to the caller.
1842      */
1843     private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding, int w, int h,
1844             boolean clipAlpha) {
1845         final int outlineColor = getResources().getColor(android.R.color.holo_blue_light);
1846         final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
1847         canvas.setBitmap(b);
1848 
1849         Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
1850         float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(),
1851                 (h - padding) / (float) orig.getHeight());
1852         int scaledWidth = (int) (scaleFactor * orig.getWidth());
1853         int scaledHeight = (int) (scaleFactor * orig.getHeight());
1854         Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
1855 
1856         // center the image
1857         dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
1858 
1859         canvas.drawBitmap(orig, src, dst, null);
1860         mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor,
1861                 clipAlpha);
1862         canvas.setBitmap(null);
1863 
1864         return b;
1865     }
1866 
1867     void startDrag(CellLayout.CellInfo cellInfo) {
1868         View child = cellInfo.cell;
1869 
1870         // Make sure the drag was started by a long press as opposed to a long click.
1871         if (!child.isInTouchMode()) {
1872             return;
1873         }
1874 
1875         mDragInfo = cellInfo;
1876         child.setVisibility(INVISIBLE);
1877         CellLayout layout = (CellLayout) child.getParent().getParent();
1878         layout.prepareChildForDrag(child);
1879 
1880         child.clearFocus();
1881         child.setPressed(false);
1882 
1883         final Canvas canvas = new Canvas();
1884 
1885         // The outline is used to visualize where the item will land if dropped
1886         mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING);
1887         beginDragShared(child, this);
1888     }
1889 
1890     public void beginDragShared(View child, DragSource source) {
1891         Resources r = getResources();
1892 
1893         // The drag bitmap follows the touch point around on the screen
1894         final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING);
1895 
1896         final int bmpWidth = b.getWidth();
1897         final int bmpHeight = b.getHeight();
1898 
1899         float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
1900         int dragLayerX =
1901                 Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
1902         int dragLayerY =
1903                 Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2
1904                         - DRAG_BITMAP_PADDING / 2);
1905 
1906         Point dragVisualizeOffset = null;
1907         Rect dragRect = null;
1908         if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
1909             int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size);
1910             int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top);
1911             int top = child.getPaddingTop();
1912             int left = (bmpWidth - iconSize) / 2;
1913             int right = left + iconSize;
1914             int bottom = top + iconSize;
1915             dragLayerY += top;
1916             // Note: The drag region is used to calculate drag layer offsets, but the
1917             // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
1918             dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2,
1919                     iconPaddingTop - DRAG_BITMAP_PADDING / 2);
1920             dragRect = new Rect(left, top, right, bottom);
1921         } else if (child instanceof FolderIcon) {
1922             int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size);
1923             dragRect = new Rect(0, 0, child.getWidth(), previewSize);
1924         }
1925 
1926         // Clear the pressed state if necessary
1927         if (child instanceof BubbleTextView) {
1928             BubbleTextView icon = (BubbleTextView) child;
1929             icon.clearPressedOrFocusedBackground();
1930         }
1931 
1932         mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
1933                 DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
1934         b.recycle();
1935 
1936         // Show the scrolling indicator when you pick up an item
1937         showScrollingIndicator(false);
1938     }
1939 
1940     void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, int screen,
1941             int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
1942         View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
1943 
1944         final int[] cellXY = new int[2];
1945         target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
1946         addInScreen(view, container, screen, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
1947         LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen, cellXY[0],
1948                 cellXY[1]);
1949     }
1950 
1951     public boolean transitionStateShouldAllowDrop() {
1952         return ((!isSwitchingState() || mTransitionProgress > 0.5f) && mState != State.SMALL);
1953     }
1954 
1955     /**
1956      * {@inheritDoc}
1957      */
1958     public boolean acceptDrop(DragObject d) {
1959         // If it's an external drop (e.g. from All Apps), check if it should be accepted
1960         CellLayout dropTargetLayout = mDropToLayout;
1961         if (d.dragSource != this) {
1962             // Don't accept the drop if we're not over a screen at time of drop
1963             if (dropTargetLayout == null) {
1964                 return false;
1965             }
1966             if (!transitionStateShouldAllowDrop()) return false;
1967 
1968             mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
1969                     d.dragView, mDragViewVisualCenter);
1970 
1971             // We want the point to be mapped to the dragTarget.
1972             if (mLauncher.isHotseatLayout(dropTargetLayout)) {
1973                 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
1974             } else {
1975                 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
1976             }
1977 
1978             int spanX = 1;
1979             int spanY = 1;
1980             if (mDragInfo != null) {
1981                 final CellLayout.CellInfo dragCellInfo = mDragInfo;
1982                 spanX = dragCellInfo.spanX;
1983                 spanY = dragCellInfo.spanY;
1984             } else {
1985                 final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
1986                 spanX = dragInfo.spanX;
1987                 spanY = dragInfo.spanY;
1988             }
1989 
1990             int minSpanX = spanX;
1991             int minSpanY = spanY;
1992             if (d.dragInfo instanceof PendingAddWidgetInfo) {
1993                 minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
1994                 minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
1995             }
1996 
1997             mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
1998                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
1999                     mTargetCell);
2000             float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2001                     mDragViewVisualCenter[1], mTargetCell);
2002             if (willCreateUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
2003                     mTargetCell, distance, true)) {
2004                 return true;
2005             }
2006             if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
2007                     mTargetCell, distance)) {
2008                 return true;
2009             }
2010 
2011             int[] resultSpan = new int[2];
2012             mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
2013                     (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2014                     null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
2015             boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2016 
2017             // Don't accept the drop if there's no room for the item
2018             if (!foundCell) {
2019                 // Don't show the message if we are dropping on the AllApps button and the hotseat
2020                 // is full
2021                 boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2022                 if (mTargetCell != null && isHotseat) {
2023                     Hotseat hotseat = mLauncher.getHotseat();
2024                     if (hotseat.isAllAppsButtonRank(
2025                             hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
2026                         return false;
2027                     }
2028                 }
2029 
2030                 mLauncher.showOutOfSpaceMessage(isHotseat);
2031                 return false;
2032             }
2033         }
2034         return true;
2035     }
2036 
2037     boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float
2038             distance, boolean considerTimeout) {
2039         if (distance > mMaxDistanceForFolderCreation) return false;
2040         View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2041 
2042         if (dropOverView != null) {
2043             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2044             if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2045                 return false;
2046             }
2047         }
2048 
2049         boolean hasntMoved = false;
2050         if (mDragInfo != null) {
2051             hasntMoved = dropOverView == mDragInfo.cell;
2052         }
2053 
2054         if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
2055             return false;
2056         }
2057 
2058         boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
2059         boolean willBecomeShortcut =
2060                 (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
2061                 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
2062 
2063         return (aboveShortcut && willBecomeShortcut);
2064     }
2065 
2066     boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
2067             float distance) {
2068         if (distance > mMaxDistanceForFolderCreation) return false;
2069         View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2070 
2071         if (dropOverView != null) {
2072             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2073             if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2074                 return false;
2075             }
2076         }
2077 
2078         if (dropOverView instanceof FolderIcon) {
2079             FolderIcon fi = (FolderIcon) dropOverView;
2080             if (fi.acceptDrop(dragInfo)) {
2081                 return true;
2082             }
2083         }
2084         return false;
2085     }
2086 
2087     boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
2088             int[] targetCell, float distance, boolean external, DragView dragView,
2089             Runnable postAnimationRunnable) {
2090         if (distance > mMaxDistanceForFolderCreation) return false;
2091         View v = target.getChildAt(targetCell[0], targetCell[1]);
2092 
2093         boolean hasntMoved = false;
2094         if (mDragInfo != null) {
2095             CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
2096             hasntMoved = (mDragInfo.cellX == targetCell[0] &&
2097                     mDragInfo.cellY == targetCell[1]) && (cellParent == target);
2098         }
2099 
2100         if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
2101         mCreateUserFolderOnDrop = false;
2102         final int screen = (targetCell == null) ? mDragInfo.screen : indexOfChild(target);
2103 
2104         boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
2105         boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
2106 
2107         if (aboveShortcut && willBecomeShortcut) {
2108             ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
2109             ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
2110             // if the drag started here, we need to remove it from the workspace
2111             if (!external) {
2112                 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2113             }
2114 
2115             Rect folderLocation = new Rect();
2116             float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
2117             target.removeView(v);
2118 
2119             FolderIcon fi =
2120                 mLauncher.addFolder(target, container, screen, targetCell[0], targetCell[1]);
2121             destInfo.cellX = -1;
2122             destInfo.cellY = -1;
2123             sourceInfo.cellX = -1;
2124             sourceInfo.cellY = -1;
2125 
2126             // If the dragView is null, we can't animate
2127             boolean animate = dragView != null;
2128             if (animate) {
2129                 fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
2130                         postAnimationRunnable);
2131             } else {
2132                 fi.addItem(destInfo);
2133                 fi.addItem(sourceInfo);
2134             }
2135             return true;
2136         }
2137         return false;
2138     }
2139 
2140     boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
2141             float distance, DragObject d, boolean external) {
2142         if (distance > mMaxDistanceForFolderCreation) return false;
2143 
2144         View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2145         if (!mAddToExistingFolderOnDrop) return false;
2146         mAddToExistingFolderOnDrop = false;
2147 
2148         if (dropOverView instanceof FolderIcon) {
2149             FolderIcon fi = (FolderIcon) dropOverView;
2150             if (fi.acceptDrop(d.dragInfo)) {
2151                 fi.onDrop(d);
2152 
2153                 // if the drag started here, we need to remove it from the workspace
2154                 if (!external) {
2155                     getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2156                 }
2157                 return true;
2158             }
2159         }
2160         return false;
2161     }
2162 
2163     public void onDrop(final DragObject d) {
2164         mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
2165                 mDragViewVisualCenter);
2166 
2167         CellLayout dropTargetLayout = mDropToLayout;
2168 
2169         // We want the point to be mapped to the dragTarget.
2170         if (dropTargetLayout != null) {
2171             if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2172                 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2173             } else {
2174                 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2175             }
2176         }
2177 
2178         int snapScreen = -1;
2179         boolean resizeOnDrop = false;
2180         if (d.dragSource != this) {
2181             final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
2182                     (int) mDragViewVisualCenter[1] };
2183             onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
2184         } else if (mDragInfo != null) {
2185             final View cell = mDragInfo.cell;
2186 
2187             Runnable resizeRunnable = null;
2188             if (dropTargetLayout != null) {
2189                 // Move internally
2190                 boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
2191                 boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2192                 long container = hasMovedIntoHotseat ?
2193                         LauncherSettings.Favorites.CONTAINER_HOTSEAT :
2194                         LauncherSettings.Favorites.CONTAINER_DESKTOP;
2195                 int screen = (mTargetCell[0] < 0) ?
2196                         mDragInfo.screen : indexOfChild(dropTargetLayout);
2197                 int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
2198                 int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
2199                 // First we find the cell nearest to point at which the item is
2200                 // dropped, without any consideration to whether there is an item there.
2201 
2202                 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
2203                         mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
2204                 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2205                         mDragViewVisualCenter[1], mTargetCell);
2206 
2207                 // If the item being dropped is a shortcut and the nearest drop
2208                 // cell also contains a shortcut, then create a folder with the two shortcuts.
2209                 if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
2210                         dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
2211                     return;
2212                 }
2213 
2214                 if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
2215                         distance, d, false)) {
2216                     return;
2217                 }
2218 
2219                 // Aside from the special case where we're dropping a shortcut onto a shortcut,
2220                 // we need to find the nearest cell location that is vacant
2221                 ItemInfo item = (ItemInfo) d.dragInfo;
2222                 int minSpanX = item.spanX;
2223                 int minSpanY = item.spanY;
2224                 if (item.minSpanX > 0 && item.minSpanY > 0) {
2225                     minSpanX = item.minSpanX;
2226                     minSpanY = item.minSpanY;
2227                 }
2228 
2229                 int[] resultSpan = new int[2];
2230                 mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
2231                         (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
2232                         mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
2233 
2234                 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2235 
2236                 // if the widget resizes on drop
2237                 if (foundCell && (cell instanceof AppWidgetHostView) &&
2238                         (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
2239                     resizeOnDrop = true;
2240                     item.spanX = resultSpan[0];
2241                     item.spanY = resultSpan[1];
2242                     AppWidgetHostView awhv = (AppWidgetHostView) cell;
2243                     AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
2244                             resultSpan[1]);
2245                 }
2246 
2247                 if (mCurrentPage != screen && !hasMovedIntoHotseat) {
2248                     snapScreen = screen;
2249                     snapToPage(screen);
2250                 }
2251 
2252                 if (foundCell) {
2253                     final ItemInfo info = (ItemInfo) cell.getTag();
2254                     if (hasMovedLayouts) {
2255                         // Reparent the view
2256                         getParentCellLayoutForView(cell).removeView(cell);
2257                         addInScreen(cell, container, screen, mTargetCell[0], mTargetCell[1],
2258                                 info.spanX, info.spanY);
2259                     }
2260 
2261                     // update the item's position after drop
2262                     CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2263                     lp.cellX = lp.tmpCellX = mTargetCell[0];
2264                     lp.cellY = lp.tmpCellY = mTargetCell[1];
2265                     lp.cellHSpan = item.spanX;
2266                     lp.cellVSpan = item.spanY;
2267                     lp.isLockedToGrid = true;
2268                     cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screen,
2269                             mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
2270 
2271                     if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
2272                             cell instanceof LauncherAppWidgetHostView) {
2273                         final CellLayout cellLayout = dropTargetLayout;
2274                         // We post this call so that the widget has a chance to be placed
2275                         // in its final location
2276 
2277                         final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
2278                         AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
2279                         if (pinfo != null &&
2280                                 pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
2281                             final Runnable addResizeFrame = new Runnable() {
2282                                 public void run() {
2283                                     DragLayer dragLayer = mLauncher.getDragLayer();
2284                                     dragLayer.addResizeFrame(info, hostView, cellLayout);
2285                                 }
2286                             };
2287                             resizeRunnable = (new Runnable() {
2288                                 public void run() {
2289                                     if (!isPageMoving()) {
2290                                         addResizeFrame.run();
2291                                     } else {
2292                                         mDelayedResizeRunnable = addResizeFrame;
2293                                     }
2294                                 }
2295                             });
2296                         }
2297                     }
2298 
2299                     LauncherModel.moveItemInDatabase(mLauncher, info, container, screen, lp.cellX,
2300                             lp.cellY);
2301                 } else {
2302                     // If we can't find a drop location, we return the item to its original position
2303                     CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2304                     mTargetCell[0] = lp.cellX;
2305                     mTargetCell[1] = lp.cellY;
2306                     CellLayout layout = (CellLayout) cell.getParent().getParent();
2307                     layout.markCellsAsOccupiedForView(cell);
2308                 }
2309             }
2310 
2311             final CellLayout parent = (CellLayout) cell.getParent().getParent();
2312             final Runnable finalResizeRunnable = resizeRunnable;
2313             // Prepare it to be animated into its new position
2314             // This must be called after the view has been re-parented
2315             final Runnable onCompleteRunnable = new Runnable() {
2316                 @Override
2317                 public void run() {
2318                     mAnimatingViewIntoPlace = false;
2319                     updateChildrenLayersEnabled(false);
2320                     if (finalResizeRunnable != null) {
2321                         finalResizeRunnable.run();
2322                     }
2323                 }
2324             };
2325             mAnimatingViewIntoPlace = true;
2326             if (d.dragView.hasDrawn()) {
2327                 final ItemInfo info = (ItemInfo) cell.getTag();
2328                 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
2329                     int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
2330                             ANIMATE_INTO_POSITION_AND_DISAPPEAR;
2331                     animateWidgetDrop(info, parent, d.dragView,
2332                             onCompleteRunnable, animationType, cell, false);
2333                 } else {
2334                     int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
2335                     mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
2336                             onCompleteRunnable, this);
2337                 }
2338             } else {
2339                 d.deferDragViewCleanupPostAnimation = false;
2340                 cell.setVisibility(VISIBLE);
2341             }
2342             parent.onDropChild(cell);
2343         }
2344     }
2345 
2346     public void setFinalScrollForPageChange(int screen) {
2347         if (screen >= 0) {
2348             mSavedScrollX = getScrollX();
2349             CellLayout cl = (CellLayout) getChildAt(screen);
2350             mSavedTranslationX = cl.getTranslationX();
2351             mSavedRotationY = cl.getRotationY();
2352             final int newX = getChildOffset(screen) - getRelativeChildOffset(screen);
2353             setScrollX(newX);
2354             cl.setTranslationX(0f);
2355             cl.setRotationY(0f);
2356         }
2357     }
2358 
2359     public void resetFinalScrollForPageChange(int screen) {
2360         if (screen >= 0) {
2361             CellLayout cl = (CellLayout) getChildAt(screen);
2362             setScrollX(mSavedScrollX);
2363             cl.setTranslationX(mSavedTranslationX);
2364             cl.setRotationY(mSavedRotationY);
2365         }
2366     }
2367 
2368     public void getViewLocationRelativeToSelf(View v, int[] location) {
2369         getLocationInWindow(location);
2370         int x = location[0];
2371         int y = location[1];
2372 
2373         v.getLocationInWindow(location);
2374         int vX = location[0];
2375         int vY = location[1];
2376 
2377         location[0] = vX - x;
2378         location[1] = vY - y;
2379     }
2380 
2381     public void onDragEnter(DragObject d) {
2382         mDragEnforcer.onDragEnter();
2383         mCreateUserFolderOnDrop = false;
2384         mAddToExistingFolderOnDrop = false;
2385 
2386         mDropToLayout = null;
2387         CellLayout layout = getCurrentDropLayout();
2388         setCurrentDropLayout(layout);
2389         setCurrentDragOverlappingLayout(layout);
2390 
2391         // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we
2392         // don't need to show the outlines
2393         if (LauncherApplication.isScreenLarge()) {
2394             showOutlines();
2395         }
2396     }
2397 
2398     static Rect getCellLayoutMetrics(Launcher launcher, int orientation) {
2399         Resources res = launcher.getResources();
2400         Display display = launcher.getWindowManager().getDefaultDisplay();
2401         Point smallestSize = new Point();
2402         Point largestSize = new Point();
2403         display.getCurrentSizeRange(smallestSize, largestSize);
2404         if (orientation == CellLayout.LANDSCAPE) {
2405             if (mLandscapeCellLayoutMetrics == null) {
2406                 int paddingLeft = res.getDimensionPixelSize(R.dimen.workspace_left_padding_land);
2407                 int paddingRight = res.getDimensionPixelSize(R.dimen.workspace_right_padding_land);
2408                 int paddingTop = res.getDimensionPixelSize(R.dimen.workspace_top_padding_land);
2409                 int paddingBottom = res.getDimensionPixelSize(R.dimen.workspace_bottom_padding_land);
2410                 int width = largestSize.x - paddingLeft - paddingRight;
2411                 int height = smallestSize.y - paddingTop - paddingBottom;
2412                 mLandscapeCellLayoutMetrics = new Rect();
2413                 CellLayout.getMetrics(mLandscapeCellLayoutMetrics, res,
2414                         width, height, LauncherModel.getCellCountX(), LauncherModel.getCellCountY(),
2415                         orientation);
2416             }
2417             return mLandscapeCellLayoutMetrics;
2418         } else if (orientation == CellLayout.PORTRAIT) {
2419             if (mPortraitCellLayoutMetrics == null) {
2420                 int paddingLeft = res.getDimensionPixelSize(R.dimen.workspace_left_padding_land);
2421                 int paddingRight = res.getDimensionPixelSize(R.dimen.workspace_right_padding_land);
2422                 int paddingTop = res.getDimensionPixelSize(R.dimen.workspace_top_padding_land);
2423                 int paddingBottom = res.getDimensionPixelSize(R.dimen.workspace_bottom_padding_land);
2424                 int width = smallestSize.x - paddingLeft - paddingRight;
2425                 int height = largestSize.y - paddingTop - paddingBottom;
2426                 mPortraitCellLayoutMetrics = new Rect();
2427                 CellLayout.getMetrics(mPortraitCellLayoutMetrics, res,
2428                         width, height, LauncherModel.getCellCountX(), LauncherModel.getCellCountY(),
2429                         orientation);
2430             }
2431             return mPortraitCellLayoutMetrics;
2432         }
2433         return null;
2434     }
2435 
2436     public void onDragExit(DragObject d) {
2437         mDragEnforcer.onDragExit();
2438 
2439         // Here we store the final page that will be dropped to, if the workspace in fact
2440         // receives the drop
2441         if (mInScrollArea) {
2442             if (isPageMoving()) {
2443                 // If the user drops while the page is scrolling, we should use that page as the
2444                 // destination instead of the page that is being hovered over.
2445                 mDropToLayout = (CellLayout) getPageAt(getNextPage());
2446             } else {
2447                 mDropToLayout = mDragOverlappingLayout;
2448             }
2449         } else {
2450             mDropToLayout = mDragTargetLayout;
2451         }
2452 
2453         if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
2454             mCreateUserFolderOnDrop = true;
2455         } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
2456             mAddToExistingFolderOnDrop = true;
2457         }
2458 
2459         // Reset the scroll area and previous drag target
2460         onResetScrollArea();
2461         setCurrentDropLayout(null);
2462         setCurrentDragOverlappingLayout(null);
2463 
2464         mSpringLoadedDragController.cancel();
2465 
2466         if (!mIsPageMoving) {
2467             hideOutlines();
2468         }
2469     }
2470 
2471     void setCurrentDropLayout(CellLayout layout) {
2472         if (mDragTargetLayout != null) {
2473             mDragTargetLayout.revertTempState();
2474             mDragTargetLayout.onDragExit();
2475         }
2476         mDragTargetLayout = layout;
2477         if (mDragTargetLayout != null) {
2478             mDragTargetLayout.onDragEnter();
2479         }
2480         cleanupReorder(true);
2481         cleanupFolderCreation();
2482         setCurrentDropOverCell(-1, -1);
2483     }
2484 
2485     void setCurrentDragOverlappingLayout(CellLayout layout) {
2486         if (mDragOverlappingLayout != null) {
2487             mDragOverlappingLayout.setIsDragOverlapping(false);
2488         }
2489         mDragOverlappingLayout = layout;
2490         if (mDragOverlappingLayout != null) {
2491             mDragOverlappingLayout.setIsDragOverlapping(true);
2492         }
2493         invalidate();
2494     }
2495 
2496     void setCurrentDropOverCell(int x, int y) {
2497         if (x != mDragOverX || y != mDragOverY) {
2498             mDragOverX = x;
2499             mDragOverY = y;
2500             setDragMode(DRAG_MODE_NONE);
2501         }
2502     }
2503 
2504     void setDragMode(int dragMode) {
2505         if (dragMode != mDragMode) {
2506             if (dragMode == DRAG_MODE_NONE) {
2507                 cleanupAddToFolder();
2508                 // We don't want to cancel the re-order alarm every time the target cell changes
2509                 // as this feels to slow / unresponsive.
2510                 cleanupReorder(false);
2511                 cleanupFolderCreation();
2512             } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
2513                 cleanupReorder(true);
2514                 cleanupFolderCreation();
2515             } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
2516                 cleanupAddToFolder();
2517                 cleanupReorder(true);
2518             } else if (dragMode == DRAG_MODE_REORDER) {
2519                 cleanupAddToFolder();
2520                 cleanupFolderCreation();
2521             }
2522             mDragMode = dragMode;
2523         }
2524     }
2525 
2526     private void cleanupFolderCreation() {
2527         if (mDragFolderRingAnimator != null) {
2528             mDragFolderRingAnimator.animateToNaturalState();
2529         }
2530         mFolderCreationAlarm.cancelAlarm();
2531     }
2532 
2533     private void cleanupAddToFolder() {
2534         if (mDragOverFolderIcon != null) {
2535             mDragOverFolderIcon.onDragExit(null);
2536             mDragOverFolderIcon = null;
2537         }
2538     }
2539 
2540     private void cleanupReorder(boolean cancelAlarm) {
2541         // Any pending reorders are canceled
2542         if (cancelAlarm) {
2543             mReorderAlarm.cancelAlarm();
2544         }
2545         mLastReorderX = -1;
2546         mLastReorderY = -1;
2547     }
2548 
2549     public DropTarget getDropTargetDelegate(DragObject d) {
2550         return null;
2551     }
2552 
2553     /*
2554     *
2555     * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
2556     * coordinate space. The argument xy is modified with the return result.
2557     *
2558     */
2559    void mapPointFromSelfToChild(View v, float[] xy) {
2560        mapPointFromSelfToChild(v, xy, null);
2561    }
2562 
2563    /*
2564     *
2565     * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
2566     * coordinate space. The argument xy is modified with the return result.
2567     *
2568     * if cachedInverseMatrix is not null, this method will just use that matrix instead of
2569     * computing it itself; we use this to avoid redundant matrix inversions in
2570     * findMatchingPageForDragOver
2571     *
2572     */
2573    void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
2574        if (cachedInverseMatrix == null) {
2575            v.getMatrix().invert(mTempInverseMatrix);
2576            cachedInverseMatrix = mTempInverseMatrix;
2577        }
2578        int scrollX = getScrollX();
2579        if (mNextPage != INVALID_PAGE) {
2580            scrollX = mScroller.getFinalX();
2581        }
2582        xy[0] = xy[0] + scrollX - v.getLeft();
2583        xy[1] = xy[1] + getScrollY() - v.getTop();
2584        cachedInverseMatrix.mapPoints(xy);
2585    }
2586 
2587 
2588    void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
2589        hotseat.getLayout().getMatrix().invert(mTempInverseMatrix);
2590        xy[0] = xy[0] - hotseat.getLeft() - hotseat.getLayout().getLeft();
2591        xy[1] = xy[1] - hotseat.getTop() - hotseat.getLayout().getTop();
2592        mTempInverseMatrix.mapPoints(xy);
2593    }
2594 
2595    /*
2596     *
2597     * Convert the 2D coordinate xy from this CellLayout's coordinate space to
2598     * the parent View's coordinate space. The argument xy is modified with the return result.
2599     *
2600     */
2601    void mapPointFromChildToSelf(View v, float[] xy) {
2602        v.getMatrix().mapPoints(xy);
2603        int scrollX = getScrollX();
2604        if (mNextPage != INVALID_PAGE) {
2605            scrollX = mScroller.getFinalX();
2606        }
2607        xy[0] -= (scrollX - v.getLeft());
2608        xy[1] -= (getScrollY() - v.getTop());
2609    }
2610 
2611    static private float squaredDistance(float[] point1, float[] point2) {
2612         float distanceX = point1[0] - point2[0];
2613         float distanceY = point2[1] - point2[1];
2614         return distanceX * distanceX + distanceY * distanceY;
2615    }
2616 
2617     /*
2618      *
2619      * Returns true if the passed CellLayout cl overlaps with dragView
2620      *
2621      */
2622     boolean overlaps(CellLayout cl, DragView dragView,
2623             int dragViewX, int dragViewY, Matrix cachedInverseMatrix) {
2624         // Transform the coordinates of the item being dragged to the CellLayout's coordinates
2625         final float[] draggedItemTopLeft = mTempDragCoordinates;
2626         draggedItemTopLeft[0] = dragViewX;
2627         draggedItemTopLeft[1] = dragViewY;
2628         final float[] draggedItemBottomRight = mTempDragBottomRightCoordinates;
2629         draggedItemBottomRight[0] = draggedItemTopLeft[0] + dragView.getDragRegionWidth();
2630         draggedItemBottomRight[1] = draggedItemTopLeft[1] + dragView.getDragRegionHeight();
2631 
2632         // Transform the dragged item's top left coordinates
2633         // to the CellLayout's local coordinates
2634         mapPointFromSelfToChild(cl, draggedItemTopLeft, cachedInverseMatrix);
2635         float overlapRegionLeft = Math.max(0f, draggedItemTopLeft[0]);
2636         float overlapRegionTop = Math.max(0f, draggedItemTopLeft[1]);
2637 
2638         if (overlapRegionLeft <= cl.getWidth() && overlapRegionTop >= 0) {
2639             // Transform the dragged item's bottom right coordinates
2640             // to the CellLayout's local coordinates
2641             mapPointFromSelfToChild(cl, draggedItemBottomRight, cachedInverseMatrix);
2642             float overlapRegionRight = Math.min(cl.getWidth(), draggedItemBottomRight[0]);
2643             float overlapRegionBottom = Math.min(cl.getHeight(), draggedItemBottomRight[1]);
2644 
2645             if (overlapRegionRight >= 0 && overlapRegionBottom <= cl.getHeight()) {
2646                 float overlap = (overlapRegionRight - overlapRegionLeft) *
2647                          (overlapRegionBottom - overlapRegionTop);
2648                 if (overlap > 0) {
2649                     return true;
2650                 }
2651              }
2652         }
2653         return false;
2654     }
2655 
2656     /*
2657      *
2658      * This method returns the CellLayout that is currently being dragged to. In order to drag
2659      * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
2660      * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
2661      *
2662      * Return null if no CellLayout is currently being dragged over
2663      *
2664      */
2665     private CellLayout findMatchingPageForDragOver(
2666             DragView dragView, float originX, float originY, boolean exact) {
2667         // We loop through all the screens (ie CellLayouts) and see which ones overlap
2668         // with the item being dragged and then choose the one that's closest to the touch point
2669         final int screenCount = getChildCount();
2670         CellLayout bestMatchingScreen = null;
2671         float smallestDistSoFar = Float.MAX_VALUE;
2672 
2673         for (int i = 0; i < screenCount; i++) {
2674             CellLayout cl = (CellLayout) getChildAt(i);
2675 
2676             final float[] touchXy = {originX, originY};
2677             // Transform the touch coordinates to the CellLayout's local coordinates
2678             // If the touch point is within the bounds of the cell layout, we can return immediately
2679             cl.getMatrix().invert(mTempInverseMatrix);
2680             mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
2681 
2682             if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
2683                     touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
2684                 return cl;
2685             }
2686 
2687             if (!exact) {
2688                 // Get the center of the cell layout in screen coordinates
2689                 final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
2690                 cellLayoutCenter[0] = cl.getWidth()/2;
2691                 cellLayoutCenter[1] = cl.getHeight()/2;
2692                 mapPointFromChildToSelf(cl, cellLayoutCenter);
2693 
2694                 touchXy[0] = originX;
2695                 touchXy[1] = originY;
2696 
2697                 // Calculate the distance between the center of the CellLayout
2698                 // and the touch point
2699                 float dist = squaredDistance(touchXy, cellLayoutCenter);
2700 
2701                 if (dist < smallestDistSoFar) {
2702                     smallestDistSoFar = dist;
2703                     bestMatchingScreen = cl;
2704                 }
2705             }
2706         }
2707         return bestMatchingScreen;
2708     }
2709 
2710     // This is used to compute the visual center of the dragView. This point is then
2711     // used to visualize drop locations and determine where to drop an item. The idea is that
2712     // the visual center represents the user's interpretation of where the item is, and hence
2713     // is the appropriate point to use when determining drop location.
2714     private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
2715             DragView dragView, float[] recycle) {
2716         float res[];
2717         if (recycle == null) {
2718             res = new float[2];
2719         } else {
2720             res = recycle;
2721         }
2722 
2723         // First off, the drag view has been shifted in a way that is not represented in the
2724         // x and y values or the x/yOffsets. Here we account for that shift.
2725         x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX);
2726         y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
2727 
2728         // These represent the visual top and left of drag view if a dragRect was provided.
2729         // If a dragRect was not provided, then they correspond to the actual view left and
2730         // top, as the dragRect is in that case taken to be the entire dragView.
2731         // R.dimen.dragViewOffsetY.
2732         int left = x - xOffset;
2733         int top = y - yOffset;
2734 
2735         // In order to find the visual center, we shift by half the dragRect
2736         res[0] = left + dragView.getDragRegion().width() / 2;
2737         res[1] = top + dragView.getDragRegion().height() / 2;
2738 
2739         return res;
2740     }
2741 
2742     private boolean isDragWidget(DragObject d) {
2743         return (d.dragInfo instanceof LauncherAppWidgetInfo ||
2744                 d.dragInfo instanceof PendingAddWidgetInfo);
2745     }
2746     private boolean isExternalDragWidget(DragObject d) {
2747         return d.dragSource != this && isDragWidget(d);
2748     }
2749 
2750     public void onDragOver(DragObject d) {
2751         // Skip drag over events while we are dragging over side pages
2752         if (mInScrollArea || mIsSwitchingState || mState == State.SMALL) return;
2753 
2754         Rect r = new Rect();
2755         CellLayout layout = null;
2756         ItemInfo item = (ItemInfo) d.dragInfo;
2757 
2758         // Ensure that we have proper spans for the item that we are dropping
2759         if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
2760         mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
2761             d.dragView, mDragViewVisualCenter);
2762 
2763         final View child = (mDragInfo == null) ? null : mDragInfo.cell;
2764         // Identify whether we have dragged over a side page
2765         if (isSmall()) {
2766             if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
2767                 mLauncher.getHotseat().getHitRect(r);
2768                 if (r.contains(d.x, d.y)) {
2769                     layout = mLauncher.getHotseat().getLayout();
2770                 }
2771             }
2772             if (layout == null) {
2773                 layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false);
2774             }
2775             if (layout != mDragTargetLayout) {
2776 
2777                 setCurrentDropLayout(layout);
2778                 setCurrentDragOverlappingLayout(layout);
2779 
2780                 boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
2781                 if (isInSpringLoadedMode) {
2782                     if (mLauncher.isHotseatLayout(layout)) {
2783                         mSpringLoadedDragController.cancel();
2784                     } else {
2785                         mSpringLoadedDragController.setAlarm(mDragTargetLayout);
2786                     }
2787                 }
2788             }
2789         } else {
2790             // Test to see if we are over the hotseat otherwise just use the current page
2791             if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
2792                 mLauncher.getHotseat().getHitRect(r);
2793                 if (r.contains(d.x, d.y)) {
2794                     layout = mLauncher.getHotseat().getLayout();
2795                 }
2796             }
2797             if (layout == null) {
2798                 layout = getCurrentDropLayout();
2799             }
2800             if (layout != mDragTargetLayout) {
2801                 setCurrentDropLayout(layout);
2802                 setCurrentDragOverlappingLayout(layout);
2803             }
2804         }
2805 
2806         // Handle the drag over
2807         if (mDragTargetLayout != null) {
2808             // We want the point to be mapped to the dragTarget.
2809             if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
2810                 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2811             } else {
2812                 mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
2813             }
2814 
2815             ItemInfo info = (ItemInfo) d.dragInfo;
2816 
2817             mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2818                     (int) mDragViewVisualCenter[1], item.spanX, item.spanY,
2819                     mDragTargetLayout, mTargetCell);
2820 
2821             setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
2822 
2823             float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
2824                     mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
2825 
2826             final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0],
2827                     mTargetCell[1]);
2828 
2829             manageFolderFeedback(info, mDragTargetLayout, mTargetCell,
2830                     targetCellDistance, dragOverView);
2831 
2832             int minSpanX = item.spanX;
2833             int minSpanY = item.spanY;
2834             if (item.minSpanX > 0 && item.minSpanY > 0) {
2835                 minSpanX = item.minSpanX;
2836                 minSpanY = item.minSpanY;
2837             }
2838 
2839             boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
2840                     mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
2841                     item.spanY, child, mTargetCell);
2842 
2843             if (!nearestDropOccupied) {
2844                 mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
2845                         (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
2846                         mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
2847                         d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
2848             } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
2849                     && !mReorderAlarm.alarmPending() && (mLastReorderX != mTargetCell[0] ||
2850                     mLastReorderY != mTargetCell[1])) {
2851 
2852                 // Otherwise, if we aren't adding to or creating a folder and there's no pending
2853                 // reorder, then we schedule a reorder
2854                 ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
2855                         minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child);
2856                 mReorderAlarm.setOnAlarmListener(listener);
2857                 mReorderAlarm.setAlarm(REORDER_TIMEOUT);
2858             }
2859 
2860             if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
2861                     !nearestDropOccupied) {
2862                 if (mDragTargetLayout != null) {
2863                     mDragTargetLayout.revertTempState();
2864                 }
2865             }
2866         }
2867     }
2868 
2869     private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
2870             int[] targetCell, float distance, View dragOverView) {
2871         boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
2872                 false);
2873 
2874         if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
2875                 !mFolderCreationAlarm.alarmPending()) {
2876             mFolderCreationAlarm.setOnAlarmListener(new
2877                     FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
2878             mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
2879             return;
2880         }
2881 
2882         boolean willAddToFolder =
2883                 willAddToExistingUserFolder(info, targetLayout, targetCell, distance);
2884 
2885         if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
2886             mDragOverFolderIcon = ((FolderIcon) dragOverView);
2887             mDragOverFolderIcon.onDragEnter(info);
2888             if (targetLayout != null) {
2889                 targetLayout.clearDragOutlines();
2890             }
2891             setDragMode(DRAG_MODE_ADD_TO_FOLDER);
2892             return;
2893         }
2894 
2895         if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
2896             setDragMode(DRAG_MODE_NONE);
2897         }
2898         if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
2899             setDragMode(DRAG_MODE_NONE);
2900         }
2901 
2902         return;
2903     }
2904 
2905     class FolderCreationAlarmListener implements OnAlarmListener {
2906         CellLayout layout;
2907         int cellX;
2908         int cellY;
2909 
2910         public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
2911             this.layout = layout;
2912             this.cellX = cellX;
2913             this.cellY = cellY;
2914         }
2915 
2916         public void onAlarm(Alarm alarm) {
2917             if (mDragFolderRingAnimator == null) {
2918                 mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
2919             }
2920             mDragFolderRingAnimator.setCell(cellX, cellY);
2921             mDragFolderRingAnimator.setCellLayout(layout);
2922             mDragFolderRingAnimator.animateToAcceptState();
2923             layout.showFolderAccept(mDragFolderRingAnimator);
2924             layout.clearDragOutlines();
2925             setDragMode(DRAG_MODE_CREATE_FOLDER);
2926         }
2927     }
2928 
2929     class ReorderAlarmListener implements OnAlarmListener {
2930         float[] dragViewCenter;
2931         int minSpanX, minSpanY, spanX, spanY;
2932         DragView dragView;
2933         View child;
2934 
2935         public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
2936                 int spanY, DragView dragView, View child) {
2937             this.dragViewCenter = dragViewCenter;
2938             this.minSpanX = minSpanX;
2939             this.minSpanY = minSpanY;
2940             this.spanX = spanX;
2941             this.spanY = spanY;
2942             this.child = child;
2943             this.dragView = dragView;
2944         }
2945 
2946         public void onAlarm(Alarm alarm) {
2947             int[] resultSpan = new int[2];
2948             mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2949                     (int) mDragViewVisualCenter[1], spanX, spanY, mDragTargetLayout, mTargetCell);
2950             mLastReorderX = mTargetCell[0];
2951             mLastReorderY = mTargetCell[1];
2952 
2953             mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
2954                 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2955                 child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
2956 
2957             if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
2958                 mDragTargetLayout.revertTempState();
2959             } else {
2960                 setDragMode(DRAG_MODE_REORDER);
2961             }
2962 
2963             boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
2964             mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
2965                 (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
2966                 mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize,
2967                 dragView.getDragVisualizeOffset(), dragView.getDragRegion());
2968         }
2969     }
2970 
2971     @Override
2972     public void getHitRect(Rect outRect) {
2973         // We want the workspace to have the whole area of the display (it will find the correct
2974         // cell layout to drop to in the existing drag/drop logic.
2975         outRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
2976     }
2977 
2978     /**
2979      * Add the item specified by dragInfo to the given layout.
2980      * @return true if successful
2981      */
2982     public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
2983         if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
2984             onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
2985             return true;
2986         }
2987         mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
2988         return false;
2989     }
2990 
2991     private void onDropExternal(int[] touchXY, Object dragInfo,
2992             CellLayout cellLayout, boolean insertAtFirst) {
2993         onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
2994     }
2995 
2996     /**
2997      * Drop an item that didn't originate on one of the workspace screens.
2998      * It may have come from Launcher (e.g. from all apps or customize), or it may have
2999      * come from another app altogether.
3000      *
3001      * NOTE: This can also be called when we are outside of a drag event, when we want
3002      * to add an item to one of the workspace screens.
3003      */
3004     private void onDropExternal(final int[] touchXY, final Object dragInfo,
3005             final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
3006         final Runnable exitSpringLoadedRunnable = new Runnable() {
3007             @Override
3008             public void run() {
3009                 mLauncher.exitSpringLoadedDragModeDelayed(true, false, null);
3010             }
3011         };
3012 
3013         ItemInfo info = (ItemInfo) dragInfo;
3014         int spanX = info.spanX;
3015         int spanY = info.spanY;
3016         if (mDragInfo != null) {
3017             spanX = mDragInfo.spanX;
3018             spanY = mDragInfo.spanY;
3019         }
3020 
3021         final long container = mLauncher.isHotseatLayout(cellLayout) ?
3022                 LauncherSettings.Favorites.CONTAINER_HOTSEAT :
3023                     LauncherSettings.Favorites.CONTAINER_DESKTOP;
3024         final int screen = indexOfChild(cellLayout);
3025         if (!mLauncher.isHotseatLayout(cellLayout) && screen != mCurrentPage
3026                 && mState != State.SPRING_LOADED) {
3027             snapToPage(screen);
3028         }
3029 
3030         if (info instanceof PendingAddItemInfo) {
3031             final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
3032 
3033             boolean findNearestVacantCell = true;
3034             if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
3035                 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3036                         cellLayout, mTargetCell);
3037                 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3038                         mDragViewVisualCenter[1], mTargetCell);
3039                 if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell,
3040                         distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
3041                                 cellLayout, mTargetCell, distance)) {
3042                     findNearestVacantCell = false;
3043                 }
3044             }
3045 
3046             final ItemInfo item = (ItemInfo) d.dragInfo;
3047             boolean updateWidgetSize = false;
3048             if (findNearestVacantCell) {
3049                 int minSpanX = item.spanX;
3050                 int minSpanY = item.spanY;
3051                 if (item.minSpanX > 0 && item.minSpanY > 0) {
3052                     minSpanX = item.minSpanX;
3053                     minSpanY = item.minSpanY;
3054                 }
3055                 int[] resultSpan = new int[2];
3056                 mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
3057                         (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
3058                         null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
3059 
3060                 if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) {
3061                     updateWidgetSize = true;
3062                 }
3063                 item.spanX = resultSpan[0];
3064                 item.spanY = resultSpan[1];
3065             }
3066 
3067             Runnable onAnimationCompleteRunnable = new Runnable() {
3068                 @Override
3069                 public void run() {
3070                     // When dragging and dropping from customization tray, we deal with creating
3071                     // widgets/shortcuts/folders in a slightly different way
3072                     switch (pendingInfo.itemType) {
3073                     case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
3074                         int span[] = new int[2];
3075                         span[0] = item.spanX;
3076                         span[1] = item.spanY;
3077                         mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
3078                                 container, screen, mTargetCell, span, null);
3079                         break;
3080                     case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3081                         mLauncher.processShortcutFromDrop(pendingInfo.componentName,
3082                                 container, screen, mTargetCell, null);
3083                         break;
3084                     default:
3085                         throw new IllegalStateException("Unknown item type: " +
3086                                 pendingInfo.itemType);
3087                     }
3088                 }
3089             };
3090             View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
3091                     ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
3092 
3093             if (finalView instanceof AppWidgetHostView && updateWidgetSize) {
3094                 AppWidgetHostView awhv = (AppWidgetHostView) finalView;
3095                 AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, item.spanX,
3096                         item.spanY);
3097             }
3098 
3099             int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3100             if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
3101                     ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
3102                 animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
3103             }
3104             animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
3105                     animationStyle, finalView, true);
3106         } else {
3107             // This is for other drag/drop cases, like dragging from All Apps
3108             View view = null;
3109 
3110             switch (info.itemType) {
3111             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3112             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3113                 if (info.container == NO_ID && info instanceof ApplicationInfo) {
3114                     // Came from all apps -- make a copy
3115                     info = new ShortcutInfo((ApplicationInfo) info);
3116                 }
3117                 view = mLauncher.createShortcut(R.layout.application, cellLayout,
3118                         (ShortcutInfo) info);
3119                 break;
3120             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3121                 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
3122                         (FolderInfo) info, mIconCache);
3123                 break;
3124             default:
3125                 throw new IllegalStateException("Unknown item type: " + info.itemType);
3126             }
3127 
3128             // First we find the cell nearest to point at which the item is
3129             // dropped, without any consideration to whether there is an item there.
3130             if (touchXY != null) {
3131                 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3132                         cellLayout, mTargetCell);
3133                 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3134                         mDragViewVisualCenter[1], mTargetCell);
3135                 d.postAnimationRunnable = exitSpringLoadedRunnable;
3136                 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
3137                         true, d.dragView, d.postAnimationRunnable)) {
3138                     return;
3139                 }
3140                 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
3141                         true)) {
3142                     return;
3143                 }
3144             }
3145 
3146             if (touchXY != null) {
3147                 // when dragging and dropping, just find the closest free spot
3148                 mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
3149                         (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
3150                         null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
3151             } else {
3152                 cellLayout.findCellForSpan(mTargetCell, 1, 1);
3153             }
3154             addInScreen(view, container, screen, mTargetCell[0], mTargetCell[1], info.spanX,
3155                     info.spanY, insertAtFirst);
3156             cellLayout.onDropChild(view);
3157             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
3158             cellLayout.getShortcutsAndWidgets().measureChild(view);
3159 
3160 
3161             LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen,
3162                     lp.cellX, lp.cellY);
3163 
3164             if (d.dragView != null) {
3165                 // We wrap the animation call in the temporary set and reset of the current
3166                 // cellLayout to its final transform -- this means we animate the drag view to
3167                 // the correct final location.
3168                 setFinalTransitionTransform(cellLayout);
3169                 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
3170                         exitSpringLoadedRunnable);
3171                 resetTransitionTransform(cellLayout);
3172             }
3173         }
3174     }
3175 
3176     public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
3177         int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
3178                 widgetInfo.spanY, widgetInfo, false);
3179         int visibility = layout.getVisibility();
3180         layout.setVisibility(VISIBLE);
3181 
3182         int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
3183         int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
3184         Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
3185                 Bitmap.Config.ARGB_8888);
3186         Canvas c = new Canvas(b);
3187 
3188         layout.measure(width, height);
3189         layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
3190         layout.draw(c);
3191         c.setBitmap(null);
3192         layout.setVisibility(visibility);
3193         return b;
3194     }
3195 
3196     private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
3197             DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell,
3198             boolean external, boolean scale) {
3199         // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
3200         // location and size on the home screen.
3201         int spanX = info.spanX;
3202         int spanY = info.spanY;
3203 
3204         Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
3205         loc[0] = r.left;
3206         loc[1] = r.top;
3207 
3208         setFinalTransitionTransform(layout);
3209         float cellLayoutScale =
3210                 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc);
3211         resetTransitionTransform(layout);
3212 
3213         float dragViewScaleX;
3214         float dragViewScaleY;
3215         if (scale) {
3216             dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
3217             dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
3218         } else {
3219             dragViewScaleX = 1f;
3220             dragViewScaleY = 1f;
3221         }
3222 
3223         // The animation will scale the dragView about its center, so we need to center about
3224         // the final location.
3225         loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
3226         loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
3227 
3228         scaleXY[0] = dragViewScaleX * cellLayoutScale;
3229         scaleXY[1] = dragViewScaleY * cellLayoutScale;
3230     }
3231 
3232     public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
3233             final Runnable onCompleteRunnable, int animationType, final View finalView,
3234             boolean external) {
3235         Rect from = new Rect();
3236         mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
3237 
3238         int[] finalPos = new int[2];
3239         float scaleXY[] = new float[2];
3240         boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
3241         getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
3242                 external, scalePreview);
3243 
3244         Resources res = mLauncher.getResources();
3245         int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
3246 
3247         // In the case where we've prebound the widget, we remove it from the DragLayer
3248         if (finalView instanceof AppWidgetHostView && external) {
3249             Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView");
3250             mLauncher.getDragLayer().removeView(finalView);
3251         }
3252         if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
3253             Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
3254             dragView.setCrossFadeBitmap(crossFadeBitmap);
3255             dragView.crossFade((int) (duration * 0.8f));
3256         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) {
3257             scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0],  scaleXY[1]);
3258         }
3259 
3260         DragLayer dragLayer = mLauncher.getDragLayer();
3261         if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
3262             mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
3263                     DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
3264         } else {
3265             int endStyle;
3266             if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
3267                 endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
3268             } else {
3269                 endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
3270             }
3271 
3272             Runnable onComplete = new Runnable() {
3273                 @Override
3274                 public void run() {
3275                     if (finalView != null) {
3276                         finalView.setVisibility(VISIBLE);
3277                     }
3278                     if (onCompleteRunnable != null) {
3279                         onCompleteRunnable.run();
3280                     }
3281                 }
3282             };
3283             dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
3284                     finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
3285                     duration, this);
3286         }
3287     }
3288 
3289     public void setFinalTransitionTransform(CellLayout layout) {
3290         if (isSwitchingState()) {
3291             int index = indexOfChild(layout);
3292             mCurrentScaleX = layout.getScaleX();
3293             mCurrentScaleY = layout.getScaleY();
3294             mCurrentTranslationX = layout.getTranslationX();
3295             mCurrentTranslationY = layout.getTranslationY();
3296             mCurrentRotationY = layout.getRotationY();
3297             layout.setScaleX(mNewScaleXs[index]);
3298             layout.setScaleY(mNewScaleYs[index]);
3299             layout.setTranslationX(mNewTranslationXs[index]);
3300             layout.setTranslationY(mNewTranslationYs[index]);
3301             layout.setRotationY(mNewRotationYs[index]);
3302         }
3303     }
3304     public void resetTransitionTransform(CellLayout layout) {
3305         if (isSwitchingState()) {
3306             mCurrentScaleX = layout.getScaleX();
3307             mCurrentScaleY = layout.getScaleY();
3308             mCurrentTranslationX = layout.getTranslationX();
3309             mCurrentTranslationY = layout.getTranslationY();
3310             mCurrentRotationY = layout.getRotationY();
3311             layout.setScaleX(mCurrentScaleX);
3312             layout.setScaleY(mCurrentScaleY);
3313             layout.setTranslationX(mCurrentTranslationX);
3314             layout.setTranslationY(mCurrentTranslationY);
3315             layout.setRotationY(mCurrentRotationY);
3316         }
3317     }
3318 
3319     /**
3320      * Return the current {@link CellLayout}, correctly picking the destination
3321      * screen while a scroll is in progress.
3322      */
3323     public CellLayout getCurrentDropLayout() {
3324         return (CellLayout) getChildAt(getNextPage());
3325     }
3326 
3327     /**
3328      * Return the current CellInfo describing our current drag; this method exists
3329      * so that Launcher can sync this object with the correct info when the activity is created/
3330      * destroyed
3331      *
3332      */
3333     public CellLayout.CellInfo getDragInfo() {
3334         return mDragInfo;
3335     }
3336 
3337     /**
3338      * Calculate the nearest cell where the given object would be dropped.
3339      *
3340      * pixelX and pixelY should be in the coordinate system of layout
3341      */
3342     private int[] findNearestArea(int pixelX, int pixelY,
3343             int spanX, int spanY, CellLayout layout, int[] recycle) {
3344         return layout.findNearestArea(
3345                 pixelX, pixelY, spanX, spanY, recycle);
3346     }
3347 
3348     void setup(DragController dragController) {
3349         mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
3350         mDragController = dragController;
3351 
3352         // hardware layers on children are enabled on startup, but should be disabled until
3353         // needed
3354         updateChildrenLayersEnabled(false);
3355         setWallpaperDimension();
3356     }
3357 
3358     /**
3359      * Called at the end of a drag which originated on the workspace.
3360      */
3361     public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
3362             boolean success) {
3363         if (success) {
3364             if (target != this) {
3365                 if (mDragInfo != null) {
3366                     getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
3367                     if (mDragInfo.cell instanceof DropTarget) {
3368                         mDragController.removeDropTarget((DropTarget) mDragInfo.cell);
3369                     }
3370                 }
3371             }
3372         } else if (mDragInfo != null) {
3373             CellLayout cellLayout;
3374             if (mLauncher.isHotseatLayout(target)) {
3375                 cellLayout = mLauncher.getHotseat().getLayout();
3376             } else {
3377                 cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
3378             }
3379             cellLayout.onDropChild(mDragInfo.cell);
3380         }
3381         if (d.cancelled &&  mDragInfo.cell != null) {
3382                 mDragInfo.cell.setVisibility(VISIBLE);
3383         }
3384         mDragOutline = null;
3385         mDragInfo = null;
3386 
3387         // Hide the scrolling indicator after you pick up an item
3388         hideScrollingIndicator(false);
3389     }
3390 
3391     void updateItemLocationsInDatabase(CellLayout cl) {
3392         int count = cl.getShortcutsAndWidgets().getChildCount();
3393 
3394         int screen = indexOfChild(cl);
3395         int container = Favorites.CONTAINER_DESKTOP;
3396 
3397         if (mLauncher.isHotseatLayout(cl)) {
3398             screen = -1;
3399             container = Favorites.CONTAINER_HOTSEAT;
3400         }
3401 
3402         for (int i = 0; i < count; i++) {
3403             View v = cl.getShortcutsAndWidgets().getChildAt(i);
3404             ItemInfo info = (ItemInfo) v.getTag();
3405             // Null check required as the AllApps button doesn't have an item info
3406             if (info != null && info.requiresDbUpdate) {
3407                 info.requiresDbUpdate = false;
3408                 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screen, info.cellX,
3409                         info.cellY, info.spanX, info.spanY);
3410             }
3411         }
3412     }
3413 
3414     @Override
3415     public boolean supportsFlingToDelete() {
3416         return true;
3417     }
3418 
3419     @Override
3420     public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
3421         // Do nothing
3422     }
3423 
3424     @Override
3425     public void onFlingToDeleteCompleted() {
3426         // Do nothing
3427     }
3428 
3429     public boolean isDropEnabled() {
3430         return true;
3431     }
3432 
3433     @Override
3434     protected void onRestoreInstanceState(Parcelable state) {
3435         super.onRestoreInstanceState(state);
3436         Launcher.setScreen(mCurrentPage);
3437     }
3438 
3439     @Override
3440     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
3441         // We don't dispatch restoreInstanceState to our children using this code path.
3442         // Some pages will be restored immediately as their items are bound immediately, and
3443         // others we will need to wait until after their items are bound.
3444         mSavedStates = container;
3445     }
3446 
3447     public void restoreInstanceStateForChild(int child) {
3448         if (mSavedStates != null) {
3449             mRestoredPages.add(child);
3450             CellLayout cl = (CellLayout) getChildAt(child);
3451             cl.restoreInstanceState(mSavedStates);
3452         }
3453     }
3454 
3455     public void restoreInstanceStateForRemainingPages() {
3456         int count = getChildCount();
3457         for (int i = 0; i < count; i++) {
3458             if (!mRestoredPages.contains(i)) {
3459                 restoreInstanceStateForChild(i);
3460             }
3461         }
3462         mRestoredPages.clear();
3463     }
3464 
3465     @Override
3466     public void scrollLeft() {
3467         if (!isSmall() && !mIsSwitchingState) {
3468             super.scrollLeft();
3469         }
3470         Folder openFolder = getOpenFolder();
3471         if (openFolder != null) {
3472             openFolder.completeDragExit();
3473         }
3474     }
3475 
3476     @Override
3477     public void scrollRight() {
3478         if (!isSmall() && !mIsSwitchingState) {
3479             super.scrollRight();
3480         }
3481         Folder openFolder = getOpenFolder();
3482         if (openFolder != null) {
3483             openFolder.completeDragExit();
3484         }
3485     }
3486 
3487     @Override
3488     public boolean onEnterScrollArea(int x, int y, int direction) {
3489         // Ignore the scroll area if we are dragging over the hot seat
3490         boolean isPortrait = !LauncherApplication.isScreenLandscape(getContext());
3491         if (mLauncher.getHotseat() != null && isPortrait) {
3492             Rect r = new Rect();
3493             mLauncher.getHotseat().getHitRect(r);
3494             if (r.contains(x, y)) {
3495                 return false;
3496             }
3497         }
3498 
3499         boolean result = false;
3500         if (!isSmall() && !mIsSwitchingState) {
3501             mInScrollArea = true;
3502 
3503             final int page = getNextPage() +
3504                        (direction == DragController.SCROLL_LEFT ? -1 : 1);
3505 
3506             // We always want to exit the current layout to ensure parity of enter / exit
3507             setCurrentDropLayout(null);
3508 
3509             if (0 <= page && page < getChildCount()) {
3510                 CellLayout layout = (CellLayout) getChildAt(page);
3511                 setCurrentDragOverlappingLayout(layout);
3512 
3513                 // Workspace is responsible for drawing the edge glow on adjacent pages,
3514                 // so we need to redraw the workspace when this may have changed.
3515                 invalidate();
3516                 result = true;
3517             }
3518         }
3519         return result;
3520     }
3521 
3522     @Override
3523     public boolean onExitScrollArea() {
3524         boolean result = false;
3525         if (mInScrollArea) {
3526             invalidate();
3527             CellLayout layout = getCurrentDropLayout();
3528             setCurrentDropLayout(layout);
3529             setCurrentDragOverlappingLayout(layout);
3530 
3531             result = true;
3532             mInScrollArea = false;
3533         }
3534         return result;
3535     }
3536 
3537     private void onResetScrollArea() {
3538         setCurrentDragOverlappingLayout(null);
3539         mInScrollArea = false;
3540     }
3541 
3542     /**
3543      * Returns a specific CellLayout
3544      */
3545     CellLayout getParentCellLayoutForView(View v) {
3546         ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
3547         for (CellLayout layout : layouts) {
3548             if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
3549                 return layout;
3550             }
3551         }
3552         return null;
3553     }
3554 
3555     /**
3556      * Returns a list of all the CellLayouts in the workspace.
3557      */
3558     ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
3559         ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
3560         int screenCount = getChildCount();
3561         for (int screen = 0; screen < screenCount; screen++) {
3562             layouts.add(((CellLayout) getChildAt(screen)));
3563         }
3564         if (mLauncher.getHotseat() != null) {
3565             layouts.add(mLauncher.getHotseat().getLayout());
3566         }
3567         return layouts;
3568     }
3569 
3570     /**
3571      * We should only use this to search for specific children.  Do not use this method to modify
3572      * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
3573      * the hotseat and workspace pages
3574      */
3575     ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
3576         ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3577                 new ArrayList<ShortcutAndWidgetContainer>();
3578         int screenCount = getChildCount();
3579         for (int screen = 0; screen < screenCount; screen++) {
3580             childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
3581         }
3582         if (mLauncher.getHotseat() != null) {
3583             childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
3584         }
3585         return childrenLayouts;
3586     }
3587 
3588     public Folder getFolderForTag(Object tag) {
3589         ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3590                 getAllShortcutAndWidgetContainers();
3591         for (ShortcutAndWidgetContainer layout: childrenLayouts) {
3592             int count = layout.getChildCount();
3593             for (int i = 0; i < count; i++) {
3594                 View child = layout.getChildAt(i);
3595                 if (child instanceof Folder) {
3596                     Folder f = (Folder) child;
3597                     if (f.getInfo() == tag && f.getInfo().opened) {
3598                         return f;
3599                     }
3600                 }
3601             }
3602         }
3603         return null;
3604     }
3605 
3606     public View getViewForTag(Object tag) {
3607         ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3608                 getAllShortcutAndWidgetContainers();
3609         for (ShortcutAndWidgetContainer layout: childrenLayouts) {
3610             int count = layout.getChildCount();
3611             for (int i = 0; i < count; i++) {
3612                 View child = layout.getChildAt(i);
3613                 if (child.getTag() == tag) {
3614                     return child;
3615                 }
3616             }
3617         }
3618         return null;
3619     }
3620 
3621     void clearDropTargets() {
3622         ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
3623                 getAllShortcutAndWidgetContainers();
3624         for (ShortcutAndWidgetContainer layout: childrenLayouts) {
3625             int childCount = layout.getChildCount();
3626             for (int j = 0; j < childCount; j++) {
3627                 View v = layout.getChildAt(j);
3628                 if (v instanceof DropTarget) {
3629                     mDragController.removeDropTarget((DropTarget) v);
3630                 }
3631             }
3632         }
3633     }
3634 
3635     void removeItems(final ArrayList<String> packages) {
3636         final HashSet<String> packageNames = new HashSet<String>();
3637         packageNames.addAll(packages);
3638 
3639         ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
3640         for (final CellLayout layoutParent: cellLayouts) {
3641             final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
3642 
3643             // Avoid ANRs by treating each screen separately
3644             post(new Runnable() {
3645                 public void run() {
3646                     final ArrayList<View> childrenToRemove = new ArrayList<View>();
3647                     childrenToRemove.clear();
3648 
3649                     int childCount = layout.getChildCount();
3650                     for (int j = 0; j < childCount; j++) {
3651                         final View view = layout.getChildAt(j);
3652                         Object tag = view.getTag();
3653 
3654                         if (tag instanceof ShortcutInfo) {
3655                             final ShortcutInfo info = (ShortcutInfo) tag;
3656                             final Intent intent = info.intent;
3657                             final ComponentName name = intent.getComponent();
3658 
3659                             if (name != null) {
3660                                 if (packageNames.contains(name.getPackageName())) {
3661                                     LauncherModel.deleteItemFromDatabase(mLauncher, info);
3662                                     childrenToRemove.add(view);
3663                                 }
3664                             }
3665                         } else if (tag instanceof FolderInfo) {
3666                             final FolderInfo info = (FolderInfo) tag;
3667                             final ArrayList<ShortcutInfo> contents = info.contents;
3668                             final int contentsCount = contents.size();
3669                             final ArrayList<ShortcutInfo> appsToRemoveFromFolder =
3670                                     new ArrayList<ShortcutInfo>();
3671 
3672                             for (int k = 0; k < contentsCount; k++) {
3673                                 final ShortcutInfo appInfo = contents.get(k);
3674                                 final Intent intent = appInfo.intent;
3675                                 final ComponentName name = intent.getComponent();
3676 
3677                                 if (name != null) {
3678                                     if (packageNames.contains(name.getPackageName())) {
3679                                         appsToRemoveFromFolder.add(appInfo);
3680                                     }
3681                                 }
3682                             }
3683                             for (ShortcutInfo item: appsToRemoveFromFolder) {
3684                                 info.remove(item);
3685                                 LauncherModel.deleteItemFromDatabase(mLauncher, item);
3686                             }
3687                         } else if (tag instanceof LauncherAppWidgetInfo) {
3688                             final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag;
3689                             final ComponentName provider = info.providerName;
3690                             if (provider != null) {
3691                                 if (packageNames.contains(provider.getPackageName())) {
3692                                     LauncherModel.deleteItemFromDatabase(mLauncher, info);
3693                                     childrenToRemove.add(view);
3694                                 }
3695                             }
3696                         }
3697                     }
3698 
3699                     childCount = childrenToRemove.size();
3700                     for (int j = 0; j < childCount; j++) {
3701                         View child = childrenToRemove.get(j);
3702                         // Note: We can not remove the view directly from CellLayoutChildren as this
3703                         // does not re-mark the spaces as unoccupied.
3704                         layoutParent.removeViewInLayout(child);
3705                         if (child instanceof DropTarget) {
3706                             mDragController.removeDropTarget((DropTarget)child);
3707                         }
3708                     }
3709 
3710                     if (childCount > 0) {
3711                         layout.requestLayout();
3712                         layout.invalidate();
3713                     }
3714                 }
3715             });
3716         }
3717 
3718         // Clean up new-apps animation list
3719         final Context context = getContext();
3720         post(new Runnable() {
3721             @Override
3722             public void run() {
3723                 String spKey = LauncherApplication.getSharedPreferencesKey();
3724                 SharedPreferences sp = context.getSharedPreferences(spKey,
3725                         Context.MODE_PRIVATE);
3726                 Set<String> newApps = sp.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY,
3727                         null);
3728 
3729                 // Remove all queued items that match the same package
3730                 if (newApps != null) {
3731                     synchronized (newApps) {
3732                         Iterator<String> iter = newApps.iterator();
3733                         while (iter.hasNext()) {
3734                             try {
3735                                 Intent intent = Intent.parseUri(iter.next(), 0);
3736                                 String pn = ItemInfo.getPackageName(intent);
3737                                 if (packageNames.contains(pn)) {
3738                                     iter.remove();
3739                                 }
3740 
3741                                 // It is possible that we've queued an item to be loaded, yet it has
3742                                 // not been added to the workspace, so remove those items as well.
3743                                 ArrayList<ItemInfo> shortcuts;
3744                                 shortcuts = LauncherModel.getWorkspaceShortcutItemInfosWithIntent(
3745                                         intent);
3746                                 for (ItemInfo info : shortcuts) {
3747                                     LauncherModel.deleteItemFromDatabase(context, info);
3748                                 }
3749                             } catch (URISyntaxException e) {}
3750                         }
3751                     }
3752                 }
3753             }
3754         });
3755     }
3756 
3757     void updateShortcuts(ArrayList<ApplicationInfo> apps) {
3758         ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers();
3759         for (ShortcutAndWidgetContainer layout: childrenLayouts) {
3760             int childCount = layout.getChildCount();
3761             for (int j = 0; j < childCount; j++) {
3762                 final View view = layout.getChildAt(j);
3763                 Object tag = view.getTag();
3764                 if (tag instanceof ShortcutInfo) {
3765                     ShortcutInfo info = (ShortcutInfo) tag;
3766                     // We need to check for ACTION_MAIN otherwise getComponent() might
3767                     // return null for some shortcuts (for instance, for shortcuts to
3768                     // web pages.)
3769                     final Intent intent = info.intent;
3770                     final ComponentName name = intent.getComponent();
3771                     if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
3772                             Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
3773                         final int appCount = apps.size();
3774                         for (int k = 0; k < appCount; k++) {
3775                             ApplicationInfo app = apps.get(k);
3776                             if (app.componentName.equals(name)) {
3777                                 BubbleTextView shortcut = (BubbleTextView) view;
3778                                 info.updateIcon(mIconCache);
3779                                 info.title = app.title.toString();
3780                                 shortcut.applyFromShortcutInfo(info, mIconCache);
3781                             }
3782                         }
3783                     }
3784                 }
3785             }
3786         }
3787     }
3788 
3789     void moveToDefaultScreen(boolean animate) {
3790         if (!isSmall()) {
3791             if (animate) {
3792                 snapToPage(mDefaultPage);
3793             } else {
3794                 setCurrentPage(mDefaultPage);
3795             }
3796         }
3797         getChildAt(mDefaultPage).requestFocus();
3798     }
3799 
3800     @Override
3801     public void syncPages() {
3802     }
3803 
3804     @Override
3805     public void syncPageItems(int page, boolean immediate) {
3806     }
3807 
3808     @Override
3809     protected String getCurrentPageDescription() {
3810         int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
3811         return String.format(getContext().getString(R.string.workspace_scroll_format),
3812                 page + 1, getChildCount());
3813     }
3814 
3815     public void getLocationInDragLayer(int[] loc) {
3816         mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
3817     }
3818 
3819     void setFadeForOverScroll(float fade) {
3820         if (!isScrollingIndicatorEnabled()) return;
3821 
3822         mOverscrollFade = fade;
3823         float reducedFade = 0.5f + 0.5f * (1 - fade);
3824         final ViewGroup parent = (ViewGroup) getParent();
3825         final ImageView qsbDivider = (ImageView) (parent.findViewById(R.id.qsb_divider));
3826         final ImageView dockDivider = (ImageView) (parent.findViewById(R.id.dock_divider));
3827         final View scrollIndicator = getScrollingIndicator();
3828 
3829         cancelScrollingIndicatorAnimations();
3830         if (qsbDivider != null) qsbDivider.setAlpha(reducedFade);
3831         if (dockDivider != null) dockDivider.setAlpha(reducedFade);
3832         scrollIndicator.setAlpha(1 - fade);
3833     }
3834 }
3835