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