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