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