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