• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static com.android.launcher3.dragndrop.DraggableView.DRAGGABLE_ICON;
20 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
21 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_REORDER_PREVIEW_OFFSET;
22 
23 import android.animation.Animator;
24 import android.animation.AnimatorListenerAdapter;
25 import android.animation.TimeInterpolator;
26 import android.animation.ValueAnimator;
27 import android.animation.ValueAnimator.AnimatorUpdateListener;
28 import android.annotation.SuppressLint;
29 import android.content.Context;
30 import android.content.res.Resources;
31 import android.content.res.TypedArray;
32 import android.graphics.Canvas;
33 import android.graphics.Color;
34 import android.graphics.Paint;
35 import android.graphics.Point;
36 import android.graphics.Rect;
37 import android.graphics.RectF;
38 import android.graphics.drawable.Drawable;
39 import android.os.Parcelable;
40 import android.util.ArrayMap;
41 import android.util.AttributeSet;
42 import android.util.FloatProperty;
43 import android.util.Log;
44 import android.util.SparseArray;
45 import android.view.MotionEvent;
46 import android.view.View;
47 import android.view.ViewDebug;
48 import android.view.ViewGroup;
49 import android.view.accessibility.AccessibilityEvent;
50 import android.view.accessibility.AccessibilityNodeInfo;
51 
52 import androidx.annotation.IntDef;
53 import androidx.annotation.Nullable;
54 import androidx.annotation.Px;
55 import androidx.core.graphics.ColorUtils;
56 import androidx.core.view.ViewCompat;
57 
58 import com.android.app.animation.Interpolators;
59 import com.android.launcher3.LauncherSettings.Favorites;
60 import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
61 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
62 import com.android.launcher3.celllayout.CellPosMapper.CellPos;
63 import com.android.launcher3.celllayout.DelegatedCellDrawing;
64 import com.android.launcher3.celllayout.ItemConfiguration;
65 import com.android.launcher3.celllayout.ReorderAlgorithm;
66 import com.android.launcher3.celllayout.ReorderParameters;
67 import com.android.launcher3.celllayout.ReorderPreviewAnimation;
68 import com.android.launcher3.config.FeatureFlags;
69 import com.android.launcher3.dragndrop.DraggableView;
70 import com.android.launcher3.folder.PreviewBackground;
71 import com.android.launcher3.model.data.ItemInfo;
72 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
73 import com.android.launcher3.util.CellAndSpan;
74 import com.android.launcher3.util.GridOccupancy;
75 import com.android.launcher3.util.MSDLPlayerWrapper;
76 import com.android.launcher3.util.MultiTranslateDelegate;
77 import com.android.launcher3.util.ParcelableSparseArray;
78 import com.android.launcher3.util.Themes;
79 import com.android.launcher3.util.Thunk;
80 import com.android.launcher3.views.ActivityContext;
81 import com.android.launcher3.widget.LauncherAppWidgetHostView;
82 
83 import com.google.android.msdl.data.model.MSDLToken;
84 
85 import java.lang.annotation.Retention;
86 import java.lang.annotation.RetentionPolicy;
87 import java.util.ArrayList;
88 import java.util.Arrays;
89 import java.util.Stack;
90 
91 public class CellLayout extends ViewGroup {
92     private static final String TAG = "CellLayout";
93     private static final boolean LOGD = true;
94 
95     /** The color of the "leave-behind" shape when a folder is opened from Hotseat. */
96     private static final int FOLDER_LEAVE_BEHIND_COLOR = Color.argb(160, 245, 245, 245);
97 
98     protected final ActivityContext mActivity;
99     @ViewDebug.ExportedProperty(category = "launcher")
100     @Thunk int mCellWidth;
101     @ViewDebug.ExportedProperty(category = "launcher")
102     @Thunk int mCellHeight;
103     private int mFixedCellWidth;
104     private int mFixedCellHeight;
105     @ViewDebug.ExportedProperty(category = "launcher")
106     protected Point mBorderSpace;
107 
108     @ViewDebug.ExportedProperty(category = "launcher")
109     protected int mCountX;
110     @ViewDebug.ExportedProperty(category = "launcher")
111     protected int mCountY;
112 
113     private boolean mDropPending = false;
114 
115     // These are temporary variables to prevent having to allocate a new object just to
116     // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
117     @Thunk final int[] mTmpPoint = new int[2];
118     @Thunk final int[] mTempLocation = new int[2];
119 
120     @Thunk final Rect mTempOnDrawCellToRect = new Rect();
121 
122     protected GridOccupancy mOccupied;
123     public GridOccupancy mTmpOccupied;
124 
125     private OnTouchListener mInterceptTouchListener;
126 
127     private final ArrayList<DelegatedCellDrawing> mDelegatedCellDrawings = new ArrayList<>();
128     final PreviewBackground mFolderLeaveBehind = new PreviewBackground(getContext());
129 
130     private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active };
131     private static final int[] BACKGROUND_STATE_DEFAULT = EMPTY_STATE_SET;
132     protected final Drawable mBackground;
133 
134     // These values allow a fixed measurement to be set on the CellLayout.
135     private int mFixedWidth = -1;
136     private int mFixedHeight = -1;
137 
138     // If we're actively dragging something over this screen, mIsDragOverlapping is true
139     private boolean mIsDragOverlapping = false;
140 
141     // These arrays are used to implement the drag visualization on x-large screens.
142     // They are used as circular arrays, indexed by mDragOutlineCurrent.
143     @Thunk final CellLayoutLayoutParams[] mDragOutlines = new CellLayoutLayoutParams[4];
144     @Thunk final float[] mDragOutlineAlphas = new float[mDragOutlines.length];
145     private final InterruptibleInOutAnimator[] mDragOutlineAnims =
146             new InterruptibleInOutAnimator[mDragOutlines.length];
147 
148     // Used as an index into the above 3 arrays; indicates which is the most current value.
149     private int mDragOutlineCurrent = 0;
150     private final Paint mDragOutlinePaint = new Paint();
151 
152     @Thunk final ArrayMap<CellLayoutLayoutParams, Animator> mReorderAnimators = new ArrayMap<>();
153     @Thunk final ArrayMap<Reorderable, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
154 
155     private boolean mItemPlacementDirty = false;
156 
157     // Used to visualize the grid and drop locations
158     private boolean mVisualizeCells = false;
159     private boolean mVisualizeDropLocation = true;
160     private RectF mVisualizeGridRect = new RectF();
161     private Paint mVisualizeGridPaint = new Paint();
162     private int mGridVisualizationRoundingRadius;
163     private float mGridAlpha = 0f;
164     private int mGridColor = 0;
165     protected float mSpringLoadedProgress = 0f;
166     private float mScrollProgress = 0f;
167 
168     // When a drag operation is in progress, holds the nearest cell to the touch point
169     private final int[] mDragCell = new int[2];
170     private final int[] mDragCellSpan = new int[2];
171 
172     private boolean mDragging = false;
173     public boolean mHasOnLayoutBeenCalled = false;
174     private boolean mPlayDragHaptics = false;
175 
176     private final TimeInterpolator mEaseOutInterpolator;
177     protected final ShortcutAndWidgetContainer mShortcutsAndWidgets;
178     @Px
179     protected int mSpaceBetweenCellLayoutsPx = 0;
180 
181     @Retention(RetentionPolicy.SOURCE)
182     @IntDef({WORKSPACE, HOTSEAT, FOLDER})
183     public @interface ContainerType{}
184     public static final int WORKSPACE = 0;
185     public static final int HOTSEAT = 1;
186     public static final int FOLDER = 2;
187 
188     @ContainerType private final int mContainerType;
189 
190     public static final float DEFAULT_SCALE = 1f;
191 
192     public static final int MODE_SHOW_REORDER_HINT = 0;
193     public static final int MODE_DRAG_OVER = 1;
194     public static final int MODE_ON_DROP = 2;
195     public static final int MODE_ON_DROP_EXTERNAL = 3;
196     public static final int MODE_ACCEPT_DROP = 4;
197     private static final boolean DESTRUCTIVE_REORDER = false;
198     private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
199 
200     public static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
201     public static final int REORDER_ANIMATION_DURATION = 150;
202     @Thunk final float mReorderPreviewAnimationMagnitude;
203 
204     public final int[] mDirectionVector = new int[2];
205 
206     ItemConfiguration mPreviousSolution = null;
207 
208     private final Rect mTempRect = new Rect();
209 
210     private static final Paint sPaint = new Paint();
211 
212     private final MSDLPlayerWrapper mMSDLPlayerWrapper;
213 
214     // Related to accessible drag and drop
215     DragAndDropAccessibilityDelegate mTouchHelper;
216 
217     CellLayoutContainer mCellLayoutContainer;
218 
219     public static final FloatProperty<CellLayout> SPRING_LOADED_PROGRESS =
220             new FloatProperty<CellLayout>("spring_loaded_progress") {
221                 @Override
222                 public Float get(CellLayout cl) {
223                     return cl.getSpringLoadedProgress();
224                 }
225 
226                 @Override
227                 public void setValue(CellLayout cl, float progress) {
228                     cl.setSpringLoadedProgress(progress);
229                 }
230             };
231 
CellLayout(Context context, CellLayoutContainer container)232     public CellLayout(Context context, CellLayoutContainer container) {
233         this(context, (AttributeSet) null);
234         this.mCellLayoutContainer = container;
235     }
236 
CellLayout(Context context, AttributeSet attrs)237     public CellLayout(Context context, AttributeSet attrs) {
238         this(context, attrs, 0);
239     }
240 
CellLayout(Context context, AttributeSet attrs, int defStyle)241     public CellLayout(Context context, AttributeSet attrs, int defStyle) {
242         super(context, attrs, defStyle);
243         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
244         mContainerType = a.getInteger(R.styleable.CellLayout_containerType, WORKSPACE);
245         a.recycle();
246 
247         mMSDLPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context);
248 
249         // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
250         // the user where a dragged item will land when dropped.
251         setWillNotDraw(false);
252         setClipToPadding(false);
253         setClipChildren(false);
254         mActivity = ActivityContext.lookupContext(context);
255         DeviceProfile deviceProfile = mActivity.getDeviceProfile();
256 
257         resetCellSizeInternal(deviceProfile);
258 
259         mCountX = deviceProfile.inv.numColumns;
260         mCountY = deviceProfile.inv.numRows;
261         mOccupied =  new GridOccupancy(mCountX, mCountY);
262         mTmpOccupied = new GridOccupancy(mCountX, mCountY);
263 
264         mFolderLeaveBehind.mDelegateCellX = -1;
265         mFolderLeaveBehind.mDelegateCellY = -1;
266 
267         setAlwaysDrawnWithCacheEnabled(false);
268 
269         Resources res = getResources();
270 
271         mBackground = getContext().getDrawable(R.drawable.bg_celllayout);
272         mBackground.setCallback(this);
273         mBackground.setAlpha(0);
274 
275         mGridColor = Themes.getAttrColor(getContext(), R.attr.workspaceAccentColor);
276         mGridVisualizationRoundingRadius =
277                 res.getDimensionPixelSize(R.dimen.grid_visualization_rounding_radius);
278         mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * deviceProfile.iconSizePx);
279 
280         // Initialize the data structures used for the drag visualization.
281         mEaseOutInterpolator = Interpolators.DECELERATE_QUINT; // Quint ease out
282         mDragCell[0] = mDragCell[1] = -1;
283         mDragCellSpan[0] = mDragCellSpan[1] = -1;
284         for (int i = 0; i < mDragOutlines.length; i++) {
285             mDragOutlines[i] = new CellLayoutLayoutParams(0, 0, 0, 0);
286         }
287         mDragOutlinePaint.setColor(Themes.getAttrColor(context, R.attr.workspaceTextColor));
288 
289         // When dragging things around the home screens, we show a green outline of
290         // where the item will land. The outlines gradually fade out, leaving a trail
291         // behind the drag path.
292         // Set up all the animations that are used to implement this fading.
293         final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
294         final float fromAlphaValue = 0;
295         final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
296 
297         Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
298 
299         for (int i = 0; i < mDragOutlineAnims.length; i++) {
300             final InterruptibleInOutAnimator anim =
301                     new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
302             anim.getAnimator().setInterpolator(mEaseOutInterpolator);
303             final int thisIndex = i;
304             anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
305                 public void onAnimationUpdate(ValueAnimator animation) {
306                     // If an animation is started and then stopped very quickly, we can still
307                     // get spurious updates we've cleared the tag. Guard against this.
308                     mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
309                     CellLayout.this.invalidate();
310                 }
311             });
312             // The animation holds a reference to the drag outline bitmap as long is it's
313             // running. This way the bitmap can be GCed when the animations are complete.
314             mDragOutlineAnims[i] = anim;
315         }
316 
317         mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType);
318         mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
319                 mBorderSpace);
320         addView(mShortcutsAndWidgets);
321     }
322 
getCellLayoutContainer()323     public CellLayoutContainer getCellLayoutContainer() {
324         return mCellLayoutContainer;
325     }
326 
setCellLayoutContainer(CellLayoutContainer cellLayoutContainer)327     public void setCellLayoutContainer(CellLayoutContainer cellLayoutContainer) {
328         mCellLayoutContainer = cellLayoutContainer;
329     }
330 
331     /**
332      * Sets or clears a delegate used for accessible drag and drop
333      */
setDragAndDropAccessibilityDelegate(DragAndDropAccessibilityDelegate delegate)334     public void setDragAndDropAccessibilityDelegate(DragAndDropAccessibilityDelegate delegate) {
335         ViewCompat.setAccessibilityDelegate(this, delegate);
336 
337         mTouchHelper = delegate;
338         int accessibilityFlag = mTouchHelper != null
339                 ? IMPORTANT_FOR_ACCESSIBILITY_YES : IMPORTANT_FOR_ACCESSIBILITY_NO;
340         setImportantForAccessibility(accessibilityFlag);
341         getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
342         // ExploreByTouchHelper sets focusability. Clear it when the delegate is cleared.
343         setFocusable(delegate != null);
344         // Invalidate the accessibility hierarchy
345         if (getParent() != null) {
346             getParent().notifySubtreeAccessibilityStateChanged(
347                     this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
348         }
349     }
350 
351     /**
352      * Returns the currently set accessibility delegate
353      */
getDragAndDropAccessibilityDelegate()354     public DragAndDropAccessibilityDelegate getDragAndDropAccessibilityDelegate() {
355         return mTouchHelper;
356     }
357 
358     @Override
dispatchHoverEvent(MotionEvent event)359     public boolean dispatchHoverEvent(MotionEvent event) {
360         // Always attempt to dispatch hover events to accessibility first.
361         if (mTouchHelper != null && mTouchHelper.dispatchHoverEvent(event)) {
362             return true;
363         }
364         return super.dispatchHoverEvent(event);
365     }
366 
367     @Override
onInterceptTouchEvent(MotionEvent ev)368     public boolean onInterceptTouchEvent(MotionEvent ev) {
369         return mTouchHelper != null
370                 || (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev));
371     }
372 
enableHardwareLayer(boolean hasLayer)373     public void enableHardwareLayer(boolean hasLayer) {
374         mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
375     }
376 
isHardwareLayerEnabled()377     public boolean isHardwareLayerEnabled() {
378         return mShortcutsAndWidgets.getLayerType() == LAYER_TYPE_HARDWARE;
379     }
380 
381     /**
382      * Change sizes of cells
383      *
384      * @param width  the new width of the cells
385      * @param height the new height of the cells
386      */
setCellDimensions(int width, int height)387     public void setCellDimensions(int width, int height) {
388         mFixedCellWidth = mCellWidth = width;
389         mFixedCellHeight = mCellHeight = height;
390         mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
391                 mBorderSpace);
392     }
393 
resetCellSizeInternal(DeviceProfile deviceProfile)394     private void resetCellSizeInternal(DeviceProfile deviceProfile) {
395         switch (mContainerType) {
396             case FOLDER:
397                 mBorderSpace = new Point(deviceProfile.folderCellLayoutBorderSpacePx);
398                 break;
399             case HOTSEAT:
400                 mBorderSpace = new Point(deviceProfile.hotseatBorderSpace,
401                         deviceProfile.hotseatBorderSpace);
402                 break;
403             case WORKSPACE:
404             default:
405                 mBorderSpace = new Point(deviceProfile.cellLayoutBorderSpacePx);
406                 break;
407         }
408 
409         mCellWidth = mCellHeight = -1;
410         mFixedCellWidth = mFixedCellHeight = -1;
411     }
412 
413     /**
414      * Reset the cell sizes and border space
415      */
resetCellSize(DeviceProfile deviceProfile)416     public void resetCellSize(DeviceProfile deviceProfile) {
417         resetCellSizeInternal(deviceProfile);
418         requestLayout();
419     }
420 
setGridSize(int x, int y)421     public void setGridSize(int x, int y) {
422         mCountX = x;
423         mCountY = y;
424         mOccupied = new GridOccupancy(mCountX, mCountY);
425         mTmpOccupied = new GridOccupancy(mCountX, mCountY);
426         mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
427                 mBorderSpace);
428         requestLayout();
429     }
430 
431     // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
setInvertIfRtl(boolean invert)432     public void setInvertIfRtl(boolean invert) {
433         mShortcutsAndWidgets.setInvertIfRtl(invert);
434     }
435 
setDropPending(boolean pending)436     public void setDropPending(boolean pending) {
437         mDropPending = pending;
438     }
439 
isDropPending()440     public boolean isDropPending() {
441         return mDropPending;
442     }
443 
setIsDragOverlapping(boolean isDragOverlapping)444     void setIsDragOverlapping(boolean isDragOverlapping) {
445         if (mIsDragOverlapping != isDragOverlapping) {
446             mIsDragOverlapping = isDragOverlapping;
447             mBackground.setState(mIsDragOverlapping
448                     ? BACKGROUND_STATE_ACTIVE : BACKGROUND_STATE_DEFAULT);
449             invalidate();
450         }
451     }
452 
453     @Override
dispatchSaveInstanceState(SparseArray<Parcelable> container)454     protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
455         ParcelableSparseArray jail = getJailedArray(container);
456         super.dispatchSaveInstanceState(jail);
457         container.put(R.id.cell_layout_jail_id, jail);
458     }
459 
460     @Override
dispatchRestoreInstanceState(SparseArray<Parcelable> container)461     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
462         super.dispatchRestoreInstanceState(getJailedArray(container));
463     }
464 
465     /**
466      * Wrap the SparseArray in another Parcelable so that the item ids do not conflict with our
467      * our internal resource ids
468      */
getJailedArray(SparseArray<Parcelable> container)469     private ParcelableSparseArray getJailedArray(SparseArray<Parcelable> container) {
470         final Parcelable parcelable = container.get(R.id.cell_layout_jail_id);
471         return parcelable instanceof ParcelableSparseArray ?
472                 (ParcelableSparseArray) parcelable : new ParcelableSparseArray();
473     }
474 
getIsDragOverlapping()475     public boolean getIsDragOverlapping() {
476         return mIsDragOverlapping;
477     }
478 
479     @Override
onDraw(Canvas canvas)480     protected void onDraw(Canvas canvas) {
481         // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
482         // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
483         // When we're small, we are either drawn normally or in the "accepts drops" state (during
484         // a drag). However, we also drag the mini hover background *over* one of those two
485         // backgrounds
486         if (mBackground.getAlpha() > 0) {
487             mBackground.draw(canvas);
488         }
489 
490         if (DEBUG_VISUALIZE_OCCUPIED) {
491             Rect cellBounds = new Rect();
492             // Will contain the bounds of the cell including spacing between cells.
493             Rect cellBoundsWithSpacing = new Rect();
494             int[] targetCell = new int[2];
495             int[] cellCenter = new int[2];
496             Paint debugPaint = new Paint();
497             debugPaint.setStrokeWidth(Utilities.dpToPx(1));
498             for (int x = 0; x < mCountX; x++) {
499                 for (int y = 0; y < mCountY; y++) {
500                     if (!mOccupied.cells[x][y]) {
501                         continue;
502                     }
503                     targetCell[0] = x;
504                     targetCell[1] = y;
505 
506                     boolean canCreateFolder = canCreateFolder(getChildAt(x, y));
507                     cellToRect(x, y, 1, 1, cellBounds);
508                     cellBoundsWithSpacing.set(cellBounds);
509                     cellBoundsWithSpacing.inset(-mBorderSpace.x / 2, -mBorderSpace.y / 2);
510                     getWorkspaceCellVisualCenter(x, y, cellCenter);
511 
512                     canvas.save();
513                     canvas.clipRect(cellBoundsWithSpacing);
514 
515                     // Draw reorder drag target.
516                     debugPaint.setColor(Color.RED);
517                     canvas.drawCircle(cellCenter[0], cellCenter[1],
518                             getReorderRadius(targetCell, 1, 1), debugPaint);
519 
520                     // Draw folder creation drag target.
521                     if (canCreateFolder) {
522                         debugPaint.setColor(Color.GREEN);
523                         canvas.drawCircle(cellCenter[0], cellCenter[1],
524                                 getFolderCreationRadius(targetCell), debugPaint);
525                     }
526 
527                     canvas.restore();
528                 }
529             }
530         }
531 
532         for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
533             DelegatedCellDrawing cellDrawing = mDelegatedCellDrawings.get(i);
534             canvas.save();
535             if (cellDrawing.mDelegateCellX >= 0 && cellDrawing.mDelegateCellY >= 0) {
536                 cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation);
537                 canvas.translate(mTempLocation[0], mTempLocation[1]);
538             }
539             cellDrawing.drawUnderItem(canvas);
540             canvas.restore();
541         }
542 
543         if (mFolderLeaveBehind.mDelegateCellX >= 0 && mFolderLeaveBehind.mDelegateCellY >= 0) {
544             cellToPoint(mFolderLeaveBehind.mDelegateCellX,
545                     mFolderLeaveBehind.mDelegateCellY, mTempLocation);
546             canvas.save();
547             canvas.translate(mTempLocation[0], mTempLocation[1]);
548             mFolderLeaveBehind.drawLeaveBehind(canvas, FOLDER_LEAVE_BEHIND_COLOR);
549             canvas.restore();
550         }
551 
552         if (mVisualizeCells || mVisualizeDropLocation) {
553             visualizeGrid(canvas);
554         }
555     }
556 
557     /**
558      * Returns whether dropping an icon on the given View can create (or add to) a folder.
559      */
canCreateFolder(View child)560     private boolean canCreateFolder(View child) {
561         return child instanceof DraggableView
562                 && ((DraggableView) child).getViewType() == DRAGGABLE_ICON;
563     }
564 
565     /**
566      * Indicates the progress of the Workspace entering the SpringLoaded state; allows the
567      * CellLayout to update various visuals for this state.
568      *
569      * @param progress
570      */
setSpringLoadedProgress(float progress)571     public void setSpringLoadedProgress(float progress) {
572         if (Float.compare(progress, mSpringLoadedProgress) != 0) {
573             mSpringLoadedProgress = progress;
574             updateBgAlpha();
575             setGridAlpha(progress);
576         }
577     }
578 
579     /**
580      * See setSpringLoadedProgress
581      * @return progress
582      */
getSpringLoadedProgress()583     public float getSpringLoadedProgress() {
584         return mSpringLoadedProgress;
585     }
586 
updateBgAlpha()587     protected void updateBgAlpha() {
588         mBackground.setAlpha((int) (mSpringLoadedProgress * 255));
589     }
590 
591     /**
592      * Set the progress of this page's scroll
593      *
594      * @param progress 0 if the screen is centered, +/-1 if it is to the right / left respectively
595      */
setScrollProgress(float progress)596     public void setScrollProgress(float progress) {
597         if (Float.compare(Math.abs(progress), mScrollProgress) != 0) {
598             mScrollProgress = Math.abs(progress);
599             updateBgAlpha();
600         }
601     }
602 
setGridAlpha(float gridAlpha)603     private void setGridAlpha(float gridAlpha) {
604         if (Float.compare(gridAlpha, mGridAlpha) != 0) {
605             mGridAlpha = gridAlpha;
606             invalidate();
607         }
608     }
609 
visualizeGrid(Canvas canvas)610     protected void visualizeGrid(Canvas canvas) {
611         DeviceProfile dp = mActivity.getDeviceProfile();
612         int paddingX = Math.min((mCellWidth - dp.iconSizePx) / 2, dp.gridVisualizationPaddingX);
613         int paddingY = Math.min((mCellHeight - dp.iconSizePx) / 2, dp.gridVisualizationPaddingY);
614 
615         mVisualizeGridPaint.setStrokeWidth(8);
616 
617         // This is used for debugging purposes only
618         if (mVisualizeCells) {
619             int paintAlpha = (int) (120 * mGridAlpha);
620             mVisualizeGridPaint.setColor(ColorUtils.setAlphaComponent(mGridColor, paintAlpha));
621             for (int i = 0; i < mCountX; i++) {
622                 for (int j = 0; j < mCountY; j++) {
623                     cellToRect(i, j, 1, 1, mTempOnDrawCellToRect);
624                     mVisualizeGridRect.set(mTempOnDrawCellToRect);
625                     mVisualizeGridRect.inset(paddingX, paddingY);
626                     mVisualizeGridPaint.setStyle(Paint.Style.FILL);
627                     canvas.drawRoundRect(mVisualizeGridRect, mGridVisualizationRoundingRadius,
628                             mGridVisualizationRoundingRadius, mVisualizeGridPaint);
629                 }
630             }
631         }
632 
633         if (mVisualizeDropLocation) {
634             for (int i = 0; i < mDragOutlines.length; i++) {
635                 final float alpha = mDragOutlineAlphas[i];
636                 if (alpha <= 0) continue;
637                 CellLayoutLayoutParams params = mDragOutlines[i];
638                 cellToRect(params.getCellX(), params.getCellY(), params.cellHSpan, params.cellVSpan,
639                         mTempOnDrawCellToRect);
640                 mVisualizeGridRect.set(mTempOnDrawCellToRect);
641                 mVisualizeGridRect.inset(paddingX, paddingY);
642 
643                 mVisualizeGridPaint.setAlpha(255);
644                 mVisualizeGridPaint.setStyle(Paint.Style.STROKE);
645                 mVisualizeGridPaint.setColor(Color.argb((int) (alpha),
646                         Color.red(mGridColor), Color.green(mGridColor), Color.blue(mGridColor)));
647 
648                 canvas.save();
649                 canvas.translate(getMarginForGivenCellParams(params), 0);
650                 canvas.drawRoundRect(mVisualizeGridRect, mGridVisualizationRoundingRadius,
651                         mGridVisualizationRoundingRadius, mVisualizeGridPaint);
652                 canvas.restore();
653             }
654         }
655     }
656 
getMarginForGivenCellParams(CellLayoutLayoutParams params)657     protected float getMarginForGivenCellParams(CellLayoutLayoutParams params) {
658         return 0;
659     }
660 
661     @Override
dispatchDraw(Canvas canvas)662     protected void dispatchDraw(Canvas canvas) {
663         super.dispatchDraw(canvas);
664 
665         for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
666             DelegatedCellDrawing bg = mDelegatedCellDrawings.get(i);
667             canvas.save();
668             if (bg.mDelegateCellX >= 0 && bg.mDelegateCellY >= 0) {
669                 cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation);
670                 canvas.translate(mTempLocation[0], mTempLocation[1]);
671             }
672             bg.drawOverItem(canvas);
673             canvas.restore();
674         }
675     }
676 
677     /**
678      * Add Delegated cell drawing
679      */
addDelegatedCellDrawing(DelegatedCellDrawing bg)680     public void addDelegatedCellDrawing(DelegatedCellDrawing bg) {
681         mDelegatedCellDrawings.add(bg);
682     }
683 
684     /**
685      * Remove item from DelegatedCellDrawings
686      */
removeDelegatedCellDrawing(DelegatedCellDrawing bg)687     public void removeDelegatedCellDrawing(DelegatedCellDrawing bg) {
688         mDelegatedCellDrawings.remove(bg);
689     }
690 
setFolderLeaveBehindCell(int x, int y)691     public void setFolderLeaveBehindCell(int x, int y) {
692         View child = getChildAt(x, y);
693         mFolderLeaveBehind.setup(getContext(), mActivity, null,
694                 child.getMeasuredWidth(), child.getPaddingTop());
695 
696         mFolderLeaveBehind.mDelegateCellX = x;
697         mFolderLeaveBehind.mDelegateCellY = y;
698         invalidate();
699     }
700 
clearFolderLeaveBehind()701     public void clearFolderLeaveBehind() {
702         mFolderLeaveBehind.mDelegateCellX = -1;
703         mFolderLeaveBehind.mDelegateCellY = -1;
704         invalidate();
705     }
706 
707     @Override
shouldDelayChildPressedState()708     public boolean shouldDelayChildPressedState() {
709         return false;
710     }
711 
restoreInstanceState(SparseArray<Parcelable> states)712     public void restoreInstanceState(SparseArray<Parcelable> states) {
713         try {
714             dispatchRestoreInstanceState(states);
715         } catch (IllegalArgumentException ex) {
716             if (FeatureFlags.IS_STUDIO_BUILD) {
717                 throw ex;
718             }
719             // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
720             Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
721         }
722     }
723 
724     @Override
cancelLongPress()725     public void cancelLongPress() {
726         super.cancelLongPress();
727 
728         // Cancel long press for all children
729         final int count = getChildCount();
730         for (int i = 0; i < count; i++) {
731             final View child = getChildAt(i);
732             child.cancelLongPress();
733         }
734     }
735 
setOnInterceptTouchListener(View.OnTouchListener listener)736     public void setOnInterceptTouchListener(View.OnTouchListener listener) {
737         mInterceptTouchListener = listener;
738     }
739 
getCountX()740     public int getCountX() {
741         return mCountX;
742     }
743 
getCountY()744     public int getCountY() {
745         return mCountY;
746     }
747 
acceptsWidget()748     public boolean acceptsWidget() {
749         return mContainerType == WORKSPACE;
750     }
751 
752     /**
753      * Adds the given view to the CellLayout
754      *
755      * @param child view to add.
756      * @param index index of the CellLayout children where to add the view.
757      * @param childId id of the view.
758      * @param params represent the logic of the view on the CellLayout.
759      * @param markCells if the occupied cells should be marked or not
760      * @return if adding the view was successful
761      */
addViewToCellLayout(View child, int index, int childId, CellLayoutLayoutParams params, boolean markCells)762     public boolean addViewToCellLayout(View child, int index, int childId,
763             CellLayoutLayoutParams params, boolean markCells) {
764         final CellLayoutLayoutParams lp = params;
765 
766         // Hotseat icons - remove text
767         if (child instanceof BubbleTextView) {
768             BubbleTextView bubbleChild = (BubbleTextView) child;
769             bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
770         }
771 
772         child.setScaleX(DEFAULT_SCALE);
773         child.setScaleY(DEFAULT_SCALE);
774 
775         // Generate an id for each view, this assumes we have at most 256x256 cells
776         // per workspace screen
777         if (lp.getCellX() >= 0 && lp.getCellX() <= mCountX - 1
778                 && lp.getCellY() >= 0 && lp.getCellY() <= mCountY - 1) {
779             // If the horizontal or vertical span is set to -1, it is taken to
780             // mean that it spans the extent of the CellLayout
781             if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
782             if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
783 
784             child.setId(childId);
785             if (LOGD) {
786                 Log.d(TAG, "Adding view to ShortcutsAndWidgetsContainer: " + child);
787             }
788             mShortcutsAndWidgets.addView(child, index, lp);
789 
790             // Whenever an app is added, if Accessibility service is enabled, focus on that app.
791             if (mActivity instanceof Launcher) {
792                 child.setTag(R.id.perform_a11y_action_on_launcher_state_normal_tag,
793                         AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
794             }
795 
796             if (markCells) markCellsAsOccupiedForView(child);
797 
798             return true;
799         }
800         return false;
801     }
802 
803     @Override
removeAllViews()804     public void removeAllViews() {
805         mOccupied.clear();
806         mShortcutsAndWidgets.removeAllViews();
807     }
808 
809     @Override
removeAllViewsInLayout()810     public void removeAllViewsInLayout() {
811         if (mShortcutsAndWidgets.getChildCount() > 0) {
812             mOccupied.clear();
813             mShortcutsAndWidgets.removeAllViewsInLayout();
814         }
815     }
816 
817     @Override
removeView(View view)818     public void removeView(View view) {
819         markCellsAsUnoccupiedForView(view);
820         mShortcutsAndWidgets.removeView(view);
821     }
822 
823     @Override
removeViewAt(int index)824     public void removeViewAt(int index) {
825         markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
826         mShortcutsAndWidgets.removeViewAt(index);
827     }
828 
829     @Override
removeViewInLayout(View view)830     public void removeViewInLayout(View view) {
831         markCellsAsUnoccupiedForView(view);
832         mShortcutsAndWidgets.removeViewInLayout(view);
833     }
834 
835     @Override
removeViews(int start, int count)836     public void removeViews(int start, int count) {
837         for (int i = start; i < start + count; i++) {
838             markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
839         }
840         mShortcutsAndWidgets.removeViews(start, count);
841     }
842 
843     @Override
removeViewsInLayout(int start, int count)844     public void removeViewsInLayout(int start, int count) {
845         for (int i = start; i < start + count; i++) {
846             markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
847         }
848         mShortcutsAndWidgets.removeViewsInLayout(start, count);
849     }
850 
851     /**
852      * Given a point, return the cell that strictly encloses that point
853      * @param x X coordinate of the point
854      * @param y Y coordinate of the point
855      * @param result Array of 2 ints to hold the x and y coordinate of the cell
856      */
pointToCellExact(int x, int y, int[] result)857     public void pointToCellExact(int x, int y, int[] result) {
858         final int hStartPadding = getPaddingLeft();
859         final int vStartPadding = getPaddingTop();
860 
861         result[0] = (x - hStartPadding) / (mCellWidth + mBorderSpace.x);
862         result[1] = (y - vStartPadding) / (mCellHeight + mBorderSpace.y);
863 
864         final int xAxis = mCountX;
865         final int yAxis = mCountY;
866 
867         if (result[0] < 0) result[0] = 0;
868         if (result[0] >= xAxis) result[0] = xAxis - 1;
869         if (result[1] < 0) result[1] = 0;
870         if (result[1] >= yAxis) result[1] = yAxis - 1;
871     }
872 
873     /**
874      * Given a cell coordinate, return the point that represents the upper left corner of that cell
875      *
876      * @param cellX X coordinate of the cell
877      * @param cellY Y coordinate of the cell
878      *
879      * @param result Array of 2 ints to hold the x and y coordinate of the point
880      */
cellToPoint(int cellX, int cellY, int[] result)881     void cellToPoint(int cellX, int cellY, int[] result) {
882         cellToRect(cellX, cellY, 1, 1, mTempRect);
883         result[0] = mTempRect.left;
884         result[1] = mTempRect.top;
885     }
886 
887     /**
888      * Given a cell coordinate, return the point that represents the center of the cell
889      *
890      * @param cellX X coordinate of the cell
891      * @param cellY Y coordinate of the cell
892      *
893      * @param result Array of 2 ints to hold the x and y coordinate of the point
894      */
cellToCenterPoint(int cellX, int cellY, int[] result)895     void cellToCenterPoint(int cellX, int cellY, int[] result) {
896         regionToCenterPoint(cellX, cellY, 1, 1, result);
897     }
898 
899     /**
900      * Given a cell coordinate and span return the point that represents the center of the region
901      *
902      * @param cellX X coordinate of the cell
903      * @param cellY Y coordinate of the cell
904      *
905      * @param result Array of 2 ints to hold the x and y coordinate of the point
906      */
regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result)907     public void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
908         cellToRect(cellX, cellY, spanX, spanY, mTempRect);
909         result[0] = mTempRect.centerX();
910         result[1] = mTempRect.centerY();
911     }
912 
913     /**
914      * Returns the distance between the given coordinate and the visual center of the given cell.
915      */
getDistanceFromWorkspaceCellVisualCenter(float x, float y, int[] cell)916     public float getDistanceFromWorkspaceCellVisualCenter(float x, float y, int[] cell) {
917         getWorkspaceCellVisualCenter(cell[0], cell[1], mTmpPoint);
918         return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
919     }
920 
getWorkspaceCellVisualCenter(int cellX, int cellY, int[] outPoint)921     private void getWorkspaceCellVisualCenter(int cellX, int cellY, int[] outPoint) {
922         View child = getChildAt(cellX, cellY);
923         if (child instanceof DraggableView) {
924             DraggableView draggableChild = (DraggableView) child;
925             if (draggableChild.getViewType() == DRAGGABLE_ICON) {
926                 cellToPoint(cellX, cellY, outPoint);
927                 draggableChild.getWorkspaceVisualDragBounds(mTempRect);
928                 mTempRect.offset(outPoint[0], outPoint[1]);
929                 outPoint[0] = mTempRect.centerX();
930                 outPoint[1] = mTempRect.centerY();
931                 return;
932             }
933         }
934         cellToCenterPoint(cellX, cellY, outPoint);
935     }
936 
937     /**
938      * Returns the max distance from the center of a cell that can accept a drop to create a folder.
939      */
getFolderCreationRadius(int[] targetCell)940     public float getFolderCreationRadius(int[] targetCell) {
941         DeviceProfile grid = mActivity.getDeviceProfile();
942         float iconVisibleRadius = ICON_VISIBLE_AREA_FACTOR * grid.iconSizePx / 2;
943         // Halfway between reorder radius and icon.
944         return (getReorderRadius(targetCell, 1, 1) + iconVisibleRadius) / 2;
945     }
946 
947     /**
948      * Returns the max distance from the center of a cell that will start to reorder on drag over.
949      */
getReorderRadius(int[] targetCell, int spanX, int spanY)950     public float getReorderRadius(int[] targetCell, int spanX, int spanY) {
951         int[] centerPoint = mTmpPoint;
952         getWorkspaceCellVisualCenter(targetCell[0], targetCell[1], centerPoint);
953 
954         Rect cellBoundsWithSpacing = mTempRect;
955         cellToRect(targetCell[0], targetCell[1], spanX, spanY, cellBoundsWithSpacing);
956         cellBoundsWithSpacing.inset(-mBorderSpace.x / 2, -mBorderSpace.y / 2);
957 
958         if (canCreateFolder(getChildAt(targetCell[0], targetCell[1])) && spanX == 1 && spanY == 1) {
959             // Take only the circle in the smaller dimension, to ensure we don't start reordering
960             // too soon before accepting a folder drop.
961             int minRadius = centerPoint[0] - cellBoundsWithSpacing.left;
962             minRadius = Math.min(minRadius, centerPoint[1] - cellBoundsWithSpacing.top);
963             minRadius = Math.min(minRadius, cellBoundsWithSpacing.right - centerPoint[0]);
964             minRadius = Math.min(minRadius, cellBoundsWithSpacing.bottom - centerPoint[1]);
965             return minRadius;
966         }
967         // Take up the entire cell, including space between this cell and the adjacent ones.
968         // Multiply by span to scale radius
969         return (float) Math.hypot(spanX * cellBoundsWithSpacing.width() / 2f,
970                 spanY * cellBoundsWithSpacing.height() / 2f);
971     }
972 
getCellWidth()973     public int getCellWidth() {
974         return mCellWidth;
975     }
976 
getCellHeight()977     public int getCellHeight() {
978         return mCellHeight;
979     }
980 
setFixedSize(int width, int height)981     public void setFixedSize(int width, int height) {
982         mFixedWidth = width;
983         mFixedHeight = height;
984     }
985 
986     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)987     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
988         int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
989         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
990         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
991         int heightSize =  MeasureSpec.getSize(heightMeasureSpec);
992         int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
993         int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
994 
995         if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
996             int cw = DeviceProfile.calculateCellWidth(childWidthSize, mBorderSpace.x,
997                     mCountX);
998             int ch = DeviceProfile.calculateCellHeight(childHeightSize, mBorderSpace.y,
999                     mCountY);
1000             if (cw != mCellWidth || ch != mCellHeight) {
1001                 mCellWidth = cw;
1002                 mCellHeight = ch;
1003                 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
1004                         mBorderSpace);
1005             }
1006         }
1007 
1008         int newWidth = childWidthSize;
1009         int newHeight = childHeightSize;
1010         if (mFixedWidth > 0 && mFixedHeight > 0) {
1011             newWidth = mFixedWidth;
1012             newHeight = mFixedHeight;
1013         } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
1014             throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
1015         }
1016 
1017         mShortcutsAndWidgets.measure(
1018                 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
1019                 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
1020 
1021         int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
1022         int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
1023         if (mFixedWidth > 0 && mFixedHeight > 0) {
1024             setMeasuredDimension(maxWidth, maxHeight);
1025         } else {
1026             setMeasuredDimension(widthSize, heightSize);
1027         }
1028     }
1029 
1030     @Override
onLayout(boolean changed, int l, int t, int r, int b)1031     protected void onLayout(boolean changed, int l, int t, int r, int b) {
1032         mHasOnLayoutBeenCalled = true; // b/349929393 - is the required call to onLayout not done?
1033         int left = getPaddingLeft();
1034         left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
1035         int right = r - l - getPaddingRight();
1036         right -= (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
1037 
1038         int top = getPaddingTop();
1039         int bottom = b - t - getPaddingBottom();
1040 
1041         // Expand the background drawing bounds by the padding baked into the background drawable
1042         mBackground.getPadding(mTempRect);
1043         mBackground.setBounds(
1044                 left - mTempRect.left - getPaddingLeft(),
1045                 top - mTempRect.top - getPaddingTop(),
1046                 right + mTempRect.right + getPaddingRight(),
1047                 bottom + mTempRect.bottom + getPaddingBottom());
1048 
1049         mShortcutsAndWidgets.layout(left, top, right, bottom);
1050     }
1051 
1052     /**
1053      * Returns the amount of space left over after subtracting padding and cells. This space will be
1054      * very small, a few pixels at most, and is a result of rounding down when calculating the cell
1055      * width in {@link DeviceProfile#calculateCellWidth(int, int, int)}.
1056      */
getUnusedHorizontalSpace()1057     public int getUnusedHorizontalSpace() {
1058         return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth)
1059                 - ((mCountX - 1) * mBorderSpace.x);
1060     }
1061 
1062     @Override
verifyDrawable(Drawable who)1063     protected boolean verifyDrawable(Drawable who) {
1064         return super.verifyDrawable(who) || (who == mBackground);
1065     }
1066 
getShortcutsAndWidgets()1067     public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
1068         return mShortcutsAndWidgets;
1069     }
1070 
getChildAt(int cellX, int cellY)1071     public View getChildAt(int cellX, int cellY) {
1072         return mShortcutsAndWidgets.getChildAt(cellX, cellY);
1073     }
1074 
animateChildToPosition(final View child, int cellX, int cellY, int duration, int delay, boolean permanent, boolean adjustOccupied)1075     public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
1076             int delay, boolean permanent, boolean adjustOccupied) {
1077         ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
1078 
1079         if (clc.indexOfChild(child) != -1 && (child instanceof Reorderable)) {
1080             final CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
1081             final ItemInfo info = (ItemInfo) child.getTag();
1082             final Reorderable item = (Reorderable) child;
1083 
1084             // We cancel any existing animations
1085             if (mReorderAnimators.containsKey(lp)) {
1086                 mReorderAnimators.get(lp).cancel();
1087                 mReorderAnimators.remove(lp);
1088             }
1089 
1090 
1091             if (adjustOccupied) {
1092                 GridOccupancy occupied = permanent ? mOccupied : mTmpOccupied;
1093                 occupied.markCells(lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan, false);
1094                 occupied.markCells(cellX, cellY, lp.cellHSpan, lp.cellVSpan, true);
1095             }
1096 
1097             // Compute the new x and y position based on the new cellX and cellY
1098             // We leverage the actual layout logic in the layout params and hence need to modify
1099             // state and revert that state.
1100             final int oldX = lp.x;
1101             final int oldY = lp.y;
1102             lp.isLockedToGrid = true;
1103             if (permanent) {
1104                 lp.setCellX(cellX);
1105                 lp.setCellY(cellY);
1106             } else {
1107                 lp.setTmpCellX(cellX);
1108                 lp.setTmpCellY(cellY);
1109             }
1110             clc.setupLp(child);
1111             final int newX = lp.x;
1112             final int newY = lp.y;
1113             lp.x = oldX;
1114             lp.y = oldY;
1115             lp.isLockedToGrid = false;
1116             // End compute new x and y
1117 
1118             MultiTranslateDelegate mtd = item.getTranslateDelegate();
1119             float initPreviewOffsetX = mtd.getTranslationX(INDEX_REORDER_PREVIEW_OFFSET).getValue();
1120             float initPreviewOffsetY = mtd.getTranslationY(INDEX_REORDER_PREVIEW_OFFSET).getValue();
1121             final float finalPreviewOffsetX = newX - oldX;
1122             final float finalPreviewOffsetY = newY - oldY;
1123 
1124             // Exit early if we're not actually moving the view
1125             if (finalPreviewOffsetX == 0 && finalPreviewOffsetY == 0
1126                     && initPreviewOffsetX == 0 && initPreviewOffsetY == 0) {
1127                 lp.isLockedToGrid = true;
1128                 return true;
1129             }
1130 
1131             ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
1132             va.setDuration(duration);
1133             mReorderAnimators.put(lp, va);
1134 
1135             va.addUpdateListener(new AnimatorUpdateListener() {
1136                 @Override
1137                 public void onAnimationUpdate(ValueAnimator animation) {
1138                     float r = (Float) animation.getAnimatedValue();
1139                     float x = (1 - r) * initPreviewOffsetX + r * finalPreviewOffsetX;
1140                     float y = (1 - r) * initPreviewOffsetY + r * finalPreviewOffsetY;
1141                     item.getTranslateDelegate().setTranslation(INDEX_REORDER_PREVIEW_OFFSET, x, y);
1142                 }
1143             });
1144             va.addListener(new AnimatorListenerAdapter() {
1145                 boolean cancelled = false;
1146                 public void onAnimationEnd(Animator animation) {
1147                     // If the animation was cancelled, it means that another animation
1148                     // has interrupted this one, and we don't want to lock the item into
1149                     // place just yet.
1150                     if (!cancelled) {
1151                         lp.isLockedToGrid = true;
1152                         item.getTranslateDelegate()
1153                                 .setTranslation(INDEX_REORDER_PREVIEW_OFFSET, 0, 0);
1154                         child.requestLayout();
1155                     }
1156                     if (mReorderAnimators.containsKey(lp)) {
1157                         mReorderAnimators.remove(lp);
1158                     }
1159                 }
1160                 public void onAnimationCancel(Animator animation) {
1161                     cancelled = true;
1162                 }
1163             });
1164             va.setStartDelay(delay);
1165             va.start();
1166             return true;
1167         }
1168         return false;
1169     }
1170 
visualizeDropLocation(int cellX, int cellY, int spanX, int spanY, DropTarget.DragObject dragObject)1171     void visualizeDropLocation(int cellX, int cellY, int spanX, int spanY,
1172             DropTarget.DragObject dragObject) {
1173         if (mDragCell[0] != cellX || mDragCell[1] != cellY || mDragCellSpan[0] != spanX
1174                 || mDragCellSpan[1] != spanY) {
1175             determineIfDragHapticsPlay();
1176             if (mPlayDragHaptics && Flags.msdlFeedback()) {
1177                 mMSDLPlayerWrapper.playToken(MSDLToken.DRAG_INDICATOR_DISCRETE);
1178             }
1179             mDragCell[0] = cellX;
1180             mDragCell[1] = cellY;
1181             mDragCellSpan[0] = spanX;
1182             mDragCellSpan[1] = spanY;
1183 
1184             final int oldIndex = mDragOutlineCurrent;
1185             mDragOutlineAnims[oldIndex].animateOut();
1186             mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
1187 
1188             CellLayoutLayoutParams cell = mDragOutlines[mDragOutlineCurrent];
1189             cell.setCellX(cellX);
1190             cell.setCellY(cellY);
1191             cell.cellHSpan = spanX;
1192             cell.cellVSpan = spanY;
1193 
1194             mDragOutlineAnims[mDragOutlineCurrent].animateIn();
1195             invalidate();
1196 
1197             if (dragObject.stateAnnouncer != null) {
1198                 dragObject.stateAnnouncer.announce(getItemMoveDescription(cellX, cellY));
1199             }
1200 
1201         }
1202     }
1203 
determineIfDragHapticsPlay()1204     private void determineIfDragHapticsPlay() {
1205         if (mDragCell[0] != -1 || mDragCell[1] != -1
1206                 || mDragCellSpan[0] != -1 || mDragCellSpan[1] != -1) {
1207             // The nearest cell is known and we can play haptics
1208             mPlayDragHaptics = true;
1209         }
1210     }
1211 
1212     @SuppressLint("StringFormatMatches")
getItemMoveDescription(int cellX, int cellY)1213     public String getItemMoveDescription(int cellX, int cellY) {
1214         if (mContainerType == HOTSEAT) {
1215             return getContext().getString(R.string.move_to_hotseat_position,
1216                     Math.max(cellX, cellY) + 1);
1217         } else {
1218             int row = cellY + 1;
1219             int col = Utilities.isRtl(getResources()) ? mCountX - cellX : cellX + 1;
1220             int panelCount = mCellLayoutContainer.getPanelCount();
1221             int pageIndex = mCellLayoutContainer.getCellLayoutIndex(this);
1222             if (panelCount > 1) {
1223                 // Increment the column if the target is on the right side of a two panel home
1224                 col += (pageIndex % panelCount) * mCountX;
1225             }
1226             return getContext().getString(R.string.move_to_empty_cell_description, row, col,
1227                     mCellLayoutContainer.getPageDescription(pageIndex));
1228         }
1229     }
1230 
clearDragOutlines()1231     public void clearDragOutlines() {
1232         final int oldIndex = mDragOutlineCurrent;
1233         mDragOutlineAnims[oldIndex].animateOut();
1234         mDragCell[0] = mDragCell[1] = -1;
1235     }
1236 
1237     /**
1238      * Find a vacant area that will fit the given bounds nearest the requested
1239      * cell location. Uses Euclidean distance to score multiple vacant areas.
1240      *
1241      * @param pixelX The X location at which you want to search for a vacant area.
1242      * @param pixelY The Y location at which you want to search for a vacant area.
1243      * @param minSpanX The minimum horizontal span required
1244      * @param minSpanY The minimum vertical span required
1245      * @param spanX Horizontal span of the object.
1246      * @param spanY Vertical span of the object.
1247      * @param result Array in which to place the result, or null (in which case a new array will
1248      *        be allocated)
1249      * @return The X, Y cell of a vacant area that can contain this object,
1250      *         nearest the requested location.
1251      */
findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, int[] result, int[] resultSpan)1252     public int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
1253             int spanX, int spanY, int[] result, int[] resultSpan) {
1254         return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, false,
1255                 result, resultSpan);
1256     }
1257 
1258     /**
1259      * Find a vacant area that will fit the given bounds nearest the requested
1260      * cell location. Uses Euclidean distance to score multiple vacant areas.
1261      * @param relativeXPos The X location relative to the Cell layout at which you want to search
1262      *                     for a vacant area.
1263      * @param relativeYPos The Y location relative to the Cell layout at which you want to search
1264      *                     for a vacant area.
1265      * @param minSpanX The minimum horizontal span required
1266      * @param minSpanY The minimum vertical span required
1267      * @param spanX Horizontal span of the object.
1268      * @param spanY Vertical span of the object.
1269      * @param ignoreOccupied If true, the result can be an occupied cell
1270      * @param result Array in which to place the result, or null (in which case a new array will
1271      *        be allocated)
1272      * @return The X, Y cell of a vacant area that can contain this object,
1273      *         nearest the requested location.
1274      */
findNearestArea(int relativeXPos, int relativeYPos, int minSpanX, int minSpanY, int spanX, int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan)1275     protected int[] findNearestArea(int relativeXPos, int relativeYPos, int minSpanX, int minSpanY,
1276             int spanX, int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
1277         // For items with a spanX / spanY > 1, the passed in point (relativeXPos, relativeYPos)
1278         // corresponds to the center of the item, but we are searching based on the top-left cell,
1279         // so we translate the point over to correspond to the top-left.
1280         relativeXPos = (int) (relativeXPos - (mCellWidth + mBorderSpace.x) * (spanX - 1) / 2f);
1281         relativeYPos = (int) (relativeYPos - (mCellHeight + mBorderSpace.y) * (spanY - 1) / 2f);
1282 
1283         // Keep track of best-scoring drop area
1284         final int[] bestXY = result != null ? result : new int[2];
1285         double bestDistance = Double.MAX_VALUE;
1286         final Rect bestRect = new Rect(-1, -1, -1, -1);
1287         final Stack<Rect> validRegions = new Stack<>();
1288 
1289         final int countX = mCountX;
1290         final int countY = mCountY;
1291 
1292         if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1293                 spanX < minSpanX || spanY < minSpanY) {
1294             return bestXY;
1295         }
1296 
1297         for (int y = 0; y < countY - (minSpanY - 1); y++) {
1298             inner:
1299             for (int x = 0; x < countX - (minSpanX - 1); x++) {
1300                 int ySize = -1;
1301                 int xSize = -1;
1302                 if (!ignoreOccupied) {
1303                     // First, let's see if this thing fits anywhere
1304                     for (int i = 0; i < minSpanX; i++) {
1305                         for (int j = 0; j < minSpanY; j++) {
1306                             if (mOccupied.cells[x + i][y + j]) {
1307                                 continue inner;
1308                             }
1309                         }
1310                     }
1311                     xSize = minSpanX;
1312                     ySize = minSpanY;
1313 
1314                     // We know that the item will fit at _some_ acceptable size, now let's see
1315                     // how big we can make it. We'll alternate between incrementing x and y spans
1316                     // until we hit a limit.
1317                     boolean incX = true;
1318                     boolean hitMaxX = xSize >= spanX;
1319                     boolean hitMaxY = ySize >= spanY;
1320                     while (!(hitMaxX && hitMaxY)) {
1321                         if (incX && !hitMaxX) {
1322                             for (int j = 0; j < ySize; j++) {
1323                                 if (x + xSize > countX -1 || mOccupied.cells[x + xSize][y + j]) {
1324                                     // We can't move out horizontally
1325                                     hitMaxX = true;
1326                                 }
1327                             }
1328                             if (!hitMaxX) {
1329                                 xSize++;
1330                             }
1331                         } else if (!hitMaxY) {
1332                             for (int i = 0; i < xSize; i++) {
1333                                 if (y + ySize > countY - 1 || mOccupied.cells[x + i][y + ySize]) {
1334                                     // We can't move out vertically
1335                                     hitMaxY = true;
1336                                 }
1337                             }
1338                             if (!hitMaxY) {
1339                                 ySize++;
1340                             }
1341                         }
1342                         hitMaxX |= xSize >= spanX;
1343                         hitMaxY |= ySize >= spanY;
1344                         incX = !incX;
1345                     }
1346                 }
1347                 final int[] cellXY = mTmpPoint;
1348                 cellToCenterPoint(x, y, cellXY);
1349 
1350                 // We verify that the current rect is not a sub-rect of any of our previous
1351                 // candidates. In this case, the current rect is disqualified in favour of the
1352                 // containing rect.
1353                 Rect currentRect = new Rect(x, y, x + xSize, y + ySize);
1354                 boolean contained = false;
1355                 for (Rect r : validRegions) {
1356                     if (r.contains(currentRect)) {
1357                         contained = true;
1358                         break;
1359                     }
1360                 }
1361                 validRegions.push(currentRect);
1362                 double distance = Math.hypot(cellXY[0] - relativeXPos,  cellXY[1] - relativeYPos);
1363 
1364                 if ((distance <= bestDistance && !contained) ||
1365                         currentRect.contains(bestRect)) {
1366                     bestDistance = distance;
1367                     bestXY[0] = x;
1368                     bestXY[1] = y;
1369                     if (resultSpan != null) {
1370                         resultSpan[0] = xSize;
1371                         resultSpan[1] = ySize;
1372                     }
1373                     bestRect.set(currentRect);
1374                 }
1375             }
1376         }
1377 
1378         // Return -1, -1 if no suitable location found
1379         if (bestDistance == Double.MAX_VALUE) {
1380             bestXY[0] = -1;
1381             bestXY[1] = -1;
1382         }
1383         return bestXY;
1384     }
1385 
getOccupied()1386     public GridOccupancy getOccupied() {
1387         return mOccupied;
1388     }
1389 
copySolutionToTempState(ItemConfiguration solution, View dragView)1390     private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
1391         mTmpOccupied.clear();
1392 
1393         int childCount = mShortcutsAndWidgets.getChildCount();
1394         for (int i = 0; i < childCount; i++) {
1395             View child = mShortcutsAndWidgets.getChildAt(i);
1396             if (child == dragView) continue;
1397             CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
1398             CellAndSpan c = solution.map.get(child);
1399             if (c != null) {
1400                 lp.setTmpCellX(c.cellX);
1401                 lp.setTmpCellY(c.cellY);
1402                 lp.cellHSpan = c.spanX;
1403                 lp.cellVSpan = c.spanY;
1404                 mTmpOccupied.markCells(c, true);
1405             }
1406         }
1407         mTmpOccupied.markCells(solution, true);
1408     }
1409 
animateItemsToSolution(ItemConfiguration solution, View dragView, boolean commitDragView)1410     private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
1411             commitDragView) {
1412 
1413         GridOccupancy occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
1414         occupied.clear();
1415 
1416         int childCount = mShortcutsAndWidgets.getChildCount();
1417         for (int i = 0; i < childCount; i++) {
1418             View child = mShortcutsAndWidgets.getChildAt(i);
1419             if (child == dragView) continue;
1420             CellAndSpan c = solution.map.get(child);
1421             if (c != null) {
1422                 animateChildToPosition(child, c.cellX, c.cellY, REORDER_ANIMATION_DURATION, 0,
1423                         DESTRUCTIVE_REORDER, false);
1424                 occupied.markCells(c, true);
1425             }
1426         }
1427         if (commitDragView) {
1428             occupied.markCells(solution, true);
1429         }
1430     }
1431 
1432 
1433     // This method starts or changes the reorder preview animations
beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution, View dragView, int mode)1434     private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
1435             View dragView, int mode) {
1436         int childCount = mShortcutsAndWidgets.getChildCount();
1437         for (int i = 0; i < childCount; i++) {
1438             View child = mShortcutsAndWidgets.getChildAt(i);
1439             if (child == dragView) continue;
1440             CellAndSpan c = solution.map.get(child);
1441             boolean skip = mode == ReorderPreviewAnimation.MODE_HINT
1442                     && !solution.intersectingViews.contains(child);
1443 
1444             CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
1445             if (c != null && !skip && (child instanceof Reorderable)) {
1446                 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode,
1447                         lp.getCellX(), lp.getCellY(), c.cellX, c.cellY, c.spanX, c.spanY,
1448                         mReorderPreviewAnimationMagnitude, this, mShakeAnimators);
1449                 rha.animate();
1450             }
1451         }
1452     }
1453 
completeAndClearReorderPreviewAnimations()1454     private void completeAndClearReorderPreviewAnimations() {
1455         for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
1456             a.finishAnimation();
1457         }
1458         mShakeAnimators.clear();
1459     }
1460 
commitTempPlacement(View dragView)1461     private void commitTempPlacement(View dragView) {
1462         mTmpOccupied.copyTo(mOccupied);
1463 
1464         int screenId = mCellLayoutContainer.getCellLayoutId(this);
1465         int container = Favorites.CONTAINER_DESKTOP;
1466 
1467         if (mContainerType == HOTSEAT) {
1468             screenId = -1;
1469             container = Favorites.CONTAINER_HOTSEAT;
1470         }
1471 
1472         int childCount = mShortcutsAndWidgets.getChildCount();
1473         for (int i = 0; i < childCount; i++) {
1474             View child = mShortcutsAndWidgets.getChildAt(i);
1475             CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
1476             ItemInfo info = (ItemInfo) child.getTag();
1477             // We do a null check here because the item info can be null in the case of the
1478             // AllApps button in the hotseat.
1479             if (info != null && child != dragView) {
1480                 CellPos presenterPos = mActivity.getCellPosMapper().mapModelToPresenter(info);
1481                 final boolean requiresDbUpdate = (presenterPos.cellX != lp.getTmpCellX()
1482                         || presenterPos.cellY != lp.getTmpCellY() || info.spanX != lp.cellHSpan
1483                         || info.spanY != lp.cellVSpan || presenterPos.screenId != screenId);
1484 
1485                 lp.setCellX(lp.getTmpCellX());
1486                 lp.setCellY(lp.getTmpCellY());
1487                 if (requiresDbUpdate) {
1488                     Launcher.cast(mActivity).getModelWriter().modifyItemInDatabase(info, container,
1489                             screenId, lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan);
1490                 }
1491             }
1492         }
1493     }
1494 
setUseTempCoords(boolean useTempCoords)1495     private void setUseTempCoords(boolean useTempCoords) {
1496         int childCount = mShortcutsAndWidgets.getChildCount();
1497         for (int i = 0; i < childCount; i++) {
1498             CellLayoutLayoutParams lp = (CellLayoutLayoutParams) mShortcutsAndWidgets.getChildAt(
1499                     i).getLayoutParams();
1500             lp.useTmpCoords = useTempCoords;
1501         }
1502     }
1503 
1504     /**
1505      * For a given region, return the rectangle of the overlapping cell and span with the given
1506      * region including the region itself. If there is no overlap the rectangle will be
1507      * invalid i.e. -1, 0, -1, 0.
1508      */
1509     @Nullable
getIntersectingRectanglesInRegion(final Rect region, final View dragView)1510     public Rect getIntersectingRectanglesInRegion(final Rect region, final View dragView) {
1511         Rect boundingRect = new Rect(region);
1512         Rect r1 = new Rect();
1513         boolean isOverlapping = false;
1514         final int count = mShortcutsAndWidgets.getChildCount();
1515         for (int i = 0; i < count; i++) {
1516             View child = mShortcutsAndWidgets.getChildAt(i);
1517             if (child == dragView) continue;
1518             CellLayoutLayoutParams
1519                     lp = (CellLayoutLayoutParams) child.getLayoutParams();
1520             r1.set(lp.getCellX(), lp.getCellY(), lp.getCellX() + lp.cellHSpan,
1521                     lp.getCellY() + lp.cellVSpan);
1522             if (Rect.intersects(region, r1)) {
1523                 isOverlapping = true;
1524                 boundingRect.union(r1);
1525             }
1526         }
1527         return isOverlapping ? boundingRect : null;
1528     }
1529 
isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY, View dragView, int[] result)1530     public boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
1531             View dragView, int[] result) {
1532         result = findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY, result);
1533         return getIntersectingRectanglesInRegion(
1534                 new Rect(result[0], result[1], result[0] + spanX, result[1] + spanY),
1535                 dragView
1536         ) != null;
1537     }
1538 
revertTempState()1539     void revertTempState() {
1540         completeAndClearReorderPreviewAnimations();
1541         if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
1542             final int count = mShortcutsAndWidgets.getChildCount();
1543             for (int i = 0; i < count; i++) {
1544                 View child = mShortcutsAndWidgets.getChildAt(i);
1545                 CellLayoutLayoutParams
1546                         lp = (CellLayoutLayoutParams) child.getLayoutParams();
1547                 if (lp.getTmpCellX() != lp.getCellX() || lp.getTmpCellY() != lp.getCellY()) {
1548                     lp.setTmpCellX(lp.getCellX());
1549                     lp.setTmpCellY(lp.getCellY());
1550                     animateChildToPosition(child, lp.getCellX(), lp.getCellY(),
1551                             REORDER_ANIMATION_DURATION, 0, false, false);
1552                 }
1553             }
1554             setItemPlacementDirty(false);
1555         }
1556     }
1557 
createAreaForResize(int cellX, int cellY, int spanX, int spanY, View dragView, int[] direction, boolean commit)1558     boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
1559             View dragView, int[] direction, boolean commit) {
1560         int[] pixelXY = new int[2];
1561         regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
1562 
1563         // First we determine if things have moved enough to cause a different layout
1564         ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
1565                 spanX,  spanY, direction, dragView,  true);
1566 
1567         setUseTempCoords(true);
1568         if (swapSolution != null && swapSolution.isSolution) {
1569             // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
1570             // committing anything or animating anything as we just want to determine if a solution
1571             // exists
1572             copySolutionToTempState(swapSolution, dragView);
1573             setItemPlacementDirty(true);
1574             animateItemsToSolution(swapSolution, dragView, commit);
1575 
1576             if (commit) {
1577                 commitTempPlacement(null);
1578                 completeAndClearReorderPreviewAnimations();
1579                 setItemPlacementDirty(false);
1580             } else {
1581                 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
1582                         ReorderPreviewAnimation.MODE_PREVIEW);
1583             }
1584             mShortcutsAndWidgets.requestLayout();
1585         }
1586         return swapSolution.isSolution;
1587     }
1588 
createReorderAlgorithm()1589     public ReorderAlgorithm createReorderAlgorithm() {
1590         return new ReorderAlgorithm(this);
1591     }
1592 
findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX)1593     protected ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
1594             int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX) {
1595         ItemConfiguration configuration = new ItemConfiguration();
1596         copyCurrentStateToSolution(configuration);
1597         ReorderParameters parameters = new ReorderParameters(pixelX, pixelY, spanX, spanY, minSpanX,
1598                 minSpanY, dragView, configuration);
1599         int[] directionVector = direction != null ? direction : mDirectionVector;
1600         return createReorderAlgorithm().findReorderSolution(parameters, directionVector, decX);
1601     }
1602 
copyCurrentStateToSolution(ItemConfiguration solution)1603     public void copyCurrentStateToSolution(ItemConfiguration solution) {
1604         int childCount = mShortcutsAndWidgets.getChildCount();
1605         for (int i = 0; i < childCount; i++) {
1606             View child = mShortcutsAndWidgets.getChildAt(i);
1607             CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
1608             solution.add(child,
1609                     new CellAndSpan(lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan));
1610         }
1611     }
1612 
1613     /**
1614      * When the user drags an Item in the workspace sometimes we need to move the items already in
1615      * the workspace to make space for the new item, this function return a solution for that
1616      * reorder.
1617      *
1618      * @param pixelX X coordinate in the screen of the dragView in pixels
1619      * @param pixelY Y coordinate in the screen of the dragView in pixels
1620      * @param minSpanX minimum horizontal span the item can be shrunk to
1621      * @param minSpanY minimum vertical span the item can be shrunk to
1622      * @param spanX occupied horizontal span
1623      * @param spanY occupied vertical span
1624      * @param dragView the view of the item being draged
1625      * @return returns a solution for the given parameters, the solution contains all the icons and
1626      *         the locations they should be in the given solution.
1627      */
calculateReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, View dragView)1628     public ItemConfiguration calculateReorder(int pixelX, int pixelY, int minSpanX, int minSpanY,
1629             int spanX, int spanY, View dragView) {
1630         ItemConfiguration configuration = new ItemConfiguration();
1631         copyCurrentStateToSolution(configuration);
1632         return createReorderAlgorithm().calculateReorder(
1633                 new ReorderParameters(pixelX, pixelY, spanX, spanY,  minSpanX, minSpanY, dragView,
1634                         configuration)
1635         );
1636     }
1637 
performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, View dragView, int[] result, int[] resultSpan, int mode)1638     int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
1639             View dragView, int[] result, int[] resultSpan, int mode) {
1640         if (resultSpan == null) {
1641             resultSpan = new int[]{-1, -1};
1642         }
1643         if (result == null) {
1644             result = new int[]{-1, -1};
1645         }
1646 
1647         ItemConfiguration finalSolution = null;
1648         // We want the solution to match the animation of the preview and to match the drop so we
1649         // only recalculate in mode MODE_SHOW_REORDER_HINT because that the first one to run in the
1650         // reorder cycle.
1651         if (mode == MODE_SHOW_REORDER_HINT || mPreviousSolution == null) {
1652             finalSolution = calculateReorder(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
1653                     dragView);
1654             mPreviousSolution = finalSolution;
1655         } else {
1656             finalSolution = mPreviousSolution;
1657             // We reset this vector after drop
1658             if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
1659                 mPreviousSolution = null;
1660             }
1661         }
1662 
1663         if (finalSolution == null || !finalSolution.isSolution) {
1664             result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
1665         } else {
1666             result[0] = finalSolution.cellX;
1667             result[1] = finalSolution.cellY;
1668             resultSpan[0] = finalSolution.spanX;
1669             resultSpan[1] = finalSolution.spanY;
1670             performReorder(finalSolution, dragView, mode);
1671         }
1672         return result;
1673     }
1674 
1675     /**
1676      * Animates and submits in the DB the given ItemConfiguration depending of the mode.
1677      *
1678      * @param solution represents widgets on the screen which the Workspace will animate to and
1679      * would be submitted to the database.
1680      * @param dragView view which is being dragged over the workspace that trigger the reorder
1681      * @param mode depending on the mode different animations would be played and depending on the
1682      *             mode the solution would be submitted or not the database.
1683      *             The possible modes are {@link MODE_SHOW_REORDER_HINT}, {@link MODE_DRAG_OVER},
1684      *             {@link MODE_ON_DROP}, {@link MODE_ON_DROP_EXTERNAL}, {@link  MODE_ACCEPT_DROP}
1685      *             defined in {@link CellLayout}.
1686      */
performReorder(ItemConfiguration solution, View dragView, int mode)1687     public void performReorder(ItemConfiguration solution, View dragView, int mode) {
1688         if (mode == MODE_SHOW_REORDER_HINT) {
1689             beginOrAdjustReorderPreviewAnimations(solution, dragView,
1690                     ReorderPreviewAnimation.MODE_HINT);
1691             return;
1692         }
1693         // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
1694         // committing anything or animating anything as we just want to determine if a solution
1695         // exists
1696         if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
1697             if (!DESTRUCTIVE_REORDER) {
1698                 setUseTempCoords(true);
1699             }
1700 
1701             if (!DESTRUCTIVE_REORDER) {
1702                 copySolutionToTempState(solution, dragView);
1703             }
1704             setItemPlacementDirty(true);
1705             animateItemsToSolution(solution, dragView, mode == MODE_ON_DROP);
1706 
1707             if (!DESTRUCTIVE_REORDER
1708                     && (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
1709                 // Since the temp solution didn't update dragView, don't commit it either
1710                 commitTempPlacement(dragView);
1711                 completeAndClearReorderPreviewAnimations();
1712                 setItemPlacementDirty(false);
1713             } else {
1714                 beginOrAdjustReorderPreviewAnimations(solution, dragView,
1715                         ReorderPreviewAnimation.MODE_PREVIEW);
1716             }
1717         }
1718 
1719         if (mode == MODE_ON_DROP && !DESTRUCTIVE_REORDER) {
1720             setUseTempCoords(false);
1721         }
1722 
1723         mShortcutsAndWidgets.requestLayout();
1724     }
1725 
setItemPlacementDirty(boolean dirty)1726     void setItemPlacementDirty(boolean dirty) {
1727         mItemPlacementDirty = dirty;
1728     }
isItemPlacementDirty()1729     boolean isItemPlacementDirty() {
1730         return mItemPlacementDirty;
1731     }
1732 
1733     /**
1734      * Find a starting cell position that will fit the given bounds nearest the requested
1735      * cell location. Uses Euclidean distance to score multiple vacant areas.
1736      *
1737      * @param pixelX The X location at which you want to search for a vacant area.
1738      * @param pixelY The Y location at which you want to search for a vacant area.
1739      * @param spanX Horizontal span of the object.
1740      * @param spanY Vertical span of the object.
1741      * @param result Previously returned value to possibly recycle.
1742      * @return The X, Y cell of a vacant area that can contain this object,
1743      *         nearest the requested location.
1744      */
findNearestAreaIgnoreOccupied(int pixelX, int pixelY, int spanX, int spanY, int[] result)1745     public int[] findNearestAreaIgnoreOccupied(int pixelX, int pixelY, int spanX, int spanY,
1746             int[] result) {
1747         return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, true, result, null);
1748     }
1749 
existsEmptyCell()1750     boolean existsEmptyCell() {
1751         return findCellForSpan(null, 1, 1);
1752     }
1753 
1754     /**
1755      * Finds the upper-left coordinate of the first rectangle in the grid that can
1756      * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
1757      * then this method will only return coordinates for rectangles that contain the cell
1758      * (intersectX, intersectY)
1759      *
1760      * @param cellXY The array that will contain the position of a vacant cell if such a cell
1761      *               can be found.
1762      * @param spanX The horizontal span of the cell we want to find.
1763      * @param spanY The vertical span of the cell we want to find.
1764      *
1765      * @return True if a vacant cell of the specified dimension was found, false otherwise.
1766      */
findCellForSpan(int[] cellXY, int spanX, int spanY)1767     public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
1768         if (cellXY == null) {
1769             cellXY = new int[2];
1770         }
1771         return mOccupied.findVacantCell(cellXY, spanX, spanY);
1772     }
1773 
1774     /**
1775      * A drag event has begun over this layout.
1776      * It may have begun over this layout (in which case onDragChild is called first),
1777      * or it may have begun on another layout.
1778      */
onDragEnter()1779     void onDragEnter() {
1780         mDragging = true;
1781         mPreviousSolution = null;
1782     }
1783 
1784     /**
1785      * Called when drag has left this CellLayout or has been completed (successfully or not)
1786      */
onDragExit()1787     void onDragExit() {
1788         // This can actually be called when we aren't in a drag, e.g. when adding a new
1789         // item to this layout via the customize drawer.
1790         // Guard against that case.
1791         if (mDragging) {
1792             mDragging = false;
1793         }
1794 
1795         // Invalidate the drag data
1796         mPreviousSolution = null;
1797         mDragCell[0] = mDragCell[1] = -1;
1798         mDragCellSpan[0] = mDragCellSpan[1] = -1;
1799         mDragOutlineAnims[mDragOutlineCurrent].animateOut();
1800         mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
1801         revertTempState();
1802         setIsDragOverlapping(false);
1803     }
1804 
1805     /**
1806      * Mark a child as having been dropped.
1807      * At the beginning of the drag operation, the child may have been on another
1808      * screen, but it is re-parented before this method is called.
1809      *
1810      * @param child The child that is being dropped
1811      */
onDropChild(View child)1812     void onDropChild(View child) {
1813         mPlayDragHaptics = false;
1814         if (child != null) {
1815             CellLayoutLayoutParams
1816                     lp = (CellLayoutLayoutParams) child.getLayoutParams();
1817             lp.dropped = true;
1818             child.requestLayout();
1819             markCellsAsOccupiedForView(child);
1820         }
1821     }
1822 
1823     /**
1824      * Computes a bounding rectangle for a range of cells
1825      *
1826      * @param cellX X coordinate of upper left corner expressed as a cell position
1827      * @param cellY Y coordinate of upper left corner expressed as a cell position
1828      * @param cellHSpan Width in cells
1829      * @param cellVSpan Height in cells
1830      * @param resultRect Rect into which to put the results
1831      */
cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect)1832     public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
1833         final int cellWidth = mCellWidth;
1834         final int cellHeight = mCellHeight;
1835 
1836         // We observe a shift of 1 pixel on the x coordinate compared to the actual cell coordinates
1837         final int hStartPadding = getPaddingLeft()
1838                 + (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
1839         final int vStartPadding = getPaddingTop();
1840 
1841         int x = hStartPadding + (cellX * mBorderSpace.x) + (cellX * cellWidth)
1842                 + getTranslationXForCell(cellX, cellY);
1843         int y = vStartPadding + (cellY * mBorderSpace.y) + (cellY * cellHeight);
1844 
1845         int width = cellHSpan * cellWidth + ((cellHSpan - 1) * mBorderSpace.x);
1846         int height = cellVSpan * cellHeight + ((cellVSpan - 1) * mBorderSpace.y);
1847 
1848         resultRect.set(x, y, x + width, y + height);
1849     }
1850 
1851     /** Enables successors to provide an X adjustment for the cell. */
getTranslationXForCell(int cellX, int cellY)1852     protected int getTranslationXForCell(int cellX, int cellY) {
1853         return 0;
1854     }
1855 
markCellsAsOccupiedForView(View view)1856     public void markCellsAsOccupiedForView(View view) {
1857         if (view instanceof LauncherAppWidgetHostView
1858                 && view.getTag() instanceof LauncherAppWidgetInfo) {
1859             LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
1860             CellPos pos = mActivity.getCellPosMapper().mapModelToPresenter(info);
1861             mOccupied.markCells(pos.cellX, pos.cellY, info.spanX, info.spanY, true);
1862             return;
1863         }
1864         if (view == null || view.getParent() != mShortcutsAndWidgets) return;
1865         CellLayoutLayoutParams
1866                 lp = (CellLayoutLayoutParams) view.getLayoutParams();
1867         mOccupied.markCells(lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan, true);
1868     }
1869 
markCellsAsUnoccupiedForView(View view)1870     public void markCellsAsUnoccupiedForView(View view) {
1871         if (view instanceof LauncherAppWidgetHostView
1872                 && view.getTag() instanceof LauncherAppWidgetInfo) {
1873             LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
1874             CellPos pos = mActivity.getCellPosMapper().mapModelToPresenter(info);
1875             mOccupied.markCells(pos.cellX, pos.cellY, info.spanX, info.spanY, false);
1876             return;
1877         }
1878         if (view == null || view.getParent() != mShortcutsAndWidgets) return;
1879         CellLayoutLayoutParams
1880                 lp = (CellLayoutLayoutParams) view.getLayoutParams();
1881         mOccupied.markCells(lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan, false);
1882     }
1883 
getDesiredWidth()1884     public int getDesiredWidth() {
1885         return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth)
1886                 + ((mCountX - 1) * mBorderSpace.x);
1887     }
1888 
getDesiredHeight()1889     public int getDesiredHeight()  {
1890         return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight)
1891                 + ((mCountY - 1) * mBorderSpace.y);
1892     }
1893 
isOccupied(int x, int y)1894     public boolean isOccupied(int x, int y) {
1895         if (x >= 0 && x < mCountX && y >= 0 && y < mCountY) {
1896             return mOccupied.cells[x][y];
1897         }
1898         if (BuildConfig.IS_STUDIO_BUILD) {
1899             throw new RuntimeException("Position exceeds the bound of this CellLayout");
1900         }
1901         return true;
1902     }
1903 
1904     @Override
generateLayoutParams(AttributeSet attrs)1905     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1906         return new CellLayoutLayoutParams(getContext(), attrs);
1907     }
1908 
1909     @Override
checkLayoutParams(ViewGroup.LayoutParams p)1910     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1911         return p instanceof CellLayoutLayoutParams;
1912     }
1913 
1914     @Override
generateLayoutParams(ViewGroup.LayoutParams p)1915     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1916         return new CellLayoutLayoutParams(p);
1917     }
1918 
1919     /**
1920      * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing
1921      * if necessary).
1922      */
hasReorderSolution(ItemInfo itemInfo)1923     public boolean hasReorderSolution(ItemInfo itemInfo) {
1924         int[] cellPoint = new int[2];
1925         // Check for a solution starting at every cell.
1926         for (int cellX = 0; cellX < getCountX(); cellX++) {
1927             for (int cellY = 0; cellY < getCountY(); cellY++) {
1928                 cellToPoint(cellX, cellY, cellPoint);
1929                 if (findReorderSolution(cellPoint[0], cellPoint[1], itemInfo.minSpanX,
1930                         itemInfo.minSpanY, itemInfo.spanX, itemInfo.spanY, mDirectionVector, null,
1931                         true).isSolution) {
1932                     return true;
1933                 }
1934             }
1935         }
1936         return false;
1937     }
1938 
1939     /**
1940      * Finds solution to accept hotseat migration to cell layout. commits solution if commitConfig
1941      */
makeSpaceForHotseatMigration(boolean commitConfig)1942     public boolean makeSpaceForHotseatMigration(boolean commitConfig) {
1943         int[] cellPoint = new int[2];
1944         int[] directionVector = new int[]{0, -1};
1945         cellToPoint(0, mCountY, cellPoint);
1946         ItemConfiguration configuration = findReorderSolution(
1947                 cellPoint[0] /* pixelX */,
1948                 cellPoint[1] /* pixelY */,
1949                 mCountX /* minSpanX */,
1950                 1 /* minSpanY */,
1951                 mCountX /* spanX */,
1952                 1 /* spanY */,
1953                 directionVector /* direction */,
1954                 null /* dragView */,
1955                 false /* decX */
1956         );
1957         if (configuration.isSolution) {
1958             if (commitConfig) {
1959                 copySolutionToTempState(configuration, null);
1960                 commitTempPlacement(null);
1961                 // undo marking cells occupied since there is actually nothing being placed yet.
1962                 mOccupied.markCells(0, mCountY - 1, mCountX, 1, false);
1963             }
1964             return true;
1965         }
1966         return false;
1967     }
1968 
1969     /**
1970      * returns a copy of cell layout's grid occupancy
1971      */
cloneGridOccupancy()1972     public GridOccupancy cloneGridOccupancy() {
1973         GridOccupancy occupancy = new GridOccupancy(mCountX, mCountY);
1974         mOccupied.copyTo(occupancy);
1975         return occupancy;
1976     }
1977 
isRegionVacant(int x, int y, int spanX, int spanY)1978     public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
1979         return mOccupied.isRegionVacant(x, y, spanX, spanY);
1980     }
1981 
setSpaceBetweenCellLayoutsPx(@x int spaceBetweenCellLayoutsPx)1982     public void setSpaceBetweenCellLayoutsPx(@Px int spaceBetweenCellLayoutsPx) {
1983         mSpaceBetweenCellLayoutsPx = spaceBetweenCellLayoutsPx;
1984     }
1985 }
1986