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