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