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