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