blob: 947388bc7b5eefdd1c4d70fef212d58b972f9acc [file] [log] [blame]
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001/*
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
Daniel Sandler325dc232013-06-05 22:57:57 -040017package com.android.launcher3;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080018
Sunny Goyaleaf7a952020-07-29 16:54:20 -070019import static android.animation.ValueAnimator.areAnimatorsEnabled;
20
Sunny Goyalf0b6db72018-08-13 16:10:14 -070021import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
Jon Miranda22679f82021-02-17 17:44:03 -050022import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS;
Sunny Goyalf0b6db72018-08-13 16:10:14 -070023
Joe Onorato4be866d2010-10-10 11:26:02 -070024import android.animation.Animator;
Michael Jurka629758f2012-06-14 16:18:21 -070025import android.animation.AnimatorListenerAdapter;
Sunny Goyal849c6a22018-08-08 16:33:46 -070026import android.animation.ObjectAnimator;
Chet Haase00397b12010-10-07 11:13:10 -070027import android.animation.TimeInterpolator;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070028import android.animation.ValueAnimator;
29import android.animation.ValueAnimator.AnimatorUpdateListener;
Sunny Goyal726bee72018-03-05 12:54:24 -080030import android.annotation.SuppressLint;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080031import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040032import android.content.res.Resources;
Sunny Goyalc13403c2016-11-18 23:44:48 -080033import android.content.res.TypedArray;
Joe Onorato4be866d2010-10-10 11:26:02 -070034import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070035import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080036import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070037import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070038import android.graphics.Point;
Adam Cohend9162062020-03-24 16:35:35 -070039import android.graphics.PointF;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080040import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080041import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070042import android.graphics.drawable.Drawable;
Adam Cohen1462de32012-07-24 22:34:36 -070043import android.os.Parcelable;
Rajeev Kumar9962dbe2017-06-12 12:16:20 -070044import android.util.ArrayMap;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080045import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070046import android.util.Log;
Sunny Goyal849c6a22018-08-08 16:33:46 -070047import android.util.Property;
Adam Cohen1462de32012-07-24 22:34:36 -070048import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080049import android.view.MotionEvent;
50import android.view.View;
51import android.view.ViewDebug;
52import android.view.ViewGroup;
Adam Cohenc9735cf2015-01-23 16:11:55 -080053import android.view.accessibility.AccessibilityEvent;
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -070054
vadimt04f356f2019-02-14 18:46:36 -080055import androidx.annotation.IntDef;
56import androidx.core.view.ViewCompat;
57
Sunny Goyalaa8ef112015-06-12 20:04:41 -070058import com.android.launcher3.LauncherSettings.Favorites;
Sunny Goyale9b651e2015-04-24 11:44:51 -070059import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -070060import com.android.launcher3.anim.Interpolators;
Sunny Goyal3d706ad2017-03-06 16:56:39 -080061import com.android.launcher3.config.FeatureFlags;
Adam Cohen65086992020-02-19 08:40:49 -080062import com.android.launcher3.dragndrop.DraggableView;
Jon Mirandaa0233f72017-06-22 18:34:45 -070063import com.android.launcher3.folder.PreviewBackground;
Sunny Goyal06e21a22016-08-11 16:02:02 -070064import com.android.launcher3.graphics.DragPreviewProvider;
Sunny Goyale396abf2020-04-06 15:11:17 -070065import com.android.launcher3.model.data.ItemInfo;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -070066import com.android.launcher3.util.CellAndSpan;
67import com.android.launcher3.util.GridOccupancy;
Sunny Goyale2fd14b2015-08-27 17:45:46 -070068import com.android.launcher3.util.ParcelableSparseArray;
Sunny Goyal9b29ca52017-02-17 10:39:44 -080069import com.android.launcher3.util.Themes;
Adam Cohen091440a2015-03-18 14:16:05 -070070import com.android.launcher3.util.Thunk;
Sunny Goyalab770a12018-11-14 15:17:26 -080071import com.android.launcher3.views.ActivityContext;
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -070072
Sunny Goyalc13403c2016-11-18 23:44:48 -080073import java.lang.annotation.Retention;
74import java.lang.annotation.RetentionPolicy;
Adam Cohen69ce2e52011-07-03 19:25:21 -070075import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070076import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080077import java.util.Collections;
78import java.util.Comparator;
Adam Cohend41fbf52012-02-16 23:53:59 -080079import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070080
Sunny Goyalc4d32012020-04-03 17:10:11 -070081public class CellLayout extends ViewGroup {
Tony Wickhama0628cc2015-10-14 15:23:04 -070082 private static final String TAG = "CellLayout";
83 private static final boolean LOGD = false;
Winson Chungaafa03c2010-06-11 17:34:16 -070084
Sunny Goyalab770a12018-11-14 15:17:26 -080085 protected final ActivityContext mActivity;
Sunny Goyal4ffec482016-02-09 11:28:52 -080086 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070087 @Thunk int mCellWidth;
Sunny Goyal4ffec482016-02-09 11:28:52 -080088 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070089 @Thunk int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070090 private int mFixedCellWidth;
91 private int mFixedCellHeight;
Jon Miranda228877d2021-02-09 11:05:00 -050092 @ViewDebug.ExportedProperty(category = "launcher")
93 private final int mBorderSpacing;
Winson Chungaafa03c2010-06-11 17:34:16 -070094
Sunny Goyal4ffec482016-02-09 11:28:52 -080095 @ViewDebug.ExportedProperty(category = "launcher")
Sunny Goyalff4ba2d2016-04-02 14:12:34 -070096 private int mCountX;
Sunny Goyal4ffec482016-02-09 11:28:52 -080097 @ViewDebug.ExportedProperty(category = "launcher")
Sunny Goyalff4ba2d2016-04-02 14:12:34 -070098 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080099
Adam Cohen917e3882013-10-31 15:03:35 -0700100 private boolean mDropPending = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800101
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700102 // These are temporary variables to prevent having to allocate a new object just to
103 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Adam Cohen091440a2015-03-18 14:16:05 -0700104 @Thunk final int[] mTmpPoint = new int[2];
Sunny Goyal2805e632015-05-20 15:35:32 -0700105 @Thunk final int[] mTempLocation = new int[2];
Adam Cohend9162062020-03-24 16:35:35 -0700106 final PointF mTmpPointF = new PointF();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700107
Adam Cohen65086992020-02-19 08:40:49 -0800108 // Used to visualize / debug the Grid of the CellLayout
109 private static final boolean VISUALIZE_GRID = false;
110 private Rect mVisualizeGridRect = new Rect();
111 private Paint mVisualizeGridPaint = new Paint();
112
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700113 private GridOccupancy mOccupied;
114 private GridOccupancy mTmpOccupied;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800115
Michael Jurkadee05892010-07-27 10:01:56 -0700116 private OnTouchListener mInterceptTouchListener;
117
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800118 private final ArrayList<DelegatedCellDrawing> mDelegatedCellDrawings = new ArrayList<>();
Jon Mirandaa0233f72017-06-22 18:34:45 -0700119 final PreviewBackground mFolderLeaveBehind = new PreviewBackground();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700120
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800121 private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active };
Sunny Goyale15e2a82017-12-15 13:05:42 -0800122 private static final int[] BACKGROUND_STATE_DEFAULT = EMPTY_STATE_SET;
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800123 private final Drawable mBackground;
Sunny Goyal2805e632015-05-20 15:35:32 -0700124
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700125 // These values allow a fixed measurement to be set on the CellLayout.
126 private int mFixedWidth = -1;
127 private int mFixedHeight = -1;
128
Michael Jurka33945b22010-12-21 18:19:38 -0800129 // If we're actively dragging something over this screen, mIsDragOverlapping is true
130 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700131
Winson Chung150fbab2010-09-29 17:14:26 -0700132 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700133 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700134 @Thunk final Rect[] mDragOutlines = new Rect[4];
135 @Thunk final float[] mDragOutlineAlphas = new float[mDragOutlines.length];
136 private final InterruptibleInOutAnimator[] mDragOutlineAnims =
Joe Onorato4be866d2010-10-10 11:26:02 -0700137 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700138
139 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700140 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700141 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700142
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700143 @Thunk final ArrayMap<LayoutParams, Animator> mReorderAnimators = new ArrayMap<>();
Adam Cohend9162062020-03-24 16:35:35 -0700144 @Thunk final ArrayMap<Reorderable, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
Adam Cohen19f37922012-03-21 11:59:11 -0700145
146 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700147
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700148 // When a drag operation is in progress, holds the nearest cell to the touch point
149 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800150
Joe Onorato4be866d2010-10-10 11:26:02 -0700151 private boolean mDragging = false;
152
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700153 private final TimeInterpolator mEaseOutInterpolator;
154 private final ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700155
Sunny Goyalc13403c2016-11-18 23:44:48 -0800156 @Retention(RetentionPolicy.SOURCE)
157 @IntDef({WORKSPACE, HOTSEAT, FOLDER})
158 public @interface ContainerType{}
159 public static final int WORKSPACE = 0;
160 public static final int HOTSEAT = 1;
161 public static final int FOLDER = 2;
162
163 @ContainerType private final int mContainerType;
164
Jon Mirandab28c4fc2017-06-20 10:58:36 -0700165 private final float mChildScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800166
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800167 public static final int MODE_SHOW_REORDER_HINT = 0;
168 public static final int MODE_DRAG_OVER = 1;
169 public static final int MODE_ON_DROP = 2;
170 public static final int MODE_ON_DROP_EXTERNAL = 3;
171 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700172 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800173 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
174
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800175 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700176 private static final int REORDER_ANIMATION_DURATION = 150;
Sunny Goyalc13403c2016-11-18 23:44:48 -0800177 @Thunk final float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700178
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700179 private final ArrayList<View> mIntersectingViews = new ArrayList<>();
180 private final Rect mOccupiedRect = new Rect();
181 private final int[] mDirectionVector = new int[2];
182 final int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700183 private static final int INVALID_DIRECTION = -100;
Adam Cohen482ed822012-03-02 14:15:13 -0800184
Sunny Goyal2805e632015-05-20 15:35:32 -0700185 private final Rect mTempRect = new Rect();
Winson Chung3a6e7f32013-10-09 15:50:52 -0700186
Sunny Goyal73b5a272019-12-09 14:55:56 -0800187 private static final Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700188
Adam Cohenc9735cf2015-01-23 16:11:55 -0800189 // Related to accessible drag and drop
Adam Cohen6e7c37a2020-06-25 19:22:37 -0700190 DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800191
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800192 public CellLayout(Context context) {
193 this(context, null);
194 }
195
196 public CellLayout(Context context, AttributeSet attrs) {
197 this(context, attrs, 0);
198 }
199
200 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
201 super(context, attrs, defStyle);
Sunny Goyalc13403c2016-11-18 23:44:48 -0800202 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
203 mContainerType = a.getInteger(R.styleable.CellLayout_containerType, WORKSPACE);
204 a.recycle();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700205
206 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
207 // the user where a dragged item will land when dropped.
208 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800209 setClipToPadding(false);
Sunny Goyalab770a12018-11-14 15:17:26 -0800210 mActivity = ActivityContext.lookupContext(context);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700211
Sunny Goyalc4d32012020-04-03 17:10:11 -0700212 DeviceProfile grid = mActivity.getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800213
Jon Miranda228877d2021-02-09 11:05:00 -0500214 mBorderSpacing = grid.cellLayoutBorderSpacingPx;
Winson Chung11a1a532013-09-13 11:14:45 -0700215 mCellWidth = mCellHeight = -1;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800216 mFixedCellWidth = mFixedCellHeight = -1;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700217
218 mCountX = grid.inv.numColumns;
219 mCountY = grid.inv.numRows;
220 mOccupied = new GridOccupancy(mCountX, mCountY);
221 mTmpOccupied = new GridOccupancy(mCountX, mCountY);
222
Adam Cohen5b53f292012-03-29 14:30:35 -0700223 mPreviousReorderDirection[0] = INVALID_DIRECTION;
224 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800225
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800226 mFolderLeaveBehind.mDelegateCellX = -1;
227 mFolderLeaveBehind.mDelegateCellY = -1;
Adam Cohenefca0272016-02-24 19:19:06 -0800228
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800229 setAlwaysDrawnWithCacheEnabled(false);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700230 final Resources res = getResources();
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700231
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800232 mBackground = res.getDrawable(R.drawable.bg_celllayout);
Sunny Goyal2805e632015-05-20 15:35:32 -0700233 mBackground.setCallback(this);
Sunny Goyalaeb16432017-10-16 11:46:41 -0700234 mBackground.setAlpha(0);
Michael Jurka33945b22010-12-21 18:19:38 -0800235
Sunny Goyalc13403c2016-11-18 23:44:48 -0800236 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700237
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700238 // Initialize the data structures used for the drag visualization.
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -0700239 mEaseOutInterpolator = Interpolators.DEACCEL_2_5; // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700240 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700241 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800242 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700243 }
Mario Bertschler54ba6012017-06-08 10:53:53 -0700244 mDragOutlinePaint.setColor(Themes.getAttrColor(context, R.attr.workspaceTextColor));
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700245
246 // When dragging things around the home screens, we show a green outline of
247 // where the item will land. The outlines gradually fade out, leaving a trail
248 // behind the drag path.
249 // Set up all the animations that are used to implement this fading.
250 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700251 final float fromAlphaValue = 0;
252 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700253
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700254 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700255
256 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700257 final InterruptibleInOutAnimator anim =
Sunny Goyal849c6a22018-08-08 16:33:46 -0700258 new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700259 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700260 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700261 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700262 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700263 final Bitmap outline = (Bitmap)anim.getTag();
264
265 // If an animation is started and then stopped very quickly, we can still
266 // get spurious updates we've cleared the tag. Guard against this.
267 if (outline == null) {
Tony Wickhama0628cc2015-10-14 15:23:04 -0700268 if (LOGD) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700269 Object val = animation.getAnimatedValue();
270 Log.d(TAG, "anim " + thisIndex + " update: " + val +
271 ", isStopped " + anim.isStopped());
272 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700273 // Try to prevent it from continuing to run
274 animation.cancel();
275 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700276 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800277 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700278 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700279 }
280 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700281 // The animation holds a reference to the drag outline bitmap as long is it's
282 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700283 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700284 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700285 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700286 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700287 anim.setTag(null);
288 }
289 }
290 });
291 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700292 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700293
Sunny Goyalc13403c2016-11-18 23:44:48 -0800294 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType);
Jon Miranda228877d2021-02-09 11:05:00 -0500295 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
296 mBorderSpacing);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700297 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700298 }
299
Sunny Goyal9b180102020-03-11 10:02:29 -0700300 /**
301 * Sets or clears a delegate used for accessible drag and drop
302 */
303 public void setDragAndDropAccessibilityDelegate(DragAndDropAccessibilityDelegate delegate) {
304 setOnClickListener(delegate);
Sunny Goyal9b180102020-03-11 10:02:29 -0700305 ViewCompat.setAccessibilityDelegate(this, delegate);
306
Adam Cohen6e7c37a2020-06-25 19:22:37 -0700307 mTouchHelper = delegate;
308 int accessibilityFlag = mTouchHelper != null
Sunny Goyal9b180102020-03-11 10:02:29 -0700309 ? IMPORTANT_FOR_ACCESSIBILITY_YES : IMPORTANT_FOR_ACCESSIBILITY_NO;
310 setImportantForAccessibility(accessibilityFlag);
311 getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
Adam Cohenc9735cf2015-01-23 16:11:55 -0800312
Sunny Goyal384b5782021-02-09 22:50:02 -0800313 // ExploreByTouchHelper sets focusability. Clear it when the delegate is cleared.
314 setFocusable(delegate != null);
Adam Cohenc9735cf2015-01-23 16:11:55 -0800315 // Invalidate the accessibility hierarchy
316 if (getParent() != null) {
317 getParent().notifySubtreeAccessibilityStateChanged(
318 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
319 }
320 }
321
Sunny Goyala4647b62021-02-02 13:45:34 -0800322 /**
323 * Returns the currently set accessibility delegate
324 */
325 public DragAndDropAccessibilityDelegate getDragAndDropAccessibilityDelegate() {
326 return mTouchHelper;
327 }
328
Adam Cohenc9735cf2015-01-23 16:11:55 -0800329 @Override
Adam Cohen6e7c37a2020-06-25 19:22:37 -0700330 public boolean dispatchHoverEvent(MotionEvent event) {
331 // Always attempt to dispatch hover events to accessibility first.
332 if (mTouchHelper != null && mTouchHelper.dispatchHoverEvent(event)) {
333 return true;
334 }
335 return super.dispatchHoverEvent(event);
336 }
337
338 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800339 public boolean onInterceptTouchEvent(MotionEvent ev) {
Winson Chungf9935182020-10-23 09:26:44 -0700340 return mTouchHelper != null
341 || (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev));
Adam Cohenc9735cf2015-01-23 16:11:55 -0800342 }
343
Chris Craik01f2d7f2013-10-01 14:41:56 -0700344 public void enableHardwareLayer(boolean hasLayer) {
345 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700346 }
347
vadimt04f356f2019-02-14 18:46:36 -0800348 public boolean isHardwareLayerEnabled() {
349 return mShortcutsAndWidgets.getLayerType() == LAYER_TYPE_HARDWARE;
350 }
351
Winson Chung5f8afe62013-08-12 16:19:28 -0700352 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700353 mFixedCellWidth = mCellWidth = width;
354 mFixedCellHeight = mCellHeight = height;
Jon Miranda228877d2021-02-09 11:05:00 -0500355 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
356 mBorderSpacing);
Winson Chung5f8afe62013-08-12 16:19:28 -0700357 }
358
Adam Cohen2801caf2011-05-13 20:57:39 -0700359 public void setGridSize(int x, int y) {
360 mCountX = x;
361 mCountY = y;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700362 mOccupied = new GridOccupancy(mCountX, mCountY);
363 mTmpOccupied = new GridOccupancy(mCountX, mCountY);
Adam Cohen7fbec102012-03-27 12:42:19 -0700364 mTempRectStack.clear();
Jon Miranda228877d2021-02-09 11:05:00 -0500365 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
366 mBorderSpacing);
Adam Cohen76fc0852011-06-17 13:26:23 -0700367 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700368 }
369
Adam Cohen2374abf2013-04-16 14:56:57 -0700370 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
371 public void setInvertIfRtl(boolean invert) {
372 mShortcutsAndWidgets.setInvertIfRtl(invert);
373 }
374
Adam Cohen917e3882013-10-31 15:03:35 -0700375 public void setDropPending(boolean pending) {
376 mDropPending = pending;
377 }
378
379 public boolean isDropPending() {
380 return mDropPending;
381 }
382
Adam Cohenc50438c2014-08-19 17:43:05 -0700383 void setIsDragOverlapping(boolean isDragOverlapping) {
384 if (mIsDragOverlapping != isDragOverlapping) {
385 mIsDragOverlapping = isDragOverlapping;
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800386 mBackground.setState(mIsDragOverlapping
387 ? BACKGROUND_STATE_ACTIVE : BACKGROUND_STATE_DEFAULT);
Adam Cohenc50438c2014-08-19 17:43:05 -0700388 invalidate();
389 }
390 }
391
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700392 @Override
393 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700394 ParcelableSparseArray jail = getJailedArray(container);
395 super.dispatchSaveInstanceState(jail);
396 container.put(R.id.cell_layout_jail_id, jail);
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700397 }
398
399 @Override
400 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700401 super.dispatchRestoreInstanceState(getJailedArray(container));
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700402 }
403
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700404 /**
405 * Wrap the SparseArray in another Parcelable so that the item ids do not conflict with our
406 * our internal resource ids
407 */
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700408 private ParcelableSparseArray getJailedArray(SparseArray<Parcelable> container) {
409 final Parcelable parcelable = container.get(R.id.cell_layout_jail_id);
410 return parcelable instanceof ParcelableSparseArray ?
411 (ParcelableSparseArray) parcelable : new ParcelableSparseArray();
412 }
413
Tony Wickham0f97b782015-12-02 17:55:07 -0800414 public boolean getIsDragOverlapping() {
415 return mIsDragOverlapping;
416 }
417
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700418 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700419 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700420 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
421 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
422 // When we're small, we are either drawn normally or in the "accepts drops" state (during
423 // a drag). However, we also drag the mini hover background *over* one of those two
424 // backgrounds
Sunny Goyalaeb16432017-10-16 11:46:41 -0700425 if (mBackground.getAlpha() > 0) {
Sunny Goyal2805e632015-05-20 15:35:32 -0700426 mBackground.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700427 }
Romain Guya6abce82009-11-10 02:54:41 -0800428
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700429 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700430 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700431 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700432 if (alpha > 0) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700433 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700434 paint.setAlpha((int)(alpha + .5f));
Sunny Goyal106bf642015-07-16 12:18:06 -0700435 canvas.drawBitmap(b, null, mDragOutlines[i], paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700436 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700437 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800438
Adam Cohen482ed822012-03-02 14:15:13 -0800439 if (DEBUG_VISUALIZE_OCCUPIED) {
440 int[] pt = new int[2];
441 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700442 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800443 for (int i = 0; i < mCountX; i++) {
444 for (int j = 0; j < mCountY; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700445 if (mOccupied.cells[i][j]) {
Adam Cohen482ed822012-03-02 14:15:13 -0800446 cellToPoint(i, j, pt);
447 canvas.save();
448 canvas.translate(pt[0], pt[1]);
449 cd.draw(canvas);
450 canvas.restore();
451 }
452 }
453 }
454 }
455
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800456 for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
457 DelegatedCellDrawing cellDrawing = mDelegatedCellDrawings.get(i);
458 cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation);
Adam Cohenefca0272016-02-24 19:19:06 -0800459 canvas.save();
460 canvas.translate(mTempLocation[0], mTempLocation[1]);
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800461 cellDrawing.drawUnderItem(canvas);
Adam Cohenefca0272016-02-24 19:19:06 -0800462 canvas.restore();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700463 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700464
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800465 if (mFolderLeaveBehind.mDelegateCellX >= 0 && mFolderLeaveBehind.mDelegateCellY >= 0) {
466 cellToPoint(mFolderLeaveBehind.mDelegateCellX,
467 mFolderLeaveBehind.mDelegateCellY, mTempLocation);
Adam Cohenefca0272016-02-24 19:19:06 -0800468 canvas.save();
469 canvas.translate(mTempLocation[0], mTempLocation[1]);
Sunny Goyal19b93b72017-02-19 20:21:37 -0800470 mFolderLeaveBehind.drawLeaveBehind(canvas);
Adam Cohenefca0272016-02-24 19:19:06 -0800471 canvas.restore();
Adam Cohenc51934b2011-07-26 21:07:43 -0700472 }
Adam Cohen65086992020-02-19 08:40:49 -0800473
474 if (VISUALIZE_GRID) {
475 visualizeGrid(canvas);
476 }
477 }
478
479 protected void visualizeGrid(Canvas canvas) {
480 mVisualizeGridRect.set(0, 0, mCellWidth, mCellHeight);
481 mVisualizeGridPaint.setStrokeWidth(4);
482
483 for (int i = 0; i < mCountX; i++) {
484 for (int j = 0; j < mCountY; j++) {
485 canvas.save();
486
Jon Miranda228877d2021-02-09 11:05:00 -0500487 int transX = i * mCellWidth + (i * mBorderSpacing);
488 int transY = j * mCellHeight + (j * mBorderSpacing);
Adam Cohen65086992020-02-19 08:40:49 -0800489
490 canvas.translate(getPaddingLeft() + transX, getPaddingTop() + transY);
491
492 mVisualizeGridPaint.setStyle(Paint.Style.FILL);
493 mVisualizeGridPaint.setColor(Color.argb(80, 255, 100, 100));
494
495 canvas.drawRect(mVisualizeGridRect, mVisualizeGridPaint);
496
497 mVisualizeGridPaint.setStyle(Paint.Style.STROKE);
498 mVisualizeGridPaint.setColor(Color.argb(255, 255, 100, 100));
499
500 canvas.drawRect(mVisualizeGridRect, mVisualizeGridPaint);
501 canvas.restore();
502 }
503 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700504 }
505
Adam Cohenefca0272016-02-24 19:19:06 -0800506 @Override
507 protected void dispatchDraw(Canvas canvas) {
508 super.dispatchDraw(canvas);
509
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800510 for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
511 DelegatedCellDrawing bg = mDelegatedCellDrawings.get(i);
512 cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation);
513 canvas.save();
514 canvas.translate(mTempLocation[0], mTempLocation[1]);
515 bg.drawOverItem(canvas);
516 canvas.restore();
Adam Cohenefca0272016-02-24 19:19:06 -0800517 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700518 }
519
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800520 /**
521 * Add Delegated cell drawing
522 */
523 public void addDelegatedCellDrawing(DelegatedCellDrawing bg) {
524 mDelegatedCellDrawings.add(bg);
Adam Cohenefca0272016-02-24 19:19:06 -0800525 }
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800526
527 /**
528 * Remove item from DelegatedCellDrawings
529 */
530 public void removeDelegatedCellDrawing(DelegatedCellDrawing bg) {
531 mDelegatedCellDrawings.remove(bg);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700532 }
533
Adam Cohenc51934b2011-07-26 21:07:43 -0700534 public void setFolderLeaveBehindCell(int x, int y) {
Adam Cohenefca0272016-02-24 19:19:06 -0800535 View child = getChildAt(x, y);
Sunny Goyalab770a12018-11-14 15:17:26 -0800536 mFolderLeaveBehind.setup(getContext(), mActivity, null,
Adam Cohenefca0272016-02-24 19:19:06 -0800537 child.getMeasuredWidth(), child.getPaddingTop());
538
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800539 mFolderLeaveBehind.mDelegateCellX = x;
540 mFolderLeaveBehind.mDelegateCellY = y;
Adam Cohenc51934b2011-07-26 21:07:43 -0700541 invalidate();
542 }
543
544 public void clearFolderLeaveBehind() {
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800545 mFolderLeaveBehind.mDelegateCellX = -1;
546 mFolderLeaveBehind.mDelegateCellY = -1;
Adam Cohenc51934b2011-07-26 21:07:43 -0700547 invalidate();
548 }
549
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700550 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700551 public boolean shouldDelayChildPressedState() {
552 return false;
553 }
554
Adam Cohen1462de32012-07-24 22:34:36 -0700555 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700556 try {
557 dispatchRestoreInstanceState(states);
558 } catch (IllegalArgumentException ex) {
Zak Cohen3eeb41d2020-02-14 14:15:13 -0800559 if (FeatureFlags.IS_STUDIO_BUILD) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700560 throw ex;
561 }
562 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
563 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
564 }
Adam Cohen1462de32012-07-24 22:34:36 -0700565 }
566
Michael Jurkae6235dd2011-10-04 15:02:05 -0700567 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700568 public void cancelLongPress() {
569 super.cancelLongPress();
570
571 // Cancel long press for all children
572 final int count = getChildCount();
573 for (int i = 0; i < count; i++) {
574 final View child = getChildAt(i);
575 child.cancelLongPress();
576 }
577 }
578
Michael Jurkadee05892010-07-27 10:01:56 -0700579 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
580 mInterceptTouchListener = listener;
581 }
582
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800583 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700584 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800585 }
586
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800587 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700588 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800589 }
590
Sunny Goyalc13403c2016-11-18 23:44:48 -0800591 public boolean acceptsWidget() {
592 return mContainerType == WORKSPACE;
Sunny Goyale9b651e2015-04-24 11:44:51 -0700593 }
594
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800595 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700596 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700597 final LayoutParams lp = params;
598
Andrew Flynnde38e422012-05-08 11:22:15 -0700599 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800600 if (child instanceof BubbleTextView) {
601 BubbleTextView bubbleChild = (BubbleTextView) child;
Jon Mirandaf1eae802017-10-04 11:23:33 -0700602 bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
Jon Miranda22679f82021-02-17 17:44:03 -0500603 if (ENABLE_FOUR_COLUMNS.get()) {
604 bubbleChild.setCenterVertically(mContainerType != HOTSEAT);
605 }
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800606 }
607
Sunny Goyalc13403c2016-11-18 23:44:48 -0800608 child.setScaleX(mChildScale);
609 child.setScaleY(mChildScale);
Adam Cohen307fe232012-08-16 17:55:58 -0700610
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800611 // Generate an id for each view, this assumes we have at most 256x256 cells
612 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700613 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700614 // If the horizontal or vertical span is set to -1, it is taken to
615 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700616 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
617 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800618
Winson Chungaafa03c2010-06-11 17:34:16 -0700619 child.setId(childId);
Tony Wickhama0628cc2015-10-14 15:23:04 -0700620 if (LOGD) {
621 Log.d(TAG, "Adding view to ShortcutsAndWidgetsContainer: " + child);
622 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700623 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700624
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700625 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700626
Winson Chungaafa03c2010-06-11 17:34:16 -0700627 return true;
628 }
629 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800630 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700631
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800632 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700633 public void removeAllViews() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700634 mOccupied.clear();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700635 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700636 }
637
638 @Override
639 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700640 if (mShortcutsAndWidgets.getChildCount() > 0) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700641 mOccupied.clear();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700642 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700643 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700644 }
645
646 @Override
647 public void removeView(View view) {
648 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700649 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700650 }
651
652 @Override
653 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700654 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
655 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700656 }
657
658 @Override
659 public void removeViewInLayout(View view) {
660 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700661 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700662 }
663
664 @Override
665 public void removeViews(int start, int count) {
666 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700667 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700668 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700669 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700670 }
671
672 @Override
673 public void removeViewsInLayout(int start, int count) {
674 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700675 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700676 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700677 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800678 }
679
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700680 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700681 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800682 * @param x X coordinate of the point
683 * @param y Y coordinate of the point
684 * @param result Array of 2 ints to hold the x and y coordinate of the cell
685 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700686 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700687 final int hStartPadding = getPaddingLeft();
688 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800689
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530690 result[0] = (x - hStartPadding) / mCellWidth;
691 result[1] = (y - vStartPadding) / mCellHeight;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800692
Adam Cohend22015c2010-07-26 22:02:18 -0700693 final int xAxis = mCountX;
694 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800695
696 if (result[0] < 0) result[0] = 0;
697 if (result[0] >= xAxis) result[0] = xAxis - 1;
698 if (result[1] < 0) result[1] = 0;
699 if (result[1] >= yAxis) result[1] = yAxis - 1;
700 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700701
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800702 /**
703 * Given a point, return the cell that most closely encloses that point
704 * @param x X coordinate of the point
705 * @param y Y coordinate of the point
706 * @param result Array of 2 ints to hold the x and y coordinate of the cell
707 */
708 void pointToCellRounded(int x, int y, int[] result) {
709 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
710 }
711
712 /**
713 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700714 *
715 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800716 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700717 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800718 * @param result Array of 2 ints to hold the x and y coordinate of the point
719 */
720 void cellToPoint(int cellX, int cellY, int[] result) {
Jon Miranda228877d2021-02-09 11:05:00 -0500721 cellToRect(cellX, cellY, 1, 1, mTempRect);
722 result[0] = mTempRect.left;
723 result[1] = mTempRect.top;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800724 }
725
Adam Cohene3e27a82011-04-15 12:07:39 -0700726 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800727 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700728 *
729 * @param cellX X coordinate of the cell
730 * @param cellY Y coordinate of the cell
731 *
732 * @param result Array of 2 ints to hold the x and y coordinate of the point
733 */
734 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700735 regionToCenterPoint(cellX, cellY, 1, 1, result);
736 }
737
738 /**
739 * Given a cell coordinate and span return the point that represents the center of the regio
740 *
741 * @param cellX X coordinate of the cell
742 * @param cellY Y coordinate of the cell
743 *
744 * @param result Array of 2 ints to hold the x and y coordinate of the point
745 */
746 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Jon Miranda228877d2021-02-09 11:05:00 -0500747 cellToRect(cellX, cellY, spanX, spanY, mTempRect);
748 result[0] = mTempRect.centerX();
749 result[1] = mTempRect.centerY();
Adam Cohen19f37922012-03-21 11:59:11 -0700750 }
751
Adam Cohen482ed822012-03-02 14:15:13 -0800752 public float getDistanceFromCell(float x, float y, int[] cell) {
753 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700754 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800755 }
756
Adam Cohenf9c184a2016-01-15 16:47:43 -0800757 public int getCellWidth() {
Romain Guy84f296c2009-11-04 15:00:44 -0800758 return mCellWidth;
759 }
760
Sunny Goyal0b754e52017-08-07 07:42:45 -0700761 public int getCellHeight() {
Romain Guy84f296c2009-11-04 15:00:44 -0800762 return mCellHeight;
763 }
764
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700765 public void setFixedSize(int width, int height) {
766 mFixedWidth = width;
767 mFixedHeight = height;
768 }
769
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800770 @Override
771 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800772 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800773 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700774 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
775 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700776 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
777 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Sunny Goyalae6e3182019-04-30 12:04:37 -0700778
Winson Chung11a1a532013-09-13 11:14:45 -0700779 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Jon Miranda228877d2021-02-09 11:05:00 -0500780 int cw = DeviceProfile.calculateCellWidth(childWidthSize, mBorderSpacing,
781 mCountX);
782 int ch = DeviceProfile.calculateCellHeight(childHeightSize, mBorderSpacing,
783 mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700784 if (cw != mCellWidth || ch != mCellHeight) {
785 mCellWidth = cw;
786 mCellHeight = ch;
Jon Miranda228877d2021-02-09 11:05:00 -0500787 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
788 mBorderSpacing);
Winson Chung11a1a532013-09-13 11:14:45 -0700789 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700790 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700791
Winson Chung2d75f122013-09-23 16:53:31 -0700792 int newWidth = childWidthSize;
793 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700794 if (mFixedWidth > 0 && mFixedHeight > 0) {
795 newWidth = mFixedWidth;
796 newHeight = mFixedHeight;
797 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800798 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
799 }
800
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700801 mShortcutsAndWidgets.measure(
802 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
803 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
804
805 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
806 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -0700807 if (mFixedWidth > 0 && mFixedHeight > 0) {
808 setMeasuredDimension(maxWidth, maxHeight);
809 } else {
810 setMeasuredDimension(widthSize, heightSize);
811 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800812 }
813
814 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700815 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Tony Wickham26b01422015-11-10 14:44:32 -0800816 int left = getPaddingLeft();
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700817 left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Sunny Goyal7c786f72016-06-01 14:08:21 -0700818 int right = r - l - getPaddingRight();
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700819 right -= (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Sunny Goyal7c786f72016-06-01 14:08:21 -0700820
Winson Chung38848ca2013-10-08 12:03:44 -0700821 int top = getPaddingTop();
Sunny Goyal7c786f72016-06-01 14:08:21 -0700822 int bottom = b - t - getPaddingBottom();
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700823
Sunny Goyal7c786f72016-06-01 14:08:21 -0700824 // Expand the background drawing bounds by the padding baked into the background drawable
825 mBackground.getPadding(mTempRect);
826 mBackground.setBounds(
Jon Miranda28032002017-07-13 16:18:56 -0700827 left - mTempRect.left - getPaddingLeft(),
828 top - mTempRect.top - getPaddingTop(),
829 right + mTempRect.right + getPaddingRight(),
830 bottom + mTempRect.bottom + getPaddingBottom());
Sunny Goyalae6e3182019-04-30 12:04:37 -0700831
Sunny Goyalc4d32012020-04-03 17:10:11 -0700832 mShortcutsAndWidgets.layout(left, top, right, bottom);
Sunny Goyal7c786f72016-06-01 14:08:21 -0700833 }
834
Tony Wickhama501d492015-11-03 18:05:01 -0800835 /**
836 * Returns the amount of space left over after subtracting padding and cells. This space will be
837 * very small, a few pixels at most, and is a result of rounding down when calculating the cell
Jon Miranda228877d2021-02-09 11:05:00 -0500838 * width in {@link DeviceProfile#calculateCellWidth(int, int, int)}.
Tony Wickhama501d492015-11-03 18:05:01 -0800839 */
840 public int getUnusedHorizontalSpace() {
Jon Miranda228877d2021-02-09 11:05:00 -0500841 return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth)
842 - ((mCountX - 1) * mBorderSpacing);
Tony Wickhama501d492015-11-03 18:05:01 -0800843 }
844
Sunny Goyalaeb16432017-10-16 11:46:41 -0700845 public Drawable getScrimBackground() {
846 return mBackground;
Michael Jurkadee05892010-07-27 10:01:56 -0700847 }
848
Sunny Goyal2805e632015-05-20 15:35:32 -0700849 @Override
850 protected boolean verifyDrawable(Drawable who) {
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700851 return super.verifyDrawable(who) || (who == mBackground);
Sunny Goyal2805e632015-05-20 15:35:32 -0700852 }
853
Michael Jurkaa52570f2012-03-20 03:18:20 -0700854 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700855 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700856 }
857
Jon Miranda228877d2021-02-09 11:05:00 -0500858 public View getChildAt(int cellX, int cellY) {
859 return mShortcutsAndWidgets.getChildAt(cellX, cellY);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700860 }
861
Adam Cohen76fc0852011-06-17 13:26:23 -0700862 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800863 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700864 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800865
Adam Cohen1d13c0b2020-04-21 16:29:12 -0700866 if (clc.indexOfChild(child) != -1 && (child instanceof Reorderable)) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700867 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
868 final ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen1d13c0b2020-04-21 16:29:12 -0700869 final Reorderable item = (Reorderable) child;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700870
871 // We cancel any existing animations
872 if (mReorderAnimators.containsKey(lp)) {
873 mReorderAnimators.get(lp).cancel();
874 mReorderAnimators.remove(lp);
875 }
876
Adam Cohen1d13c0b2020-04-21 16:29:12 -0700877
Adam Cohen482ed822012-03-02 14:15:13 -0800878 if (adjustOccupied) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700879 GridOccupancy occupied = permanent ? mOccupied : mTmpOccupied;
880 occupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
881 occupied.markCells(cellX, cellY, lp.cellHSpan, lp.cellVSpan, true);
Adam Cohen482ed822012-03-02 14:15:13 -0800882 }
Adam Cohen1d13c0b2020-04-21 16:29:12 -0700883
884 // Compute the new x and y position based on the new cellX and cellY
885 // We leverage the actual layout logic in the layout params and hence need to modify
886 // state and revert that state.
887 final int oldX = lp.x;
888 final int oldY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700889 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -0800890 if (permanent) {
891 lp.cellX = info.cellX = cellX;
892 lp.cellY = info.cellY = cellY;
893 } else {
894 lp.tmpCellX = cellX;
895 lp.tmpCellY = cellY;
896 }
Jon Mirandae96798e2016-12-07 12:10:44 -0800897 clc.setupLp(child);
Adam Cohen482ed822012-03-02 14:15:13 -0800898 final int newX = lp.x;
899 final int newY = lp.y;
Adam Cohen76fc0852011-06-17 13:26:23 -0700900 lp.x = oldX;
901 lp.y = oldY;
Adam Cohen1d13c0b2020-04-21 16:29:12 -0700902 lp.isLockedToGrid = false;
903 // End compute new x and y
904
905 item.getReorderPreviewOffset(mTmpPointF);
906 final float initPreviewOffsetX = mTmpPointF.x;
907 final float initPreviewOffsetY = mTmpPointF.y;
908 final float finalPreviewOffsetX = newX - oldX;
909 final float finalPreviewOffsetY = newY - oldY;
910
Adam Cohen76fc0852011-06-17 13:26:23 -0700911
Adam Cohen482ed822012-03-02 14:15:13 -0800912 // Exit early if we're not actually moving the view
Adam Cohen1d13c0b2020-04-21 16:29:12 -0700913 if (finalPreviewOffsetX == 0 && finalPreviewOffsetY == 0
914 && initPreviewOffsetX == 0 && initPreviewOffsetY == 0) {
Adam Cohen482ed822012-03-02 14:15:13 -0800915 lp.isLockedToGrid = true;
916 return true;
917 }
918
Sunny Goyal849c6a22018-08-08 16:33:46 -0700919 ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -0800920 va.setDuration(duration);
921 mReorderAnimators.put(lp, va);
922
923 va.addUpdateListener(new AnimatorUpdateListener() {
924 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -0700925 public void onAnimationUpdate(ValueAnimator animation) {
Jon Mirandae96798e2016-12-07 12:10:44 -0800926 float r = (Float) animation.getAnimatedValue();
Adam Cohen1d13c0b2020-04-21 16:29:12 -0700927 float x = (1 - r) * initPreviewOffsetX + r * finalPreviewOffsetX;
928 float y = (1 - r) * initPreviewOffsetY + r * finalPreviewOffsetY;
929 item.setReorderPreviewOffset(x, y);
Adam Cohenbfbfd262011-06-13 16:55:12 -0700930 }
931 });
Adam Cohen482ed822012-03-02 14:15:13 -0800932 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700933 boolean cancelled = false;
934 public void onAnimationEnd(Animator animation) {
935 // If the animation was cancelled, it means that another animation
936 // has interrupted this one, and we don't want to lock the item into
937 // place just yet.
938 if (!cancelled) {
939 lp.isLockedToGrid = true;
Adam Cohen1d13c0b2020-04-21 16:29:12 -0700940 item.setReorderPreviewOffset(0, 0);
Adam Cohen482ed822012-03-02 14:15:13 -0800941 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -0700942 }
943 if (mReorderAnimators.containsKey(lp)) {
944 mReorderAnimators.remove(lp);
945 }
946 }
947 public void onAnimationCancel(Animator animation) {
948 cancelled = true;
949 }
950 });
Adam Cohen482ed822012-03-02 14:15:13 -0800951 va.setStartDelay(delay);
952 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -0700953 return true;
954 }
955 return false;
956 }
957
Adam Cohen65086992020-02-19 08:40:49 -0800958 void visualizeDropLocation(DraggableView v, DragPreviewProvider outlineProvider, int cellX, int
959 cellY, int spanX, int spanY, boolean resize, DropTarget.DragObject dragObject) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -0700960 final int oldDragCellX = mDragCell[0];
961 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -0800962
Hyunyoung Song0de01172016-10-05 16:27:48 -0700963 if (outlineProvider == null || outlineProvider.generatedDragOutline == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -0700964 return;
965 }
966
Hyunyoung Song0de01172016-10-05 16:27:48 -0700967 Bitmap dragOutline = outlineProvider.generatedDragOutline;
Adam Cohen482ed822012-03-02 14:15:13 -0800968 if (cellX != oldDragCellX || cellY != oldDragCellY) {
969 mDragCell[0] = cellX;
970 mDragCell[1] = cellY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700971
Joe Onorato4be866d2010-10-10 11:26:02 -0700972 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -0700973 mDragOutlineAnims[oldIndex].animateOut();
974 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -0800975 Rect r = mDragOutlines[mDragOutlineCurrent];
Sunny Goyal106bf642015-07-16 12:18:06 -0700976
Adam Cohen65086992020-02-19 08:40:49 -0800977 cellToRect(cellX, cellY, spanX, spanY, r);
978 int left = r.left;
979 int top = r.top;
980
981 int width = dragOutline.getWidth();
982 int height = dragOutline.getHeight();
983
Adam Cohend41fbf52012-02-16 23:53:59 -0800984 if (resize) {
Adam Cohen65086992020-02-19 08:40:49 -0800985 width = r.width();
986 height = r.height();
Adam Cohend41fbf52012-02-16 23:53:59 -0800987 }
Winson Chung150fbab2010-09-29 17:14:26 -0700988
Adam Cohen370164c2020-04-08 18:00:30 -0700989 // Center horizontaly
Jon Miranda228877d2021-02-09 11:05:00 -0500990 left += (r.width() - dragOutline.getWidth()) / 2;
Adam Cohen370164c2020-04-08 18:00:30 -0700991
992 if (v != null && v.getViewType() == DraggableView.DRAGGABLE_WIDGET) {
993 // Center vertically
Jon Miranda228877d2021-02-09 11:05:00 -0500994 top += (r.height() - dragOutline.getHeight()) / 2;
Adam Cohen370164c2020-04-08 18:00:30 -0700995 } else if (v != null && v.getViewType() == DraggableView.DRAGGABLE_ICON) {
Adam Cohen65086992020-02-19 08:40:49 -0800996 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
997 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
998 top += cellPaddingY;
999 }
1000
1001 r.set(left, top, left + width, top + height);
1002
Jon Miranda6f6a06a2016-12-15 11:24:18 -08001003 Utilities.scaleRectAboutCenter(r, mChildScale);
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001004 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1005 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Sunny Goyale78e3d72015-09-24 11:23:31 -07001006
1007 if (dragObject.stateAnnouncer != null) {
Sunny Goyalc13403c2016-11-18 23:44:48 -08001008 dragObject.stateAnnouncer.announce(getItemMoveDescription(cellX, cellY));
Sunny Goyale78e3d72015-09-24 11:23:31 -07001009 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001010 }
1011 }
1012
Sunny Goyal726bee72018-03-05 12:54:24 -08001013 @SuppressLint("StringFormatMatches")
Sunny Goyalc13403c2016-11-18 23:44:48 -08001014 public String getItemMoveDescription(int cellX, int cellY) {
1015 if (mContainerType == HOTSEAT) {
1016 return getContext().getString(R.string.move_to_hotseat_position,
1017 Math.max(cellX, cellY) + 1);
1018 } else {
1019 return getContext().getString(R.string.move_to_empty_cell,
1020 cellY + 1, cellX + 1);
1021 }
1022 }
1023
Adam Cohene0310962011-04-18 16:15:31 -07001024 public void clearDragOutlines() {
1025 final int oldIndex = mDragOutlineCurrent;
1026 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001027 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001028 }
1029
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001030 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001031 * Find a vacant area that will fit the given bounds nearest the requested
1032 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001033 *
Romain Guy51afc022009-05-04 18:03:43 -07001034 * @param pixelX The X location at which you want to search for a vacant area.
1035 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001036 * @param minSpanX The minimum horizontal span required
1037 * @param minSpanY The minimum vertical span required
1038 * @param spanX Horizontal span of the object.
1039 * @param spanY Vertical span of the object.
1040 * @param result Array in which to place the result, or null (in which case a new array will
1041 * be allocated)
1042 * @return The X, Y cell of a vacant area that can contain this object,
1043 * nearest the requested location.
1044 */
1045 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1046 int spanY, int[] result, int[] resultSpan) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001047 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
Adam Cohend41fbf52012-02-16 23:53:59 -08001048 result, resultSpan);
1049 }
1050
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001051 private final Stack<Rect> mTempRectStack = new Stack<>();
Adam Cohend41fbf52012-02-16 23:53:59 -08001052 private void lazyInitTempRectStack() {
1053 if (mTempRectStack.isEmpty()) {
1054 for (int i = 0; i < mCountX * mCountY; i++) {
1055 mTempRectStack.push(new Rect());
1056 }
1057 }
1058 }
Adam Cohen482ed822012-03-02 14:15:13 -08001059
Adam Cohend41fbf52012-02-16 23:53:59 -08001060 private void recycleTempRects(Stack<Rect> used) {
1061 while (!used.isEmpty()) {
1062 mTempRectStack.push(used.pop());
1063 }
1064 }
1065
1066 /**
1067 * Find a vacant area that will fit the given bounds nearest the requested
1068 * cell location. Uses Euclidean distance to score multiple vacant areas.
1069 *
1070 * @param pixelX The X location at which you want to search for a vacant area.
1071 * @param pixelY The Y location at which you want to search for a vacant area.
1072 * @param minSpanX The minimum horizontal span required
1073 * @param minSpanY The minimum vertical span required
1074 * @param spanX Horizontal span of the object.
1075 * @param spanY Vertical span of the object.
1076 * @param ignoreOccupied If true, the result can be an occupied cell
1077 * @param result Array in which to place the result, or null (in which case a new array will
1078 * be allocated)
1079 * @return The X, Y cell of a vacant area that can contain this object,
1080 * nearest the requested location.
1081 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001082 private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1083 int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001084 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001085
Adam Cohene3e27a82011-04-15 12:07:39 -07001086 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1087 // to the center of the item, but we are searching based on the top-left cell, so
1088 // we translate the point over to correspond to the top-left.
Sunny Goyalaa8a8712016-11-20 15:26:01 +05301089 pixelX -= mCellWidth * (spanX - 1) / 2f;
1090 pixelY -= mCellHeight * (spanY - 1) / 2f;
Adam Cohene3e27a82011-04-15 12:07:39 -07001091
Jeff Sharkey70864282009-04-07 21:08:40 -07001092 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001093 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001094 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001095 final Rect bestRect = new Rect(-1, -1, -1, -1);
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001096 final Stack<Rect> validRegions = new Stack<>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001097
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001098 final int countX = mCountX;
1099 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001100
Adam Cohend41fbf52012-02-16 23:53:59 -08001101 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1102 spanX < minSpanX || spanY < minSpanY) {
1103 return bestXY;
1104 }
1105
1106 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001107 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001108 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1109 int ySize = -1;
1110 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001111 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001112 // First, let's see if this thing fits anywhere
1113 for (int i = 0; i < minSpanX; i++) {
1114 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001115 if (mOccupied.cells[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001116 continue inner;
1117 }
Michael Jurkac28de512010-08-13 11:27:44 -07001118 }
1119 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001120 xSize = minSpanX;
1121 ySize = minSpanY;
1122
1123 // We know that the item will fit at _some_ acceptable size, now let's see
1124 // how big we can make it. We'll alternate between incrementing x and y spans
1125 // until we hit a limit.
1126 boolean incX = true;
1127 boolean hitMaxX = xSize >= spanX;
1128 boolean hitMaxY = ySize >= spanY;
1129 while (!(hitMaxX && hitMaxY)) {
1130 if (incX && !hitMaxX) {
1131 for (int j = 0; j < ySize; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001132 if (x + xSize > countX -1 || mOccupied.cells[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001133 // We can't move out horizontally
1134 hitMaxX = true;
1135 }
1136 }
1137 if (!hitMaxX) {
1138 xSize++;
1139 }
1140 } else if (!hitMaxY) {
1141 for (int i = 0; i < xSize; i++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001142 if (y + ySize > countY - 1 || mOccupied.cells[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001143 // We can't move out vertically
1144 hitMaxY = true;
1145 }
1146 }
1147 if (!hitMaxY) {
1148 ySize++;
1149 }
1150 }
1151 hitMaxX |= xSize >= spanX;
1152 hitMaxY |= ySize >= spanY;
1153 incX = !incX;
1154 }
1155 incX = true;
1156 hitMaxX = xSize >= spanX;
1157 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001158 }
Sunny Goyal2805e632015-05-20 15:35:32 -07001159 final int[] cellXY = mTmpPoint;
Adam Cohene3e27a82011-04-15 12:07:39 -07001160 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001161
Adam Cohend41fbf52012-02-16 23:53:59 -08001162 // We verify that the current rect is not a sub-rect of any of our previous
1163 // candidates. In this case, the current rect is disqualified in favour of the
1164 // containing rect.
1165 Rect currentRect = mTempRectStack.pop();
1166 currentRect.set(x, y, x + xSize, y + ySize);
1167 boolean contained = false;
1168 for (Rect r : validRegions) {
1169 if (r.contains(currentRect)) {
1170 contained = true;
1171 break;
1172 }
1173 }
1174 validRegions.push(currentRect);
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001175 double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY);
Adam Cohen482ed822012-03-02 14:15:13 -08001176
Adam Cohend41fbf52012-02-16 23:53:59 -08001177 if ((distance <= bestDistance && !contained) ||
1178 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001179 bestDistance = distance;
1180 bestXY[0] = x;
1181 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001182 if (resultSpan != null) {
1183 resultSpan[0] = xSize;
1184 resultSpan[1] = ySize;
1185 }
1186 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001187 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001188 }
1189 }
1190
Adam Cohenc0dcf592011-06-01 15:30:43 -07001191 // Return -1, -1 if no suitable location found
1192 if (bestDistance == Double.MAX_VALUE) {
1193 bestXY[0] = -1;
1194 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001195 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001196 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001197 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001198 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001199
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001200 /**
Adam Cohen482ed822012-03-02 14:15:13 -08001201 * Find a vacant area that will fit the given bounds nearest the requested
1202 * cell location, and will also weigh in a suggested direction vector of the
1203 * desired location. This method computers distance based on unit grid distances,
1204 * not pixel distances.
1205 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001206 * @param cellX The X cell nearest to which you want to search for a vacant area.
1207 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001208 * @param spanX Horizontal span of the object.
1209 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001210 * @param direction The favored direction in which the views should move from x, y
Sunny Goyal9eba1fd2015-10-16 08:58:57 -07001211 * @param occupied The array which represents which cells in the CellLayout are occupied
Adam Cohen47a876d2012-03-19 13:21:41 -07001212 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001213 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001214 * @param result Array in which to place the result, or null (in which case a new array will
1215 * be allocated)
1216 * @return The X, Y cell of a vacant area that can contain this object,
1217 * nearest the requested location.
1218 */
1219 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001220 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001221 // Keep track of best-scoring drop area
1222 final int[] bestXY = result != null ? result : new int[2];
1223 float bestDistance = Float.MAX_VALUE;
1224 int bestDirectionScore = Integer.MIN_VALUE;
1225
1226 final int countX = mCountX;
1227 final int countY = mCountY;
1228
1229 for (int y = 0; y < countY - (spanY - 1); y++) {
1230 inner:
1231 for (int x = 0; x < countX - (spanX - 1); x++) {
1232 // First, let's see if this thing fits anywhere
1233 for (int i = 0; i < spanX; i++) {
1234 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001235 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001236 continue inner;
1237 }
1238 }
1239 }
1240
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001241 float distance = (float) Math.hypot(x - cellX, y - cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001242 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001243 computeDirectionVector(x - cellX, y - cellY, curDirection);
1244 // The direction score is just the dot product of the two candidate direction
1245 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001246 int curDirectionScore = direction[0] * curDirection[0] +
1247 direction[1] * curDirection[1];
Sunny Goyal8f90dcf2016-08-18 15:01:11 -07001248 if (Float.compare(distance, bestDistance) < 0 ||
1249 (Float.compare(distance, bestDistance) == 0
1250 && curDirectionScore > bestDirectionScore)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001251 bestDistance = distance;
1252 bestDirectionScore = curDirectionScore;
1253 bestXY[0] = x;
1254 bestXY[1] = y;
1255 }
1256 }
1257 }
1258
1259 // Return -1, -1 if no suitable location found
1260 if (bestDistance == Float.MAX_VALUE) {
1261 bestXY[0] = -1;
1262 bestXY[1] = -1;
1263 }
1264 return bestXY;
1265 }
1266
1267 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001268 int[] direction, ItemConfiguration currentState) {
1269 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001270 boolean success = false;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001271 mTmpOccupied.markCells(c, false);
1272 mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001273
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001274 findNearestArea(c.cellX, c.cellY, c.spanX, c.spanY, direction,
1275 mTmpOccupied.cells, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001276
1277 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001278 c.cellX = mTempLocation[0];
1279 c.cellY = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001280 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001281 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001282 mTmpOccupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001283 return success;
1284 }
1285
Adam Cohenf3900c22012-11-16 18:28:11 -08001286 /**
1287 * This helper class defines a cluster of views. It helps with defining complex edges
1288 * of the cluster and determining how those edges interact with other views. The edges
1289 * essentially define a fine-grained boundary around the cluster of views -- like a more
1290 * precise version of a bounding box.
1291 */
1292 private class ViewCluster {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001293 final static int LEFT = 1 << 0;
1294 final static int TOP = 1 << 1;
1295 final static int RIGHT = 1 << 2;
1296 final static int BOTTOM = 1 << 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001297
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001298 final ArrayList<View> views;
1299 final ItemConfiguration config;
1300 final Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001301
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001302 final int[] leftEdge = new int[mCountY];
1303 final int[] rightEdge = new int[mCountY];
1304 final int[] topEdge = new int[mCountX];
1305 final int[] bottomEdge = new int[mCountX];
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001306 int dirtyEdges;
1307 boolean boundingRectDirty;
Adam Cohenf3900c22012-11-16 18:28:11 -08001308
1309 @SuppressWarnings("unchecked")
1310 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1311 this.views = (ArrayList<View>) views.clone();
1312 this.config = config;
1313 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001314 }
1315
Adam Cohenf3900c22012-11-16 18:28:11 -08001316 void resetEdges() {
1317 for (int i = 0; i < mCountX; i++) {
1318 topEdge[i] = -1;
1319 bottomEdge[i] = -1;
1320 }
1321 for (int i = 0; i < mCountY; i++) {
1322 leftEdge[i] = -1;
1323 rightEdge[i] = -1;
1324 }
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001325 dirtyEdges = LEFT | TOP | RIGHT | BOTTOM;
Adam Cohenf3900c22012-11-16 18:28:11 -08001326 boundingRectDirty = true;
1327 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001328
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001329 void computeEdge(int which) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001330 int count = views.size();
1331 for (int i = 0; i < count; i++) {
1332 CellAndSpan cs = config.map.get(views.get(i));
1333 switch (which) {
1334 case LEFT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001335 int left = cs.cellX;
1336 for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001337 if (left < leftEdge[j] || leftEdge[j] < 0) {
1338 leftEdge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001339 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001340 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001341 break;
1342 case RIGHT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001343 int right = cs.cellX + cs.spanX;
1344 for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001345 if (right > rightEdge[j]) {
1346 rightEdge[j] = right;
Adam Cohenf3900c22012-11-16 18:28:11 -08001347 }
1348 }
1349 break;
1350 case TOP:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001351 int top = cs.cellY;
1352 for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001353 if (top < topEdge[j] || topEdge[j] < 0) {
1354 topEdge[j] = top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001355 }
1356 }
1357 break;
1358 case BOTTOM:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001359 int bottom = cs.cellY + cs.spanY;
1360 for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001361 if (bottom > bottomEdge[j]) {
1362 bottomEdge[j] = bottom;
Adam Cohenf3900c22012-11-16 18:28:11 -08001363 }
1364 }
1365 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001366 }
1367 }
1368 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001369
1370 boolean isViewTouchingEdge(View v, int whichEdge) {
1371 CellAndSpan cs = config.map.get(v);
1372
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001373 if ((dirtyEdges & whichEdge) == whichEdge) {
1374 computeEdge(whichEdge);
1375 dirtyEdges &= ~whichEdge;
1376 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001377
1378 switch (whichEdge) {
1379 case LEFT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001380 for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) {
1381 if (leftEdge[i] == cs.cellX + cs.spanX) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001382 return true;
1383 }
1384 }
1385 break;
1386 case RIGHT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001387 for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) {
1388 if (rightEdge[i] == cs.cellX) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001389 return true;
1390 }
1391 }
1392 break;
1393 case TOP:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001394 for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) {
1395 if (topEdge[i] == cs.cellY + cs.spanY) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001396 return true;
1397 }
1398 }
1399 break;
1400 case BOTTOM:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001401 for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) {
1402 if (bottomEdge[i] == cs.cellY) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001403 return true;
1404 }
1405 }
1406 break;
1407 }
1408 return false;
1409 }
1410
1411 void shift(int whichEdge, int delta) {
1412 for (View v: views) {
1413 CellAndSpan c = config.map.get(v);
1414 switch (whichEdge) {
1415 case LEFT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001416 c.cellX -= delta;
Adam Cohenf3900c22012-11-16 18:28:11 -08001417 break;
1418 case RIGHT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001419 c.cellX += delta;
Adam Cohenf3900c22012-11-16 18:28:11 -08001420 break;
1421 case TOP:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001422 c.cellY -= delta;
Adam Cohenf3900c22012-11-16 18:28:11 -08001423 break;
1424 case BOTTOM:
1425 default:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001426 c.cellY += delta;
Adam Cohenf3900c22012-11-16 18:28:11 -08001427 break;
1428 }
1429 }
1430 resetEdges();
1431 }
1432
1433 public void addView(View v) {
1434 views.add(v);
1435 resetEdges();
1436 }
1437
1438 public Rect getBoundingRect() {
1439 if (boundingRectDirty) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001440 config.getBoundingRectForViews(views, boundingRect);
Adam Cohenf3900c22012-11-16 18:28:11 -08001441 }
1442 return boundingRect;
1443 }
1444
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001445 final PositionComparator comparator = new PositionComparator();
Adam Cohenf3900c22012-11-16 18:28:11 -08001446 class PositionComparator implements Comparator<View> {
1447 int whichEdge = 0;
1448 public int compare(View left, View right) {
1449 CellAndSpan l = config.map.get(left);
1450 CellAndSpan r = config.map.get(right);
1451 switch (whichEdge) {
1452 case LEFT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001453 return (r.cellX + r.spanX) - (l.cellX + l.spanX);
Adam Cohenf3900c22012-11-16 18:28:11 -08001454 case RIGHT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001455 return l.cellX - r.cellX;
Adam Cohenf3900c22012-11-16 18:28:11 -08001456 case TOP:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001457 return (r.cellY + r.spanY) - (l.cellY + l.spanY);
Adam Cohenf3900c22012-11-16 18:28:11 -08001458 case BOTTOM:
1459 default:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001460 return l.cellY - r.cellY;
Adam Cohenf3900c22012-11-16 18:28:11 -08001461 }
1462 }
1463 }
1464
1465 public void sortConfigurationForEdgePush(int edge) {
1466 comparator.whichEdge = edge;
1467 Collections.sort(config.sortedViews, comparator);
1468 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001469 }
1470
Adam Cohenf3900c22012-11-16 18:28:11 -08001471 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1472 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001473
Adam Cohenf3900c22012-11-16 18:28:11 -08001474 ViewCluster cluster = new ViewCluster(views, currentState);
1475 Rect clusterRect = cluster.getBoundingRect();
1476 int whichEdge;
1477 int pushDistance;
1478 boolean fail = false;
1479
1480 // Determine the edge of the cluster that will be leading the push and how far
1481 // the cluster must be shifted.
1482 if (direction[0] < 0) {
1483 whichEdge = ViewCluster.LEFT;
1484 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001485 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001486 whichEdge = ViewCluster.RIGHT;
1487 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1488 } else if (direction[1] < 0) {
1489 whichEdge = ViewCluster.TOP;
1490 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1491 } else {
1492 whichEdge = ViewCluster.BOTTOM;
1493 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001494 }
1495
Adam Cohenf3900c22012-11-16 18:28:11 -08001496 // Break early for invalid push distance.
1497 if (pushDistance <= 0) {
1498 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001499 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001500
1501 // Mark the occupied state as false for the group of views we want to move.
1502 for (View v: views) {
1503 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001504 mTmpOccupied.markCells(c, false);
Adam Cohenf3900c22012-11-16 18:28:11 -08001505 }
1506
1507 // We save the current configuration -- if we fail to find a solution we will revert
1508 // to the initial state. The process of finding a solution modifies the configuration
1509 // in place, hence the need for revert in the failure case.
1510 currentState.save();
1511
1512 // The pushing algorithm is simplified by considering the views in the order in which
1513 // they would be pushed by the cluster. For example, if the cluster is leading with its
1514 // left edge, we consider sort the views by their right edge, from right to left.
1515 cluster.sortConfigurationForEdgePush(whichEdge);
1516
1517 while (pushDistance > 0 && !fail) {
1518 for (View v: currentState.sortedViews) {
1519 // For each view that isn't in the cluster, we see if the leading edge of the
1520 // cluster is contacting the edge of that view. If so, we add that view to the
1521 // cluster.
1522 if (!cluster.views.contains(v) && v != dragView) {
1523 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1524 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1525 if (!lp.canReorder) {
1526 // The push solution includes the all apps button, this is not viable.
1527 fail = true;
1528 break;
1529 }
1530 cluster.addView(v);
1531 CellAndSpan c = currentState.map.get(v);
1532
1533 // Adding view to cluster, mark it as not occupied.
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001534 mTmpOccupied.markCells(c, false);
Adam Cohenf3900c22012-11-16 18:28:11 -08001535 }
1536 }
1537 }
1538 pushDistance--;
1539
1540 // The cluster has been completed, now we move the whole thing over in the appropriate
1541 // direction.
1542 cluster.shift(whichEdge, 1);
1543 }
1544
1545 boolean foundSolution = false;
1546 clusterRect = cluster.getBoundingRect();
1547
1548 // Due to the nature of the algorithm, the only check required to verify a valid solution
1549 // is to ensure that completed shifted cluster lies completely within the cell layout.
1550 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1551 clusterRect.bottom <= mCountY) {
1552 foundSolution = true;
1553 } else {
1554 currentState.restore();
1555 }
1556
1557 // In either case, we set the occupied array as marked for the location of the views
1558 for (View v: cluster.views) {
1559 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001560 mTmpOccupied.markCells(c, true);
Adam Cohenf3900c22012-11-16 18:28:11 -08001561 }
1562
1563 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001564 }
1565
Adam Cohen482ed822012-03-02 14:15:13 -08001566 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001567 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001568 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001569
Adam Cohen8baab352012-03-20 17:39:21 -07001570 boolean success = false;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001571 Rect boundingRect = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001572 // We construct a rect which represents the entire group of views passed in
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001573 currentState.getBoundingRectForViews(views, boundingRect);
Adam Cohen8baab352012-03-20 17:39:21 -07001574
Adam Cohen8baab352012-03-20 17:39:21 -07001575 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001576 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001577 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001578 mTmpOccupied.markCells(c, false);
Adam Cohen8baab352012-03-20 17:39:21 -07001579 }
1580
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001581 GridOccupancy blockOccupied = new GridOccupancy(boundingRect.width(), boundingRect.height());
Adam Cohen47a876d2012-03-19 13:21:41 -07001582 int top = boundingRect.top;
1583 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001584 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001585 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001586 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001587 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001588 blockOccupied.markCells(c.cellX - left, c.cellY - top, c.spanX, c.spanY, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001589 }
1590
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001591 mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001592
Adam Cohenf3900c22012-11-16 18:28:11 -08001593 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001594 boundingRect.height(), direction,
1595 mTmpOccupied.cells, blockOccupied.cells, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001596
Adam Cohen8baab352012-03-20 17:39:21 -07001597 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001598 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001599 int deltaX = mTempLocation[0] - boundingRect.left;
1600 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001601 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001602 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001603 c.cellX += deltaX;
1604 c.cellY += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001605 }
1606 success = true;
1607 }
Adam Cohen8baab352012-03-20 17:39:21 -07001608
1609 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001610 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001611 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001612 mTmpOccupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001613 }
1614 return success;
1615 }
1616
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001617 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1618 // to push items in each of the cardinal directions, in an order based on the direction vector
1619 // passed.
1620 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1621 int[] direction, View ignoreView, ItemConfiguration solution) {
1622 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001623 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001624 // separately in each of the components.
1625 int temp = direction[1];
1626 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001627
1628 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001629 ignoreView, solution)) {
1630 return true;
1631 }
1632 direction[1] = temp;
1633 temp = direction[0];
1634 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001635
1636 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001637 ignoreView, solution)) {
1638 return true;
1639 }
1640 // Revert the direction
1641 direction[0] = temp;
1642
1643 // Now we try pushing in each component of the opposite direction
1644 direction[0] *= -1;
1645 direction[1] *= -1;
1646 temp = direction[1];
1647 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001648 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001649 ignoreView, solution)) {
1650 return true;
1651 }
1652
1653 direction[1] = temp;
1654 temp = direction[0];
1655 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001656 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001657 ignoreView, solution)) {
1658 return true;
1659 }
1660 // revert the direction
1661 direction[0] = temp;
1662 direction[0] *= -1;
1663 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001664
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001665 } else {
1666 // If the direction vector has a single non-zero component, we push first in the
1667 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001668 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001669 ignoreView, solution)) {
1670 return true;
1671 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001672 // Then we try the opposite direction
1673 direction[0] *= -1;
1674 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001675 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001676 ignoreView, solution)) {
1677 return true;
1678 }
1679 // Switch the direction back
1680 direction[0] *= -1;
1681 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001682
1683 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001684 // to find a solution by pushing along the perpendicular axis.
1685
1686 // Swap the components
1687 int temp = direction[1];
1688 direction[1] = direction[0];
1689 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001690 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001691 ignoreView, solution)) {
1692 return true;
1693 }
1694
1695 // Then we try the opposite direction
1696 direction[0] *= -1;
1697 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001698 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001699 ignoreView, solution)) {
1700 return true;
1701 }
1702 // Switch the direction back
1703 direction[0] *= -1;
1704 direction[1] *= -1;
1705
1706 // Swap the components back
1707 temp = direction[1];
1708 direction[1] = direction[0];
1709 direction[0] = temp;
1710 }
1711 return false;
1712 }
1713
Adam Cohen482ed822012-03-02 14:15:13 -08001714 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001715 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001716 // Return early if get invalid cell positions
1717 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001718
Adam Cohen8baab352012-03-20 17:39:21 -07001719 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001720 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001721
Adam Cohen8baab352012-03-20 17:39:21 -07001722 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001723 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001724 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001725 if (c != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001726 c.cellX = cellX;
1727 c.cellY = cellY;
Adam Cohen19f37922012-03-21 11:59:11 -07001728 }
Adam Cohen482ed822012-03-02 14:15:13 -08001729 }
Adam Cohen482ed822012-03-02 14:15:13 -08001730 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1731 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001732 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001733 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001734 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001735 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001736 r1.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001737 if (Rect.intersects(r0, r1)) {
1738 if (!lp.canReorder) {
1739 return false;
1740 }
1741 mIntersectingViews.add(child);
1742 }
1743 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001744
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001745 solution.intersectingViews = new ArrayList<>(mIntersectingViews);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001746
Winson Chung5f8afe62013-08-12 16:19:28 -07001747 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001748 // we try to find a solution such that no displaced item travels through another item
1749 // without also displacing that item.
1750 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001751 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001752 return true;
1753 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001754
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001755 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08001756 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001757 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001758 return true;
1759 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001760
Adam Cohen482ed822012-03-02 14:15:13 -08001761 // Ok, they couldn't move as a block, let's move them individually
1762 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001763 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001764 return false;
1765 }
1766 }
1767 return true;
1768 }
1769
1770 /*
1771 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1772 * the provided point and the provided cell
1773 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001774 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Jon Mirandae96798e2016-12-07 12:10:44 -08001775 double angle = Math.atan(deltaY / deltaX);
Adam Cohen482ed822012-03-02 14:15:13 -08001776
1777 result[0] = 0;
1778 result[1] = 0;
1779 if (Math.abs(Math.cos(angle)) > 0.5f) {
1780 result[0] = (int) Math.signum(deltaX);
1781 }
1782 if (Math.abs(Math.sin(angle)) > 0.5f) {
1783 result[1] = (int) Math.signum(deltaY);
1784 }
1785 }
1786
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001787 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001788 int spanX, int spanY, int[] direction, View dragView, boolean decX,
1789 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001790 // Copy the current state into the solution. This solution will be manipulated as necessary.
1791 copyCurrentStateToSolution(solution, false);
1792 // Copy the current occupied array into the temporary occupied array. This array will be
1793 // manipulated as necessary to find a solution.
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001794 mOccupied.copyTo(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001795
1796 // We find the nearest cell into which we would place the dragged item, assuming there's
1797 // nothing in its way.
1798 int result[] = new int[2];
1799 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1800
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001801 boolean success;
Adam Cohen482ed822012-03-02 14:15:13 -08001802 // First we try the exact nearest position of the item being dragged,
1803 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001804 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1805 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001806
1807 if (!success) {
1808 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1809 // x, then 1 in y etc.
1810 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001811 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
1812 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001813 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001814 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
1815 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001816 }
1817 solution.isSolution = false;
1818 } else {
1819 solution.isSolution = true;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001820 solution.cellX = result[0];
1821 solution.cellY = result[1];
1822 solution.spanX = spanX;
1823 solution.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08001824 }
1825 return solution;
1826 }
1827
1828 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001829 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001830 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001831 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001832 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001833 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08001834 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07001835 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001836 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001837 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001838 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001839 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08001840 }
1841 }
1842
1843 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001844 mTmpOccupied.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001845
Michael Jurkaa52570f2012-03-20 03:18:20 -07001846 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001847 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001848 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001849 if (child == dragView) continue;
1850 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001851 CellAndSpan c = solution.map.get(child);
1852 if (c != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001853 lp.tmpCellX = c.cellX;
1854 lp.tmpCellY = c.cellY;
Adam Cohen8baab352012-03-20 17:39:21 -07001855 lp.cellHSpan = c.spanX;
1856 lp.cellVSpan = c.spanY;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001857 mTmpOccupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001858 }
1859 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001860 mTmpOccupied.markCells(solution, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001861 }
1862
1863 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
1864 commitDragView) {
1865
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001866 GridOccupancy occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
1867 occupied.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001868
Michael Jurkaa52570f2012-03-20 03:18:20 -07001869 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001870 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001871 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001872 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001873 CellAndSpan c = solution.map.get(child);
1874 if (c != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001875 animateChildToPosition(child, c.cellX, c.cellY, REORDER_ANIMATION_DURATION, 0,
Adam Cohen19f37922012-03-21 11:59:11 -07001876 DESTRUCTIVE_REORDER, false);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001877 occupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001878 }
1879 }
1880 if (commitDragView) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001881 occupied.markCells(solution, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001882 }
1883 }
1884
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001885
1886 // This method starts or changes the reorder preview animations
1887 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
Adam Cohen65086992020-02-19 08:40:49 -08001888 View dragView, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07001889 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07001890 for (int i = 0; i < childCount; i++) {
1891 View child = mShortcutsAndWidgets.getChildAt(i);
1892 if (child == dragView) continue;
1893 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001894 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
1895 != null && !solution.intersectingViews.contains(child);
1896
Adam Cohend9162062020-03-24 16:35:35 -07001897
Adam Cohen19f37922012-03-21 11:59:11 -07001898 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohend9162062020-03-24 16:35:35 -07001899 if (c != null && !skip && (child instanceof Reorderable)) {
1900 ReorderPreviewAnimation rha = new ReorderPreviewAnimation((Reorderable) child,
1901 mode, lp.cellX, lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07001902 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07001903 }
1904 }
1905 }
1906
Sunny Goyal849c6a22018-08-08 16:33:46 -07001907 private static final Property<ReorderPreviewAnimation, Float> ANIMATION_PROGRESS =
1908 new Property<ReorderPreviewAnimation, Float>(float.class, "animationProgress") {
1909 @Override
1910 public Float get(ReorderPreviewAnimation anim) {
1911 return anim.animationProgress;
1912 }
1913
1914 @Override
1915 public void set(ReorderPreviewAnimation anim, Float progress) {
1916 anim.setAnimationProgress(progress);
1917 }
1918 };
1919
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001920 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07001921 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001922 class ReorderPreviewAnimation {
Adam Cohend9162062020-03-24 16:35:35 -07001923 final Reorderable child;
Adam Cohend024f982012-05-23 18:26:45 -07001924 float finalDeltaX;
1925 float finalDeltaY;
1926 float initDeltaX;
1927 float initDeltaY;
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001928 final float finalScale;
Adam Cohend024f982012-05-23 18:26:45 -07001929 float initScale;
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001930 final int mode;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001931 boolean repeating = false;
1932 private static final int PREVIEW_DURATION = 300;
1933 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
1934
Jon Miranda21266912016-12-19 14:12:05 -08001935 private static final float CHILD_DIVIDEND = 4.0f;
1936
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001937 public static final int MODE_HINT = 0;
1938 public static final int MODE_PREVIEW = 1;
1939
Sunny Goyal849c6a22018-08-08 16:33:46 -07001940 float animationProgress = 0;
Sunny Goyalf0b6db72018-08-13 16:10:14 -07001941 ValueAnimator a;
Adam Cohen19f37922012-03-21 11:59:11 -07001942
Adam Cohend9162062020-03-24 16:35:35 -07001943 public ReorderPreviewAnimation(Reorderable child, int mode, int cellX0, int cellY0,
1944 int cellX1, int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07001945 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
1946 final int x0 = mTmpPoint[0];
1947 final int y0 = mTmpPoint[1];
1948 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
1949 final int x1 = mTmpPoint[0];
1950 final int y1 = mTmpPoint[1];
1951 final int dX = x1 - x0;
1952 final int dY = y1 - y0;
Jon Miranda21266912016-12-19 14:12:05 -08001953
1954 this.child = child;
1955 this.mode = mode;
Adam Cohend9162062020-03-24 16:35:35 -07001956 finalDeltaX = 0;
1957 finalDeltaY = 0;
Adam Cohen65086992020-02-19 08:40:49 -08001958
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001959 child.getReorderBounceOffset(mTmpPointF);
Adam Cohend9162062020-03-24 16:35:35 -07001960 initDeltaX = mTmpPointF.x;
1961 initDeltaY = mTmpPointF.y;
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001962 initScale = child.getReorderBounceScale();
Adam Cohend9162062020-03-24 16:35:35 -07001963 finalScale = mChildScale - (CHILD_DIVIDEND / child.getView().getWidth()) * initScale;
1964
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001965 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07001966 if (dX == dY && dX == 0) {
1967 } else {
1968 if (dY == 0) {
Adam Cohend9162062020-03-24 16:35:35 -07001969 finalDeltaX = -dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07001970 } else if (dX == 0) {
Adam Cohend9162062020-03-24 16:35:35 -07001971 finalDeltaY = -dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07001972 } else {
1973 double angle = Math.atan( (float) (dY) / dX);
Adam Cohend9162062020-03-24 16:35:35 -07001974 finalDeltaX = (int) (-dir * Math.signum(dX)
1975 * Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
1976 finalDeltaY = (int) (-dir * Math.signum(dY)
1977 * Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07001978 }
1979 }
Jon Miranda21266912016-12-19 14:12:05 -08001980 }
1981
Adam Cohend9162062020-03-24 16:35:35 -07001982 void setInitialAnimationValuesToBaseline() {
1983 initScale = mChildScale;
1984 initDeltaX = 0;
1985 initDeltaY = 0;
Adam Cohen19f37922012-03-21 11:59:11 -07001986 }
1987
Adam Cohend024f982012-05-23 18:26:45 -07001988 void animate() {
Adam Cohend9162062020-03-24 16:35:35 -07001989 boolean noMovement = (finalDeltaX == 0) && (finalDeltaY == 0);
Jon Miranda21266912016-12-19 14:12:05 -08001990
Adam Cohen19f37922012-03-21 11:59:11 -07001991 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001992 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohen19f37922012-03-21 11:59:11 -07001993 mShakeAnimators.remove(child);
Adam Cohend9162062020-03-24 16:35:35 -07001994
Jon Miranda21266912016-12-19 14:12:05 -08001995 if (noMovement) {
Adam Cohend9162062020-03-24 16:35:35 -07001996 // A previous animation for this item exists, and no new animation will exist.
1997 // Finish the old animation smoothly.
1998 oldAnimation.finishAnimation();
Adam Cohene7587d22012-05-24 18:50:02 -07001999 return;
Adam Cohend9162062020-03-24 16:35:35 -07002000 } else {
2001 // A previous animation for this item exists, and a new one will exist. Stop
2002 // the old animation in its tracks, and proceed with the new one.
2003 oldAnimation.cancel();
Adam Cohene7587d22012-05-24 18:50:02 -07002004 }
Adam Cohen19f37922012-03-21 11:59:11 -07002005 }
Jon Miranda21266912016-12-19 14:12:05 -08002006 if (noMovement) {
Adam Cohen19f37922012-03-21 11:59:11 -07002007 return;
2008 }
Adam Cohend9162062020-03-24 16:35:35 -07002009
Sunny Goyal849c6a22018-08-08 16:33:46 -07002010 ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS, 0, 1);
Adam Cohene7587d22012-05-24 18:50:02 -07002011 a = va;
Tony Wickham9e0702f2015-09-02 14:45:39 -07002012
2013 // Animations are disabled in power save mode, causing the repeated animation to jump
2014 // spastically between beginning and end states. Since this looks bad, we don't repeat
2015 // the animation in power save mode.
Sunny Goyaleaf7a952020-07-29 16:54:20 -07002016 if (areAnimatorsEnabled()) {
Tony Wickham9e0702f2015-09-02 14:45:39 -07002017 va.setRepeatMode(ValueAnimator.REVERSE);
2018 va.setRepeatCount(ValueAnimator.INFINITE);
2019 }
2020
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002021 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002022 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002023 va.addListener(new AnimatorListenerAdapter() {
2024 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002025 // We make sure to end only after a full period
Adam Cohend9162062020-03-24 16:35:35 -07002026 setInitialAnimationValuesToBaseline();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002027 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002028 }
2029 });
Adam Cohen19f37922012-03-21 11:59:11 -07002030 mShakeAnimators.put(child, this);
2031 va.start();
2032 }
2033
Sunny Goyal849c6a22018-08-08 16:33:46 -07002034 private void setAnimationProgress(float progress) {
2035 animationProgress = progress;
2036 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : animationProgress;
2037 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2038 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen1d13c0b2020-04-21 16:29:12 -07002039 child.setReorderBounceOffset(x, y);
Sunny Goyal849c6a22018-08-08 16:33:46 -07002040 float s = animationProgress * finalScale + (1 - animationProgress) * initScale;
Adam Cohen1d13c0b2020-04-21 16:29:12 -07002041 child.setReorderBounceScale(s);
Sunny Goyal849c6a22018-08-08 16:33:46 -07002042 }
2043
Adam Cohend024f982012-05-23 18:26:45 -07002044 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002045 if (a != null) {
2046 a.cancel();
2047 }
Adam Cohen19f37922012-03-21 11:59:11 -07002048 }
Adam Cohene7587d22012-05-24 18:50:02 -07002049
Adam Cohend9162062020-03-24 16:35:35 -07002050 /**
2051 * Smoothly returns the item to its baseline position / scale
2052 */
2053 @Thunk void finishAnimation() {
Adam Cohene7587d22012-05-24 18:50:02 -07002054 if (a != null) {
2055 a.cancel();
2056 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002057
Adam Cohend9162062020-03-24 16:35:35 -07002058 setInitialAnimationValuesToBaseline();
2059 ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS,
2060 animationProgress, 0);
2061 a = va;
Sunny Goyalf0b6db72018-08-13 16:10:14 -07002062 a.setInterpolator(DEACCEL_1_5);
Adam Cohend9162062020-03-24 16:35:35 -07002063 a.setDuration(REORDER_ANIMATION_DURATION);
Sunny Goyal5d2fc322015-07-06 22:52:49 -07002064 a.start();
Brandon Keely50e6e562012-05-08 16:28:49 -07002065 }
Adam Cohen19f37922012-03-21 11:59:11 -07002066 }
2067
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002068 private void completeAndClearReorderPreviewAnimations() {
2069 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Adam Cohend9162062020-03-24 16:35:35 -07002070 a.finishAnimation();
Adam Cohen19f37922012-03-21 11:59:11 -07002071 }
2072 mShakeAnimators.clear();
2073 }
2074
Adam Cohen482ed822012-03-02 14:15:13 -08002075 private void commitTempPlacement() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002076 mTmpOccupied.copyTo(mOccupied);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002077
Sunny Goyalab770a12018-11-14 15:17:26 -08002078 int screenId = Launcher.cast(mActivity).getWorkspace().getIdForScreen(this);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002079 int container = Favorites.CONTAINER_DESKTOP;
2080
Sunny Goyalc13403c2016-11-18 23:44:48 -08002081 if (mContainerType == HOTSEAT) {
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002082 screenId = -1;
2083 container = Favorites.CONTAINER_HOTSEAT;
2084 }
2085
Michael Jurkaa52570f2012-03-20 03:18:20 -07002086 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002087 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002088 View child = mShortcutsAndWidgets.getChildAt(i);
2089 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2090 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002091 // We do a null check here because the item info can be null in the case of the
2092 // AllApps button in the hotseat.
2093 if (info != null) {
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002094 final boolean requiresDbUpdate = (info.cellX != lp.tmpCellX
2095 || info.cellY != lp.tmpCellY || info.spanX != lp.cellHSpan
2096 || info.spanY != lp.cellVSpan);
2097
Adam Cohen2acce882012-03-28 19:03:19 -07002098 info.cellX = lp.cellX = lp.tmpCellX;
2099 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002100 info.spanX = lp.cellHSpan;
2101 info.spanY = lp.cellVSpan;
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002102
2103 if (requiresDbUpdate) {
Sunny Goyalab770a12018-11-14 15:17:26 -08002104 Launcher.cast(mActivity).getModelWriter().modifyItemInDatabase(info, container,
2105 screenId, info.cellX, info.cellY, info.spanX, info.spanY);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002106 }
Adam Cohen2acce882012-03-28 19:03:19 -07002107 }
Adam Cohen482ed822012-03-02 14:15:13 -08002108 }
2109 }
2110
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002111 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002112 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002113 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002114 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002115 lp.useTmpCoords = useTempCoords;
2116 }
2117 }
2118
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002119 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002120 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2121 int[] result = new int[2];
2122 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002123 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002124 resultSpan);
2125 if (result[0] >= 0 && result[1] >= 0) {
2126 copyCurrentStateToSolution(solution, false);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002127 solution.cellX = result[0];
2128 solution.cellY = result[1];
2129 solution.spanX = resultSpan[0];
2130 solution.spanY = resultSpan[1];
Adam Cohen482ed822012-03-02 14:15:13 -08002131 solution.isSolution = true;
2132 } else {
2133 solution.isSolution = false;
2134 }
2135 return solution;
2136 }
2137
Adam Cohen19f37922012-03-21 11:59:11 -07002138 /* This seems like it should be obvious and straight-forward, but when the direction vector
2139 needs to match with the notion of the dragView pushing other views, we have to employ
2140 a slightly more subtle notion of the direction vector. The question is what two points is
2141 the vector between? The center of the dragView and its desired destination? Not quite, as
2142 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2143 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2144 or right, which helps make pushing feel right.
2145 */
2146 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2147 int spanY, View dragView, int[] resultDirection) {
Adam Cohen65086992020-02-19 08:40:49 -08002148
2149 //TODO(adamcohen) b/151776141 use the items visual center for the direction vector
Adam Cohen19f37922012-03-21 11:59:11 -07002150 int[] targetDestination = new int[2];
2151
2152 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2153 Rect dragRect = new Rect();
Jon Miranda228877d2021-02-09 11:05:00 -05002154 cellToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
Adam Cohen19f37922012-03-21 11:59:11 -07002155 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2156
2157 Rect dropRegionRect = new Rect();
2158 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2159 dragView, dropRegionRect, mIntersectingViews);
2160
2161 int dropRegionSpanX = dropRegionRect.width();
2162 int dropRegionSpanY = dropRegionRect.height();
2163
Jon Miranda228877d2021-02-09 11:05:00 -05002164 cellToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
Adam Cohen19f37922012-03-21 11:59:11 -07002165 dropRegionRect.height(), dropRegionRect);
2166
2167 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2168 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2169
2170 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2171 deltaX = 0;
2172 }
2173 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2174 deltaY = 0;
2175 }
2176
2177 if (deltaX == 0 && deltaY == 0) {
2178 // No idea what to do, give a random direction.
2179 resultDirection[0] = 1;
2180 resultDirection[1] = 0;
2181 } else {
2182 computeDirectionVector(deltaX, deltaY, resultDirection);
2183 }
2184 }
2185
2186 // For a given cell and span, fetch the set of views intersecting the region.
2187 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2188 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2189 if (boundingRect != null) {
2190 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2191 }
2192 intersectingViews.clear();
2193 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2194 Rect r1 = new Rect();
2195 final int count = mShortcutsAndWidgets.getChildCount();
2196 for (int i = 0; i < count; i++) {
2197 View child = mShortcutsAndWidgets.getChildAt(i);
2198 if (child == dragView) continue;
2199 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2200 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2201 if (Rect.intersects(r0, r1)) {
2202 mIntersectingViews.add(child);
2203 if (boundingRect != null) {
2204 boundingRect.union(r1);
2205 }
2206 }
2207 }
2208 }
2209
2210 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2211 View dragView, int[] result) {
2212 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2213 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2214 mIntersectingViews);
2215 return !mIntersectingViews.isEmpty();
2216 }
2217
2218 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002219 completeAndClearReorderPreviewAnimations();
2220 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2221 final int count = mShortcutsAndWidgets.getChildCount();
2222 for (int i = 0; i < count; i++) {
2223 View child = mShortcutsAndWidgets.getChildAt(i);
2224 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2225 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2226 lp.tmpCellX = lp.cellX;
2227 lp.tmpCellY = lp.cellY;
2228 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2229 0, false, false);
2230 }
Adam Cohen19f37922012-03-21 11:59:11 -07002231 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002232 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002233 }
Adam Cohen19f37922012-03-21 11:59:11 -07002234 }
2235
Adam Cohenbebf0422012-04-11 18:06:28 -07002236 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2237 View dragView, int[] direction, boolean commit) {
2238 int[] pixelXY = new int[2];
2239 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2240
2241 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002242 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002243 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2244
2245 setUseTempCoords(true);
2246 if (swapSolution != null && swapSolution.isSolution) {
2247 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2248 // committing anything or animating anything as we just want to determine if a solution
2249 // exists
2250 copySolutionToTempState(swapSolution, dragView);
2251 setItemPlacementDirty(true);
2252 animateItemsToSolution(swapSolution, dragView, commit);
2253
2254 if (commit) {
2255 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002256 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002257 setItemPlacementDirty(false);
2258 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002259 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
Adam Cohen65086992020-02-19 08:40:49 -08002260 ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002261 }
2262 mShortcutsAndWidgets.requestLayout();
2263 }
2264 return swapSolution.isSolution;
2265 }
2266
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002267 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002268 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002269 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002270 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002271
2272 if (resultSpan == null) {
2273 resultSpan = new int[2];
2274 }
2275
Adam Cohen19f37922012-03-21 11:59:11 -07002276 // When we are checking drop validity or actually dropping, we don't recompute the
2277 // direction vector, since we want the solution to match the preview, and it's possible
2278 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002279 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2280 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002281 mDirectionVector[0] = mPreviousReorderDirection[0];
2282 mDirectionVector[1] = mPreviousReorderDirection[1];
2283 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002284 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2285 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2286 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002287 }
2288 } else {
2289 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2290 mPreviousReorderDirection[0] = mDirectionVector[0];
2291 mPreviousReorderDirection[1] = mDirectionVector[1];
2292 }
2293
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002294 // Find a solution involving pushing / displacing any items in the way
2295 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002296 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2297
2298 // We attempt the approach which doesn't shuffle views at all
2299 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2300 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2301
2302 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002303
2304 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2305 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002306 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2307 finalSolution = swapSolution;
2308 } else if (noShuffleSolution.isSolution) {
2309 finalSolution = noShuffleSolution;
2310 }
2311
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002312 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002313 if (finalSolution != null) {
Adam Cohen65086992020-02-19 08:40:49 -08002314 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
Adam Cohenfe692872013-12-11 14:47:23 -08002315 ReorderPreviewAnimation.MODE_HINT);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002316 result[0] = finalSolution.cellX;
2317 result[1] = finalSolution.cellY;
2318 resultSpan[0] = finalSolution.spanX;
2319 resultSpan[1] = finalSolution.spanY;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002320 } else {
2321 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2322 }
2323 return result;
2324 }
2325
Adam Cohen482ed822012-03-02 14:15:13 -08002326 boolean foundSolution = true;
2327 if (!DESTRUCTIVE_REORDER) {
2328 setUseTempCoords(true);
2329 }
2330
2331 if (finalSolution != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002332 result[0] = finalSolution.cellX;
2333 result[1] = finalSolution.cellY;
2334 resultSpan[0] = finalSolution.spanX;
2335 resultSpan[1] = finalSolution.spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002336
2337 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2338 // committing anything or animating anything as we just want to determine if a solution
2339 // exists
2340 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2341 if (!DESTRUCTIVE_REORDER) {
2342 copySolutionToTempState(finalSolution, dragView);
2343 }
2344 setItemPlacementDirty(true);
2345 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2346
Adam Cohen19f37922012-03-21 11:59:11 -07002347 if (!DESTRUCTIVE_REORDER &&
2348 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002349 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002350 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002351 setItemPlacementDirty(false);
2352 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002353 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
Adam Cohen65086992020-02-19 08:40:49 -08002354 ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002355 }
2356 }
2357 } else {
2358 foundSolution = false;
2359 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2360 }
2361
2362 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2363 setUseTempCoords(false);
2364 }
Adam Cohen482ed822012-03-02 14:15:13 -08002365
Michael Jurkaa52570f2012-03-20 03:18:20 -07002366 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002367 return result;
2368 }
2369
Adam Cohen19f37922012-03-21 11:59:11 -07002370 void setItemPlacementDirty(boolean dirty) {
2371 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002372 }
Adam Cohen19f37922012-03-21 11:59:11 -07002373 boolean isItemPlacementDirty() {
2374 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002375 }
2376
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002377 private static class ItemConfiguration extends CellAndSpan {
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07002378 final ArrayMap<View, CellAndSpan> map = new ArrayMap<>();
2379 private final ArrayMap<View, CellAndSpan> savedMap = new ArrayMap<>();
2380 final ArrayList<View> sortedViews = new ArrayList<>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002381 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002382 boolean isSolution = false;
Adam Cohen482ed822012-03-02 14:15:13 -08002383
Adam Cohenf3900c22012-11-16 18:28:11 -08002384 void save() {
2385 // Copy current state into savedMap
2386 for (View v: map.keySet()) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002387 savedMap.get(v).copyFrom(map.get(v));
Adam Cohenf3900c22012-11-16 18:28:11 -08002388 }
2389 }
2390
2391 void restore() {
2392 // Restore current state from savedMap
2393 for (View v: savedMap.keySet()) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002394 map.get(v).copyFrom(savedMap.get(v));
Adam Cohenf3900c22012-11-16 18:28:11 -08002395 }
2396 }
2397
2398 void add(View v, CellAndSpan cs) {
2399 map.put(v, cs);
2400 savedMap.put(v, new CellAndSpan());
2401 sortedViews.add(v);
2402 }
2403
Adam Cohen482ed822012-03-02 14:15:13 -08002404 int area() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002405 return spanX * spanY;
Adam Cohenf3900c22012-11-16 18:28:11 -08002406 }
2407
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002408 void getBoundingRectForViews(ArrayList<View> views, Rect outRect) {
2409 boolean first = true;
2410 for (View v: views) {
2411 CellAndSpan c = map.get(v);
2412 if (first) {
2413 outRect.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2414 first = false;
2415 } else {
2416 outRect.union(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2417 }
2418 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002419 }
Adam Cohen482ed822012-03-02 14:15:13 -08002420 }
2421
Adam Cohendf035382011-04-11 17:22:04 -07002422 /**
Adam Cohendf035382011-04-11 17:22:04 -07002423 * Find a starting cell position that will fit the given bounds nearest the requested
2424 * cell location. Uses Euclidean distance to score multiple vacant areas.
2425 *
2426 * @param pixelX The X location at which you want to search for a vacant area.
2427 * @param pixelY The Y location at which you want to search for a vacant area.
2428 * @param spanX Horizontal span of the object.
2429 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07002430 * @param result Previously returned value to possibly recycle.
2431 * @return The X, Y cell of a vacant area that can contain this object,
2432 * nearest the requested location.
2433 */
Adam Cohenf9c184a2016-01-15 16:47:43 -08002434 public int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002435 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002436 }
2437
Michael Jurka0280c3b2010-09-17 15:00:07 -07002438 boolean existsEmptyCell() {
2439 return findCellForSpan(null, 1, 1);
2440 }
2441
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002442 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002443 * Finds the upper-left coordinate of the first rectangle in the grid that can
2444 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2445 * then this method will only return coordinates for rectangles that contain the cell
2446 * (intersectX, intersectY)
2447 *
2448 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2449 * can be found.
2450 * @param spanX The horizontal span of the cell we want to find.
2451 * @param spanY The vertical span of the cell we want to find.
2452 *
2453 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002454 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002455 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002456 if (cellXY == null) {
2457 cellXY = new int[2];
Michael Jurka0280c3b2010-09-17 15:00:07 -07002458 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002459 return mOccupied.findVacantCell(cellXY, spanX, spanY);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002460 }
2461
2462 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002463 * A drag event has begun over this layout.
2464 * It may have begun over this layout (in which case onDragChild is called first),
2465 * or it may have begun on another layout.
2466 */
2467 void onDragEnter() {
Winson Chungc07918d2011-07-01 15:35:26 -07002468 mDragging = true;
2469 }
2470
2471 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002472 * Called when drag has left this CellLayout or has been completed (successfully or not)
2473 */
2474 void onDragExit() {
Joe Onorato4be866d2010-10-10 11:26:02 -07002475 // This can actually be called when we aren't in a drag, e.g. when adding a new
2476 // item to this layout via the customize drawer.
2477 // Guard against that case.
2478 if (mDragging) {
2479 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002480 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002481
2482 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002483 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002484 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2485 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002486 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002487 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002488 }
2489
2490 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002491 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002492 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002493 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002494 *
2495 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002496 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002497 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002498 if (child != null) {
2499 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002500 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002501 child.requestLayout();
Tony Wickham1cdb6d02015-09-17 11:08:27 -07002502 markCellsAsOccupiedForView(child);
Romain Guyd94533d2009-08-17 10:01:15 -07002503 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002504 }
2505
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002506 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002507 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002508 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002509 * @param cellX X coordinate of upper left corner expressed as a cell position
2510 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002511 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002512 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002513 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002514 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002515 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002516 final int cellWidth = mCellWidth;
2517 final int cellHeight = mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -07002518
Winson Chung4b825dcd2011-06-19 12:41:22 -07002519 final int hStartPadding = getPaddingLeft();
2520 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002521
Jon Miranda228877d2021-02-09 11:05:00 -05002522 int x = hStartPadding + (cellX * mBorderSpacing) + (cellX * cellWidth);
2523 int y = vStartPadding + (cellY * mBorderSpacing) + (cellY * cellHeight);
2524
2525 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * mBorderSpacing);
2526 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * mBorderSpacing);
Winson Chungaafa03c2010-06-11 17:34:16 -07002527
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002528 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002529 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002530
Adam Cohend4844c32011-02-18 19:25:06 -08002531 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002532 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002533 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002534 mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002535 }
2536
Adam Cohend4844c32011-02-18 19:25:06 -08002537 public void markCellsAsUnoccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002538 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002539 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002540 mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002541 }
2542
Adam Cohen2801caf2011-05-13 20:57:39 -07002543 public int getDesiredWidth() {
Jon Miranda228877d2021-02-09 11:05:00 -05002544 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth)
2545 + ((mCountX - 1) * mBorderSpacing);
Adam Cohen2801caf2011-05-13 20:57:39 -07002546 }
2547
2548 public int getDesiredHeight() {
Jon Miranda228877d2021-02-09 11:05:00 -05002549 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight)
2550 + ((mCountY - 1) * mBorderSpacing);
Adam Cohen2801caf2011-05-13 20:57:39 -07002551 }
2552
Michael Jurka66d72172011-04-12 16:29:25 -07002553 public boolean isOccupied(int x, int y) {
2554 if (x < mCountX && y < mCountY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002555 return mOccupied.cells[x][y];
Michael Jurka66d72172011-04-12 16:29:25 -07002556 } else {
2557 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2558 }
2559 }
2560
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002561 @Override
2562 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2563 return new CellLayout.LayoutParams(getContext(), attrs);
2564 }
2565
2566 @Override
2567 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2568 return p instanceof CellLayout.LayoutParams;
2569 }
2570
2571 @Override
2572 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2573 return new CellLayout.LayoutParams(p);
2574 }
2575
2576 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2577 /**
2578 * Horizontal location of the item in the grid.
2579 */
2580 @ViewDebug.ExportedProperty
2581 public int cellX;
2582
2583 /**
2584 * Vertical location of the item in the grid.
2585 */
2586 @ViewDebug.ExportedProperty
2587 public int cellY;
2588
2589 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002590 * Temporary horizontal location of the item in the grid during reorder
2591 */
2592 public int tmpCellX;
2593
2594 /**
2595 * Temporary vertical location of the item in the grid during reorder
2596 */
2597 public int tmpCellY;
2598
2599 /**
2600 * Indicates that the temporary coordinates should be used to layout the items
2601 */
2602 public boolean useTmpCoords;
2603
2604 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002605 * Number of cells spanned horizontally by the item.
2606 */
2607 @ViewDebug.ExportedProperty
2608 public int cellHSpan;
2609
2610 /**
2611 * Number of cells spanned vertically by the item.
2612 */
2613 @ViewDebug.ExportedProperty
2614 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002615
Adam Cohen1b607ed2011-03-03 17:26:50 -08002616 /**
2617 * Indicates whether the item will set its x, y, width and height parameters freely,
2618 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2619 */
Adam Cohend4844c32011-02-18 19:25:06 -08002620 public boolean isLockedToGrid = true;
2621
Adam Cohen482ed822012-03-02 14:15:13 -08002622 /**
2623 * Indicates whether this item can be reordered. Always true except in the case of the
Sunny Goyalda4fe1a2016-05-26 16:05:17 -07002624 * the AllApps button and QSB place holder.
Adam Cohen482ed822012-03-02 14:15:13 -08002625 */
2626 public boolean canReorder = true;
2627
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002628 // X coordinate of the view in the layout.
2629 @ViewDebug.ExportedProperty
Vadim Tryshevfedca432015-08-19 17:55:02 -07002630 public int x;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002631 // Y coordinate of the view in the layout.
2632 @ViewDebug.ExportedProperty
Vadim Tryshevfedca432015-08-19 17:55:02 -07002633 public int y;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002634
Romain Guy84f296c2009-11-04 15:00:44 -08002635 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002636
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002637 public LayoutParams(Context c, AttributeSet attrs) {
2638 super(c, attrs);
2639 cellHSpan = 1;
2640 cellVSpan = 1;
2641 }
2642
2643 public LayoutParams(ViewGroup.LayoutParams source) {
2644 super(source);
2645 cellHSpan = 1;
2646 cellVSpan = 1;
2647 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002648
2649 public LayoutParams(LayoutParams source) {
2650 super(source);
2651 this.cellX = source.cellX;
2652 this.cellY = source.cellY;
2653 this.cellHSpan = source.cellHSpan;
2654 this.cellVSpan = source.cellVSpan;
2655 }
2656
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002657 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002658 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002659 this.cellX = cellX;
2660 this.cellY = cellY;
2661 this.cellHSpan = cellHSpan;
2662 this.cellVSpan = cellVSpan;
2663 }
2664
Jon Miranda228877d2021-02-09 11:05:00 -05002665 public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
2666 int rowCount, int borderSpacing) {
2667 setup(cellWidth, cellHeight, invertHorizontally, colCount, rowCount, 1.0f, 1.0f,
2668 borderSpacing);
Jon Miranda7ae64ff2016-11-21 16:18:46 -08002669 }
2670
2671 /**
Jon Miranda228877d2021-02-09 11:05:00 -05002672 * Use this method, as opposed to {@link #setup(int, int, boolean, int, int, int)},
2673 * if the view needs to be scaled.
Jon Miranda7ae64ff2016-11-21 16:18:46 -08002674 *
2675 * ie. In multi-window mode, we setup widgets so that they are measured and laid out
2676 * using their full/invariant device profile sizes.
2677 */
2678 public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
Jon Miranda228877d2021-02-09 11:05:00 -05002679 int rowCount, float cellScaleX, float cellScaleY, int borderSpacing) {
Adam Cohend4844c32011-02-18 19:25:06 -08002680 if (isLockedToGrid) {
2681 final int myCellHSpan = cellHSpan;
2682 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002683 int myCellX = useTmpCoords ? tmpCellX : cellX;
2684 int myCellY = useTmpCoords ? tmpCellY : cellY;
2685
2686 if (invertHorizontally) {
2687 myCellX = colCount - myCellX - cellHSpan;
2688 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002689
Jon Miranda228877d2021-02-09 11:05:00 -05002690 int hBorderSpacing = (myCellHSpan - 1) * borderSpacing;
2691 int vBorderSpacing = (myCellVSpan - 1) * borderSpacing;
2692
2693 float myCellWidth = ((myCellHSpan * cellWidth) + hBorderSpacing) / cellScaleX;
2694 float myCellHeight = ((myCellVSpan * cellHeight) + vBorderSpacing) / cellScaleY;
2695
2696 width = Math.round(myCellWidth) - leftMargin - rightMargin;
2697 height = Math.round(myCellHeight) - topMargin - bottomMargin;
2698 x = leftMargin + (myCellX * cellWidth) + (myCellX * borderSpacing);
2699 y = topMargin + (myCellY * cellHeight) + (myCellY * borderSpacing);
Adam Cohend4844c32011-02-18 19:25:06 -08002700 }
2701 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002702
Sunny Goyal11a50742019-08-07 09:24:26 -07002703 /**
2704 * Sets the position to the provided point
2705 */
Jon Miranda228877d2021-02-09 11:05:00 -05002706 public void setCellXY(Point point) {
Sunny Goyal11a50742019-08-07 09:24:26 -07002707 cellX = point.x;
2708 cellY = point.y;
2709 }
2710
Winson Chungaafa03c2010-06-11 17:34:16 -07002711 public String toString() {
2712 return "(" + this.cellX + ", " + this.cellY + ")";
2713 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002714 }
2715
Michael Jurka0280c3b2010-09-17 15:00:07 -07002716 // This class stores info for two purposes:
2717 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2718 // its spanX, spanY, and the screen it is on
2719 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2720 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2721 // the CellLayout that was long clicked
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002722 public static final class CellInfo extends CellAndSpan {
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07002723 public final View cell;
Sunny Goyalefb7e842018-10-04 15:11:00 -07002724 final int screenId;
2725 final int container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002726
Sunny Goyal83a8f042015-05-19 12:52:12 -07002727 public CellInfo(View v, ItemInfo info) {
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002728 cellX = info.cellX;
2729 cellY = info.cellY;
2730 spanX = info.spanX;
2731 spanY = info.spanY;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002732 cell = v;
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002733 screenId = info.screenId;
2734 container = info.container;
2735 }
2736
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002737 @Override
2738 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002739 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2740 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002741 }
2742 }
Michael Jurkad771c962011-08-09 15:00:48 -07002743
Tony Wickham86930612015-09-09 13:50:40 -07002744 /**
Samuel Fufa1e2d0042019-11-18 17:12:46 -08002745 * A Delegated cell Drawing for drawing on CellLayout
2746 */
2747 public abstract static class DelegatedCellDrawing {
2748 public int mDelegateCellX;
2749 public int mDelegateCellY;
2750
2751 /**
2752 * Draw under CellLayout
2753 */
2754 public abstract void drawUnderItem(Canvas canvas);
2755
2756 /**
2757 * Draw over CellLayout
2758 */
2759 public abstract void drawOverItem(Canvas canvas);
2760 }
2761
2762 /**
Tony Wickham86930612015-09-09 13:50:40 -07002763 * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing
2764 * if necessary).
2765 */
2766 public boolean hasReorderSolution(ItemInfo itemInfo) {
2767 int[] cellPoint = new int[2];
2768 // Check for a solution starting at every cell.
2769 for (int cellX = 0; cellX < getCountX(); cellX++) {
2770 for (int cellY = 0; cellY < getCountY(); cellY++) {
2771 cellToPoint(cellX, cellY, cellPoint);
2772 if (findReorderSolution(cellPoint[0], cellPoint[1], itemInfo.minSpanX,
2773 itemInfo.minSpanY, itemInfo.spanX, itemInfo.spanY, mDirectionVector, null,
2774 true, new ItemConfiguration()).isSolution) {
2775 return true;
2776 }
2777 }
2778 }
2779 return false;
2780 }
2781
Samuel Fufaa4211432020-02-25 18:47:54 -08002782 /**
2783 * Finds solution to accept hotseat migration to cell layout. commits solution if commitConfig
2784 */
2785 public boolean makeSpaceForHotseatMigration(boolean commitConfig) {
Samuel Fufaa4211432020-02-25 18:47:54 -08002786 int[] cellPoint = new int[2];
2787 int[] directionVector = new int[]{0, -1};
2788 cellToPoint(0, mCountY, cellPoint);
2789 ItemConfiguration configuration = new ItemConfiguration();
2790 if (findReorderSolution(cellPoint[0], cellPoint[1], mCountX, 1, mCountX, 1,
2791 directionVector, null, false, configuration).isSolution) {
2792 if (commitConfig) {
2793 copySolutionToTempState(configuration, null);
2794 commitTempPlacement();
Samuel Fufa82bbdac2020-03-09 18:24:47 -07002795 // undo marking cells occupied since there is actually nothing being placed yet.
2796 mOccupied.markCells(0, mCountY - 1, mCountX, 1, false);
Samuel Fufaa4211432020-02-25 18:47:54 -08002797 }
2798 return true;
2799 }
2800 return false;
2801 }
2802
Samuel Fufa82bbdac2020-03-09 18:24:47 -07002803 /**
2804 * returns a copy of cell layout's grid occupancy
2805 */
2806 public GridOccupancy cloneGridOccupancy() {
2807 GridOccupancy occupancy = new GridOccupancy(mCountX, mCountY);
2808 mOccupied.copyTo(occupancy);
2809 return occupancy;
2810 }
2811
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002812 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002813 return mOccupied.isRegionVacant(x, y, spanX, spanY);
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002814 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002815}