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