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.AnimatorListenerAdapter;
     21 import android.animation.AnimatorSet;
     22 import android.animation.TimeInterpolator;
     23 import android.animation.ValueAnimator;
     24 import android.animation.ValueAnimator.AnimatorUpdateListener;
     25 import android.content.Context;
     26 import android.content.res.Resources;
     27 import android.content.res.TypedArray;
     28 import android.graphics.Bitmap;
     29 import android.graphics.Canvas;
     30 import android.graphics.Color;
     31 import android.graphics.Paint;
     32 import android.graphics.Point;
     33 import android.graphics.PorterDuff;
     34 import android.graphics.PorterDuffXfermode;
     35 import android.graphics.Rect;
     36 import android.graphics.drawable.ColorDrawable;
     37 import android.graphics.drawable.Drawable;
     38 import android.graphics.drawable.NinePatchDrawable;
     39 import android.os.Parcelable;
     40 import android.util.AttributeSet;
     41 import android.util.Log;
     42 import android.util.SparseArray;
     43 import android.view.MotionEvent;
     44 import android.view.View;
     45 import android.view.ViewDebug;
     46 import android.view.ViewGroup;
     47 import android.view.animation.Animation;
     48 import android.view.animation.DecelerateInterpolator;
     49 import android.view.animation.LayoutAnimationController;
     50 
     51 import com.android.launcher.R;
     52 import com.android.launcher2.FolderIcon.FolderRingAnimator;
     53 
     54 import java.util.ArrayList;
     55 import java.util.Arrays;
     56 import java.util.HashMap;
     57 import java.util.Stack;
     58 
     59 public class CellLayout extends ViewGroup {
     60     static final String TAG = "CellLayout";
     61 
     62     private Launcher mLauncher;
     63     private int mCellWidth;
     64     private int mCellHeight;
     65 
     66     private int mCountX;
     67     private int mCountY;
     68 
     69     private int mOriginalWidthGap;
     70     private int mOriginalHeightGap;
     71     private int mWidthGap;
     72     private int mHeightGap;
     73     private int mMaxGap;
     74     private boolean mScrollingTransformsDirty = false;
     75 
     76     private final Rect mRect = new Rect();
     77     private final CellInfo mCellInfo = new CellInfo();
     78 
     79     // These are temporary variables to prevent having to allocate a new object just to
     80     // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
     81     private final int[] mTmpXY = new int[2];
     82     private final int[] mTmpPoint = new int[2];
     83     int[] mTempLocation = new int[2];
     84 
     85     boolean[][] mOccupied;
     86     boolean[][] mTmpOccupied;
     87     private boolean mLastDownOnOccupiedCell = false;
     88 
     89     private OnTouchListener mInterceptTouchListener;
     90 
     91     private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
     92     private int[] mFolderLeaveBehindCell = {-1, -1};
     93 
     94     private int mForegroundAlpha = 0;
     95     private float mBackgroundAlpha;
     96     private float mBackgroundAlphaMultiplier = 1.0f;
     97 
     98     private Drawable mNormalBackground;
     99     private Drawable mActiveGlowBackground;
    100     private Drawable mOverScrollForegroundDrawable;
    101     private Drawable mOverScrollLeft;
    102     private Drawable mOverScrollRight;
    103     private Rect mBackgroundRect;
    104     private Rect mForegroundRect;
    105     private int mForegroundPadding;
    106 
    107     // If we're actively dragging something over this screen, mIsDragOverlapping is true
    108     private boolean mIsDragOverlapping = false;
    109     private final Point mDragCenter = new Point();
    110 
    111     // These arrays are used to implement the drag visualization on x-large screens.
    112     // They are used as circular arrays, indexed by mDragOutlineCurrent.
    113     private Rect[] mDragOutlines = new Rect[4];
    114     private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
    115     private InterruptibleInOutAnimator[] mDragOutlineAnims =
    116             new InterruptibleInOutAnimator[mDragOutlines.length];
    117 
    118     // Used as an index into the above 3 arrays; indicates which is the most current value.
    119     private int mDragOutlineCurrent = 0;
    120     private final Paint mDragOutlinePaint = new Paint();
    121 
    122     private BubbleTextView mPressedOrFocusedIcon;
    123 
    124     private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
    125             HashMap<CellLayout.LayoutParams, Animator>();
    126     private HashMap<View, ReorderHintAnimation>
    127             mShakeAnimators = new HashMap<View, ReorderHintAnimation>();
    128 
    129     private boolean mItemPlacementDirty = false;
    130 
    131     // When a drag operation is in progress, holds the nearest cell to the touch point
    132     private final int[] mDragCell = new int[2];
    133 
    134     private boolean mDragging = false;
    135 
    136     private TimeInterpolator mEaseOutInterpolator;
    137     private ShortcutAndWidgetContainer mShortcutsAndWidgets;
    138 
    139     private boolean mIsHotseat = false;
    140     private float mHotseatScale = 1f;
    141 
    142     public static final int MODE_DRAG_OVER = 0;
    143     public static final int MODE_ON_DROP = 1;
    144     public static final int MODE_ON_DROP_EXTERNAL = 2;
    145     public static final int MODE_ACCEPT_DROP = 3;
    146     private static final boolean DESTRUCTIVE_REORDER = false;
    147     private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
    148 
    149     static final int LANDSCAPE = 0;
    150     static final int PORTRAIT = 1;
    151 
    152     private static final float REORDER_HINT_MAGNITUDE = 0.12f;
    153     private static final int REORDER_ANIMATION_DURATION = 150;
    154     private float mReorderHintAnimationMagnitude;
    155 
    156     private ArrayList<View> mIntersectingViews = new ArrayList<View>();
    157     private Rect mOccupiedRect = new Rect();
    158     private int[] mDirectionVector = new int[2];
    159     int[] mPreviousReorderDirection = new int[2];
    160     private static final int INVALID_DIRECTION = -100;
    161     private DropTarget.DragEnforcer mDragEnforcer;
    162 
    163     private final static PorterDuffXfermode sAddBlendMode =
    164             new PorterDuffXfermode(PorterDuff.Mode.ADD);
    165     private final static Paint sPaint = new Paint();
    166 
    167     public CellLayout(Context context) {
    168         this(context, null);
    169     }
    170 
    171     public CellLayout(Context context, AttributeSet attrs) {
    172         this(context, attrs, 0);
    173     }
    174 
    175     public CellLayout(Context context, AttributeSet attrs, int defStyle) {
    176         super(context, attrs, defStyle);
    177         mDragEnforcer = new DropTarget.DragEnforcer(context);
    178 
    179         // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
    180         // the user where a dragged item will land when dropped.
    181         setWillNotDraw(false);
    182         mLauncher = (Launcher) context;
    183 
    184         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
    185 
    186         mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
    187         mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
    188         mWidthGap = mOriginalWidthGap = a.getDimensionPixelSize(R.styleable.CellLayout_widthGap, 0);
    189         mHeightGap = mOriginalHeightGap = a.getDimensionPixelSize(R.styleable.CellLayout_heightGap, 0);
    190         mMaxGap = a.getDimensionPixelSize(R.styleable.CellLayout_maxGap, 0);
    191         mCountX = LauncherModel.getCellCountX();
    192         mCountY = LauncherModel.getCellCountY();
    193         mOccupied = new boolean[mCountX][mCountY];
    194         mTmpOccupied = new boolean[mCountX][mCountY];
    195         mPreviousReorderDirection[0] = INVALID_DIRECTION;
    196         mPreviousReorderDirection[1] = INVALID_DIRECTION;
    197 
    198         a.recycle();
    199 
    200         setAlwaysDrawnWithCacheEnabled(false);
    201 
    202         final Resources res = getResources();
    203         mHotseatScale = (res.getInteger(R.integer.hotseat_item_scale_percentage) / 100f);
    204 
    205         mNormalBackground = res.getDrawable(R.drawable.homescreen_blue_normal_holo);
    206         mActiveGlowBackground = res.getDrawable(R.drawable.homescreen_blue_strong_holo);
    207 
    208         mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
    209         mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
    210         mForegroundPadding =
    211                 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
    212 
    213         mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE *
    214                 res.getDimensionPixelSize(R.dimen.app_icon_size));
    215 
    216         mNormalBackground.setFilterBitmap(true);
    217         mActiveGlowBackground.setFilterBitmap(true);
    218 
    219         // Initialize the data structures used for the drag visualization.
    220 
    221         mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
    222 
    223 
    224         mDragCell[0] = mDragCell[1] = -1;
    225         for (int i = 0; i < mDragOutlines.length; i++) {
    226             mDragOutlines[i] = new Rect(-1, -1, -1, -1);
    227         }
    228 
    229         // When dragging things around the home screens, we show a green outline of
    230         // where the item will land. The outlines gradually fade out, leaving a trail
    231         // behind the drag path.
    232         // Set up all the animations that are used to implement this fading.
    233         final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
    234         final float fromAlphaValue = 0;
    235         final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
    236 
    237         Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
    238 
    239         for (int i = 0; i < mDragOutlineAnims.length; i++) {
    240             final InterruptibleInOutAnimator anim =
    241                 new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
    242             anim.getAnimator().setInterpolator(mEaseOutInterpolator);
    243             final int thisIndex = i;
    244             anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
    245                 public void onAnimationUpdate(ValueAnimator animation) {
    246                     final Bitmap outline = (Bitmap)anim.getTag();
    247 
    248                     // If an animation is started and then stopped very quickly, we can still
    249                     // get spurious updates we've cleared the tag. Guard against this.
    250                     if (outline == null) {
    251                         @SuppressWarnings("all") // suppress dead code warning
    252                         final boolean debug = false;
    253                         if (debug) {
    254                             Object val = animation.getAnimatedValue();
    255                             Log.d(TAG, "anim " + thisIndex + " update: " + val +
    256                                      ", isStopped " + anim.isStopped());
    257                         }
    258                         // Try to prevent it from continuing to run
    259                         animation.cancel();
    260                     } else {
    261                         mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
    262                         CellLayout.this.invalidate(mDragOutlines[thisIndex]);
    263                     }
    264                 }
    265             });
    266             // The animation holds a reference to the drag outline bitmap as long is it's
    267             // running. This way the bitmap can be GCed when the animations are complete.
    268             anim.getAnimator().addListener(new AnimatorListenerAdapter() {
    269                 @Override
    270                 public void onAnimationEnd(Animator animation) {
    271                     if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
    272                         anim.setTag(null);
    273                     }
    274                 }
    275             });
    276             mDragOutlineAnims[i] = anim;
    277         }
    278 
    279         mBackgroundRect = new Rect();
    280         mForegroundRect = new Rect();
    281 
    282         mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
    283         mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
    284         addView(mShortcutsAndWidgets);
    285     }
    286 
    287     static int widthInPortrait(Resources r, int numCells) {
    288         // We use this method from Workspace to figure out how many rows/columns Launcher should
    289         // have. We ignore the left/right padding on CellLayout because it turns out in our design
    290         // the padding extends outside the visible screen size, but it looked fine anyway.
    291         int cellWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
    292         int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
    293                 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
    294 
    295         return  minGap * (numCells - 1) + cellWidth * numCells;
    296     }
    297 
    298     static int heightInLandscape(Resources r, int numCells) {
    299         // We use this method from Workspace to figure out how many rows/columns Launcher should
    300         // have. We ignore the left/right padding on CellLayout because it turns out in our design
    301         // the padding extends outside the visible screen size, but it looked fine anyway.
    302         int cellHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
    303         int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
    304                 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
    305 
    306         return minGap * (numCells - 1) + cellHeight * numCells;
    307     }
    308 
    309     public void enableHardwareLayers() {
    310         mShortcutsAndWidgets.setLayerType(LAYER_TYPE_HARDWARE, sPaint);
    311     }
    312 
    313     public void disableHardwareLayers() {
    314         mShortcutsAndWidgets.setLayerType(LAYER_TYPE_NONE, sPaint);
    315     }
    316 
    317     public void buildHardwareLayer() {
    318         mShortcutsAndWidgets.buildLayer();
    319     }
    320 
    321     public float getChildrenScale() {
    322         return mIsHotseat ? mHotseatScale : 1.0f;
    323     }
    324 
    325     public void setGridSize(int x, int y) {
    326         mCountX = x;
    327         mCountY = y;
    328         mOccupied = new boolean[mCountX][mCountY];
    329         mTmpOccupied = new boolean[mCountX][mCountY];
    330         mTempRectStack.clear();
    331         requestLayout();
    332     }
    333 
    334     private void invalidateBubbleTextView(BubbleTextView icon) {
    335         final int padding = icon.getPressedOrFocusedBackgroundPadding();
    336         invalidate(icon.getLeft() + getPaddingLeft() - padding,
    337                 icon.getTop() + getPaddingTop() - padding,
    338                 icon.getRight() + getPaddingLeft() + padding,
    339                 icon.getBottom() + getPaddingTop() + padding);
    340     }
    341 
    342     void setOverScrollAmount(float r, boolean left) {
    343         if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
    344             mOverScrollForegroundDrawable = mOverScrollLeft;
    345         } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
    346             mOverScrollForegroundDrawable = mOverScrollRight;
    347         }
    348 
    349         mForegroundAlpha = (int) Math.round((r * 255));
    350         mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
    351         invalidate();
    352     }
    353 
    354     void setPressedOrFocusedIcon(BubbleTextView icon) {
    355         // We draw the pressed or focused BubbleTextView's background in CellLayout because it
    356         // requires an expanded clip rect (due to the glow's blur radius)
    357         BubbleTextView oldIcon = mPressedOrFocusedIcon;
    358         mPressedOrFocusedIcon = icon;
    359         if (oldIcon != null) {
    360             invalidateBubbleTextView(oldIcon);
    361         }
    362         if (mPressedOrFocusedIcon != null) {
    363             invalidateBubbleTextView(mPressedOrFocusedIcon);
    364         }
    365     }
    366 
    367     void setIsDragOverlapping(boolean isDragOverlapping) {
    368         if (mIsDragOverlapping != isDragOverlapping) {
    369             mIsDragOverlapping = isDragOverlapping;
    370             invalidate();
    371         }
    372     }
    373 
    374     boolean getIsDragOverlapping() {
    375         return mIsDragOverlapping;
    376     }
    377 
    378     protected void setOverscrollTransformsDirty(boolean dirty) {
    379         mScrollingTransformsDirty = dirty;
    380     }
    381 
    382     protected void resetOverscrollTransforms() {
    383         if (mScrollingTransformsDirty) {
    384             setOverscrollTransformsDirty(false);
    385             setTranslationX(0);
    386             setRotationY(0);
    387             // It doesn't matter if we pass true or false here, the important thing is that we
    388             // pass 0, which results in the overscroll drawable not being drawn any more.
    389             setOverScrollAmount(0, false);
    390             setPivotX(getMeasuredWidth() / 2);
    391             setPivotY(getMeasuredHeight() / 2);
    392         }
    393     }
    394 
    395     public void scaleRect(Rect r, float scale) {
    396         if (scale != 1.0f) {
    397             r.left = (int) (r.left * scale + 0.5f);
    398             r.top = (int) (r.top * scale + 0.5f);
    399             r.right = (int) (r.right * scale + 0.5f);
    400             r.bottom = (int) (r.bottom * scale + 0.5f);
    401         }
    402     }
    403 
    404     Rect temp = new Rect();
    405     void scaleRectAboutCenter(Rect in, Rect out, float scale) {
    406         int cx = in.centerX();
    407         int cy = in.centerY();
    408         out.set(in);
    409         out.offset(-cx, -cy);
    410         scaleRect(out, scale);
    411         out.offset(cx, cy);
    412     }
    413 
    414     @Override
    415     protected void onDraw(Canvas canvas) {
    416         // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
    417         // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
    418         // When we're small, we are either drawn normally or in the "accepts drops" state (during
    419         // a drag). However, we also drag the mini hover background *over* one of those two
    420         // backgrounds
    421         if (mBackgroundAlpha > 0.0f) {
    422             Drawable bg;
    423 
    424             if (mIsDragOverlapping) {
    425                 // In the mini case, we draw the active_glow bg *over* the active background
    426                 bg = mActiveGlowBackground;
    427             } else {
    428                 bg = mNormalBackground;
    429             }
    430 
    431             bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
    432             bg.setBounds(mBackgroundRect);
    433             bg.draw(canvas);
    434         }
    435 
    436         final Paint paint = mDragOutlinePaint;
    437         for (int i = 0; i < mDragOutlines.length; i++) {
    438             final float alpha = mDragOutlineAlphas[i];
    439             if (alpha > 0) {
    440                 final Rect r = mDragOutlines[i];
    441                 scaleRectAboutCenter(r, temp, getChildrenScale());
    442                 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
    443                 paint.setAlpha((int)(alpha + .5f));
    444                 canvas.drawBitmap(b, null, temp, paint);
    445             }
    446         }
    447 
    448         // We draw the pressed or focused BubbleTextView's background in CellLayout because it
    449         // requires an expanded clip rect (due to the glow's blur radius)
    450         if (mPressedOrFocusedIcon != null) {
    451             final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
    452             final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
    453             if (b != null) {
    454                 canvas.drawBitmap(b,
    455                         mPressedOrFocusedIcon.getLeft() + getPaddingLeft() - padding,
    456                         mPressedOrFocusedIcon.getTop() + getPaddingTop() - padding,
    457                         null);
    458             }
    459         }
    460 
    461         if (DEBUG_VISUALIZE_OCCUPIED) {
    462             int[] pt = new int[2];
    463             ColorDrawable cd = new ColorDrawable(Color.RED);
    464             cd.setBounds(0, 0,  mCellWidth, mCellHeight);
    465             for (int i = 0; i < mCountX; i++) {
    466                 for (int j = 0; j < mCountY; j++) {
    467                     if (mOccupied[i][j]) {
    468                         cellToPoint(i, j, pt);
    469                         canvas.save();
    470                         canvas.translate(pt[0], pt[1]);
    471                         cd.draw(canvas);
    472                         canvas.restore();
    473                     }
    474                 }
    475             }
    476         }
    477 
    478         int previewOffset = FolderRingAnimator.sPreviewSize;
    479 
    480         // The folder outer / inner ring image(s)
    481         for (int i = 0; i < mFolderOuterRings.size(); i++) {
    482             FolderRingAnimator fra = mFolderOuterRings.get(i);
    483 
    484             // Draw outer ring
    485             Drawable d = FolderRingAnimator.sSharedOuterRingDrawable;
    486             int width = (int) fra.getOuterRingSize();
    487             int height = width;
    488             cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
    489 
    490             int centerX = mTempLocation[0] + mCellWidth / 2;
    491             int centerY = mTempLocation[1] + previewOffset / 2;
    492 
    493             canvas.save();
    494             canvas.translate(centerX - width / 2, centerY - height / 2);
    495             d.setBounds(0, 0, width, height);
    496             d.draw(canvas);
    497             canvas.restore();
    498 
    499             // Draw inner ring
    500             d = FolderRingAnimator.sSharedInnerRingDrawable;
    501             width = (int) fra.getInnerRingSize();
    502             height = width;
    503             cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
    504 
    505             centerX = mTempLocation[0] + mCellWidth / 2;
    506             centerY = mTempLocation[1] + previewOffset / 2;
    507             canvas.save();
    508             canvas.translate(centerX - width / 2, centerY - width / 2);
    509             d.setBounds(0, 0, width, height);
    510             d.draw(canvas);
    511             canvas.restore();
    512         }
    513 
    514         if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
    515             Drawable d = FolderIcon.sSharedFolderLeaveBehind;
    516             int width = d.getIntrinsicWidth();
    517             int height = d.getIntrinsicHeight();
    518 
    519             cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
    520             int centerX = mTempLocation[0] + mCellWidth / 2;
    521             int centerY = mTempLocation[1] + previewOffset / 2;
    522 
    523             canvas.save();
    524             canvas.translate(centerX - width / 2, centerY - width / 2);
    525             d.setBounds(0, 0, width, height);
    526             d.draw(canvas);
    527             canvas.restore();
    528         }
    529     }
    530 
    531     @Override
    532     protected void dispatchDraw(Canvas canvas) {
    533         super.dispatchDraw(canvas);
    534         if (mForegroundAlpha > 0) {
    535             mOverScrollForegroundDrawable.setBounds(mForegroundRect);
    536             Paint p = ((NinePatchDrawable) mOverScrollForegroundDrawable).getPaint();
    537             p.setXfermode(sAddBlendMode);
    538             mOverScrollForegroundDrawable.draw(canvas);
    539             p.setXfermode(null);
    540         }
    541     }
    542 
    543     public void showFolderAccept(FolderRingAnimator fra) {
    544         mFolderOuterRings.add(fra);
    545     }
    546 
    547     public void hideFolderAccept(FolderRingAnimator fra) {
    548         if (mFolderOuterRings.contains(fra)) {
    549             mFolderOuterRings.remove(fra);
    550         }
    551         invalidate();
    552     }
    553 
    554     public void setFolderLeaveBehindCell(int x, int y) {
    555         mFolderLeaveBehindCell[0] = x;
    556         mFolderLeaveBehindCell[1] = y;
    557         invalidate();
    558     }
    559 
    560     public void clearFolderLeaveBehind() {
    561         mFolderLeaveBehindCell[0] = -1;
    562         mFolderLeaveBehindCell[1] = -1;
    563         invalidate();
    564     }
    565 
    566     @Override
    567     public boolean shouldDelayChildPressedState() {
    568         return false;
    569     }
    570 
    571     public void restoreInstanceState(SparseArray<Parcelable> states) {
    572         dispatchRestoreInstanceState(states);
    573     }
    574 
    575     @Override
    576     public void cancelLongPress() {
    577         super.cancelLongPress();
    578 
    579         // Cancel long press for all children
    580         final int count = getChildCount();
    581         for (int i = 0; i < count; i++) {
    582             final View child = getChildAt(i);
    583             child.cancelLongPress();
    584         }
    585     }
    586 
    587     public void setOnInterceptTouchListener(View.OnTouchListener listener) {
    588         mInterceptTouchListener = listener;
    589     }
    590 
    591     int getCountX() {
    592         return mCountX;
    593     }
    594 
    595     int getCountY() {
    596         return mCountY;
    597     }
    598 
    599     public void setIsHotseat(boolean isHotseat) {
    600         mIsHotseat = isHotseat;
    601     }
    602 
    603     public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
    604             boolean markCells) {
    605         final LayoutParams lp = params;
    606 
    607         // Hotseat icons - remove text
    608         if (child instanceof BubbleTextView) {
    609             BubbleTextView bubbleChild = (BubbleTextView) child;
    610 
    611             Resources res = getResources();
    612             if (mIsHotseat) {
    613                 bubbleChild.setTextColor(res.getColor(android.R.color.transparent));
    614             } else {
    615                 bubbleChild.setTextColor(res.getColor(R.color.workspace_icon_text_color));
    616             }
    617         }
    618 
    619         child.setScaleX(getChildrenScale());
    620         child.setScaleY(getChildrenScale());
    621 
    622         // Generate an id for each view, this assumes we have at most 256x256 cells
    623         // per workspace screen
    624         if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
    625             // If the horizontal or vertical span is set to -1, it is taken to
    626             // mean that it spans the extent of the CellLayout
    627             if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
    628             if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
    629 
    630             child.setId(childId);
    631 
    632             mShortcutsAndWidgets.addView(child, index, lp);
    633 
    634             if (markCells) markCellsAsOccupiedForView(child);
    635 
    636             return true;
    637         }
    638         return false;
    639     }
    640 
    641     @Override
    642     public void removeAllViews() {
    643         clearOccupiedCells();
    644         mShortcutsAndWidgets.removeAllViews();
    645     }
    646 
    647     @Override
    648     public void removeAllViewsInLayout() {
    649         if (mShortcutsAndWidgets.getChildCount() > 0) {
    650             clearOccupiedCells();
    651             mShortcutsAndWidgets.removeAllViewsInLayout();
    652         }
    653     }
    654 
    655     public void removeViewWithoutMarkingCells(View view) {
    656         mShortcutsAndWidgets.removeView(view);
    657     }
    658 
    659     @Override
    660     public void removeView(View view) {
    661         markCellsAsUnoccupiedForView(view);
    662         mShortcutsAndWidgets.removeView(view);
    663     }
    664 
    665     @Override
    666     public void removeViewAt(int index) {
    667         markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
    668         mShortcutsAndWidgets.removeViewAt(index);
    669     }
    670 
    671     @Override
    672     public void removeViewInLayout(View view) {
    673         markCellsAsUnoccupiedForView(view);
    674         mShortcutsAndWidgets.removeViewInLayout(view);
    675     }
    676 
    677     @Override
    678     public void removeViews(int start, int count) {
    679         for (int i = start; i < start + count; i++) {
    680             markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
    681         }
    682         mShortcutsAndWidgets.removeViews(start, count);
    683     }
    684 
    685     @Override
    686     public void removeViewsInLayout(int start, int count) {
    687         for (int i = start; i < start + count; i++) {
    688             markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
    689         }
    690         mShortcutsAndWidgets.removeViewsInLayout(start, count);
    691     }
    692 
    693     @Override
    694     protected void onAttachedToWindow() {
    695         super.onAttachedToWindow();
    696         mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
    697     }
    698 
    699     public void setTagToCellInfoForPoint(int touchX, int touchY) {
    700         final CellInfo cellInfo = mCellInfo;
    701         Rect frame = mRect;
    702         final int x = touchX + getScrollX();
    703         final int y = touchY + getScrollY();
    704         final int count = mShortcutsAndWidgets.getChildCount();
    705 
    706         boolean found = false;
    707         for (int i = count - 1; i >= 0; i--) {
    708             final View child = mShortcutsAndWidgets.getChildAt(i);
    709             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    710 
    711             if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
    712                     lp.isLockedToGrid) {
    713                 child.getHitRect(frame);
    714 
    715                 float scale = child.getScaleX();
    716                 frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
    717                         child.getBottom());
    718                 // The child hit rect is relative to the CellLayoutChildren parent, so we need to
    719                 // offset that by this CellLayout's padding to test an (x,y) point that is relative
    720                 // to this view.
    721                 frame.offset(getPaddingLeft(), getPaddingTop());
    722                 frame.inset((int) (frame.width() * (1f - scale) / 2),
    723                         (int) (frame.height() * (1f - scale) / 2));
    724 
    725                 if (frame.contains(x, y)) {
    726                     cellInfo.cell = child;
    727                     cellInfo.cellX = lp.cellX;
    728                     cellInfo.cellY = lp.cellY;
    729                     cellInfo.spanX = lp.cellHSpan;
    730                     cellInfo.spanY = lp.cellVSpan;
    731                     found = true;
    732                     break;
    733                 }
    734             }
    735         }
    736 
    737         mLastDownOnOccupiedCell = found;
    738 
    739         if (!found) {
    740             final int cellXY[] = mTmpXY;
    741             pointToCellExact(x, y, cellXY);
    742 
    743             cellInfo.cell = null;
    744             cellInfo.cellX = cellXY[0];
    745             cellInfo.cellY = cellXY[1];
    746             cellInfo.spanX = 1;
    747             cellInfo.spanY = 1;
    748         }
    749         setTag(cellInfo);
    750     }
    751 
    752     @Override
    753     public boolean onInterceptTouchEvent(MotionEvent ev) {
    754         // First we clear the tag to ensure that on every touch down we start with a fresh slate,
    755         // even in the case where we return early. Not clearing here was causing bugs whereby on
    756         // long-press we'd end up picking up an item from a previous drag operation.
    757         final int action = ev.getAction();
    758 
    759         if (action == MotionEvent.ACTION_DOWN) {
    760             clearTagCellInfo();
    761         }
    762 
    763         if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
    764             return true;
    765         }
    766 
    767         if (action == MotionEvent.ACTION_DOWN) {
    768             setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
    769         }
    770 
    771         return false;
    772     }
    773 
    774     private void clearTagCellInfo() {
    775         final CellInfo cellInfo = mCellInfo;
    776         cellInfo.cell = null;
    777         cellInfo.cellX = -1;
    778         cellInfo.cellY = -1;
    779         cellInfo.spanX = 0;
    780         cellInfo.spanY = 0;
    781         setTag(cellInfo);
    782     }
    783 
    784     public CellInfo getTag() {
    785         return (CellInfo) super.getTag();
    786     }
    787 
    788     /**
    789      * Given a point, return the cell that strictly encloses that point
    790      * @param x X coordinate of the point
    791      * @param y Y coordinate of the point
    792      * @param result Array of 2 ints to hold the x and y coordinate of the cell
    793      */
    794     void pointToCellExact(int x, int y, int[] result) {
    795         final int hStartPadding = getPaddingLeft();
    796         final int vStartPadding = getPaddingTop();
    797 
    798         result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
    799         result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
    800 
    801         final int xAxis = mCountX;
    802         final int yAxis = mCountY;
    803 
    804         if (result[0] < 0) result[0] = 0;
    805         if (result[0] >= xAxis) result[0] = xAxis - 1;
    806         if (result[1] < 0) result[1] = 0;
    807         if (result[1] >= yAxis) result[1] = yAxis - 1;
    808     }
    809 
    810     /**
    811      * Given a point, return the cell that most closely encloses that point
    812      * @param x X coordinate of the point
    813      * @param y Y coordinate of the point
    814      * @param result Array of 2 ints to hold the x and y coordinate of the cell
    815      */
    816     void pointToCellRounded(int x, int y, int[] result) {
    817         pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
    818     }
    819 
    820     /**
    821      * Given a cell coordinate, return the point that represents the upper left corner of that cell
    822      *
    823      * @param cellX X coordinate of the cell
    824      * @param cellY Y coordinate of the cell
    825      *
    826      * @param result Array of 2 ints to hold the x and y coordinate of the point
    827      */
    828     void cellToPoint(int cellX, int cellY, int[] result) {
    829         final int hStartPadding = getPaddingLeft();
    830         final int vStartPadding = getPaddingTop();
    831 
    832         result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
    833         result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
    834     }
    835 
    836     /**
    837      * Given a cell coordinate, return the point that represents the center of the cell
    838      *
    839      * @param cellX X coordinate of the cell
    840      * @param cellY Y coordinate of the cell
    841      *
    842      * @param result Array of 2 ints to hold the x and y coordinate of the point
    843      */
    844     void cellToCenterPoint(int cellX, int cellY, int[] result) {
    845         regionToCenterPoint(cellX, cellY, 1, 1, result);
    846     }
    847 
    848     /**
    849      * Given a cell coordinate and span return the point that represents the center of the regio
    850      *
    851      * @param cellX X coordinate of the cell
    852      * @param cellY Y coordinate of the cell
    853      *
    854      * @param result Array of 2 ints to hold the x and y coordinate of the point
    855      */
    856     void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
    857         final int hStartPadding = getPaddingLeft();
    858         final int vStartPadding = getPaddingTop();
    859         result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
    860                 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
    861         result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
    862                 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
    863     }
    864 
    865      /**
    866      * Given a cell coordinate and span fills out a corresponding pixel rect
    867      *
    868      * @param cellX X coordinate of the cell
    869      * @param cellY Y coordinate of the cell
    870      * @param result Rect in which to write the result
    871      */
    872      void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
    873         final int hStartPadding = getPaddingLeft();
    874         final int vStartPadding = getPaddingTop();
    875         final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
    876         final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
    877         result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
    878                 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
    879     }
    880 
    881     public float getDistanceFromCell(float x, float y, int[] cell) {
    882         cellToCenterPoint(cell[0], cell[1], mTmpPoint);
    883         float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
    884                 Math.pow(y - mTmpPoint[1], 2));
    885         return distance;
    886     }
    887 
    888     int getCellWidth() {
    889         return mCellWidth;
    890     }
    891 
    892     int getCellHeight() {
    893         return mCellHeight;
    894     }
    895 
    896     int getWidthGap() {
    897         return mWidthGap;
    898     }
    899 
    900     int getHeightGap() {
    901         return mHeightGap;
    902     }
    903 
    904     Rect getContentRect(Rect r) {
    905         if (r == null) {
    906             r = new Rect();
    907         }
    908         int left = getPaddingLeft();
    909         int top = getPaddingTop();
    910         int right = left + getWidth() - getPaddingLeft() - getPaddingRight();
    911         int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom();
    912         r.set(left, top, right, bottom);
    913         return r;
    914     }
    915 
    916     static void getMetrics(Rect metrics, Resources res, int measureWidth, int measureHeight,
    917             int countX, int countY, int orientation) {
    918         int numWidthGaps = countX - 1;
    919         int numHeightGaps = countY - 1;
    920 
    921         int widthGap;
    922         int heightGap;
    923         int cellWidth;
    924         int cellHeight;
    925         int paddingLeft;
    926         int paddingRight;
    927         int paddingTop;
    928         int paddingBottom;
    929 
    930         int maxGap = res.getDimensionPixelSize(R.dimen.workspace_max_gap);
    931         if (orientation == LANDSCAPE) {
    932             cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_land);
    933             cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_land);
    934             widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_land);
    935             heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_land);
    936             paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_land);
    937             paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_land);
    938             paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_land);
    939             paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_land);
    940         } else {
    941             // PORTRAIT
    942             cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_port);
    943             cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_port);
    944             widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_port);
    945             heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_port);
    946             paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_port);
    947             paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_port);
    948             paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_port);
    949             paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_port);
    950         }
    951 
    952         if (widthGap < 0 || heightGap < 0) {
    953             int hSpace = measureWidth - paddingLeft - paddingRight;
    954             int vSpace = measureHeight - paddingTop - paddingBottom;
    955             int hFreeSpace = hSpace - (countX * cellWidth);
    956             int vFreeSpace = vSpace - (countY * cellHeight);
    957             widthGap = Math.min(maxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
    958             heightGap = Math.min(maxGap, numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
    959         }
    960         metrics.set(cellWidth, cellHeight, widthGap, heightGap);
    961     }
    962 
    963     @Override
    964     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    965         int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    966         int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    967 
    968         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    969         int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
    970 
    971         if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
    972             throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
    973         }
    974 
    975         int numWidthGaps = mCountX - 1;
    976         int numHeightGaps = mCountY - 1;
    977 
    978         if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
    979             int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight();
    980             int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom();
    981             int hFreeSpace = hSpace - (mCountX * mCellWidth);
    982             int vFreeSpace = vSpace - (mCountY * mCellHeight);
    983             mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
    984             mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
    985             mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
    986         } else {
    987             mWidthGap = mOriginalWidthGap;
    988             mHeightGap = mOriginalHeightGap;
    989         }
    990 
    991         // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
    992         int newWidth = widthSpecSize;
    993         int newHeight = heightSpecSize;
    994         if (widthSpecMode == MeasureSpec.AT_MOST) {
    995             newWidth = getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
    996                 ((mCountX - 1) * mWidthGap);
    997             newHeight = getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
    998                 ((mCountY - 1) * mHeightGap);
    999             setMeasuredDimension(newWidth, newHeight);
   1000         }
   1001 
   1002         int count = getChildCount();
   1003         for (int i = 0; i < count; i++) {
   1004             View child = getChildAt(i);
   1005             int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() -
   1006                     getPaddingRight(), MeasureSpec.EXACTLY);
   1007             int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() -
   1008                     getPaddingBottom(), MeasureSpec.EXACTLY);
   1009             child.measure(childWidthMeasureSpec, childheightMeasureSpec);
   1010         }
   1011         setMeasuredDimension(newWidth, newHeight);
   1012     }
   1013 
   1014     @Override
   1015     protected void onLayout(boolean changed, int l, int t, int r, int b) {
   1016         int count = getChildCount();
   1017         for (int i = 0; i < count; i++) {
   1018             View child = getChildAt(i);
   1019             child.layout(getPaddingLeft(), getPaddingTop(),
   1020                     r - l - getPaddingRight(), b - t - getPaddingBottom());
   1021         }
   1022     }
   1023 
   1024     @Override
   1025     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
   1026         super.onSizeChanged(w, h, oldw, oldh);
   1027         mBackgroundRect.set(0, 0, w, h);
   1028         mForegroundRect.set(mForegroundPadding, mForegroundPadding,
   1029                 w - mForegroundPadding, h - mForegroundPadding);
   1030     }
   1031 
   1032     @Override
   1033     protected void setChildrenDrawingCacheEnabled(boolean enabled) {
   1034         mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
   1035     }
   1036 
   1037     @Override
   1038     protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
   1039         mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
   1040     }
   1041 
   1042     public float getBackgroundAlpha() {
   1043         return mBackgroundAlpha;
   1044     }
   1045 
   1046     public void setBackgroundAlphaMultiplier(float multiplier) {
   1047         if (mBackgroundAlphaMultiplier != multiplier) {
   1048             mBackgroundAlphaMultiplier = multiplier;
   1049             invalidate();
   1050         }
   1051     }
   1052 
   1053     public float getBackgroundAlphaMultiplier() {
   1054         return mBackgroundAlphaMultiplier;
   1055     }
   1056 
   1057     public void setBackgroundAlpha(float alpha) {
   1058         if (mBackgroundAlpha != alpha) {
   1059             mBackgroundAlpha = alpha;
   1060             invalidate();
   1061         }
   1062     }
   1063 
   1064     public void setShortcutAndWidgetAlpha(float alpha) {
   1065         final int childCount = getChildCount();
   1066         for (int i = 0; i < childCount; i++) {
   1067             getChildAt(i).setAlpha(alpha);
   1068         }
   1069     }
   1070 
   1071     public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
   1072         if (getChildCount() > 0) {
   1073             return (ShortcutAndWidgetContainer) getChildAt(0);
   1074         }
   1075         return null;
   1076     }
   1077 
   1078     public View getChildAt(int x, int y) {
   1079         return mShortcutsAndWidgets.getChildAt(x, y);
   1080     }
   1081 
   1082     public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
   1083             int delay, boolean permanent, boolean adjustOccupied) {
   1084         ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
   1085         boolean[][] occupied = mOccupied;
   1086         if (!permanent) {
   1087             occupied = mTmpOccupied;
   1088         }
   1089 
   1090         if (clc.indexOfChild(child) != -1) {
   1091             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
   1092             final ItemInfo info = (ItemInfo) child.getTag();
   1093 
   1094             // We cancel any existing animations
   1095             if (mReorderAnimators.containsKey(lp)) {
   1096                 mReorderAnimators.get(lp).cancel();
   1097                 mReorderAnimators.remove(lp);
   1098             }
   1099 
   1100             final int oldX = lp.x;
   1101             final int oldY = lp.y;
   1102             if (adjustOccupied) {
   1103                 occupied[lp.cellX][lp.cellY] = false;
   1104                 occupied[cellX][cellY] = true;
   1105             }
   1106             lp.isLockedToGrid = true;
   1107             if (permanent) {
   1108                 lp.cellX = info.cellX = cellX;
   1109                 lp.cellY = info.cellY = cellY;
   1110             } else {
   1111                 lp.tmpCellX = cellX;
   1112                 lp.tmpCellY = cellY;
   1113             }
   1114             clc.setupLp(lp);
   1115             lp.isLockedToGrid = false;
   1116             final int newX = lp.x;
   1117             final int newY = lp.y;
   1118 
   1119             lp.x = oldX;
   1120             lp.y = oldY;
   1121 
   1122             // Exit early if we're not actually moving the view
   1123             if (oldX == newX && oldY == newY) {
   1124                 lp.isLockedToGrid = true;
   1125                 return true;
   1126             }
   1127 
   1128             ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
   1129             va.setDuration(duration);
   1130             mReorderAnimators.put(lp, va);
   1131 
   1132             va.addUpdateListener(new AnimatorUpdateListener() {
   1133                 @Override
   1134                 public void onAnimationUpdate(ValueAnimator animation) {
   1135                     float r = ((Float) animation.getAnimatedValue()).floatValue();
   1136                     lp.x = (int) ((1 - r) * oldX + r * newX);
   1137                     lp.y = (int) ((1 - r) * oldY + r * newY);
   1138                     child.requestLayout();
   1139                 }
   1140             });
   1141             va.addListener(new AnimatorListenerAdapter() {
   1142                 boolean cancelled = false;
   1143                 public void onAnimationEnd(Animator animation) {
   1144                     // If the animation was cancelled, it means that another animation
   1145                     // has interrupted this one, and we don't want to lock the item into
   1146                     // place just yet.
   1147                     if (!cancelled) {
   1148                         lp.isLockedToGrid = true;
   1149                         child.requestLayout();
   1150                     }
   1151                     if (mReorderAnimators.containsKey(lp)) {
   1152                         mReorderAnimators.remove(lp);
   1153                     }
   1154                 }
   1155                 public void onAnimationCancel(Animator animation) {
   1156                     cancelled = true;
   1157                 }
   1158             });
   1159             va.setStartDelay(delay);
   1160             va.start();
   1161             return true;
   1162         }
   1163         return false;
   1164     }
   1165 
   1166     /**
   1167      * Estimate where the top left cell of the dragged item will land if it is dropped.
   1168      *
   1169      * @param originX The X value of the top left corner of the item
   1170      * @param originY The Y value of the top left corner of the item
   1171      * @param spanX The number of horizontal cells that the item spans
   1172      * @param spanY The number of vertical cells that the item spans
   1173      * @param result The estimated drop cell X and Y.
   1174      */
   1175     void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
   1176         final int countX = mCountX;
   1177         final int countY = mCountY;
   1178 
   1179         // pointToCellRounded takes the top left of a cell but will pad that with
   1180         // cellWidth/2 and cellHeight/2 when finding the matching cell
   1181         pointToCellRounded(originX, originY, result);
   1182 
   1183         // If the item isn't fully on this screen, snap to the edges
   1184         int rightOverhang = result[0] + spanX - countX;
   1185         if (rightOverhang > 0) {
   1186             result[0] -= rightOverhang; // Snap to right
   1187         }
   1188         result[0] = Math.max(0, result[0]); // Snap to left
   1189         int bottomOverhang = result[1] + spanY - countY;
   1190         if (bottomOverhang > 0) {
   1191             result[1] -= bottomOverhang; // Snap to bottom
   1192         }
   1193         result[1] = Math.max(0, result[1]); // Snap to top
   1194     }
   1195 
   1196     void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
   1197             int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
   1198         final int oldDragCellX = mDragCell[0];
   1199         final int oldDragCellY = mDragCell[1];
   1200 
   1201         if (v != null && dragOffset == null) {
   1202             mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
   1203         } else {
   1204             mDragCenter.set(originX, originY);
   1205         }
   1206 
   1207         if (dragOutline == null && v == null) {
   1208             return;
   1209         }
   1210 
   1211         if (cellX != oldDragCellX || cellY != oldDragCellY) {
   1212             mDragCell[0] = cellX;
   1213             mDragCell[1] = cellY;
   1214             // Find the top left corner of the rect the object will occupy
   1215             final int[] topLeft = mTmpPoint;
   1216             cellToPoint(cellX, cellY, topLeft);
   1217 
   1218             int left = topLeft[0];
   1219             int top = topLeft[1];
   1220 
   1221             if (v != null && dragOffset == null) {
   1222                 // When drawing the drag outline, it did not account for margin offsets
   1223                 // added by the view's parent.
   1224                 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
   1225                 left += lp.leftMargin;
   1226                 top += lp.topMargin;
   1227 
   1228                 // Offsets due to the size difference between the View and the dragOutline.
   1229                 // There is a size difference to account for the outer blur, which may lie
   1230                 // outside the bounds of the view.
   1231                 top += (v.getHeight() - dragOutline.getHeight()) / 2;
   1232                 // We center about the x axis
   1233                 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
   1234                         - dragOutline.getWidth()) / 2;
   1235             } else {
   1236                 if (dragOffset != null && dragRegion != null) {
   1237                     // Center the drag region *horizontally* in the cell and apply a drag
   1238                     // outline offset
   1239                     left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
   1240                              - dragRegion.width()) / 2;
   1241                     top += dragOffset.y;
   1242                 } else {
   1243                     // Center the drag outline in the cell
   1244                     left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
   1245                             - dragOutline.getWidth()) / 2;
   1246                     top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
   1247                             - dragOutline.getHeight()) / 2;
   1248                 }
   1249             }
   1250             final int oldIndex = mDragOutlineCurrent;
   1251             mDragOutlineAnims[oldIndex].animateOut();
   1252             mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
   1253             Rect r = mDragOutlines[mDragOutlineCurrent];
   1254             r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
   1255             if (resize) {
   1256                 cellToRect(cellX, cellY, spanX, spanY, r);
   1257             }
   1258 
   1259             mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
   1260             mDragOutlineAnims[mDragOutlineCurrent].animateIn();
   1261         }
   1262     }
   1263 
   1264     public void clearDragOutlines() {
   1265         final int oldIndex = mDragOutlineCurrent;
   1266         mDragOutlineAnims[oldIndex].animateOut();
   1267         mDragCell[0] = mDragCell[1] = -1;
   1268     }
   1269 
   1270     /**
   1271      * Find a vacant area that will fit the given bounds nearest the requested
   1272      * cell location. Uses Euclidean distance to score multiple vacant areas.
   1273      *
   1274      * @param pixelX The X location at which you want to search for a vacant area.
   1275      * @param pixelY The Y location at which you want to search for a vacant area.
   1276      * @param spanX Horizontal span of the object.
   1277      * @param spanY Vertical span of the object.
   1278      * @param result Array in which to place the result, or null (in which case a new array will
   1279      *        be allocated)
   1280      * @return The X, Y cell of a vacant area that can contain this object,
   1281      *         nearest the requested location.
   1282      */
   1283     int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
   1284             int[] result) {
   1285         return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
   1286     }
   1287 
   1288     /**
   1289      * Find a vacant area that will fit the given bounds nearest the requested
   1290      * cell location. Uses Euclidean distance to score multiple vacant areas.
   1291      *
   1292      * @param pixelX The X location at which you want to search for a vacant area.
   1293      * @param pixelY The Y location at which you want to search for a vacant area.
   1294      * @param minSpanX The minimum horizontal span required
   1295      * @param minSpanY The minimum vertical span required
   1296      * @param spanX Horizontal span of the object.
   1297      * @param spanY Vertical span of the object.
   1298      * @param result Array in which to place the result, or null (in which case a new array will
   1299      *        be allocated)
   1300      * @return The X, Y cell of a vacant area that can contain this object,
   1301      *         nearest the requested location.
   1302      */
   1303     int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
   1304             int spanY, int[] result, int[] resultSpan) {
   1305         return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
   1306                 result, resultSpan);
   1307     }
   1308 
   1309     /**
   1310      * Find a vacant area that will fit the given bounds nearest the requested
   1311      * cell location. Uses Euclidean distance to score multiple vacant areas.
   1312      *
   1313      * @param pixelX The X location at which you want to search for a vacant area.
   1314      * @param pixelY The Y location at which you want to search for a vacant area.
   1315      * @param spanX Horizontal span of the object.
   1316      * @param spanY Vertical span of the object.
   1317      * @param ignoreOccupied If true, the result can be an occupied cell
   1318      * @param result Array in which to place the result, or null (in which case a new array will
   1319      *        be allocated)
   1320      * @return The X, Y cell of a vacant area that can contain this object,
   1321      *         nearest the requested location.
   1322      */
   1323     int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
   1324             boolean ignoreOccupied, int[] result) {
   1325         return findNearestArea(pixelX, pixelY, spanX, spanY,
   1326                 spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
   1327     }
   1328 
   1329     private final Stack<Rect> mTempRectStack = new Stack<Rect>();
   1330     private void lazyInitTempRectStack() {
   1331         if (mTempRectStack.isEmpty()) {
   1332             for (int i = 0; i < mCountX * mCountY; i++) {
   1333                 mTempRectStack.push(new Rect());
   1334             }
   1335         }
   1336     }
   1337 
   1338     private void recycleTempRects(Stack<Rect> used) {
   1339         while (!used.isEmpty()) {
   1340             mTempRectStack.push(used.pop());
   1341         }
   1342     }
   1343 
   1344     /**
   1345      * Find a vacant area that will fit the given bounds nearest the requested
   1346      * cell location. Uses Euclidean distance to score multiple vacant areas.
   1347      *
   1348      * @param pixelX The X location at which you want to search for a vacant area.
   1349      * @param pixelY The Y location at which you want to search for a vacant area.
   1350      * @param minSpanX The minimum horizontal span required
   1351      * @param minSpanY The minimum vertical span required
   1352      * @param spanX Horizontal span of the object.
   1353      * @param spanY Vertical span of the object.
   1354      * @param ignoreOccupied If true, the result can be an occupied cell
   1355      * @param result Array in which to place the result, or null (in which case a new array will
   1356      *        be allocated)
   1357      * @return The X, Y cell of a vacant area that can contain this object,
   1358      *         nearest the requested location.
   1359      */
   1360     int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
   1361             View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
   1362             boolean[][] occupied) {
   1363         lazyInitTempRectStack();
   1364         // mark space take by ignoreView as available (method checks if ignoreView is null)
   1365         markCellsAsUnoccupiedForView(ignoreView, occupied);
   1366 
   1367         // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
   1368         // to the center of the item, but we are searching based on the top-left cell, so
   1369         // we translate the point over to correspond to the top-left.
   1370         pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
   1371         pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
   1372 
   1373         // Keep track of best-scoring drop area
   1374         final int[] bestXY = result != null ? result : new int[2];
   1375         double bestDistance = Double.MAX_VALUE;
   1376         final Rect bestRect = new Rect(-1, -1, -1, -1);
   1377         final Stack<Rect> validRegions = new Stack<Rect>();
   1378 
   1379         final int countX = mCountX;
   1380         final int countY = mCountY;
   1381 
   1382         if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
   1383                 spanX < minSpanX || spanY < minSpanY) {
   1384             return bestXY;
   1385         }
   1386 
   1387         for (int y = 0; y < countY - (minSpanY - 1); y++) {
   1388             inner:
   1389             for (int x = 0; x < countX - (minSpanX - 1); x++) {
   1390                 int ySize = -1;
   1391                 int xSize = -1;
   1392                 if (ignoreOccupied) {
   1393                     // First, let's see if this thing fits anywhere
   1394                     for (int i = 0; i < minSpanX; i++) {
   1395                         for (int j = 0; j < minSpanY; j++) {
   1396                             if (occupied[x + i][y + j]) {
   1397                                 continue inner;
   1398                             }
   1399                         }
   1400                     }
   1401                     xSize = minSpanX;
   1402                     ySize = minSpanY;
   1403 
   1404                     // We know that the item will fit at _some_ acceptable size, now let's see
   1405                     // how big we can make it. We'll alternate between incrementing x and y spans
   1406                     // until we hit a limit.
   1407                     boolean incX = true;
   1408                     boolean hitMaxX = xSize >= spanX;
   1409                     boolean hitMaxY = ySize >= spanY;
   1410                     while (!(hitMaxX && hitMaxY)) {
   1411                         if (incX && !hitMaxX) {
   1412                             for (int j = 0; j < ySize; j++) {
   1413                                 if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
   1414                                     // We can't move out horizontally
   1415                                     hitMaxX = true;
   1416                                 }
   1417                             }
   1418                             if (!hitMaxX) {
   1419                                 xSize++;
   1420                             }
   1421                         } else if (!hitMaxY) {
   1422                             for (int i = 0; i < xSize; i++) {
   1423                                 if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
   1424                                     // We can't move out vertically
   1425                                     hitMaxY = true;
   1426                                 }
   1427                             }
   1428                             if (!hitMaxY) {
   1429                                 ySize++;
   1430                             }
   1431                         }
   1432                         hitMaxX |= xSize >= spanX;
   1433                         hitMaxY |= ySize >= spanY;
   1434                         incX = !incX;
   1435                     }
   1436                     incX = true;
   1437                     hitMaxX = xSize >= spanX;
   1438                     hitMaxY = ySize >= spanY;
   1439                 }
   1440                 final int[] cellXY = mTmpXY;
   1441                 cellToCenterPoint(x, y, cellXY);
   1442 
   1443                 // We verify that the current rect is not a sub-rect of any of our previous
   1444                 // candidates. In this case, the current rect is disqualified in favour of the
   1445                 // containing rect.
   1446                 Rect currentRect = mTempRectStack.pop();
   1447                 currentRect.set(x, y, x + xSize, y + ySize);
   1448                 boolean contained = false;
   1449                 for (Rect r : validRegions) {
   1450                     if (r.contains(currentRect)) {
   1451                         contained = true;
   1452                         break;
   1453                     }
   1454                 }
   1455                 validRegions.push(currentRect);
   1456                 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
   1457                         + Math.pow(cellXY[1] - pixelY, 2));
   1458 
   1459                 if ((distance <= bestDistance && !contained) ||
   1460                         currentRect.contains(bestRect)) {
   1461                     bestDistance = distance;
   1462                     bestXY[0] = x;
   1463                     bestXY[1] = y;
   1464                     if (resultSpan != null) {
   1465                         resultSpan[0] = xSize;
   1466                         resultSpan[1] = ySize;
   1467                     }
   1468                     bestRect.set(currentRect);
   1469                 }
   1470             }
   1471         }
   1472         // re-mark space taken by ignoreView as occupied
   1473         markCellsAsOccupiedForView(ignoreView, occupied);
   1474 
   1475         // Return -1, -1 if no suitable location found
   1476         if (bestDistance == Double.MAX_VALUE) {
   1477             bestXY[0] = -1;
   1478             bestXY[1] = -1;
   1479         }
   1480         recycleTempRects(validRegions);
   1481         return bestXY;
   1482     }
   1483 
   1484      /**
   1485      * Find a vacant area that will fit the given bounds nearest the requested
   1486      * cell location, and will also weigh in a suggested direction vector of the
   1487      * desired location. This method computers distance based on unit grid distances,
   1488      * not pixel distances.
   1489      *
   1490      * @param cellX The X cell nearest to which you want to search for a vacant area.
   1491      * @param cellY The Y cell nearest which you want to search for a vacant area.
   1492      * @param spanX Horizontal span of the object.
   1493      * @param spanY Vertical span of the object.
   1494      * @param direction The favored direction in which the views should move from x, y
   1495      * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
   1496      *        matches exactly. Otherwise we find the best matching direction.
   1497      * @param occoupied The array which represents which cells in the CellLayout are occupied
   1498      * @param blockOccupied The array which represents which cells in the specified block (cellX,
   1499      *        cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
   1500      * @param result Array in which to place the result, or null (in which case a new array will
   1501      *        be allocated)
   1502      * @return The X, Y cell of a vacant area that can contain this object,
   1503      *         nearest the requested location.
   1504      */
   1505     private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
   1506             boolean[][] occupied, boolean blockOccupied[][], int[] result) {
   1507         // Keep track of best-scoring drop area
   1508         final int[] bestXY = result != null ? result : new int[2];
   1509         float bestDistance = Float.MAX_VALUE;
   1510         int bestDirectionScore = Integer.MIN_VALUE;
   1511 
   1512         final int countX = mCountX;
   1513         final int countY = mCountY;
   1514 
   1515         for (int y = 0; y < countY - (spanY - 1); y++) {
   1516             inner:
   1517             for (int x = 0; x < countX - (spanX - 1); x++) {
   1518                 // First, let's see if this thing fits anywhere
   1519                 for (int i = 0; i < spanX; i++) {
   1520                     for (int j = 0; j < spanY; j++) {
   1521                         if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
   1522                             continue inner;
   1523                         }
   1524                     }
   1525                 }
   1526 
   1527                 float distance = (float)
   1528                         Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
   1529                 int[] curDirection = mTmpPoint;
   1530                 computeDirectionVector(x - cellX, y - cellY, curDirection);
   1531                 // The direction score is just the dot product of the two candidate direction
   1532                 // and that passed in.
   1533                 int curDirectionScore = direction[0] * curDirection[0] +
   1534                         direction[1] * curDirection[1];
   1535                 boolean exactDirectionOnly = false;
   1536                 boolean directionMatches = direction[0] == curDirection[0] &&
   1537                         direction[0] == curDirection[0];
   1538                 if ((directionMatches || !exactDirectionOnly) &&
   1539                         Float.compare(distance,  bestDistance) < 0 || (Float.compare(distance,
   1540                         bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
   1541                     bestDistance = distance;
   1542                     bestDirectionScore = curDirectionScore;
   1543                     bestXY[0] = x;
   1544                     bestXY[1] = y;
   1545                 }
   1546             }
   1547         }
   1548 
   1549         // Return -1, -1 if no suitable location found
   1550         if (bestDistance == Float.MAX_VALUE) {
   1551             bestXY[0] = -1;
   1552             bestXY[1] = -1;
   1553         }
   1554         return bestXY;
   1555     }
   1556 
   1557     private int[] findNearestAreaInDirection(int cellX, int cellY, int spanX, int spanY,
   1558             int[] direction,boolean[][] occupied,
   1559             boolean blockOccupied[][], int[] result) {
   1560         // Keep track of best-scoring drop area
   1561         final int[] bestXY = result != null ? result : new int[2];
   1562         bestXY[0] = -1;
   1563         bestXY[1] = -1;
   1564         float bestDistance = Float.MAX_VALUE;
   1565 
   1566         // We use this to march in a single direction
   1567         if ((direction[0] != 0 && direction[1] != 0) ||
   1568                 (direction[0] == 0 && direction[1] == 0)) {
   1569             return bestXY;
   1570         }
   1571 
   1572         // This will only incrememnet one of x or y based on the assertion above
   1573         int x = cellX + direction[0];
   1574         int y = cellY + direction[1];
   1575         while (x >= 0 && x + spanX <= mCountX && y >= 0 && y + spanY <= mCountY) {
   1576             boolean fail = false;
   1577             for (int i = 0; i < spanX; i++) {
   1578                 for (int j = 0; j < spanY; j++) {
   1579                     if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
   1580                         fail = true;
   1581                     }
   1582                 }
   1583             }
   1584             if (!fail) {
   1585                 float distance = (float)
   1586                         Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
   1587                 if (Float.compare(distance,  bestDistance) < 0) {
   1588                     bestDistance = distance;
   1589                     bestXY[0] = x;
   1590                     bestXY[1] = y;
   1591                 }
   1592             }
   1593             x += direction[0];
   1594             y += direction[1];
   1595         }
   1596         return bestXY;
   1597     }
   1598 
   1599     private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
   1600             int[] direction, ItemConfiguration currentState) {
   1601         CellAndSpan c = currentState.map.get(v);
   1602         boolean success = false;
   1603         markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
   1604         markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
   1605 
   1606         findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
   1607 
   1608         if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
   1609             c.x = mTempLocation[0];
   1610             c.y = mTempLocation[1];
   1611             success = true;
   1612 
   1613         }
   1614         markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
   1615         return success;
   1616     }
   1617 
   1618     // This method looks in the specified direction to see if there are additional views adjacent
   1619     // to the current set of views. If there are, then these views are added to the current
   1620     // set of views. This is performed iteratively, giving a cascading push behaviour.
   1621     private boolean addViewInDirection(ArrayList<View> views, Rect boundingRect, int[] direction,
   1622             boolean[][] occupied, View dragView, ItemConfiguration currentState) {
   1623         boolean found = false;
   1624 
   1625         int childCount = mShortcutsAndWidgets.getChildCount();
   1626         Rect r0 = new Rect(boundingRect);
   1627         Rect r1 = new Rect();
   1628 
   1629         // First, we consider the rect of the views that we are trying to translate
   1630         int deltaX = 0;
   1631         int deltaY = 0;
   1632         if (direction[1] < 0) {
   1633             r0.set(r0.left, r0.top - 1, r0.right, r0.bottom - 1);
   1634             deltaY = -1;
   1635         } else if (direction[1] > 0) {
   1636             r0.set(r0.left, r0.top + 1, r0.right, r0.bottom + 1);
   1637             deltaY = 1;
   1638         } else if (direction[0] < 0) {
   1639             r0.set(r0.left - 1, r0.top, r0.right - 1, r0.bottom);
   1640             deltaX = -1;
   1641         } else if (direction[0] > 0) {
   1642             r0.set(r0.left + 1, r0.top, r0.right + 1, r0.bottom);
   1643             deltaX = 1;
   1644         }
   1645 
   1646         // Now we see which views, if any, are being overlapped by shifting the current group
   1647         // of views in the desired direction.
   1648         for (int i = 0; i < childCount; i++) {
   1649             // We don't need to worry about views already in our group, or the current drag view.
   1650             View child = mShortcutsAndWidgets.getChildAt(i);
   1651             if (views.contains(child) || child == dragView) continue;
   1652             CellAndSpan c = currentState.map.get(child);
   1653 
   1654             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   1655             r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
   1656             if (Rect.intersects(r0, r1)) {
   1657                 if (!lp.canReorder) {
   1658                     return false;
   1659                 }
   1660                 // First we verify that the view in question is at the border of the extents
   1661                 // of the block of items we are pushing
   1662                 if ((direction[0] < 0 && c.x == r0.left) ||
   1663                         (direction[0] > 0 && c.x == r0.right - 1) ||
   1664                         (direction[1] < 0 && c.y == r0.top) ||
   1665                         (direction[1] > 0 && c.y == r0.bottom - 1)) {
   1666                     boolean pushed = false;
   1667                     // Since the bounding rect is a coarse description of the region (there can
   1668                     // be holes at the edge of the block), we need to check to verify that a solid
   1669                     // piece is intersecting. This ensures that interlocking is possible.
   1670                     for (int x = c.x; x < c.x + c.spanX; x++) {
   1671                         for (int y = c.y; y < c.y + c.spanY; y++) {
   1672                             if (occupied[x - deltaX][y - deltaY]) {
   1673                                 pushed = true;
   1674                                 break;
   1675                             }
   1676                             if (pushed) break;
   1677                         }
   1678                     }
   1679                     if (pushed) {
   1680                         views.add(child);
   1681                         boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
   1682                         found = true;
   1683                     }
   1684                 }
   1685             }
   1686         }
   1687         return found;
   1688     }
   1689 
   1690     private void completeSetOfViewsToMove(ArrayList<View> views, Rect boundingRect, int[] direction,
   1691             boolean[][] occupied, View dragView, ItemConfiguration currentState) {
   1692         Rect r0 = new Rect(boundingRect);
   1693         int minRuns = 0;
   1694 
   1695         // The first thing we do is to reduce the bounding rect to first or last row or column,
   1696         // depending on the direction. Then, we add any necessary views that are already contained
   1697         // by the bounding rect, but aren't in the list of intersecting views, and will be pushed
   1698         // by something already in the intersecting views.
   1699         if (direction[1] < 0) {
   1700             r0.set(r0.left, r0.bottom - 1, r0.right, r0.bottom);
   1701         } else if (direction[1] > 0) {
   1702             r0.set(r0.left, r0.top, r0.right, r0.top + 1);
   1703         } else if (direction[0] < 0) {
   1704             r0.set(r0.right - 1, r0.top, r0.right, r0.bottom);
   1705         } else if (direction[0] > 0) {
   1706             r0.set(r0.left, r0.top, r0.left + 1, r0.bottom);
   1707         }
   1708 
   1709         minRuns = Math.max(Math.abs(boundingRect.width() - r0.width()),
   1710                 Math.abs(boundingRect.height() - r0.height())) + 1;
   1711 
   1712         // Here the first number of runs (minRuns) accounts for the the comment above, and
   1713         // further runs execute based on whether the intersecting views / bounding rect need
   1714         // to be expanded to include other views that will be pushed.
   1715         while (addViewInDirection(views, r0, direction, mTmpOccupied,
   1716                 dragView, currentState) || minRuns > 0) {
   1717             minRuns--;
   1718         }
   1719         boundingRect.union(r0);
   1720     }
   1721 
   1722     private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
   1723             int[] direction, boolean push, View dragView, ItemConfiguration currentState) {
   1724         if (views.size() == 0) return true;
   1725 
   1726         boolean success = false;
   1727         Rect boundingRect = null;
   1728         // We construct a rect which represents the entire group of views passed in
   1729         for (View v: views) {
   1730             CellAndSpan c = currentState.map.get(v);
   1731             if (boundingRect == null) {
   1732                 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
   1733             } else {
   1734                 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
   1735             }
   1736         }
   1737 
   1738         @SuppressWarnings("unchecked")
   1739         ArrayList<View> dup = (ArrayList<View>) views.clone();
   1740         if (push) {
   1741             completeSetOfViewsToMove(dup, boundingRect, direction, mTmpOccupied, dragView,
   1742                     currentState);
   1743         }
   1744 
   1745         // Mark the occupied state as false for the group of views we want to move.
   1746         for (View v: dup) {
   1747             CellAndSpan c = currentState.map.get(v);
   1748             markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
   1749         }
   1750 
   1751         boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
   1752         int top = boundingRect.top;
   1753         int left = boundingRect.left;
   1754         // We mark more precisely which parts of the bounding rect are truly occupied, allowing
   1755         // for interlocking.
   1756         for (View v: dup) {
   1757             CellAndSpan c = currentState.map.get(v);
   1758             markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
   1759         }
   1760 
   1761         markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
   1762 
   1763         if (push) {
   1764             findNearestAreaInDirection(boundingRect.left, boundingRect.top, boundingRect.width(),
   1765                     boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
   1766         } else {
   1767             findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
   1768                     boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
   1769         }
   1770 
   1771         // If we successfuly found a location by pushing the block of views, we commit it
   1772         if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
   1773             int deltaX = mTempLocation[0] - boundingRect.left;
   1774             int deltaY = mTempLocation[1] - boundingRect.top;
   1775             for (View v: dup) {
   1776                 CellAndSpan c = currentState.map.get(v);
   1777                 c.x += deltaX;
   1778                 c.y += deltaY;
   1779             }
   1780             success = true;
   1781         }
   1782 
   1783         // In either case, we set the occupied array as marked for the location of the views
   1784         for (View v: dup) {
   1785             CellAndSpan c = currentState.map.get(v);
   1786             markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
   1787         }
   1788         return success;
   1789     }
   1790 
   1791     private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
   1792         markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
   1793     }
   1794 
   1795     // This method tries to find a reordering solution which satisfies the push mechanic by trying
   1796     // to push items in each of the cardinal directions, in an order based on the direction vector
   1797     // passed.
   1798     private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
   1799             int[] direction, View ignoreView, ItemConfiguration solution) {
   1800         if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
   1801             // If the direction vector has two non-zero components, we try pushing
   1802             // separately in each of the components.
   1803             int temp = direction[1];
   1804             direction[1] = 0;
   1805             if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
   1806                     ignoreView, solution)) {
   1807                 return true;
   1808             }
   1809             direction[1] = temp;
   1810             temp = direction[0];
   1811             direction[0] = 0;
   1812             if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
   1813                     ignoreView, solution)) {
   1814                 return true;
   1815             }
   1816             // Revert the direction
   1817             direction[0] = temp;
   1818 
   1819             // Now we try pushing in each component of the opposite direction
   1820             direction[0] *= -1;
   1821             direction[1] *= -1;
   1822             temp = direction[1];
   1823             direction[1] = 0;
   1824             if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
   1825                     ignoreView, solution)) {
   1826                 return true;
   1827             }
   1828 
   1829             direction[1] = temp;
   1830             temp = direction[0];
   1831             direction[0] = 0;
   1832             if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
   1833                     ignoreView, solution)) {
   1834                 return true;
   1835             }
   1836             // revert the direction
   1837             direction[0] = temp;
   1838             direction[0] *= -1;
   1839             direction[1] *= -1;
   1840 
   1841         } else {
   1842             // If the direction vector has a single non-zero component, we push first in the
   1843             // direction of the vector
   1844             if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
   1845                     ignoreView, solution)) {
   1846                 return true;
   1847             }
   1848 
   1849             // Then we try the opposite direction
   1850             direction[0] *= -1;
   1851             direction[1] *= -1;
   1852             if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
   1853                     ignoreView, solution)) {
   1854                 return true;
   1855             }
   1856             // Switch the direction back
   1857             direction[0] *= -1;
   1858             direction[1] *= -1;
   1859 
   1860             // If we have failed to find a push solution with the above, then we try
   1861             // to find a solution by pushing along the perpendicular axis.
   1862 
   1863             // Swap the components
   1864             int temp = direction[1];
   1865             direction[1] = direction[0];
   1866             direction[0] = temp;
   1867             if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
   1868                     ignoreView, solution)) {
   1869                 return true;
   1870             }
   1871 
   1872             // Then we try the opposite direction
   1873             direction[0] *= -1;
   1874             direction[1] *= -1;
   1875             if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
   1876                     ignoreView, solution)) {
   1877                 return true;
   1878             }
   1879             // Switch the direction back
   1880             direction[0] *= -1;
   1881             direction[1] *= -1;
   1882 
   1883             // Swap the components back
   1884             temp = direction[1];
   1885             direction[1] = direction[0];
   1886             direction[0] = temp;
   1887         }
   1888         return false;
   1889     }
   1890 
   1891     private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
   1892             View ignoreView, ItemConfiguration solution) {
   1893         // Return early if get invalid cell positions
   1894         if (cellX < 0 || cellY < 0) return false;
   1895 
   1896         mIntersectingViews.clear();
   1897         mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
   1898 
   1899         // Mark the desired location of the view currently being dragged.
   1900         if (ignoreView != null) {
   1901             CellAndSpan c = solution.map.get(ignoreView);
   1902             if (c != null) {
   1903                 c.x = cellX;
   1904                 c.y = cellY;
   1905             }
   1906         }
   1907         Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
   1908         Rect r1 = new Rect();
   1909         for (View child: solution.map.keySet()) {
   1910             if (child == ignoreView) continue;
   1911             CellAndSpan c = solution.map.get(child);
   1912             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   1913             r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
   1914             if (Rect.intersects(r0, r1)) {
   1915                 if (!lp.canReorder) {
   1916                     return false;
   1917                 }
   1918                 mIntersectingViews.add(child);
   1919             }
   1920         }
   1921 
   1922         // First we try to find a solution which respects the push mechanic. That is,
   1923         // we try to find a solution such that no displaced item travels through another item
   1924         // without also displacing that item.
   1925         if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
   1926                 solution)) {
   1927             return true;
   1928         }
   1929 
   1930         // Next we try moving the views as a block, but without requiring the push mechanic.
   1931         if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, false, ignoreView,
   1932                 solution)) {
   1933             return true;
   1934         }
   1935 
   1936         // Ok, they couldn't move as a block, let's move them individually
   1937         for (View v : mIntersectingViews) {
   1938             if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
   1939                 return false;
   1940             }
   1941         }
   1942         return true;
   1943     }
   1944 
   1945     /*
   1946      * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
   1947      * the provided point and the provided cell
   1948      */
   1949     private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
   1950         double angle = Math.atan(((float) deltaY) / deltaX);
   1951 
   1952         result[0] = 0;
   1953         result[1] = 0;
   1954         if (Math.abs(Math.cos(angle)) > 0.5f) {
   1955             result[0] = (int) Math.signum(deltaX);
   1956         }
   1957         if (Math.abs(Math.sin(angle)) > 0.5f) {
   1958             result[1] = (int) Math.signum(deltaY);
   1959         }
   1960     }
   1961 
   1962     private void copyOccupiedArray(boolean[][] occupied) {
   1963         for (int i = 0; i < mCountX; i++) {
   1964             for (int j = 0; j < mCountY; j++) {
   1965                 occupied[i][j] = mOccupied[i][j];
   1966             }
   1967         }
   1968     }
   1969 
   1970     ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
   1971             int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
   1972         // Copy the current state into the solution. This solution will be manipulated as necessary.
   1973         copyCurrentStateToSolution(solution, false);
   1974         // Copy the current occupied array into the temporary occupied array. This array will be
   1975         // manipulated as necessary to find a solution.
   1976         copyOccupiedArray(mTmpOccupied);
   1977 
   1978         // We find the nearest cell into which we would place the dragged item, assuming there's
   1979         // nothing in its way.
   1980         int result[] = new int[2];
   1981         result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
   1982 
   1983         boolean success = false;
   1984         // First we try the exact nearest position of the item being dragged,
   1985         // we will then want to try to move this around to other neighbouring positions
   1986         success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
   1987                 solution);
   1988 
   1989         if (!success) {
   1990             // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
   1991             // x, then 1 in y etc.
   1992             if (spanX > minSpanX && (minSpanY == spanY || decX)) {
   1993                 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
   1994                         dragView, false, solution);
   1995             } else if (spanY > minSpanY) {
   1996                 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
   1997                         dragView, true, solution);
   1998             }
   1999             solution.isSolution = false;
   2000         } else {
   2001             solution.isSolution = true;
   2002             solution.dragViewX = result[0];
   2003             solution.dragViewY = result[1];
   2004             solution.dragViewSpanX = spanX;
   2005             solution.dragViewSpanY = spanY;
   2006         }
   2007         return solution;
   2008     }
   2009 
   2010     private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
   2011         int childCount = mShortcutsAndWidgets.getChildCount();
   2012         for (int i = 0; i < childCount; i++) {
   2013             View child = mShortcutsAndWidgets.getChildAt(i);
   2014             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   2015             CellAndSpan c;
   2016             if (temp) {
   2017                 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
   2018             } else {
   2019                 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
   2020             }
   2021             solution.map.put(child, c);
   2022         }
   2023     }
   2024 
   2025     private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
   2026         for (int i = 0; i < mCountX; i++) {
   2027             for (int j = 0; j < mCountY; j++) {
   2028                 mTmpOccupied[i][j] = false;
   2029             }
   2030         }
   2031 
   2032         int childCount = mShortcutsAndWidgets.getChildCount();
   2033         for (int i = 0; i < childCount; i++) {
   2034             View child = mShortcutsAndWidgets.getChildAt(i);
   2035             if (child == dragView) continue;
   2036             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   2037             CellAndSpan c = solution.map.get(child);
   2038             if (c != null) {
   2039                 lp.tmpCellX = c.x;
   2040                 lp.tmpCellY = c.y;
   2041                 lp.cellHSpan = c.spanX;
   2042                 lp.cellVSpan = c.spanY;
   2043                 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
   2044             }
   2045         }
   2046         markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
   2047                 solution.dragViewSpanY, mTmpOccupied, true);
   2048     }
   2049 
   2050     private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
   2051             commitDragView) {
   2052 
   2053         boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
   2054         for (int i = 0; i < mCountX; i++) {
   2055             for (int j = 0; j < mCountY; j++) {
   2056                 occupied[i][j] = false;
   2057             }
   2058         }
   2059 
   2060         int childCount = mShortcutsAndWidgets.getChildCount();
   2061         for (int i = 0; i < childCount; i++) {
   2062             View child = mShortcutsAndWidgets.getChildAt(i);
   2063             if (child == dragView) continue;
   2064             CellAndSpan c = solution.map.get(child);
   2065             if (c != null) {
   2066                 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
   2067                         DESTRUCTIVE_REORDER, false);
   2068                 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
   2069             }
   2070         }
   2071         if (commitDragView) {
   2072             markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
   2073                     solution.dragViewSpanY, occupied, true);
   2074         }
   2075     }
   2076 
   2077     // This method starts or changes the reorder hint animations
   2078     private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
   2079         int childCount = mShortcutsAndWidgets.getChildCount();
   2080         for (int i = 0; i < childCount; i++) {
   2081             View child = mShortcutsAndWidgets.getChildAt(i);
   2082             if (child == dragView) continue;
   2083             CellAndSpan c = solution.map.get(child);
   2084             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   2085             if (c != null) {
   2086                 ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
   2087                         c.x, c.y, c.spanX, c.spanY);
   2088                 rha.animate();
   2089             }
   2090         }
   2091     }
   2092 
   2093     // Class which represents the reorder hint animations. These animations show that an item is
   2094     // in a temporary state, and hint at where the item will return to.
   2095     class ReorderHintAnimation {
   2096         View child;
   2097         float finalDeltaX;
   2098         float finalDeltaY;
   2099         float initDeltaX;
   2100         float initDeltaY;
   2101         float finalScale;
   2102         float initScale;
   2103         private static final int DURATION = 300;
   2104         Animator a;
   2105 
   2106         public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
   2107                 int spanX, int spanY) {
   2108             regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
   2109             final int x0 = mTmpPoint[0];
   2110             final int y0 = mTmpPoint[1];
   2111             regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
   2112             final int x1 = mTmpPoint[0];
   2113             final int y1 = mTmpPoint[1];
   2114             final int dX = x1 - x0;
   2115             final int dY = y1 - y0;
   2116             finalDeltaX = 0;
   2117             finalDeltaY = 0;
   2118             if (dX == dY && dX == 0) {
   2119             } else {
   2120                 if (dY == 0) {
   2121                     finalDeltaX = - Math.signum(dX) * mReorderHintAnimationMagnitude;
   2122                 } else if (dX == 0) {
   2123                     finalDeltaY = - Math.signum(dY) * mReorderHintAnimationMagnitude;
   2124                 } else {
   2125                     double angle = Math.atan( (float) (dY) / dX);
   2126                     finalDeltaX = (int) (- Math.signum(dX) *
   2127                             Math.abs(Math.cos(angle) * mReorderHintAnimationMagnitude));
   2128                     finalDeltaY = (int) (- Math.signum(dY) *
   2129                             Math.abs(Math.sin(angle) * mReorderHintAnimationMagnitude));
   2130                 }
   2131             }
   2132             initDeltaX = child.getTranslationX();
   2133             initDeltaY = child.getTranslationY();
   2134             finalScale = getChildrenScale() - 4.0f / child.getWidth();
   2135             initScale = child.getScaleX();
   2136             this.child = child;
   2137         }
   2138 
   2139         void animate() {
   2140             if (mShakeAnimators.containsKey(child)) {
   2141                 ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
   2142                 oldAnimation.cancel();
   2143                 mShakeAnimators.remove(child);
   2144                 if (finalDeltaX == 0 && finalDeltaY == 0) {
   2145                     completeAnimationImmediately();
   2146                     return;
   2147                 }
   2148             }
   2149             if (finalDeltaX == 0 && finalDeltaY == 0) {
   2150                 return;
   2151             }
   2152             ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
   2153             a = va;
   2154             va.setRepeatMode(ValueAnimator.REVERSE);
   2155             va.setRepeatCount(ValueAnimator.INFINITE);
   2156             va.setDuration(DURATION);
   2157             va.setStartDelay((int) (Math.random() * 60));
   2158             va.addUpdateListener(new AnimatorUpdateListener() {
   2159                 @Override
   2160                 public void onAnimationUpdate(ValueAnimator animation) {
   2161                     float r = ((Float) animation.getAnimatedValue()).floatValue();
   2162                     float x = r * finalDeltaX + (1 - r) * initDeltaX;
   2163                     float y = r * finalDeltaY + (1 - r) * initDeltaY;
   2164                     child.setTranslationX(x);
   2165                     child.setTranslationY(y);
   2166                     float s = r * finalScale + (1 - r) * initScale;
   2167                     child.setScaleX(s);
   2168                     child.setScaleY(s);
   2169                 }
   2170             });
   2171             va.addListener(new AnimatorListenerAdapter() {
   2172                 public void onAnimationRepeat(Animator animation) {
   2173                     // We make sure to end only after a full period
   2174                     initDeltaX = 0;
   2175                     initDeltaY = 0;
   2176                     initScale = getChildrenScale();
   2177                 }
   2178             });
   2179             mShakeAnimators.put(child, this);
   2180             va.start();
   2181         }
   2182 
   2183         private void cancel() {
   2184             if (a != null) {
   2185                 a.cancel();
   2186             }
   2187         }
   2188 
   2189         private void completeAnimationImmediately() {
   2190             if (a != null) {
   2191                 a.cancel();
   2192             }
   2193 
   2194             AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
   2195             a = s;
   2196             s.playTogether(
   2197                 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
   2198                 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
   2199                 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
   2200                 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
   2201             );
   2202             s.setDuration(REORDER_ANIMATION_DURATION);
   2203             s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
   2204             s.start();
   2205         }
   2206     }
   2207 
   2208     private void completeAndClearReorderHintAnimations() {
   2209         for (ReorderHintAnimation a: mShakeAnimators.values()) {
   2210             a.completeAnimationImmediately();
   2211         }
   2212         mShakeAnimators.clear();
   2213     }
   2214 
   2215     private void commitTempPlacement() {
   2216         for (int i = 0; i < mCountX; i++) {
   2217             for (int j = 0; j < mCountY; j++) {
   2218                 mOccupied[i][j] = mTmpOccupied[i][j];
   2219             }
   2220         }
   2221         int childCount = mShortcutsAndWidgets.getChildCount();
   2222         for (int i = 0; i < childCount; i++) {
   2223             View child = mShortcutsAndWidgets.getChildAt(i);
   2224             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   2225             ItemInfo info = (ItemInfo) child.getTag();
   2226             // We do a null check here because the item info can be null in the case of the
   2227             // AllApps button in the hotseat.
   2228             if (info != null) {
   2229                 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
   2230                         info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
   2231                     info.requiresDbUpdate = true;
   2232                 }
   2233                 info.cellX = lp.cellX = lp.tmpCellX;
   2234                 info.cellY = lp.cellY = lp.tmpCellY;
   2235                 info.spanX = lp.cellHSpan;
   2236                 info.spanY = lp.cellVSpan;
   2237             }
   2238         }
   2239         mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
   2240     }
   2241 
   2242     public void setUseTempCoords(boolean useTempCoords) {
   2243         int childCount = mShortcutsAndWidgets.getChildCount();
   2244         for (int i = 0; i < childCount; i++) {
   2245             LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
   2246             lp.useTmpCoords = useTempCoords;
   2247         }
   2248     }
   2249 
   2250     ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
   2251             int spanX, int spanY, View dragView, ItemConfiguration solution) {
   2252         int[] result = new int[2];
   2253         int[] resultSpan = new int[2];
   2254         findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
   2255                 resultSpan);
   2256         if (result[0] >= 0 && result[1] >= 0) {
   2257             copyCurrentStateToSolution(solution, false);
   2258             solution.dragViewX = result[0];
   2259             solution.dragViewY = result[1];
   2260             solution.dragViewSpanX = resultSpan[0];
   2261             solution.dragViewSpanY = resultSpan[1];
   2262             solution.isSolution = true;
   2263         } else {
   2264             solution.isSolution = false;
   2265         }
   2266         return solution;
   2267     }
   2268 
   2269     public void prepareChildForDrag(View child) {
   2270         markCellsAsUnoccupiedForView(child);
   2271     }
   2272 
   2273     /* This seems like it should be obvious and straight-forward, but when the direction vector
   2274     needs to match with the notion of the dragView pushing other views, we have to employ
   2275     a slightly more subtle notion of the direction vector. The question is what two points is
   2276     the vector between? The center of the dragView and its desired destination? Not quite, as
   2277     this doesn't necessarily coincide with the interaction of the dragView and items occupying
   2278     those cells. Instead we use some heuristics to often lock the vector to up, down, left
   2279     or right, which helps make pushing feel right.
   2280     */
   2281     private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
   2282             int spanY, View dragView, int[] resultDirection) {
   2283         int[] targetDestination = new int[2];
   2284 
   2285         findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
   2286         Rect dragRect = new Rect();
   2287         regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
   2288         dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
   2289 
   2290         Rect dropRegionRect = new Rect();
   2291         getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
   2292                 dragView, dropRegionRect, mIntersectingViews);
   2293 
   2294         int dropRegionSpanX = dropRegionRect.width();
   2295         int dropRegionSpanY = dropRegionRect.height();
   2296 
   2297         regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
   2298                 dropRegionRect.height(), dropRegionRect);
   2299 
   2300         int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
   2301         int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
   2302 
   2303         if (dropRegionSpanX == mCountX || spanX == mCountX) {
   2304             deltaX = 0;
   2305         }
   2306         if (dropRegionSpanY == mCountY || spanY == mCountY) {
   2307             deltaY = 0;
   2308         }
   2309 
   2310         if (deltaX == 0 && deltaY == 0) {
   2311             // No idea what to do, give a random direction.
   2312             resultDirection[0] = 1;
   2313             resultDirection[1] = 0;
   2314         } else {
   2315             computeDirectionVector(deltaX, deltaY, resultDirection);
   2316         }
   2317     }
   2318 
   2319     // For a given cell and span, fetch the set of views intersecting the region.
   2320     private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
   2321             View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
   2322         if (boundingRect != null) {
   2323             boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
   2324         }
   2325         intersectingViews.clear();
   2326         Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
   2327         Rect r1 = new Rect();
   2328         final int count = mShortcutsAndWidgets.getChildCount();
   2329         for (int i = 0; i < count; i++) {
   2330             View child = mShortcutsAndWidgets.getChildAt(i);
   2331             if (child == dragView) continue;
   2332             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   2333             r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
   2334             if (Rect.intersects(r0, r1)) {
   2335                 mIntersectingViews.add(child);
   2336                 if (boundingRect != null) {
   2337                     boundingRect.union(r1);
   2338                 }
   2339             }
   2340         }
   2341     }
   2342 
   2343     boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
   2344             View dragView, int[] result) {
   2345         result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
   2346         getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
   2347                 mIntersectingViews);
   2348         return !mIntersectingViews.isEmpty();
   2349     }
   2350 
   2351     void revertTempState() {
   2352         if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
   2353         final int count = mShortcutsAndWidgets.getChildCount();
   2354         for (int i = 0; i < count; i++) {
   2355             View child = mShortcutsAndWidgets.getChildAt(i);
   2356             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   2357             if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
   2358                 lp.tmpCellX = lp.cellX;
   2359                 lp.tmpCellY = lp.cellY;
   2360                 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
   2361                         0, false, false);
   2362             }
   2363         }
   2364         completeAndClearReorderHintAnimations();
   2365         setItemPlacementDirty(false);
   2366     }
   2367 
   2368     boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
   2369             View dragView, int[] direction, boolean commit) {
   2370         int[] pixelXY = new int[2];
   2371         regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
   2372 
   2373         // First we determine if things have moved enough to cause a different layout
   2374         ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY,
   2375                  spanX,  spanY, direction, dragView,  true,  new ItemConfiguration());
   2376 
   2377         setUseTempCoords(true);
   2378         if (swapSolution != null && swapSolution.isSolution) {
   2379             // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
   2380             // committing anything or animating anything as we just want to determine if a solution
   2381             // exists
   2382             copySolutionToTempState(swapSolution, dragView);
   2383             setItemPlacementDirty(true);
   2384             animateItemsToSolution(swapSolution, dragView, commit);
   2385 
   2386             if (commit) {
   2387                 commitTempPlacement();
   2388                 completeAndClearReorderHintAnimations();
   2389                 setItemPlacementDirty(false);
   2390             } else {
   2391                 beginOrAdjustHintAnimations(swapSolution, dragView,
   2392                         REORDER_ANIMATION_DURATION);
   2393             }
   2394             mShortcutsAndWidgets.requestLayout();
   2395         }
   2396         return swapSolution.isSolution;
   2397     }
   2398 
   2399     int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
   2400             View dragView, int[] result, int resultSpan[], int mode) {
   2401         // First we determine if things have moved enough to cause a different layout
   2402         result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
   2403 
   2404         if (resultSpan == null) {
   2405             resultSpan = new int[2];
   2406         }
   2407 
   2408         // When we are checking drop validity or actually dropping, we don't recompute the
   2409         // direction vector, since we want the solution to match the preview, and it's possible
   2410         // that the exact position of the item has changed to result in a new reordering outcome.
   2411         if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
   2412                && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
   2413             mDirectionVector[0] = mPreviousReorderDirection[0];
   2414             mDirectionVector[1] = mPreviousReorderDirection[1];
   2415             // We reset this vector after drop
   2416             if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
   2417                 mPreviousReorderDirection[0] = INVALID_DIRECTION;
   2418                 mPreviousReorderDirection[1] = INVALID_DIRECTION;
   2419             }
   2420         } else {
   2421             getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
   2422             mPreviousReorderDirection[0] = mDirectionVector[0];
   2423             mPreviousReorderDirection[1] = mDirectionVector[1];
   2424         }
   2425 
   2426         ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
   2427                  spanX,  spanY, mDirectionVector, dragView,  true,  new ItemConfiguration());
   2428 
   2429         // We attempt the approach which doesn't shuffle views at all
   2430         ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
   2431                 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
   2432 
   2433         ItemConfiguration finalSolution = null;
   2434         if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
   2435             finalSolution = swapSolution;
   2436         } else if (noShuffleSolution.isSolution) {
   2437             finalSolution = noShuffleSolution;
   2438         }
   2439 
   2440         boolean foundSolution = true;
   2441         if (!DESTRUCTIVE_REORDER) {
   2442             setUseTempCoords(true);
   2443         }
   2444 
   2445         if (finalSolution != null) {
   2446             result[0] = finalSolution.dragViewX;
   2447             result[1] = finalSolution.dragViewY;
   2448             resultSpan[0] = finalSolution.dragViewSpanX;
   2449             resultSpan[1] = finalSolution.dragViewSpanY;
   2450 
   2451             // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
   2452             // committing anything or animating anything as we just want to determine if a solution
   2453             // exists
   2454             if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
   2455                 if (!DESTRUCTIVE_REORDER) {
   2456                     copySolutionToTempState(finalSolution, dragView);
   2457                 }
   2458                 setItemPlacementDirty(true);
   2459                 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
   2460 
   2461                 if (!DESTRUCTIVE_REORDER &&
   2462                         (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
   2463                     commitTempPlacement();
   2464                     completeAndClearReorderHintAnimations();
   2465                     setItemPlacementDirty(false);
   2466                 } else {
   2467                     beginOrAdjustHintAnimations(finalSolution, dragView,
   2468                             REORDER_ANIMATION_DURATION);
   2469                 }
   2470             }
   2471         } else {
   2472             foundSolution = false;
   2473             result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
   2474         }
   2475 
   2476         if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
   2477             setUseTempCoords(false);
   2478         }
   2479 
   2480         mShortcutsAndWidgets.requestLayout();
   2481         return result;
   2482     }
   2483 
   2484     void setItemPlacementDirty(boolean dirty) {
   2485         mItemPlacementDirty = dirty;
   2486     }
   2487     boolean isItemPlacementDirty() {
   2488         return mItemPlacementDirty;
   2489     }
   2490 
   2491     private class ItemConfiguration {
   2492         HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
   2493         boolean isSolution = false;
   2494         int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
   2495 
   2496         int area() {
   2497             return dragViewSpanX * dragViewSpanY;
   2498         }
   2499     }
   2500 
   2501     private class CellAndSpan {
   2502         int x, y;
   2503         int spanX, spanY;
   2504 
   2505         public CellAndSpan(int x, int y, int spanX, int spanY) {
   2506             this.x = x;
   2507             this.y = y;
   2508             this.spanX = spanX;
   2509             this.spanY = spanY;
   2510         }
   2511     }
   2512 
   2513     /**
   2514      * Find a vacant area that will fit the given bounds nearest the requested
   2515      * cell location. Uses Euclidean distance to score multiple vacant areas.
   2516      *
   2517      * @param pixelX The X location at which you want to search for a vacant area.
   2518      * @param pixelY The Y location at which you want to search for a vacant area.
   2519      * @param spanX Horizontal span of the object.
   2520      * @param spanY Vertical span of the object.
   2521      * @param ignoreView Considers space occupied by this view as unoccupied
   2522      * @param result Previously returned value to possibly recycle.
   2523      * @return The X, Y cell of a vacant area that can contain this object,
   2524      *         nearest the requested location.
   2525      */
   2526     int[] findNearestVacantArea(
   2527             int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
   2528         return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
   2529     }
   2530 
   2531     /**
   2532      * Find a vacant area that will fit the given bounds nearest the requested
   2533      * cell location. Uses Euclidean distance to score multiple vacant areas.
   2534      *
   2535      * @param pixelX The X location at which you want to search for a vacant area.
   2536      * @param pixelY The Y location at which you want to search for a vacant area.
   2537      * @param minSpanX The minimum horizontal span required
   2538      * @param minSpanY The minimum vertical span required
   2539      * @param spanX Horizontal span of the object.
   2540      * @param spanY Vertical span of the object.
   2541      * @param ignoreView Considers space occupied by this view as unoccupied
   2542      * @param result Previously returned value to possibly recycle.
   2543      * @return The X, Y cell of a vacant area that can contain this object,
   2544      *         nearest the requested location.
   2545      */
   2546     int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
   2547             int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
   2548         return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
   2549                 result, resultSpan, mOccupied);
   2550     }
   2551 
   2552     /**
   2553      * Find a starting cell position that will fit the given bounds nearest the requested
   2554      * cell location. Uses Euclidean distance to score multiple vacant areas.
   2555      *
   2556      * @param pixelX The X location at which you want to search for a vacant area.
   2557      * @param pixelY The Y location at which you want to search for a vacant area.
   2558      * @param spanX Horizontal span of the object.
   2559      * @param spanY Vertical span of the object.
   2560      * @param ignoreView Considers space occupied by this view as unoccupied
   2561      * @param result Previously returned value to possibly recycle.
   2562      * @return The X, Y cell of a vacant area that can contain this object,
   2563      *         nearest the requested location.
   2564      */
   2565     int[] findNearestArea(
   2566             int pixelX, int pixelY, int spanX, int spanY, int[] result) {
   2567         return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
   2568     }
   2569 
   2570     boolean existsEmptyCell() {
   2571         return findCellForSpan(null, 1, 1);
   2572     }
   2573 
   2574     /**
   2575      * Finds the upper-left coordinate of the first rectangle in the grid that can
   2576      * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
   2577      * then this method will only return coordinates for rectangles that contain the cell
   2578      * (intersectX, intersectY)
   2579      *
   2580      * @param cellXY The array that will contain the position of a vacant cell if such a cell
   2581      *               can be found.
   2582      * @param spanX The horizontal span of the cell we want to find.
   2583      * @param spanY The vertical span of the cell we want to find.
   2584      *
   2585      * @return True if a vacant cell of the specified dimension was found, false otherwise.
   2586      */
   2587     boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
   2588         return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
   2589     }
   2590 
   2591     /**
   2592      * Like above, but ignores any cells occupied by the item "ignoreView"
   2593      *
   2594      * @param cellXY The array that will contain the position of a vacant cell if such a cell
   2595      *               can be found.
   2596      * @param spanX The horizontal span of the cell we want to find.
   2597      * @param spanY The vertical span of the cell we want to find.
   2598      * @param ignoreView The home screen item we should treat as not occupying any space
   2599      * @return
   2600      */
   2601     boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
   2602         return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
   2603                 ignoreView, mOccupied);
   2604     }
   2605 
   2606     /**
   2607      * Like above, but if intersectX and intersectY are not -1, then this method will try to
   2608      * return coordinates for rectangles that contain the cell [intersectX, intersectY]
   2609      *
   2610      * @param spanX The horizontal span of the cell we want to find.
   2611      * @param spanY The vertical span of the cell we want to find.
   2612      * @param ignoreView The home screen item we should treat as not occupying any space
   2613      * @param intersectX The X coordinate of the cell that we should try to overlap
   2614      * @param intersectX The Y coordinate of the cell that we should try to overlap
   2615      *
   2616      * @return True if a vacant cell of the specified dimension was found, false otherwise.
   2617      */
   2618     boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
   2619             int intersectX, int intersectY) {
   2620         return findCellForSpanThatIntersectsIgnoring(
   2621                 cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
   2622     }
   2623 
   2624     /**
   2625      * The superset of the above two methods
   2626      */
   2627     boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
   2628             int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
   2629         // mark space take by ignoreView as available (method checks if ignoreView is null)
   2630         markCellsAsUnoccupiedForView(ignoreView, occupied);
   2631 
   2632         boolean foundCell = false;
   2633         while (true) {
   2634             int startX = 0;
   2635             if (intersectX >= 0) {
   2636                 startX = Math.max(startX, intersectX - (spanX - 1));
   2637             }
   2638             int endX = mCountX - (spanX - 1);
   2639             if (intersectX >= 0) {
   2640                 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
   2641             }
   2642             int startY = 0;
   2643             if (intersectY >= 0) {
   2644                 startY = Math.max(startY, intersectY - (spanY - 1));
   2645             }
   2646             int endY = mCountY - (spanY - 1);
   2647             if (intersectY >= 0) {
   2648                 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
   2649             }
   2650 
   2651             for (int y = startY; y < endY && !foundCell; y++) {
   2652                 inner:
   2653                 for (int x = startX; x < endX; x++) {
   2654                     for (int i = 0; i < spanX; i++) {
   2655                         for (int j = 0; j < spanY; j++) {
   2656                             if (occupied[x + i][y + j]) {
   2657                                 // small optimization: we can skip to after the column we just found
   2658                                 // an occupied cell
   2659                                 x += i;
   2660                                 continue inner;
   2661                             }
   2662                         }
   2663                     }
   2664                     if (cellXY != null) {
   2665                         cellXY[0] = x;
   2666                         cellXY[1] = y;
   2667                     }
   2668                     foundCell = true;
   2669                     break;
   2670                 }
   2671             }
   2672             if (intersectX == -1 && intersectY == -1) {
   2673                 break;
   2674             } else {
   2675                 // if we failed to find anything, try again but without any requirements of
   2676                 // intersecting
   2677                 intersectX = -1;
   2678                 intersectY = -1;
   2679                 continue;
   2680             }
   2681         }
   2682 
   2683         // re-mark space taken by ignoreView as occupied
   2684         markCellsAsOccupiedForView(ignoreView, occupied);
   2685         return foundCell;
   2686     }
   2687 
   2688     /**
   2689      * A drag event has begun over this layout.
   2690      * It may have begun over this layout (in which case onDragChild is called first),
   2691      * or it may have begun on another layout.
   2692      */
   2693     void onDragEnter() {
   2694         mDragEnforcer.onDragEnter();
   2695         mDragging = true;
   2696     }
   2697 
   2698     /**
   2699      * Called when drag has left this CellLayout or has been completed (successfully or not)
   2700      */
   2701     void onDragExit() {
   2702         mDragEnforcer.onDragExit();
   2703         // This can actually be called when we aren't in a drag, e.g. when adding a new
   2704         // item to this layout via the customize drawer.
   2705         // Guard against that case.
   2706         if (mDragging) {
   2707             mDragging = false;
   2708         }
   2709 
   2710         // Invalidate the drag data
   2711         mDragCell[0] = mDragCell[1] = -1;
   2712         mDragOutlineAnims[mDragOutlineCurrent].animateOut();
   2713         mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
   2714         revertTempState();
   2715         setIsDragOverlapping(false);
   2716     }
   2717 
   2718     /**
   2719      * Mark a child as having been dropped.
   2720      * At the beginning of the drag operation, the child may have been on another
   2721      * screen, but it is re-parented before this method is called.
   2722      *
   2723      * @param child The child that is being dropped
   2724      */
   2725     void onDropChild(View child) {
   2726         if (child != null) {
   2727             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   2728             lp.dropped = true;
   2729             child.requestLayout();
   2730         }
   2731     }
   2732 
   2733     /**
   2734      * Computes a bounding rectangle for a range of cells
   2735      *
   2736      * @param cellX X coordinate of upper left corner expressed as a cell position
   2737      * @param cellY Y coordinate of upper left corner expressed as a cell position
   2738      * @param cellHSpan Width in cells
   2739      * @param cellVSpan Height in cells
   2740      * @param resultRect Rect into which to put the results
   2741      */
   2742     public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
   2743         final int cellWidth = mCellWidth;
   2744         final int cellHeight = mCellHeight;
   2745         final int widthGap = mWidthGap;
   2746         final int heightGap = mHeightGap;
   2747 
   2748         final int hStartPadding = getPaddingLeft();
   2749         final int vStartPadding = getPaddingTop();
   2750 
   2751         int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
   2752         int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
   2753 
   2754         int x = hStartPadding + cellX * (cellWidth + widthGap);
   2755         int y = vStartPadding + cellY * (cellHeight + heightGap);
   2756 
   2757         resultRect.set(x, y, x + width, y + height);
   2758     }
   2759 
   2760     /**
   2761      * Computes the required horizontal and vertical cell spans to always
   2762      * fit the given rectangle.
   2763      *
   2764      * @param width Width in pixels
   2765      * @param height Height in pixels
   2766      * @param result An array of length 2 in which to store the result (may be null).
   2767      */
   2768     public int[] rectToCell(int width, int height, int[] result) {
   2769         return rectToCell(getResources(), width, height, result);
   2770     }
   2771 
   2772     public static int[] rectToCell(Resources resources, int width, int height, int[] result) {
   2773         // Always assume we're working with the smallest span to make sure we
   2774         // reserve enough space in both orientations.
   2775         int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
   2776         int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
   2777         int smallerSize = Math.min(actualWidth, actualHeight);
   2778 
   2779         // Always round up to next largest cell
   2780         int spanX = (int) Math.ceil(width / (float) smallerSize);
   2781         int spanY = (int) Math.ceil(height / (float) smallerSize);
   2782 
   2783         if (result == null) {
   2784             return new int[] { spanX, spanY };
   2785         }
   2786         result[0] = spanX;
   2787         result[1] = spanY;
   2788         return result;
   2789     }
   2790 
   2791     public int[] cellSpansToSize(int hSpans, int vSpans) {
   2792         int[] size = new int[2];
   2793         size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
   2794         size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
   2795         return size;
   2796     }
   2797 
   2798     /**
   2799      * Calculate the grid spans needed to fit given item
   2800      */
   2801     public void calculateSpans(ItemInfo info) {
   2802         final int minWidth;
   2803         final int minHeight;
   2804 
   2805         if (info instanceof LauncherAppWidgetInfo) {
   2806             minWidth = ((LauncherAppWidgetInfo) info).minWidth;
   2807             minHeight = ((LauncherAppWidgetInfo) info).minHeight;
   2808         } else if (info instanceof PendingAddWidgetInfo) {
   2809             minWidth = ((PendingAddWidgetInfo) info).minWidth;
   2810             minHeight = ((PendingAddWidgetInfo) info).minHeight;
   2811         } else {
   2812             // It's not a widget, so it must be 1x1
   2813             info.spanX = info.spanY = 1;
   2814             return;
   2815         }
   2816         int[] spans = rectToCell(minWidth, minHeight, null);
   2817         info.spanX = spans[0];
   2818         info.spanY = spans[1];
   2819     }
   2820 
   2821     /**
   2822      * Find the first vacant cell, if there is one.
   2823      *
   2824      * @param vacant Holds the x and y coordinate of the vacant cell
   2825      * @param spanX Horizontal cell span.
   2826      * @param spanY Vertical cell span.
   2827      *
   2828      * @return True if a vacant cell was found
   2829      */
   2830     public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
   2831 
   2832         return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
   2833     }
   2834 
   2835     static boolean findVacantCell(int[] vacant, int spanX, int spanY,
   2836             int xCount, int yCount, boolean[][] occupied) {
   2837 
   2838         for (int y = 0; y < yCount; y++) {
   2839             for (int x = 0; x < xCount; x++) {
   2840                 boolean available = !occupied[x][y];
   2841 out:            for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
   2842                     for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
   2843                         available = available && !occupied[i][j];
   2844                         if (!available) break out;
   2845                     }
   2846                 }
   2847 
   2848                 if (available) {
   2849                     vacant[0] = x;
   2850                     vacant[1] = y;
   2851                     return true;
   2852                 }
   2853             }
   2854         }
   2855 
   2856         return false;
   2857     }
   2858 
   2859     private void clearOccupiedCells() {
   2860         for (int x = 0; x < mCountX; x++) {
   2861             for (int y = 0; y < mCountY; y++) {
   2862                 mOccupied[x][y] = false;
   2863             }
   2864         }
   2865     }
   2866 
   2867     public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
   2868         markCellsAsUnoccupiedForView(view);
   2869         markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
   2870     }
   2871 
   2872     public void markCellsAsOccupiedForView(View view) {
   2873         markCellsAsOccupiedForView(view, mOccupied);
   2874     }
   2875     public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
   2876         if (view == null || view.getParent() != mShortcutsAndWidgets) return;
   2877         LayoutParams lp = (LayoutParams) view.getLayoutParams();
   2878         markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
   2879     }
   2880 
   2881     public void markCellsAsUnoccupiedForView(View view) {
   2882         markCellsAsUnoccupiedForView(view, mOccupied);
   2883     }
   2884     public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
   2885         if (view == null || view.getParent() != mShortcutsAndWidgets) return;
   2886         LayoutParams lp = (LayoutParams) view.getLayoutParams();
   2887         markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
   2888     }
   2889 
   2890     private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
   2891             boolean value) {
   2892         if (cellX < 0 || cellY < 0) return;
   2893         for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
   2894             for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
   2895                 occupied[x][y] = value;
   2896             }
   2897         }
   2898     }
   2899 
   2900     public int getDesiredWidth() {
   2901         return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
   2902                 (Math.max((mCountX - 1), 0) * mWidthGap);
   2903     }
   2904 
   2905     public int getDesiredHeight()  {
   2906         return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
   2907                 (Math.max((mCountY - 1), 0) * mHeightGap);
   2908     }
   2909 
   2910     public boolean isOccupied(int x, int y) {
   2911         if (x < mCountX && y < mCountY) {
   2912             return mOccupied[x][y];
   2913         } else {
   2914             throw new RuntimeException("Position exceeds the bound of this CellLayout");
   2915         }
   2916     }
   2917 
   2918     @Override
   2919     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
   2920         return new CellLayout.LayoutParams(getContext(), attrs);
   2921     }
   2922 
   2923     @Override
   2924     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
   2925         return p instanceof CellLayout.LayoutParams;
   2926     }
   2927 
   2928     @Override
   2929     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
   2930         return new CellLayout.LayoutParams(p);
   2931     }
   2932 
   2933     public static class CellLayoutAnimationController extends LayoutAnimationController {
   2934         public CellLayoutAnimationController(Animation animation, float delay) {
   2935             super(animation, delay);
   2936         }
   2937 
   2938         @Override
   2939         protected long getDelayForView(View view) {
   2940             return (int) (Math.random() * 150);
   2941         }
   2942     }
   2943 
   2944     public static class LayoutParams extends ViewGroup.MarginLayoutParams {
   2945         /**
   2946          * Horizontal location of the item in the grid.
   2947          */
   2948         @ViewDebug.ExportedProperty
   2949         public int cellX;
   2950 
   2951         /**
   2952          * Vertical location of the item in the grid.
   2953          */
   2954         @ViewDebug.ExportedProperty
   2955         public int cellY;
   2956 
   2957         /**
   2958          * Temporary horizontal location of the item in the grid during reorder
   2959          */
   2960         public int tmpCellX;
   2961 
   2962         /**
   2963          * Temporary vertical location of the item in the grid during reorder
   2964          */
   2965         public int tmpCellY;
   2966 
   2967         /**
   2968          * Indicates that the temporary coordinates should be used to layout the items
   2969          */
   2970         public boolean useTmpCoords;
   2971 
   2972         /**
   2973          * Number of cells spanned horizontally by the item.
   2974          */
   2975         @ViewDebug.ExportedProperty
   2976         public int cellHSpan;
   2977 
   2978         /**
   2979          * Number of cells spanned vertically by the item.
   2980          */
   2981         @ViewDebug.ExportedProperty
   2982         public int cellVSpan;
   2983 
   2984         /**
   2985          * Indicates whether the item will set its x, y, width and height parameters freely,
   2986          * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
   2987          */
   2988         public boolean isLockedToGrid = true;
   2989 
   2990         /**
   2991          * Indicates whether this item can be reordered. Always true except in the case of the
   2992          * the AllApps button.
   2993          */
   2994         public boolean canReorder = true;
   2995 
   2996         // X coordinate of the view in the layout.
   2997         @ViewDebug.ExportedProperty
   2998         int x;
   2999         // Y coordinate of the view in the layout.
   3000         @ViewDebug.ExportedProperty
   3001         int y;
   3002 
   3003         boolean dropped;
   3004 
   3005         public LayoutParams(Context c, AttributeSet attrs) {
   3006             super(c, attrs);
   3007             cellHSpan = 1;
   3008             cellVSpan = 1;
   3009         }
   3010 
   3011         public LayoutParams(ViewGroup.LayoutParams source) {
   3012             super(source);
   3013             cellHSpan = 1;
   3014             cellVSpan = 1;
   3015         }
   3016 
   3017         public LayoutParams(LayoutParams source) {
   3018             super(source);
   3019             this.cellX = source.cellX;
   3020             this.cellY = source.cellY;
   3021             this.cellHSpan = source.cellHSpan;
   3022             this.cellVSpan = source.cellVSpan;
   3023         }
   3024 
   3025         public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
   3026             super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
   3027             this.cellX = cellX;
   3028             this.cellY = cellY;
   3029             this.cellHSpan = cellHSpan;
   3030             this.cellVSpan = cellVSpan;
   3031         }
   3032 
   3033         public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap) {
   3034             if (isLockedToGrid) {
   3035                 final int myCellHSpan = cellHSpan;
   3036                 final int myCellVSpan = cellVSpan;
   3037                 final int myCellX = useTmpCoords ? tmpCellX : cellX;
   3038                 final int myCellY = useTmpCoords ? tmpCellY : cellY;
   3039 
   3040                 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
   3041                         leftMargin - rightMargin;
   3042                 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
   3043                         topMargin - bottomMargin;
   3044                 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
   3045                 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
   3046             }
   3047         }
   3048 
   3049         public String toString() {
   3050             return "(" + this.cellX + ", " + this.cellY + ")";
   3051         }
   3052 
   3053         public void setWidth(int width) {
   3054             this.width = width;
   3055         }
   3056 
   3057         public int getWidth() {
   3058             return width;
   3059         }
   3060 
   3061         public void setHeight(int height) {
   3062             this.height = height;
   3063         }
   3064 
   3065         public int getHeight() {
   3066             return height;
   3067         }
   3068 
   3069         public void setX(int x) {
   3070             this.x = x;
   3071         }
   3072 
   3073         public int getX() {
   3074             return x;
   3075         }
   3076 
   3077         public void setY(int y) {
   3078             this.y = y;
   3079         }
   3080 
   3081         public int getY() {
   3082             return y;
   3083         }
   3084     }
   3085 
   3086     // This class stores info for two purposes:
   3087     // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
   3088     //    its spanX, spanY, and the screen it is on
   3089     // 2. When long clicking on an empty cell in a CellLayout, we save information about the
   3090     //    cellX and cellY coordinates and which page was clicked. We then set this as a tag on
   3091     //    the CellLayout that was long clicked
   3092     static final class CellInfo {
   3093         View cell;
   3094         int cellX = -1;
   3095         int cellY = -1;
   3096         int spanX;
   3097         int spanY;
   3098         int screen;
   3099         long container;
   3100 
   3101         @Override
   3102         public String toString() {
   3103             return "Cell[view=" + (cell == null ? "null" : cell.getClass())
   3104                     + ", x=" + cellX + ", y=" + cellY + "]";
   3105         }
   3106     }
   3107 
   3108     public boolean lastDownOnOccupiedCell() {
   3109         return mLastDownOnOccupiedCell;
   3110     }
   3111 }
   3112