blob: 444c8110b3112e16b84d6cd05679fa1cf96c6331 [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;
Federico Baron1028a722022-10-13 14:09:38 -070022import static com.android.launcher3.config.FeatureFlags.SHOW_HOME_GARDENING;
Tony Wickham0ac045f2021-11-03 13:17:02 -070023import static com.android.launcher3.dragndrop.DraggableView.DRAGGABLE_ICON;
Tony Wickham12784902021-11-03 14:02:10 -070024import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
Sunny Goyal82dfc152023-02-24 16:50:09 -080025import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_REORDER_BOUNCE_OFFSET;
26import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_REORDER_PREVIEW_OFFSET;
Sunny Goyalf0b6db72018-08-13 16:10:14 -070027
Joe Onorato4be866d2010-10-10 11:26:02 -070028import android.animation.Animator;
Michael Jurka629758f2012-06-14 16:18:21 -070029import android.animation.AnimatorListenerAdapter;
Sunny Goyal849c6a22018-08-08 16:33:46 -070030import android.animation.ObjectAnimator;
Chet Haase00397b12010-10-07 11:13:10 -070031import android.animation.TimeInterpolator;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070032import android.animation.ValueAnimator;
33import android.animation.ValueAnimator.AnimatorUpdateListener;
Sunny Goyal726bee72018-03-05 12:54:24 -080034import android.annotation.SuppressLint;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080035import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040036import android.content.res.Resources;
Sunny Goyalc13403c2016-11-18 23:44:48 -080037import android.content.res.TypedArray;
Winson Chungaafa03c2010-06-11 17:34:16 -070038import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080039import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070040import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070041import android.graphics.Point;
Adam Cohend9162062020-03-24 16:35:35 -070042import android.graphics.PointF;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080043import android.graphics.Rect;
Adam Cohenf7ca3b42021-02-22 11:03:58 -080044import android.graphics.RectF;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070045import android.graphics.drawable.Drawable;
Adam Cohen1462de32012-07-24 22:34:36 -070046import android.os.Parcelable;
Rajeev Kumar9962dbe2017-06-12 12:16:20 -070047import android.util.ArrayMap;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080048import android.util.AttributeSet;
Adam Cohenf7ca3b42021-02-22 11:03:58 -080049import android.util.FloatProperty;
Joe Onorato4be866d2010-10-10 11:26:02 -070050import android.util.Log;
Sunny Goyal849c6a22018-08-08 16:33:46 -070051import android.util.Property;
Adam Cohen1462de32012-07-24 22:34:36 -070052import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080053import android.view.MotionEvent;
54import android.view.View;
55import android.view.ViewDebug;
56import android.view.ViewGroup;
Adam Cohenc9735cf2015-01-23 16:11:55 -080057import android.view.accessibility.AccessibilityEvent;
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -070058
vadimt04f356f2019-02-14 18:46:36 -080059import androidx.annotation.IntDef;
Adam Cohenf7ca3b42021-02-22 11:03:58 -080060import androidx.core.graphics.ColorUtils;
vadimt04f356f2019-02-14 18:46:36 -080061import androidx.core.view.ViewCompat;
62
Sunny Goyalaa8ef112015-06-12 20:04:41 -070063import com.android.launcher3.LauncherSettings.Favorites;
Sunny Goyale9b651e2015-04-24 11:44:51 -070064import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -070065import com.android.launcher3.anim.Interpolators;
Sebastian Francod4682992022-10-05 13:03:09 -050066import com.android.launcher3.celllayout.CellLayoutLayoutParams;
Sunny Goyal669b71f2023-01-27 14:37:07 -080067import com.android.launcher3.celllayout.CellPosMapper.CellPos;
Sebastian Francoe4c03452022-12-27 14:50:02 -060068import com.android.launcher3.celllayout.ReorderAlgorithm;
Sunny Goyal3d706ad2017-03-06 16:56:39 -080069import com.android.launcher3.config.FeatureFlags;
Tony Wickham0ac045f2021-11-03 13:17:02 -070070import com.android.launcher3.dragndrop.DraggableView;
Jon Mirandaa0233f72017-06-22 18:34:45 -070071import com.android.launcher3.folder.PreviewBackground;
Sunny Goyale396abf2020-04-06 15:11:17 -070072import com.android.launcher3.model.data.ItemInfo;
Sebastian Francof153d912022-04-22 16:15:27 -050073import com.android.launcher3.model.data.LauncherAppWidgetInfo;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -070074import com.android.launcher3.util.CellAndSpan;
75import com.android.launcher3.util.GridOccupancy;
Sunny Goyal82dfc152023-02-24 16:50:09 -080076import com.android.launcher3.util.MultiTranslateDelegate;
Sunny Goyale2fd14b2015-08-27 17:45:46 -070077import com.android.launcher3.util.ParcelableSparseArray;
Sunny Goyal9b29ca52017-02-17 10:39:44 -080078import com.android.launcher3.util.Themes;
Adam Cohen091440a2015-03-18 14:16:05 -070079import com.android.launcher3.util.Thunk;
Sunny Goyalab770a12018-11-14 15:17:26 -080080import com.android.launcher3.views.ActivityContext;
Steven Ng32427202021-04-19 18:12:12 +010081import com.android.launcher3.widget.LauncherAppWidgetHostView;
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -070082
Sunny Goyalc13403c2016-11-18 23:44:48 -080083import java.lang.annotation.Retention;
84import java.lang.annotation.RetentionPolicy;
Adam Cohen69ce2e52011-07-03 19:25:21 -070085import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070086import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080087import java.util.Collections;
88import java.util.Comparator;
Adam Cohend41fbf52012-02-16 23:53:59 -080089import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070090
Sunny Goyalc4d32012020-04-03 17:10:11 -070091public class CellLayout extends ViewGroup {
Tony Wickhama0628cc2015-10-14 15:23:04 -070092 private static final String TAG = "CellLayout";
93 private static final boolean LOGD = false;
Winson Chungaafa03c2010-06-11 17:34:16 -070094
Sunny Goyalab770a12018-11-14 15:17:26 -080095 protected final ActivityContext mActivity;
Sunny Goyal4ffec482016-02-09 11:28:52 -080096 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070097 @Thunk int mCellWidth;
Sunny Goyal4ffec482016-02-09 11:28:52 -080098 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070099 @Thunk int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -0700100 private int mFixedCellWidth;
101 private int mFixedCellHeight;
Jon Miranda228877d2021-02-09 11:05:00 -0500102 @ViewDebug.ExportedProperty(category = "launcher")
Sebastian Franco25423862023-03-10 10:50:37 -0800103 protected Point mBorderSpace;
Winson Chungaafa03c2010-06-11 17:34:16 -0700104
Sunny Goyal4ffec482016-02-09 11:28:52 -0800105 @ViewDebug.ExportedProperty(category = "launcher")
Sebastian Franco09589322022-11-02 15:25:58 -0700106 protected int mCountX;
Sunny Goyal4ffec482016-02-09 11:28:52 -0800107 @ViewDebug.ExportedProperty(category = "launcher")
Sebastian Franco09589322022-11-02 15:25:58 -0700108 protected int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800109
Adam Cohen917e3882013-10-31 15:03:35 -0700110 private boolean mDropPending = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800111
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700112 // These are temporary variables to prevent having to allocate a new object just to
113 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Adam Cohen091440a2015-03-18 14:16:05 -0700114 @Thunk final int[] mTmpPoint = new int[2];
Sunny Goyal2805e632015-05-20 15:35:32 -0700115 @Thunk final int[] mTempLocation = new int[2];
Adam Cohend9162062020-03-24 16:35:35 -0700116 final PointF mTmpPointF = new PointF();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700117
Sebastian Franco09589322022-11-02 15:25:58 -0700118 protected GridOccupancy mOccupied;
Sebastian Francoe4c03452022-12-27 14:50:02 -0600119 public GridOccupancy mTmpOccupied;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800120
Michael Jurkadee05892010-07-27 10:01:56 -0700121 private OnTouchListener mInterceptTouchListener;
122
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800123 private final ArrayList<DelegatedCellDrawing> mDelegatedCellDrawings = new ArrayList<>();
Jon Mirandaa0233f72017-06-22 18:34:45 -0700124 final PreviewBackground mFolderLeaveBehind = new PreviewBackground();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700125
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800126 private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active };
Sunny Goyale15e2a82017-12-15 13:05:42 -0800127 private static final int[] BACKGROUND_STATE_DEFAULT = EMPTY_STATE_SET;
Sebastian Franco09589322022-11-02 15:25:58 -0700128 protected final Drawable mBackground;
Sunny Goyal2805e632015-05-20 15:35:32 -0700129
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700130 // These values allow a fixed measurement to be set on the CellLayout.
131 private int mFixedWidth = -1;
132 private int mFixedHeight = -1;
133
Michael Jurka33945b22010-12-21 18:19:38 -0800134 // If we're actively dragging something over this screen, mIsDragOverlapping is true
135 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700136
Winson Chung150fbab2010-09-29 17:14:26 -0700137 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700138 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Sebastian Francod4682992022-10-05 13:03:09 -0500139 @Thunk final CellLayoutLayoutParams[] mDragOutlines = new CellLayoutLayoutParams[4];
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700140 @Thunk final float[] mDragOutlineAlphas = new float[mDragOutlines.length];
141 private final InterruptibleInOutAnimator[] mDragOutlineAnims =
Joe Onorato4be866d2010-10-10 11:26:02 -0700142 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700143
144 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700145 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700146 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700147
Sebastian Francod4682992022-10-05 13:03:09 -0500148 @Thunk final ArrayMap<CellLayoutLayoutParams, Animator> mReorderAnimators = new ArrayMap<>();
Adam Cohend9162062020-03-24 16:35:35 -0700149 @Thunk final ArrayMap<Reorderable, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
Adam Cohen19f37922012-03-21 11:59:11 -0700150
151 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700152
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800153 // Used to visualize the grid and drop locations
154 private boolean mVisualizeCells = false;
155 private boolean mVisualizeDropLocation = true;
156 private RectF mVisualizeGridRect = new RectF();
157 private Paint mVisualizeGridPaint = new Paint();
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800158 private int mGridVisualizationRoundingRadius;
159 private float mGridAlpha = 0f;
160 private int mGridColor = 0;
Sebastian Franco09589322022-11-02 15:25:58 -0700161 protected float mSpringLoadedProgress = 0f;
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800162 private float mScrollProgress = 0f;
163
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700164 // When a drag operation is in progress, holds the nearest cell to the touch point
165 private final int[] mDragCell = new int[2];
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800166 private final int[] mDragCellSpan = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800167
Joe Onorato4be866d2010-10-10 11:26:02 -0700168 private boolean mDragging = false;
169
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700170 private final TimeInterpolator mEaseOutInterpolator;
Sebastian Franco09589322022-11-02 15:25:58 -0700171 protected final ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700172
Sunny Goyalc13403c2016-11-18 23:44:48 -0800173 @Retention(RetentionPolicy.SOURCE)
174 @IntDef({WORKSPACE, HOTSEAT, FOLDER})
175 public @interface ContainerType{}
176 public static final int WORKSPACE = 0;
177 public static final int HOTSEAT = 1;
178 public static final int FOLDER = 2;
179
180 @ContainerType private final int mContainerType;
181
Jon Mirandab28c4fc2017-06-20 10:58:36 -0700182 private final float mChildScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800183
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800184 public static final int MODE_SHOW_REORDER_HINT = 0;
185 public static final int MODE_DRAG_OVER = 1;
186 public static final int MODE_ON_DROP = 2;
187 public static final int MODE_ON_DROP_EXTERNAL = 3;
188 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700189 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800190 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
191
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800192 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700193 private static final int REORDER_ANIMATION_DURATION = 150;
Sunny Goyalc13403c2016-11-18 23:44:48 -0800194 @Thunk final float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700195
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700196 private final ArrayList<View> mIntersectingViews = new ArrayList<>();
197 private final Rect mOccupiedRect = new Rect();
Sebastian Francoe4c03452022-12-27 14:50:02 -0600198 public final int[] mDirectionVector = new int[2];
Steven Ng30dd1d62021-03-15 21:45:49 +0000199
Sebastian Franco53a15a42022-10-25 17:28:54 -0700200 ItemConfiguration mPreviousSolution = null;
Adam Cohenb209e632012-03-27 17:09:36 -0700201 private static final int INVALID_DIRECTION = -100;
Adam Cohen482ed822012-03-02 14:15:13 -0800202
Sunny Goyal2805e632015-05-20 15:35:32 -0700203 private final Rect mTempRect = new Rect();
Jonathan Miranda21930da2021-05-03 18:44:13 +0000204 private final RectF mTempRectF = new RectF();
Jon Miranda88b7f6a2021-05-03 16:49:53 -0700205 private final float[] mTmpFloatArray = new float[4];
Winson Chung3a6e7f32013-10-09 15:50:52 -0700206
Sunny Goyal73b5a272019-12-09 14:55:56 -0800207 private static final Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700208
Adam Cohenc9735cf2015-01-23 16:11:55 -0800209 // Related to accessible drag and drop
Adam Cohen6e7c37a2020-06-25 19:22:37 -0700210 DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800211
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800212
213 public static final FloatProperty<CellLayout> SPRING_LOADED_PROGRESS =
214 new FloatProperty<CellLayout>("spring_loaded_progress") {
215 @Override
216 public Float get(CellLayout cl) {
217 return cl.getSpringLoadedProgress();
218 }
219
220 @Override
221 public void setValue(CellLayout cl, float progress) {
222 cl.setSpringLoadedProgress(progress);
223 }
224 };
225
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800226 public CellLayout(Context context) {
227 this(context, null);
228 }
229
230 public CellLayout(Context context, AttributeSet attrs) {
231 this(context, attrs, 0);
232 }
233
234 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
235 super(context, attrs, defStyle);
Sunny Goyalc13403c2016-11-18 23:44:48 -0800236 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
237 mContainerType = a.getInteger(R.styleable.CellLayout_containerType, WORKSPACE);
238 a.recycle();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700239
240 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
241 // the user where a dragged item will land when dropped.
242 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800243 setClipToPadding(false);
Sunny Goyalab770a12018-11-14 15:17:26 -0800244 mActivity = ActivityContext.lookupContext(context);
Steven Ngcc505b82021-03-18 23:04:35 +0000245 DeviceProfile deviceProfile = mActivity.getDeviceProfile();
Michael Jurkaa63c4522010-08-19 13:52:27 -0700246
Thales Lima8cd020b2022-03-15 20:15:14 +0000247 resetCellSizeInternal(deviceProfile);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700248
Steven Ngcc505b82021-03-18 23:04:35 +0000249 mCountX = deviceProfile.inv.numColumns;
250 mCountY = deviceProfile.inv.numRows;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700251 mOccupied = new GridOccupancy(mCountX, mCountY);
252 mTmpOccupied = new GridOccupancy(mCountX, mCountY);
253
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800254 mFolderLeaveBehind.mDelegateCellX = -1;
255 mFolderLeaveBehind.mDelegateCellY = -1;
Adam Cohenefca0272016-02-24 19:19:06 -0800256
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800257 setAlwaysDrawnWithCacheEnabled(false);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700258
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800259 Resources res = getResources();
260
261 mBackground = getContext().getDrawable(R.drawable.bg_celllayout);
Sunny Goyal2805e632015-05-20 15:35:32 -0700262 mBackground.setCallback(this);
Sunny Goyalaeb16432017-10-16 11:46:41 -0700263 mBackground.setAlpha(0);
Michael Jurka33945b22010-12-21 18:19:38 -0800264
Yogisha Dixitc0ac1dd2021-05-29 00:26:25 +0100265 mGridColor = Themes.getAttrColor(getContext(), R.attr.workspaceAccentColor);
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800266 mGridVisualizationRoundingRadius =
267 res.getDimensionPixelSize(R.dimen.grid_visualization_rounding_radius);
Steven Ngcc505b82021-03-18 23:04:35 +0000268 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * deviceProfile.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700269
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700270 // Initialize the data structures used for the drag visualization.
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -0700271 mEaseOutInterpolator = Interpolators.DEACCEL_2_5; // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700272 mDragCell[0] = mDragCell[1] = -1;
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800273 mDragCellSpan[0] = mDragCellSpan[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700274 for (int i = 0; i < mDragOutlines.length; i++) {
Sunny Goyal669b71f2023-01-27 14:37:07 -0800275 mDragOutlines[i] = new CellLayoutLayoutParams(0, 0, 0, 0);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700276 }
Mario Bertschler54ba6012017-06-08 10:53:53 -0700277 mDragOutlinePaint.setColor(Themes.getAttrColor(context, R.attr.workspaceTextColor));
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700278
279 // When dragging things around the home screens, we show a green outline of
280 // where the item will land. The outlines gradually fade out, leaving a trail
281 // behind the drag path.
282 // Set up all the animations that are used to implement this fading.
283 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700284 final float fromAlphaValue = 0;
285 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700286
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700287 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700288
289 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700290 final InterruptibleInOutAnimator anim =
Sebastian Francof153d912022-04-22 16:15:27 -0500291 new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700292 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700293 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700294 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700295 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700296 // If an animation is started and then stopped very quickly, we can still
297 // get spurious updates we've cleared the tag. Guard against this.
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800298 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
299 CellLayout.this.invalidate();
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700300 }
301 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700302 // The animation holds a reference to the drag outline bitmap as long is it's
303 // running. This way the bitmap can be GCed when the animations are complete.
Joe Onorato4be866d2010-10-10 11:26:02 -0700304 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700305 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700306
Sunny Goyalc13403c2016-11-18 23:44:48 -0800307 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType);
Jon Miranda228877d2021-02-09 11:05:00 -0500308 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
Thales Lima78d00ad2021-09-30 11:29:06 +0100309 mBorderSpace);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700310 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700311 }
312
Sunny Goyal9b180102020-03-11 10:02:29 -0700313 /**
314 * Sets or clears a delegate used for accessible drag and drop
315 */
316 public void setDragAndDropAccessibilityDelegate(DragAndDropAccessibilityDelegate delegate) {
317 setOnClickListener(delegate);
Sunny Goyal9b180102020-03-11 10:02:29 -0700318 ViewCompat.setAccessibilityDelegate(this, delegate);
319
Adam Cohen6e7c37a2020-06-25 19:22:37 -0700320 mTouchHelper = delegate;
321 int accessibilityFlag = mTouchHelper != null
Sunny Goyal9b180102020-03-11 10:02:29 -0700322 ? IMPORTANT_FOR_ACCESSIBILITY_YES : IMPORTANT_FOR_ACCESSIBILITY_NO;
323 setImportantForAccessibility(accessibilityFlag);
324 getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
Adam Cohenc9735cf2015-01-23 16:11:55 -0800325
Sunny Goyal384b5782021-02-09 22:50:02 -0800326 // ExploreByTouchHelper sets focusability. Clear it when the delegate is cleared.
327 setFocusable(delegate != null);
Adam Cohenc9735cf2015-01-23 16:11:55 -0800328 // Invalidate the accessibility hierarchy
329 if (getParent() != null) {
330 getParent().notifySubtreeAccessibilityStateChanged(
331 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
332 }
333 }
334
Sunny Goyala4647b62021-02-02 13:45:34 -0800335 /**
336 * Returns the currently set accessibility delegate
337 */
338 public DragAndDropAccessibilityDelegate getDragAndDropAccessibilityDelegate() {
339 return mTouchHelper;
340 }
341
Adam Cohenc9735cf2015-01-23 16:11:55 -0800342 @Override
Adam Cohen6e7c37a2020-06-25 19:22:37 -0700343 public boolean dispatchHoverEvent(MotionEvent event) {
344 // Always attempt to dispatch hover events to accessibility first.
345 if (mTouchHelper != null && mTouchHelper.dispatchHoverEvent(event)) {
346 return true;
347 }
348 return super.dispatchHoverEvent(event);
349 }
350
351 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800352 public boolean onInterceptTouchEvent(MotionEvent ev) {
Winson Chungf9935182020-10-23 09:26:44 -0700353 return mTouchHelper != null
354 || (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev));
Adam Cohenc9735cf2015-01-23 16:11:55 -0800355 }
356
Chris Craik01f2d7f2013-10-01 14:41:56 -0700357 public void enableHardwareLayer(boolean hasLayer) {
358 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700359 }
360
vadimt04f356f2019-02-14 18:46:36 -0800361 public boolean isHardwareLayerEnabled() {
362 return mShortcutsAndWidgets.getLayerType() == LAYER_TYPE_HARDWARE;
363 }
364
Thales Lima8cd020b2022-03-15 20:15:14 +0000365 /**
366 * Change sizes of cells
367 *
368 * @param width the new width of the cells
369 * @param height the new height of the cells
370 */
Winson Chung5f8afe62013-08-12 16:19:28 -0700371 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700372 mFixedCellWidth = mCellWidth = width;
373 mFixedCellHeight = mCellHeight = height;
Jon Miranda228877d2021-02-09 11:05:00 -0500374 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
Thales Lima78d00ad2021-09-30 11:29:06 +0100375 mBorderSpace);
Winson Chung5f8afe62013-08-12 16:19:28 -0700376 }
377
Thales Lima8cd020b2022-03-15 20:15:14 +0000378 private void resetCellSizeInternal(DeviceProfile deviceProfile) {
379 switch (mContainerType) {
380 case FOLDER:
Thales Limab35faed2022-09-05 16:30:01 -0300381 mBorderSpace = new Point(deviceProfile.folderCellLayoutBorderSpacePx,
382 deviceProfile.folderCellLayoutBorderSpacePx);
Thales Lima8cd020b2022-03-15 20:15:14 +0000383 break;
384 case HOTSEAT:
385 mBorderSpace = new Point(deviceProfile.hotseatBorderSpace,
386 deviceProfile.hotseatBorderSpace);
387 break;
388 case WORKSPACE:
389 default:
390 mBorderSpace = new Point(deviceProfile.cellLayoutBorderSpacePx);
391 break;
392 }
393
394 mCellWidth = mCellHeight = -1;
395 mFixedCellWidth = mFixedCellHeight = -1;
396 }
397
398 /**
399 * Reset the cell sizes and border space
400 */
401 public void resetCellSize(DeviceProfile deviceProfile) {
402 resetCellSizeInternal(deviceProfile);
403 requestLayout();
404 }
405
Adam Cohen2801caf2011-05-13 20:57:39 -0700406 public void setGridSize(int x, int y) {
407 mCountX = x;
408 mCountY = y;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700409 mOccupied = new GridOccupancy(mCountX, mCountY);
410 mTmpOccupied = new GridOccupancy(mCountX, mCountY);
Jon Miranda228877d2021-02-09 11:05:00 -0500411 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
Thales Lima78d00ad2021-09-30 11:29:06 +0100412 mBorderSpace);
Adam Cohen76fc0852011-06-17 13:26:23 -0700413 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700414 }
415
Adam Cohen2374abf2013-04-16 14:56:57 -0700416 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
417 public void setInvertIfRtl(boolean invert) {
418 mShortcutsAndWidgets.setInvertIfRtl(invert);
419 }
420
Adam Cohen917e3882013-10-31 15:03:35 -0700421 public void setDropPending(boolean pending) {
422 mDropPending = pending;
423 }
424
425 public boolean isDropPending() {
426 return mDropPending;
427 }
428
Adam Cohenc50438c2014-08-19 17:43:05 -0700429 void setIsDragOverlapping(boolean isDragOverlapping) {
430 if (mIsDragOverlapping != isDragOverlapping) {
431 mIsDragOverlapping = isDragOverlapping;
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800432 mBackground.setState(mIsDragOverlapping
433 ? BACKGROUND_STATE_ACTIVE : BACKGROUND_STATE_DEFAULT);
Adam Cohenc50438c2014-08-19 17:43:05 -0700434 invalidate();
435 }
436 }
437
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700438 @Override
439 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700440 ParcelableSparseArray jail = getJailedArray(container);
441 super.dispatchSaveInstanceState(jail);
442 container.put(R.id.cell_layout_jail_id, jail);
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700443 }
444
445 @Override
446 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700447 super.dispatchRestoreInstanceState(getJailedArray(container));
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700448 }
449
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700450 /**
451 * Wrap the SparseArray in another Parcelable so that the item ids do not conflict with our
452 * our internal resource ids
453 */
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700454 private ParcelableSparseArray getJailedArray(SparseArray<Parcelable> container) {
455 final Parcelable parcelable = container.get(R.id.cell_layout_jail_id);
456 return parcelable instanceof ParcelableSparseArray ?
457 (ParcelableSparseArray) parcelable : new ParcelableSparseArray();
458 }
459
Tony Wickham0f97b782015-12-02 17:55:07 -0800460 public boolean getIsDragOverlapping() {
461 return mIsDragOverlapping;
462 }
463
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700464 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700465 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700466 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
467 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
468 // When we're small, we are either drawn normally or in the "accepts drops" state (during
469 // a drag). However, we also drag the mini hover background *over* one of those two
470 // backgrounds
Sunny Goyalaeb16432017-10-16 11:46:41 -0700471 if (mBackground.getAlpha() > 0) {
Sunny Goyal2805e632015-05-20 15:35:32 -0700472 mBackground.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700473 }
Romain Guya6abce82009-11-10 02:54:41 -0800474
Adam Cohen482ed822012-03-02 14:15:13 -0800475 if (DEBUG_VISUALIZE_OCCUPIED) {
Tony Wickham0ac045f2021-11-03 13:17:02 -0700476 Rect cellBounds = new Rect();
477 // Will contain the bounds of the cell including spacing between cells.
478 Rect cellBoundsWithSpacing = new Rect();
Tony Wickham12784902021-11-03 14:02:10 -0700479 int[] targetCell = new int[2];
Tony Wickham0ac045f2021-11-03 13:17:02 -0700480 int[] cellCenter = new int[2];
481 Paint debugPaint = new Paint();
482 debugPaint.setStrokeWidth(Utilities.dpToPx(1));
483 for (int x = 0; x < mCountX; x++) {
484 for (int y = 0; y < mCountY; y++) {
485 if (!mOccupied.cells[x][y]) {
486 continue;
Adam Cohen482ed822012-03-02 14:15:13 -0800487 }
Tony Wickham12784902021-11-03 14:02:10 -0700488 targetCell[0] = x;
489 targetCell[1] = y;
Tony Wickham0ac045f2021-11-03 13:17:02 -0700490
Tony Wickham12784902021-11-03 14:02:10 -0700491 boolean canCreateFolder = canCreateFolder(getChildAt(x, y));
Tony Wickham0ac045f2021-11-03 13:17:02 -0700492 cellToRect(x, y, 1, 1, cellBounds);
493 cellBoundsWithSpacing.set(cellBounds);
494 cellBoundsWithSpacing.inset(-mBorderSpace.x / 2, -mBorderSpace.y / 2);
Tony Wickham3cfa5ed2021-11-03 13:20:43 -0700495 getWorkspaceCellVisualCenter(x, y, cellCenter);
Tony Wickham0ac045f2021-11-03 13:17:02 -0700496
497 canvas.save();
498 canvas.clipRect(cellBoundsWithSpacing);
499
500 // Draw reorder drag target.
501 debugPaint.setColor(Color.RED);
Sebastian Franco6e1024e2022-07-29 13:46:49 -0700502 canvas.drawCircle(cellCenter[0], cellCenter[1],
503 getReorderRadius(targetCell, 1, 1), debugPaint);
Tony Wickham0ac045f2021-11-03 13:17:02 -0700504
505 // Draw folder creation drag target.
506 if (canCreateFolder) {
507 debugPaint.setColor(Color.GREEN);
508 canvas.drawCircle(cellCenter[0], cellCenter[1],
Tony Wickham12784902021-11-03 14:02:10 -0700509 getFolderCreationRadius(targetCell), debugPaint);
Tony Wickham0ac045f2021-11-03 13:17:02 -0700510 }
511
512 canvas.restore();
Adam Cohen482ed822012-03-02 14:15:13 -0800513 }
514 }
515 }
516
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800517 for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
518 DelegatedCellDrawing cellDrawing = mDelegatedCellDrawings.get(i);
519 cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation);
Adam Cohenefca0272016-02-24 19:19:06 -0800520 canvas.save();
521 canvas.translate(mTempLocation[0], mTempLocation[1]);
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800522 cellDrawing.drawUnderItem(canvas);
Adam Cohenefca0272016-02-24 19:19:06 -0800523 canvas.restore();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700524 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700525
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800526 if (mFolderLeaveBehind.mDelegateCellX >= 0 && mFolderLeaveBehind.mDelegateCellY >= 0) {
527 cellToPoint(mFolderLeaveBehind.mDelegateCellX,
528 mFolderLeaveBehind.mDelegateCellY, mTempLocation);
Adam Cohenefca0272016-02-24 19:19:06 -0800529 canvas.save();
530 canvas.translate(mTempLocation[0], mTempLocation[1]);
Sunny Goyal19b93b72017-02-19 20:21:37 -0800531 mFolderLeaveBehind.drawLeaveBehind(canvas);
Adam Cohenefca0272016-02-24 19:19:06 -0800532 canvas.restore();
Adam Cohenc51934b2011-07-26 21:07:43 -0700533 }
Adam Cohen65086992020-02-19 08:40:49 -0800534
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800535 if (mVisualizeCells || mVisualizeDropLocation) {
Adam Cohen65086992020-02-19 08:40:49 -0800536 visualizeGrid(canvas);
537 }
538 }
539
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800540 /**
Tony Wickham12784902021-11-03 14:02:10 -0700541 * Returns whether dropping an icon on the given View can create (or add to) a folder.
542 */
543 private boolean canCreateFolder(View child) {
544 return child instanceof DraggableView
545 && ((DraggableView) child).getViewType() == DRAGGABLE_ICON;
546 }
547
548 /**
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800549 * Indicates the progress of the Workspace entering the SpringLoaded state; allows the
550 * CellLayout to update various visuals for this state.
551 *
552 * @param progress
553 */
554 public void setSpringLoadedProgress(float progress) {
555 if (Float.compare(progress, mSpringLoadedProgress) != 0) {
556 mSpringLoadedProgress = progress;
Federico Baron1028a722022-10-13 14:09:38 -0700557 if (!SHOW_HOME_GARDENING.get()) {
558 updateBgAlpha();
559 }
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800560 setGridAlpha(progress);
561 }
562 }
563
564 /**
565 * See setSpringLoadedProgress
566 * @return progress
567 */
568 public float getSpringLoadedProgress() {
569 return mSpringLoadedProgress;
570 }
571
Sebastian Franco09589322022-11-02 15:25:58 -0700572 protected void updateBgAlpha() {
Adam Cohen0c4d2782021-04-29 15:56:13 -0700573 mBackground.setAlpha((int) (mSpringLoadedProgress * 255));
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800574 }
575
576 /**
577 * Set the progress of this page's scroll
578 *
579 * @param progress 0 if the screen is centered, +/-1 if it is to the right / left respectively
580 */
581 public void setScrollProgress(float progress) {
582 if (Float.compare(Math.abs(progress), mScrollProgress) != 0) {
583 mScrollProgress = Math.abs(progress);
Federico Baron1028a722022-10-13 14:09:38 -0700584 if (!SHOW_HOME_GARDENING.get()) {
585 updateBgAlpha();
586 }
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800587 }
588 }
589
590 private void setGridAlpha(float gridAlpha) {
591 if (Float.compare(gridAlpha, mGridAlpha) != 0) {
592 mGridAlpha = gridAlpha;
593 invalidate();
594 }
595 }
596
Adam Cohen65086992020-02-19 08:40:49 -0800597 protected void visualizeGrid(Canvas canvas) {
Adam Cohen0c4d2782021-04-29 15:56:13 -0700598 DeviceProfile dp = mActivity.getDeviceProfile();
Alex Chau51da2192022-05-20 13:32:10 +0100599 int paddingX = Math.min((mCellWidth - dp.iconSizePx) / 2, dp.gridVisualizationPaddingX);
600 int paddingY = Math.min((mCellHeight - dp.iconSizePx) / 2, dp.gridVisualizationPaddingY);
Adam Cohen0c4d2782021-04-29 15:56:13 -0700601 mVisualizeGridRect.set(paddingX, paddingY,
602 mCellWidth - paddingX,
603 mCellHeight - paddingY);
Adam Cohen65086992020-02-19 08:40:49 -0800604
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800605 mVisualizeGridPaint.setStrokeWidth(8);
606 int paintAlpha = (int) (120 * mGridAlpha);
607 mVisualizeGridPaint.setColor(ColorUtils.setAlphaComponent(mGridColor, paintAlpha));
Adam Cohen65086992020-02-19 08:40:49 -0800608
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800609 if (mVisualizeCells) {
610 for (int i = 0; i < mCountX; i++) {
611 for (int j = 0; j < mCountY; j++) {
Thales Lima78d00ad2021-09-30 11:29:06 +0100612 int transX = i * mCellWidth + (i * mBorderSpace.x) + getPaddingLeft()
Adam Cohen0c4d2782021-04-29 15:56:13 -0700613 + paddingX;
Thales Lima78d00ad2021-09-30 11:29:06 +0100614 int transY = j * mCellHeight + (j * mBorderSpace.y) + getPaddingTop()
Adam Cohen0c4d2782021-04-29 15:56:13 -0700615 + paddingY;
Adam Cohen65086992020-02-19 08:40:49 -0800616
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800617 mVisualizeGridRect.offsetTo(transX, transY);
618 mVisualizeGridPaint.setStyle(Paint.Style.FILL);
619 canvas.drawRoundRect(mVisualizeGridRect, mGridVisualizationRoundingRadius,
620 mGridVisualizationRoundingRadius, mVisualizeGridPaint);
621 }
622 }
623 }
Adam Cohen65086992020-02-19 08:40:49 -0800624
Federico Baron1028a722022-10-13 14:09:38 -0700625 if (mVisualizeDropLocation && !SHOW_HOME_GARDENING.get()) {
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800626 for (int i = 0; i < mDragOutlines.length; i++) {
627 final float alpha = mDragOutlineAlphas[i];
628 if (alpha <= 0) continue;
Adam Cohen65086992020-02-19 08:40:49 -0800629
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800630 mVisualizeGridPaint.setAlpha(255);
Sebastian Franco877088e2023-01-03 15:16:22 -0700631 int x = mDragOutlines[i].getCellX();
632 int y = mDragOutlines[i].getCellY();
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800633 int spanX = mDragOutlines[i].cellHSpan;
634 int spanY = mDragOutlines[i].cellVSpan;
635
Adam Cohened82e0d2021-07-21 17:24:12 -0700636 // TODO b/194414754 clean this up, reconcile with cellToRect
Adam Cohen0c4d2782021-04-29 15:56:13 -0700637 mVisualizeGridRect.set(paddingX, paddingY,
Thales Lima78d00ad2021-09-30 11:29:06 +0100638 mCellWidth * spanX + mBorderSpace.x * (spanX - 1) - paddingX,
639 mCellHeight * spanY + mBorderSpace.y * (spanY - 1) - paddingY);
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800640
Thales Lima78d00ad2021-09-30 11:29:06 +0100641 int transX = x * mCellWidth + (x * mBorderSpace.x)
Adam Cohen0c4d2782021-04-29 15:56:13 -0700642 + getPaddingLeft() + paddingX;
Thales Lima78d00ad2021-09-30 11:29:06 +0100643 int transY = y * mCellHeight + (y * mBorderSpace.y)
Adam Cohen0c4d2782021-04-29 15:56:13 -0700644 + getPaddingTop() + paddingY;
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800645
646 mVisualizeGridRect.offsetTo(transX, transY);
Adam Cohen65086992020-02-19 08:40:49 -0800647
648 mVisualizeGridPaint.setStyle(Paint.Style.STROKE);
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800649 mVisualizeGridPaint.setColor(Color.argb((int) (alpha),
650 Color.red(mGridColor), Color.green(mGridColor), Color.blue(mGridColor)));
Adam Cohen65086992020-02-19 08:40:49 -0800651
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800652 canvas.drawRoundRect(mVisualizeGridRect, mGridVisualizationRoundingRadius,
653 mGridVisualizationRoundingRadius, mVisualizeGridPaint);
Adam Cohen65086992020-02-19 08:40:49 -0800654 }
655 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700656 }
657
Adam Cohenefca0272016-02-24 19:19:06 -0800658 @Override
659 protected void dispatchDraw(Canvas canvas) {
660 super.dispatchDraw(canvas);
661
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800662 for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
663 DelegatedCellDrawing bg = mDelegatedCellDrawings.get(i);
664 cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation);
665 canvas.save();
666 canvas.translate(mTempLocation[0], mTempLocation[1]);
667 bg.drawOverItem(canvas);
668 canvas.restore();
Adam Cohenefca0272016-02-24 19:19:06 -0800669 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700670 }
671
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800672 /**
673 * Add Delegated cell drawing
674 */
675 public void addDelegatedCellDrawing(DelegatedCellDrawing bg) {
676 mDelegatedCellDrawings.add(bg);
Adam Cohenefca0272016-02-24 19:19:06 -0800677 }
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800678
679 /**
680 * Remove item from DelegatedCellDrawings
681 */
682 public void removeDelegatedCellDrawing(DelegatedCellDrawing bg) {
683 mDelegatedCellDrawings.remove(bg);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700684 }
685
Adam Cohenc51934b2011-07-26 21:07:43 -0700686 public void setFolderLeaveBehindCell(int x, int y) {
Adam Cohenefca0272016-02-24 19:19:06 -0800687 View child = getChildAt(x, y);
Sunny Goyalab770a12018-11-14 15:17:26 -0800688 mFolderLeaveBehind.setup(getContext(), mActivity, null,
Adam Cohenefca0272016-02-24 19:19:06 -0800689 child.getMeasuredWidth(), child.getPaddingTop());
690
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800691 mFolderLeaveBehind.mDelegateCellX = x;
692 mFolderLeaveBehind.mDelegateCellY = y;
Adam Cohenc51934b2011-07-26 21:07:43 -0700693 invalidate();
694 }
695
696 public void clearFolderLeaveBehind() {
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800697 mFolderLeaveBehind.mDelegateCellX = -1;
698 mFolderLeaveBehind.mDelegateCellY = -1;
Adam Cohenc51934b2011-07-26 21:07:43 -0700699 invalidate();
700 }
701
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700702 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700703 public boolean shouldDelayChildPressedState() {
704 return false;
705 }
706
Adam Cohen1462de32012-07-24 22:34:36 -0700707 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700708 try {
709 dispatchRestoreInstanceState(states);
710 } catch (IllegalArgumentException ex) {
Zak Cohen3eeb41d2020-02-14 14:15:13 -0800711 if (FeatureFlags.IS_STUDIO_BUILD) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700712 throw ex;
713 }
714 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
715 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
716 }
Adam Cohen1462de32012-07-24 22:34:36 -0700717 }
718
Michael Jurkae6235dd2011-10-04 15:02:05 -0700719 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700720 public void cancelLongPress() {
721 super.cancelLongPress();
722
723 // Cancel long press for all children
724 final int count = getChildCount();
725 for (int i = 0; i < count; i++) {
726 final View child = getChildAt(i);
727 child.cancelLongPress();
728 }
729 }
730
Michael Jurkadee05892010-07-27 10:01:56 -0700731 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
732 mInterceptTouchListener = listener;
733 }
734
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800735 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700736 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800737 }
738
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800739 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700740 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800741 }
742
Sunny Goyalc13403c2016-11-18 23:44:48 -0800743 public boolean acceptsWidget() {
744 return mContainerType == WORKSPACE;
Sunny Goyale9b651e2015-04-24 11:44:51 -0700745 }
746
Sebastian Francod4682992022-10-05 13:03:09 -0500747 /**
748 * Adds the given view to the CellLayout
749 *
750 * @param child view to add.
751 * @param index index of the CellLayout children where to add the view.
752 * @param childId id of the view.
753 * @param params represent the logic of the view on the CellLayout.
754 * @param markCells if the occupied cells should be marked or not
755 * @return if adding the view was successful
756 */
757 public boolean addViewToCellLayout(View child, int index, int childId,
758 CellLayoutLayoutParams params, boolean markCells) {
759 final CellLayoutLayoutParams lp = params;
Winson Chungaafa03c2010-06-11 17:34:16 -0700760
Andrew Flynnde38e422012-05-08 11:22:15 -0700761 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800762 if (child instanceof BubbleTextView) {
763 BubbleTextView bubbleChild = (BubbleTextView) child;
Jon Mirandaf1eae802017-10-04 11:23:33 -0700764 bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800765 }
766
Sunny Goyalc13403c2016-11-18 23:44:48 -0800767 child.setScaleX(mChildScale);
768 child.setScaleY(mChildScale);
Adam Cohen307fe232012-08-16 17:55:58 -0700769
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800770 // Generate an id for each view, this assumes we have at most 256x256 cells
771 // per workspace screen
Sebastian Franco877088e2023-01-03 15:16:22 -0700772 if (lp.getCellX() >= 0 && lp.getCellX() <= mCountX - 1
773 && lp.getCellY() >= 0 && lp.getCellY() <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700774 // If the horizontal or vertical span is set to -1, it is taken to
775 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700776 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
777 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800778
Winson Chungaafa03c2010-06-11 17:34:16 -0700779 child.setId(childId);
Tony Wickhama0628cc2015-10-14 15:23:04 -0700780 if (LOGD) {
781 Log.d(TAG, "Adding view to ShortcutsAndWidgetsContainer: " + child);
782 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700783 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700784
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700785 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700786
Winson Chungaafa03c2010-06-11 17:34:16 -0700787 return true;
788 }
789 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800790 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700791
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800792 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700793 public void removeAllViews() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700794 mOccupied.clear();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700795 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700796 }
797
798 @Override
799 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700800 if (mShortcutsAndWidgets.getChildCount() > 0) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700801 mOccupied.clear();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700802 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700803 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700804 }
805
806 @Override
807 public void removeView(View view) {
808 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700809 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700810 }
811
812 @Override
813 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700814 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
815 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700816 }
817
818 @Override
819 public void removeViewInLayout(View view) {
820 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700821 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700822 }
823
824 @Override
825 public void removeViews(int start, int count) {
826 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700827 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700828 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700829 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700830 }
831
832 @Override
833 public void removeViewsInLayout(int start, int count) {
834 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700835 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700836 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700837 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800838 }
839
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700840 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700841 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800842 * @param x X coordinate of the point
843 * @param y Y coordinate of the point
844 * @param result Array of 2 ints to hold the x and y coordinate of the cell
845 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700846 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700847 final int hStartPadding = getPaddingLeft();
848 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800849
Sebastian Francob57c0b22022-06-28 13:54:35 -0700850 result[0] = (x - hStartPadding) / (mCellWidth + mBorderSpace.x);
851 result[1] = (y - vStartPadding) / (mCellHeight + mBorderSpace.y);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800852
Adam Cohend22015c2010-07-26 22:02:18 -0700853 final int xAxis = mCountX;
854 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800855
856 if (result[0] < 0) result[0] = 0;
857 if (result[0] >= xAxis) result[0] = xAxis - 1;
858 if (result[1] < 0) result[1] = 0;
859 if (result[1] >= yAxis) result[1] = yAxis - 1;
860 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700861
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800862 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800863 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700864 *
865 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800866 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700867 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800868 * @param result Array of 2 ints to hold the x and y coordinate of the point
869 */
870 void cellToPoint(int cellX, int cellY, int[] result) {
Jon Miranda228877d2021-02-09 11:05:00 -0500871 cellToRect(cellX, cellY, 1, 1, mTempRect);
872 result[0] = mTempRect.left;
873 result[1] = mTempRect.top;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800874 }
875
Adam Cohene3e27a82011-04-15 12:07:39 -0700876 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800877 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700878 *
879 * @param cellX X coordinate of the cell
880 * @param cellY Y coordinate of the cell
881 *
882 * @param result Array of 2 ints to hold the x and y coordinate of the point
883 */
884 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700885 regionToCenterPoint(cellX, cellY, 1, 1, result);
886 }
887
888 /**
Tony Wickham0ac045f2021-11-03 13:17:02 -0700889 * Given a cell coordinate and span return the point that represents the center of the region
Adam Cohen47a876d2012-03-19 13:21:41 -0700890 *
891 * @param cellX X coordinate of the cell
892 * @param cellY Y coordinate of the cell
893 *
894 * @param result Array of 2 ints to hold the x and y coordinate of the point
895 */
896 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Jon Miranda228877d2021-02-09 11:05:00 -0500897 cellToRect(cellX, cellY, spanX, spanY, mTempRect);
898 result[0] = mTempRect.centerX();
899 result[1] = mTempRect.centerY();
Adam Cohen19f37922012-03-21 11:59:11 -0700900 }
901
Tony Wickham3cfa5ed2021-11-03 13:20:43 -0700902 /**
903 * Returns the distance between the given coordinate and the visual center of the given cell.
904 */
905 public float getDistanceFromWorkspaceCellVisualCenter(float x, float y, int[] cell) {
906 getWorkspaceCellVisualCenter(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700907 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800908 }
909
Tony Wickham3cfa5ed2021-11-03 13:20:43 -0700910 private void getWorkspaceCellVisualCenter(int cellX, int cellY, int[] outPoint) {
911 View child = getChildAt(cellX, cellY);
912 if (child instanceof DraggableView) {
913 DraggableView draggableChild = (DraggableView) child;
914 if (draggableChild.getViewType() == DRAGGABLE_ICON) {
915 cellToPoint(cellX, cellY, outPoint);
916 draggableChild.getWorkspaceVisualDragBounds(mTempRect);
917 mTempRect.offset(outPoint[0], outPoint[1]);
918 outPoint[0] = mTempRect.centerX();
919 outPoint[1] = mTempRect.centerY();
920 return;
921 }
922 }
923 cellToCenterPoint(cellX, cellY, outPoint);
924 }
925
Tony Wickham0ac045f2021-11-03 13:17:02 -0700926 /**
927 * Returns the max distance from the center of a cell that can accept a drop to create a folder.
928 */
Tony Wickham12784902021-11-03 14:02:10 -0700929 public float getFolderCreationRadius(int[] targetCell) {
Tony Wickham0ac045f2021-11-03 13:17:02 -0700930 DeviceProfile grid = mActivity.getDeviceProfile();
Tony Wickham12784902021-11-03 14:02:10 -0700931 float iconVisibleRadius = ICON_VISIBLE_AREA_FACTOR * grid.iconSizePx / 2;
932 // Halfway between reorder radius and icon.
Sebastian Franco6e1024e2022-07-29 13:46:49 -0700933 return (getReorderRadius(targetCell, 1, 1) + iconVisibleRadius) / 2;
Tony Wickham12784902021-11-03 14:02:10 -0700934 }
935
936 /**
937 * Returns the max distance from the center of a cell that will start to reorder on drag over.
938 */
Sebastian Franco6e1024e2022-07-29 13:46:49 -0700939 public float getReorderRadius(int[] targetCell, int spanX, int spanY) {
Tony Wickham12784902021-11-03 14:02:10 -0700940 int[] centerPoint = mTmpPoint;
941 getWorkspaceCellVisualCenter(targetCell[0], targetCell[1], centerPoint);
942
943 Rect cellBoundsWithSpacing = mTempRect;
Sebastian Franco6e1024e2022-07-29 13:46:49 -0700944 cellToRect(targetCell[0], targetCell[1], spanX, spanY, cellBoundsWithSpacing);
Tony Wickham12784902021-11-03 14:02:10 -0700945 cellBoundsWithSpacing.inset(-mBorderSpace.x / 2, -mBorderSpace.y / 2);
946
Sebastian Francoc8392ea2022-10-28 16:38:37 -0700947 if (canCreateFolder(getChildAt(targetCell[0], targetCell[1])) && spanX == 1 && spanY == 1) {
Tony Wickham12784902021-11-03 14:02:10 -0700948 // Take only the circle in the smaller dimension, to ensure we don't start reordering
949 // too soon before accepting a folder drop.
950 int minRadius = centerPoint[0] - cellBoundsWithSpacing.left;
951 minRadius = Math.min(minRadius, centerPoint[1] - cellBoundsWithSpacing.top);
952 minRadius = Math.min(minRadius, cellBoundsWithSpacing.right - centerPoint[0]);
953 minRadius = Math.min(minRadius, cellBoundsWithSpacing.bottom - centerPoint[1]);
954 return minRadius;
955 }
956 // Take up the entire cell, including space between this cell and the adjacent ones.
Sebastian Francoc8392ea2022-10-28 16:38:37 -0700957 // Multiply by span to scale radius
958 return (float) Math.hypot(spanX * cellBoundsWithSpacing.width() / 2f,
959 spanY * cellBoundsWithSpacing.height() / 2f);
Tony Wickham0ac045f2021-11-03 13:17:02 -0700960 }
961
Adam Cohenf9c184a2016-01-15 16:47:43 -0800962 public int getCellWidth() {
Romain Guy84f296c2009-11-04 15:00:44 -0800963 return mCellWidth;
964 }
965
Sunny Goyal0b754e52017-08-07 07:42:45 -0700966 public int getCellHeight() {
Romain Guy84f296c2009-11-04 15:00:44 -0800967 return mCellHeight;
968 }
969
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700970 public void setFixedSize(int width, int height) {
971 mFixedWidth = width;
972 mFixedHeight = height;
973 }
974
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800975 @Override
976 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800977 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800978 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700979 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
980 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700981 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
982 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Sunny Goyalae6e3182019-04-30 12:04:37 -0700983
Winson Chung11a1a532013-09-13 11:14:45 -0700984 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Thales Lima78d00ad2021-09-30 11:29:06 +0100985 int cw = DeviceProfile.calculateCellWidth(childWidthSize, mBorderSpace.x,
Jon Miranda228877d2021-02-09 11:05:00 -0500986 mCountX);
Thales Lima78d00ad2021-09-30 11:29:06 +0100987 int ch = DeviceProfile.calculateCellHeight(childHeightSize, mBorderSpace.y,
Jon Miranda228877d2021-02-09 11:05:00 -0500988 mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700989 if (cw != mCellWidth || ch != mCellHeight) {
990 mCellWidth = cw;
991 mCellHeight = ch;
Jon Miranda228877d2021-02-09 11:05:00 -0500992 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
Thales Lima78d00ad2021-09-30 11:29:06 +0100993 mBorderSpace);
Winson Chung11a1a532013-09-13 11:14:45 -0700994 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700995 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700996
Winson Chung2d75f122013-09-23 16:53:31 -0700997 int newWidth = childWidthSize;
998 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700999 if (mFixedWidth > 0 && mFixedHeight > 0) {
1000 newWidth = mFixedWidth;
1001 newHeight = mFixedHeight;
1002 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001003 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
1004 }
1005
Sunny Goyal4fe5a372015-05-14 19:55:10 -07001006 mShortcutsAndWidgets.measure(
1007 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
1008 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
1009
1010 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
1011 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -07001012 if (mFixedWidth > 0 && mFixedHeight > 0) {
1013 setMeasuredDimension(maxWidth, maxHeight);
1014 } else {
1015 setMeasuredDimension(widthSize, heightSize);
1016 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001017 }
1018
1019 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -07001020 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Tony Wickham26b01422015-11-10 14:44:32 -08001021 int left = getPaddingLeft();
Sunny Goyal7ce471b2017-08-02 03:37:39 -07001022 left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Sunny Goyal7c786f72016-06-01 14:08:21 -07001023 int right = r - l - getPaddingRight();
Sunny Goyal7ce471b2017-08-02 03:37:39 -07001024 right -= (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Sunny Goyal7c786f72016-06-01 14:08:21 -07001025
Winson Chung38848ca2013-10-08 12:03:44 -07001026 int top = getPaddingTop();
Sunny Goyal7c786f72016-06-01 14:08:21 -07001027 int bottom = b - t - getPaddingBottom();
Sunny Goyal4fe5a372015-05-14 19:55:10 -07001028
Sunny Goyal7c786f72016-06-01 14:08:21 -07001029 // Expand the background drawing bounds by the padding baked into the background drawable
1030 mBackground.getPadding(mTempRect);
1031 mBackground.setBounds(
Jon Miranda28032002017-07-13 16:18:56 -07001032 left - mTempRect.left - getPaddingLeft(),
1033 top - mTempRect.top - getPaddingTop(),
1034 right + mTempRect.right + getPaddingRight(),
1035 bottom + mTempRect.bottom + getPaddingBottom());
Sunny Goyalae6e3182019-04-30 12:04:37 -07001036
Sunny Goyalc4d32012020-04-03 17:10:11 -07001037 mShortcutsAndWidgets.layout(left, top, right, bottom);
Sunny Goyal7c786f72016-06-01 14:08:21 -07001038 }
1039
Tony Wickhama501d492015-11-03 18:05:01 -08001040 /**
1041 * Returns the amount of space left over after subtracting padding and cells. This space will be
1042 * 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 -05001043 * width in {@link DeviceProfile#calculateCellWidth(int, int, int)}.
Tony Wickhama501d492015-11-03 18:05:01 -08001044 */
1045 public int getUnusedHorizontalSpace() {
Jon Miranda228877d2021-02-09 11:05:00 -05001046 return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth)
Thales Lima78d00ad2021-09-30 11:29:06 +01001047 - ((mCountX - 1) * mBorderSpace.x);
Tony Wickhama501d492015-11-03 18:05:01 -08001048 }
1049
Sunny Goyal2805e632015-05-20 15:35:32 -07001050 @Override
1051 protected boolean verifyDrawable(Drawable who) {
Sunny Goyal7ce471b2017-08-02 03:37:39 -07001052 return super.verifyDrawable(who) || (who == mBackground);
Sunny Goyal2805e632015-05-20 15:35:32 -07001053 }
1054
Michael Jurkaa52570f2012-03-20 03:18:20 -07001055 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -07001056 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -07001057 }
1058
Jon Miranda228877d2021-02-09 11:05:00 -05001059 public View getChildAt(int cellX, int cellY) {
1060 return mShortcutsAndWidgets.getChildAt(cellX, cellY);
Patrick Dubroy440c3602010-07-13 17:50:32 -07001061 }
1062
Adam Cohen76fc0852011-06-17 13:26:23 -07001063 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -08001064 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001065 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -08001066
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001067 if (clc.indexOfChild(child) != -1 && (child instanceof Reorderable)) {
Sebastian Francod4682992022-10-05 13:03:09 -05001068 final CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001069 final ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001070 final Reorderable item = (Reorderable) child;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001071
1072 // We cancel any existing animations
1073 if (mReorderAnimators.containsKey(lp)) {
1074 mReorderAnimators.get(lp).cancel();
1075 mReorderAnimators.remove(lp);
1076 }
1077
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001078
Adam Cohen482ed822012-03-02 14:15:13 -08001079 if (adjustOccupied) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001080 GridOccupancy occupied = permanent ? mOccupied : mTmpOccupied;
Sebastian Franco877088e2023-01-03 15:16:22 -07001081 occupied.markCells(lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan, false);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001082 occupied.markCells(cellX, cellY, lp.cellHSpan, lp.cellVSpan, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001083 }
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001084
1085 // Compute the new x and y position based on the new cellX and cellY
1086 // We leverage the actual layout logic in the layout params and hence need to modify
1087 // state and revert that state.
1088 final int oldX = lp.x;
1089 final int oldY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001090 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001091 if (permanent) {
Sunny Goyal669b71f2023-01-27 14:37:07 -08001092 lp.setCellX(cellX);
1093 lp.setCellY(cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001094 } else {
Sebastian Franco877088e2023-01-03 15:16:22 -07001095 lp.setTmpCellX(cellX);
1096 lp.setTmpCellY(cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001097 }
Jon Mirandae96798e2016-12-07 12:10:44 -08001098 clc.setupLp(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001099 final int newX = lp.x;
1100 final int newY = lp.y;
Adam Cohen76fc0852011-06-17 13:26:23 -07001101 lp.x = oldX;
1102 lp.y = oldY;
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001103 lp.isLockedToGrid = false;
1104 // End compute new x and y
1105
Sunny Goyal82dfc152023-02-24 16:50:09 -08001106 MultiTranslateDelegate mtd = item.getTranslateDelegate();
1107 float initPreviewOffsetX = mtd.getTranslationX(INDEX_REORDER_PREVIEW_OFFSET).getValue();
1108 float initPreviewOffsetY = mtd.getTranslationY(INDEX_REORDER_PREVIEW_OFFSET).getValue();
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001109 final float finalPreviewOffsetX = newX - oldX;
1110 final float finalPreviewOffsetY = newY - oldY;
1111
Adam Cohen482ed822012-03-02 14:15:13 -08001112 // Exit early if we're not actually moving the view
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001113 if (finalPreviewOffsetX == 0 && finalPreviewOffsetY == 0
1114 && initPreviewOffsetX == 0 && initPreviewOffsetY == 0) {
Adam Cohen482ed822012-03-02 14:15:13 -08001115 lp.isLockedToGrid = true;
1116 return true;
1117 }
1118
Sunny Goyal849c6a22018-08-08 16:33:46 -07001119 ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001120 va.setDuration(duration);
1121 mReorderAnimators.put(lp, va);
1122
1123 va.addUpdateListener(new AnimatorUpdateListener() {
1124 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001125 public void onAnimationUpdate(ValueAnimator animation) {
Jon Mirandae96798e2016-12-07 12:10:44 -08001126 float r = (Float) animation.getAnimatedValue();
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001127 float x = (1 - r) * initPreviewOffsetX + r * finalPreviewOffsetX;
1128 float y = (1 - r) * initPreviewOffsetY + r * finalPreviewOffsetY;
Sunny Goyal82dfc152023-02-24 16:50:09 -08001129 item.getTranslateDelegate().setTranslation(INDEX_REORDER_PREVIEW_OFFSET, x, y);
Adam Cohenbfbfd262011-06-13 16:55:12 -07001130 }
1131 });
Adam Cohen482ed822012-03-02 14:15:13 -08001132 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001133 boolean cancelled = false;
1134 public void onAnimationEnd(Animator animation) {
1135 // If the animation was cancelled, it means that another animation
1136 // has interrupted this one, and we don't want to lock the item into
1137 // place just yet.
1138 if (!cancelled) {
1139 lp.isLockedToGrid = true;
Sunny Goyal82dfc152023-02-24 16:50:09 -08001140 item.getTranslateDelegate()
1141 .setTranslation(INDEX_REORDER_PREVIEW_OFFSET, 0, 0);
Adam Cohen482ed822012-03-02 14:15:13 -08001142 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001143 }
1144 if (mReorderAnimators.containsKey(lp)) {
1145 mReorderAnimators.remove(lp);
1146 }
1147 }
1148 public void onAnimationCancel(Animator animation) {
1149 cancelled = true;
1150 }
1151 });
Adam Cohen482ed822012-03-02 14:15:13 -08001152 va.setStartDelay(delay);
1153 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001154 return true;
1155 }
1156 return false;
1157 }
1158
Adam Cohenf7ca3b42021-02-22 11:03:58 -08001159 void visualizeDropLocation(int cellX, int cellY, int spanX, int spanY,
1160 DropTarget.DragObject dragObject) {
1161 if (mDragCell[0] != cellX || mDragCell[1] != cellY || mDragCellSpan[0] != spanX
1162 || mDragCellSpan[1] != spanY) {
Adam Cohen482ed822012-03-02 14:15:13 -08001163 mDragCell[0] = cellX;
1164 mDragCell[1] = cellY;
Adam Cohenf7ca3b42021-02-22 11:03:58 -08001165 mDragCellSpan[0] = spanX;
1166 mDragCellSpan[1] = spanY;
Steven Ng30dd1d62021-03-15 21:45:49 +00001167
Steven Nga999e222021-04-19 18:17:15 +01001168 // Apply color extraction on a widget when dragging.
1169 applyColorExtractionOnWidget(dragObject, mDragCell, spanX, spanY);
1170
Joe Onorato4be866d2010-10-10 11:26:02 -07001171 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001172 mDragOutlineAnims[oldIndex].animateOut();
1173 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Sunny Goyal106bf642015-07-16 12:18:06 -07001174
Sebastian Francod4682992022-10-05 13:03:09 -05001175 CellLayoutLayoutParams cell = mDragOutlines[mDragOutlineCurrent];
Sebastian Franco877088e2023-01-03 15:16:22 -07001176 cell.setCellX(cellX);
1177 cell.setCellY(cellY);
Adam Cohenf7ca3b42021-02-22 11:03:58 -08001178 cell.cellHSpan = spanX;
1179 cell.cellVSpan = spanY;
Adam Cohen65086992020-02-19 08:40:49 -08001180
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001181 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Adam Cohenf7ca3b42021-02-22 11:03:58 -08001182 invalidate();
Sunny Goyale78e3d72015-09-24 11:23:31 -07001183
1184 if (dragObject.stateAnnouncer != null) {
Sunny Goyalc13403c2016-11-18 23:44:48 -08001185 dragObject.stateAnnouncer.announce(getItemMoveDescription(cellX, cellY));
Sunny Goyale78e3d72015-09-24 11:23:31 -07001186 }
Adam Cohenf7ca3b42021-02-22 11:03:58 -08001187
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001188 }
1189 }
1190
Steven Ng30dd1d62021-03-15 21:45:49 +00001191 /** Applies the local color extraction to a dragging widget object. */
Steven Nga999e222021-04-19 18:17:15 +01001192 private void applyColorExtractionOnWidget(DropTarget.DragObject dragObject, int[] targetCell,
1193 int spanX, int spanY) {
Steven Ng30dd1d62021-03-15 21:45:49 +00001194 // Apply local extracted color if the DragView is an AppWidgetHostViewDrawable.
Steven Ng32427202021-04-19 18:12:12 +01001195 View view = dragObject.dragView.getContentView();
1196 if (view instanceof LauncherAppWidgetHostView) {
Andras Kloczl2dacbee2022-02-21 16:53:28 +00001197 int screenId = getWorkspace().getIdForScreen(this);
Steven Ng30dd1d62021-03-15 21:45:49 +00001198 cellToRect(targetCell[0], targetCell[1], spanX, spanY, mTempRect);
Jonathan Miranda21930da2021-05-03 18:44:13 +00001199
Sunny Goyal69a8eec2021-07-22 10:02:50 -07001200 ((LauncherAppWidgetHostView) view).handleDrag(mTempRect, this, screenId);
Steven Ng30dd1d62021-03-15 21:45:49 +00001201 }
1202 }
1203
Sunny Goyal726bee72018-03-05 12:54:24 -08001204 @SuppressLint("StringFormatMatches")
Sunny Goyalc13403c2016-11-18 23:44:48 -08001205 public String getItemMoveDescription(int cellX, int cellY) {
1206 if (mContainerType == HOTSEAT) {
1207 return getContext().getString(R.string.move_to_hotseat_position,
1208 Math.max(cellX, cellY) + 1);
1209 } else {
Shikha Malhotraf78da1b2022-04-11 10:23:18 +00001210 Workspace<?> workspace = getWorkspace();
Andras Kloczl2dacbee2022-02-21 16:53:28 +00001211 int row = cellY + 1;
1212 int col = workspace.mIsRtl ? mCountX - cellX : cellX + 1;
1213 int panelCount = workspace.getPanelCount();
Sebastian Franco930531f2022-06-16 16:49:11 -07001214 int screenId = workspace.getIdForScreen(this);
1215 int pageIndex = workspace.getPageIndexForScreenId(screenId);
Andras Kloczl2dacbee2022-02-21 16:53:28 +00001216 if (panelCount > 1) {
1217 // Increment the column if the target is on the right side of a two panel home
Andras Kloczl2dacbee2022-02-21 16:53:28 +00001218 col += (pageIndex % panelCount) * mCountX;
1219 }
Sebastian Franco930531f2022-06-16 16:49:11 -07001220 return getContext().getString(R.string.move_to_empty_cell_description, row, col,
1221 workspace.getPageDescription(pageIndex));
Sunny Goyalc13403c2016-11-18 23:44:48 -08001222 }
1223 }
1224
Shikha Malhotraf78da1b2022-04-11 10:23:18 +00001225 private Workspace<?> getWorkspace() {
Andras Kloczl2dacbee2022-02-21 16:53:28 +00001226 return Launcher.cast(mActivity).getWorkspace();
1227 }
1228
Adam Cohene0310962011-04-18 16:15:31 -07001229 public void clearDragOutlines() {
1230 final int oldIndex = mDragOutlineCurrent;
1231 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001232 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001233 }
1234
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001235 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001236 * Find a vacant area that will fit the given bounds nearest the requested
1237 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001238 *
Romain Guy51afc022009-05-04 18:03:43 -07001239 * @param pixelX The X location at which you want to search for a vacant area.
1240 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001241 * @param minSpanX The minimum horizontal span required
1242 * @param minSpanY The minimum vertical span required
1243 * @param spanX Horizontal span of the object.
1244 * @param spanY Vertical span of the object.
1245 * @param result Array in which to place the result, or null (in which case a new array will
1246 * be allocated)
1247 * @return The X, Y cell of a vacant area that can contain this object,
1248 * nearest the requested location.
1249 */
Sebastian Francoe4c03452022-12-27 14:50:02 -06001250 public int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
1251 int spanX, int spanY, int[] result, int[] resultSpan) {
Sebastian Francob57c0b22022-06-28 13:54:35 -07001252 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, false,
Adam Cohend41fbf52012-02-16 23:53:59 -08001253 result, resultSpan);
1254 }
1255
Adam Cohend41fbf52012-02-16 23:53:59 -08001256 /**
1257 * Find a vacant area that will fit the given bounds nearest the requested
1258 * cell location. Uses Euclidean distance to score multiple vacant areas.
Sebastian Francob57c0b22022-06-28 13:54:35 -07001259 * @param relativeXPos The X location relative to the Cell layout at which you want to search
1260 * for a vacant area.
1261 * @param relativeYPos The Y location relative to the Cell layout at which you want to search
1262 * for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001263 * @param minSpanX The minimum horizontal span required
1264 * @param minSpanY The minimum vertical span required
1265 * @param spanX Horizontal span of the object.
1266 * @param spanY Vertical span of the object.
1267 * @param ignoreOccupied If true, the result can be an occupied cell
1268 * @param result Array in which to place the result, or null (in which case a new array will
1269 * be allocated)
1270 * @return The X, Y cell of a vacant area that can contain this object,
1271 * nearest the requested location.
1272 */
Sebastian Francob57c0b22022-06-28 13:54:35 -07001273 private int[] findNearestArea(int relativeXPos, int relativeYPos, int minSpanX, int minSpanY,
1274 int spanX, int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Sebastian Francob57c0b22022-06-28 13:54:35 -07001275 // For items with a spanX / spanY > 1, the passed in point (relativeXPos, relativeYPos)
1276 // corresponds to the center of the item, but we are searching based on the top-left cell,
1277 // so we translate the point over to correspond to the top-left.
1278 relativeXPos = (int) (relativeXPos - (mCellWidth + mBorderSpace.x) * (spanX - 1) / 2f);
1279 relativeYPos = (int) (relativeYPos - (mCellHeight + mBorderSpace.y) * (spanY - 1) / 2f);
Adam Cohene3e27a82011-04-15 12:07:39 -07001280
Jeff Sharkey70864282009-04-07 21:08:40 -07001281 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001282 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001283 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001284 final Rect bestRect = new Rect(-1, -1, -1, -1);
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001285 final Stack<Rect> validRegions = new Stack<>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001286
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001287 final int countX = mCountX;
1288 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001289
Adam Cohend41fbf52012-02-16 23:53:59 -08001290 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1291 spanX < minSpanX || spanY < minSpanY) {
1292 return bestXY;
1293 }
1294
1295 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001296 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001297 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1298 int ySize = -1;
1299 int xSize = -1;
Sebastian Francob57c0b22022-06-28 13:54:35 -07001300 if (!ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001301 // First, let's see if this thing fits anywhere
1302 for (int i = 0; i < minSpanX; i++) {
1303 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001304 if (mOccupied.cells[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001305 continue inner;
1306 }
Michael Jurkac28de512010-08-13 11:27:44 -07001307 }
1308 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001309 xSize = minSpanX;
1310 ySize = minSpanY;
1311
1312 // We know that the item will fit at _some_ acceptable size, now let's see
1313 // how big we can make it. We'll alternate between incrementing x and y spans
1314 // until we hit a limit.
1315 boolean incX = true;
1316 boolean hitMaxX = xSize >= spanX;
1317 boolean hitMaxY = ySize >= spanY;
1318 while (!(hitMaxX && hitMaxY)) {
1319 if (incX && !hitMaxX) {
1320 for (int j = 0; j < ySize; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001321 if (x + xSize > countX -1 || mOccupied.cells[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001322 // We can't move out horizontally
1323 hitMaxX = true;
1324 }
1325 }
1326 if (!hitMaxX) {
1327 xSize++;
1328 }
1329 } else if (!hitMaxY) {
1330 for (int i = 0; i < xSize; i++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001331 if (y + ySize > countY - 1 || mOccupied.cells[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001332 // We can't move out vertically
1333 hitMaxY = true;
1334 }
1335 }
1336 if (!hitMaxY) {
1337 ySize++;
1338 }
1339 }
1340 hitMaxX |= xSize >= spanX;
1341 hitMaxY |= ySize >= spanY;
1342 incX = !incX;
1343 }
Michael Jurkac28de512010-08-13 11:27:44 -07001344 }
Sunny Goyal2805e632015-05-20 15:35:32 -07001345 final int[] cellXY = mTmpPoint;
Adam Cohene3e27a82011-04-15 12:07:39 -07001346 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001347
Adam Cohend41fbf52012-02-16 23:53:59 -08001348 // We verify that the current rect is not a sub-rect of any of our previous
1349 // candidates. In this case, the current rect is disqualified in favour of the
1350 // containing rect.
Sebastian Franco4a922672022-10-27 16:42:24 -07001351 Rect currentRect = new Rect(x, y, x + xSize, y + ySize);
Adam Cohend41fbf52012-02-16 23:53:59 -08001352 boolean contained = false;
1353 for (Rect r : validRegions) {
1354 if (r.contains(currentRect)) {
1355 contained = true;
1356 break;
1357 }
1358 }
1359 validRegions.push(currentRect);
Sebastian Francob57c0b22022-06-28 13:54:35 -07001360 double distance = Math.hypot(cellXY[0] - relativeXPos, cellXY[1] - relativeYPos);
Adam Cohen482ed822012-03-02 14:15:13 -08001361
Adam Cohend41fbf52012-02-16 23:53:59 -08001362 if ((distance <= bestDistance && !contained) ||
1363 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001364 bestDistance = distance;
1365 bestXY[0] = x;
1366 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001367 if (resultSpan != null) {
1368 resultSpan[0] = xSize;
1369 resultSpan[1] = ySize;
1370 }
1371 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001372 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001373 }
1374 }
1375
Adam Cohenc0dcf592011-06-01 15:30:43 -07001376 // Return -1, -1 if no suitable location found
1377 if (bestDistance == Double.MAX_VALUE) {
1378 bestXY[0] = -1;
1379 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001380 }
Adam Cohenc0dcf592011-06-01 15:30:43 -07001381 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001382 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001383
Sebastian Francoe4c03452022-12-27 14:50:02 -06001384 public GridOccupancy getOccupied() {
1385 return mOccupied;
1386 }
1387
Adam Cohen482ed822012-03-02 14:15:13 -08001388 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001389 mTmpOccupied.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001390
Michael Jurkaa52570f2012-03-20 03:18:20 -07001391 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001392 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001393 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001394 if (child == dragView) continue;
Sebastian Francod4682992022-10-05 13:03:09 -05001395 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001396 CellAndSpan c = solution.map.get(child);
1397 if (c != null) {
Sebastian Franco877088e2023-01-03 15:16:22 -07001398 lp.setTmpCellX(c.cellX);
1399 lp.setTmpCellY(c.cellY);
Adam Cohen8baab352012-03-20 17:39:21 -07001400 lp.cellHSpan = c.spanX;
1401 lp.cellVSpan = c.spanY;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001402 mTmpOccupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001403 }
1404 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001405 mTmpOccupied.markCells(solution, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001406 }
1407
1408 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
1409 commitDragView) {
1410
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001411 GridOccupancy occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
1412 occupied.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001413
Michael Jurkaa52570f2012-03-20 03:18:20 -07001414 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001415 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001416 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001417 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001418 CellAndSpan c = solution.map.get(child);
1419 if (c != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001420 animateChildToPosition(child, c.cellX, c.cellY, REORDER_ANIMATION_DURATION, 0,
Adam Cohen19f37922012-03-21 11:59:11 -07001421 DESTRUCTIVE_REORDER, false);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001422 occupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001423 }
1424 }
1425 if (commitDragView) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001426 occupied.markCells(solution, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001427 }
1428 }
1429
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001430
1431 // This method starts or changes the reorder preview animations
1432 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
Adam Cohen65086992020-02-19 08:40:49 -08001433 View dragView, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07001434 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07001435 for (int i = 0; i < childCount; i++) {
1436 View child = mShortcutsAndWidgets.getChildAt(i);
1437 if (child == dragView) continue;
1438 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001439 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
1440 != null && !solution.intersectingViews.contains(child);
1441
Adam Cohend9162062020-03-24 16:35:35 -07001442
Sebastian Francod4682992022-10-05 13:03:09 -05001443 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
Adam Cohend9162062020-03-24 16:35:35 -07001444 if (c != null && !skip && (child instanceof Reorderable)) {
Sunny Goyal82dfc152023-02-24 16:50:09 -08001445 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child,
Sebastian Franco877088e2023-01-03 15:16:22 -07001446 mode, lp.getCellX(), lp.getCellY(), c.cellX, c.cellY, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07001447 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07001448 }
1449 }
1450 }
1451
Sunny Goyal849c6a22018-08-08 16:33:46 -07001452 private static final Property<ReorderPreviewAnimation, Float> ANIMATION_PROGRESS =
1453 new Property<ReorderPreviewAnimation, Float>(float.class, "animationProgress") {
1454 @Override
1455 public Float get(ReorderPreviewAnimation anim) {
1456 return anim.animationProgress;
1457 }
1458
1459 @Override
1460 public void set(ReorderPreviewAnimation anim, Float progress) {
1461 anim.setAnimationProgress(progress);
1462 }
1463 };
1464
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001465 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07001466 // in a temporary state, and hint at where the item will return to.
Sunny Goyal82dfc152023-02-24 16:50:09 -08001467 class ReorderPreviewAnimation<T extends View & Reorderable> {
1468 final T child;
Adam Cohend024f982012-05-23 18:26:45 -07001469 float finalDeltaX;
1470 float finalDeltaY;
1471 float initDeltaX;
1472 float initDeltaY;
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001473 final float finalScale;
Adam Cohend024f982012-05-23 18:26:45 -07001474 float initScale;
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001475 final int mode;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001476 boolean repeating = false;
1477 private static final int PREVIEW_DURATION = 300;
1478 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
1479
Jon Miranda21266912016-12-19 14:12:05 -08001480 private static final float CHILD_DIVIDEND = 4.0f;
1481
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001482 public static final int MODE_HINT = 0;
1483 public static final int MODE_PREVIEW = 1;
1484
Sunny Goyal849c6a22018-08-08 16:33:46 -07001485 float animationProgress = 0;
Sunny Goyalf0b6db72018-08-13 16:10:14 -07001486 ValueAnimator a;
Adam Cohen19f37922012-03-21 11:59:11 -07001487
Sunny Goyal82dfc152023-02-24 16:50:09 -08001488 ReorderPreviewAnimation(View childView, int mode, int cellX0, int cellY0,
Adam Cohend9162062020-03-24 16:35:35 -07001489 int cellX1, int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07001490 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
1491 final int x0 = mTmpPoint[0];
1492 final int y0 = mTmpPoint[1];
1493 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
1494 final int x1 = mTmpPoint[0];
1495 final int y1 = mTmpPoint[1];
1496 final int dX = x1 - x0;
1497 final int dY = y1 - y0;
Jon Miranda21266912016-12-19 14:12:05 -08001498
Sunny Goyal82dfc152023-02-24 16:50:09 -08001499 this.child = (T) childView;
Jon Miranda21266912016-12-19 14:12:05 -08001500 this.mode = mode;
Adam Cohend9162062020-03-24 16:35:35 -07001501 finalDeltaX = 0;
1502 finalDeltaY = 0;
Adam Cohen65086992020-02-19 08:40:49 -08001503
Sunny Goyal82dfc152023-02-24 16:50:09 -08001504 MultiTranslateDelegate mtd = child.getTranslateDelegate();
1505 initDeltaX = mtd.getTranslationX(INDEX_REORDER_BOUNCE_OFFSET).getValue();
1506 initDeltaY = mtd.getTranslationY(INDEX_REORDER_BOUNCE_OFFSET).getValue();
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001507 initScale = child.getReorderBounceScale();
Sunny Goyal82dfc152023-02-24 16:50:09 -08001508 finalScale = mChildScale - (CHILD_DIVIDEND / child.getWidth()) * initScale;
Adam Cohend9162062020-03-24 16:35:35 -07001509
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001510 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07001511 if (dX == dY && dX == 0) {
1512 } else {
1513 if (dY == 0) {
Adam Cohend9162062020-03-24 16:35:35 -07001514 finalDeltaX = -dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07001515 } else if (dX == 0) {
Adam Cohend9162062020-03-24 16:35:35 -07001516 finalDeltaY = -dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07001517 } else {
1518 double angle = Math.atan( (float) (dY) / dX);
Adam Cohend9162062020-03-24 16:35:35 -07001519 finalDeltaX = (int) (-dir * Math.signum(dX)
1520 * Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
1521 finalDeltaY = (int) (-dir * Math.signum(dY)
1522 * Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07001523 }
1524 }
Jon Miranda21266912016-12-19 14:12:05 -08001525 }
1526
Adam Cohend9162062020-03-24 16:35:35 -07001527 void setInitialAnimationValuesToBaseline() {
1528 initScale = mChildScale;
1529 initDeltaX = 0;
1530 initDeltaY = 0;
Adam Cohen19f37922012-03-21 11:59:11 -07001531 }
1532
Adam Cohend024f982012-05-23 18:26:45 -07001533 void animate() {
Adam Cohend9162062020-03-24 16:35:35 -07001534 boolean noMovement = (finalDeltaX == 0) && (finalDeltaY == 0);
Jon Miranda21266912016-12-19 14:12:05 -08001535
Adam Cohen19f37922012-03-21 11:59:11 -07001536 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001537 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohen19f37922012-03-21 11:59:11 -07001538 mShakeAnimators.remove(child);
Adam Cohend9162062020-03-24 16:35:35 -07001539
Jon Miranda21266912016-12-19 14:12:05 -08001540 if (noMovement) {
Adam Cohend9162062020-03-24 16:35:35 -07001541 // A previous animation for this item exists, and no new animation will exist.
1542 // Finish the old animation smoothly.
1543 oldAnimation.finishAnimation();
Adam Cohene7587d22012-05-24 18:50:02 -07001544 return;
Adam Cohend9162062020-03-24 16:35:35 -07001545 } else {
1546 // A previous animation for this item exists, and a new one will exist. Stop
1547 // the old animation in its tracks, and proceed with the new one.
1548 oldAnimation.cancel();
Adam Cohene7587d22012-05-24 18:50:02 -07001549 }
Adam Cohen19f37922012-03-21 11:59:11 -07001550 }
Jon Miranda21266912016-12-19 14:12:05 -08001551 if (noMovement) {
Adam Cohen19f37922012-03-21 11:59:11 -07001552 return;
1553 }
Adam Cohend9162062020-03-24 16:35:35 -07001554
Sunny Goyal849c6a22018-08-08 16:33:46 -07001555 ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS, 0, 1);
Adam Cohene7587d22012-05-24 18:50:02 -07001556 a = va;
Tony Wickham9e0702f2015-09-02 14:45:39 -07001557
1558 // Animations are disabled in power save mode, causing the repeated animation to jump
1559 // spastically between beginning and end states. Since this looks bad, we don't repeat
1560 // the animation in power save mode.
Sunny Goyaleaf7a952020-07-29 16:54:20 -07001561 if (areAnimatorsEnabled()) {
Tony Wickham9e0702f2015-09-02 14:45:39 -07001562 va.setRepeatMode(ValueAnimator.REVERSE);
1563 va.setRepeatCount(ValueAnimator.INFINITE);
1564 }
1565
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001566 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07001567 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07001568 va.addListener(new AnimatorListenerAdapter() {
1569 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07001570 // We make sure to end only after a full period
Adam Cohend9162062020-03-24 16:35:35 -07001571 setInitialAnimationValuesToBaseline();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001572 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07001573 }
1574 });
Adam Cohen19f37922012-03-21 11:59:11 -07001575 mShakeAnimators.put(child, this);
1576 va.start();
1577 }
1578
Sunny Goyal849c6a22018-08-08 16:33:46 -07001579 private void setAnimationProgress(float progress) {
1580 animationProgress = progress;
1581 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : animationProgress;
1582 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
1583 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Sunny Goyal82dfc152023-02-24 16:50:09 -08001584 child.getTranslateDelegate().setTranslation(INDEX_REORDER_BOUNCE_OFFSET, x, y);
Sunny Goyal849c6a22018-08-08 16:33:46 -07001585 float s = animationProgress * finalScale + (1 - animationProgress) * initScale;
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001586 child.setReorderBounceScale(s);
Sunny Goyal849c6a22018-08-08 16:33:46 -07001587 }
1588
Adam Cohend024f982012-05-23 18:26:45 -07001589 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07001590 if (a != null) {
1591 a.cancel();
1592 }
Adam Cohen19f37922012-03-21 11:59:11 -07001593 }
Adam Cohene7587d22012-05-24 18:50:02 -07001594
Adam Cohend9162062020-03-24 16:35:35 -07001595 /**
1596 * Smoothly returns the item to its baseline position / scale
1597 */
1598 @Thunk void finishAnimation() {
Adam Cohene7587d22012-05-24 18:50:02 -07001599 if (a != null) {
1600 a.cancel();
1601 }
Brandon Keely50e6e562012-05-08 16:28:49 -07001602
Adam Cohend9162062020-03-24 16:35:35 -07001603 setInitialAnimationValuesToBaseline();
1604 ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS,
1605 animationProgress, 0);
1606 a = va;
Sunny Goyalf0b6db72018-08-13 16:10:14 -07001607 a.setInterpolator(DEACCEL_1_5);
Adam Cohend9162062020-03-24 16:35:35 -07001608 a.setDuration(REORDER_ANIMATION_DURATION);
Sunny Goyal5d2fc322015-07-06 22:52:49 -07001609 a.start();
Brandon Keely50e6e562012-05-08 16:28:49 -07001610 }
Adam Cohen19f37922012-03-21 11:59:11 -07001611 }
1612
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001613 private void completeAndClearReorderPreviewAnimations() {
1614 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Adam Cohend9162062020-03-24 16:35:35 -07001615 a.finishAnimation();
Adam Cohen19f37922012-03-21 11:59:11 -07001616 }
1617 mShakeAnimators.clear();
1618 }
1619
Sunny Goyal711c5962021-06-23 12:36:18 -07001620 private void commitTempPlacement(View dragView) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001621 mTmpOccupied.copyTo(mOccupied);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07001622
Andras Kloczl2dacbee2022-02-21 16:53:28 +00001623 int screenId = getWorkspace().getIdForScreen(this);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07001624 int container = Favorites.CONTAINER_DESKTOP;
1625
Sunny Goyalc13403c2016-11-18 23:44:48 -08001626 if (mContainerType == HOTSEAT) {
Sunny Goyalaa8ef112015-06-12 20:04:41 -07001627 screenId = -1;
1628 container = Favorites.CONTAINER_HOTSEAT;
1629 }
1630
Michael Jurkaa52570f2012-03-20 03:18:20 -07001631 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001632 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07001633 View child = mShortcutsAndWidgets.getChildAt(i);
Sebastian Francod4682992022-10-05 13:03:09 -05001634 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
Adam Cohenea889a22012-03-27 16:45:39 -07001635 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07001636 // We do a null check here because the item info can be null in the case of the
1637 // AllApps button in the hotseat.
Sunny Goyal711c5962021-06-23 12:36:18 -07001638 if (info != null && child != dragView) {
Sunny Goyal669b71f2023-01-27 14:37:07 -08001639 CellPos presenterPos = mActivity.getCellPosMapper().mapModelToPresenter(info);
1640 final boolean requiresDbUpdate = (presenterPos.cellX != lp.getTmpCellX()
1641 || presenterPos.cellY != lp.getTmpCellY() || info.spanX != lp.cellHSpan
1642 || info.spanY != lp.cellVSpan || presenterPos.screenId != screenId);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07001643
Sebastian Franco877088e2023-01-03 15:16:22 -07001644 lp.setCellX(lp.getTmpCellX());
Sebastian Franco877088e2023-01-03 15:16:22 -07001645 lp.setCellY(lp.getTmpCellY());
Sunny Goyalaa8ef112015-06-12 20:04:41 -07001646 if (requiresDbUpdate) {
Sunny Goyalab770a12018-11-14 15:17:26 -08001647 Launcher.cast(mActivity).getModelWriter().modifyItemInDatabase(info, container,
Sunny Goyal669b71f2023-01-27 14:37:07 -08001648 screenId, lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07001649 }
Adam Cohen2acce882012-03-28 19:03:19 -07001650 }
Adam Cohen482ed822012-03-02 14:15:13 -08001651 }
1652 }
1653
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001654 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001655 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001656 for (int i = 0; i < childCount; i++) {
Sebastian Francod4682992022-10-05 13:03:09 -05001657 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) mShortcutsAndWidgets.getChildAt(
1658 i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08001659 lp.useTmpCoords = useTempCoords;
1660 }
1661 }
1662
Adam Cohen19f37922012-03-21 11:59:11 -07001663 // For a given cell and span, fetch the set of views intersecting the region.
Sebastian Francoe4c03452022-12-27 14:50:02 -06001664 public void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
Adam Cohen19f37922012-03-21 11:59:11 -07001665 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
1666 if (boundingRect != null) {
1667 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
1668 }
1669 intersectingViews.clear();
1670 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1671 Rect r1 = new Rect();
1672 final int count = mShortcutsAndWidgets.getChildCount();
1673 for (int i = 0; i < count; i++) {
1674 View child = mShortcutsAndWidgets.getChildAt(i);
1675 if (child == dragView) continue;
Sebastian Francod4682992022-10-05 13:03:09 -05001676 CellLayoutLayoutParams
1677 lp = (CellLayoutLayoutParams) child.getLayoutParams();
Sebastian Franco877088e2023-01-03 15:16:22 -07001678 r1.set(lp.getCellX(), lp.getCellY(), lp.getCellX() + lp.cellHSpan,
1679 lp.getCellY() + lp.cellVSpan);
Adam Cohen19f37922012-03-21 11:59:11 -07001680 if (Rect.intersects(r0, r1)) {
1681 mIntersectingViews.add(child);
1682 if (boundingRect != null) {
1683 boundingRect.union(r1);
1684 }
1685 }
1686 }
1687 }
1688
Sebastian Francoe4c03452022-12-27 14:50:02 -06001689 public boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
Adam Cohen19f37922012-03-21 11:59:11 -07001690 View dragView, int[] result) {
Sebastián Francof9a6ac22022-11-15 22:56:37 +00001691 result = findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY, result);
Adam Cohen19f37922012-03-21 11:59:11 -07001692 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
1693 mIntersectingViews);
1694 return !mIntersectingViews.isEmpty();
1695 }
1696
1697 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001698 completeAndClearReorderPreviewAnimations();
1699 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
1700 final int count = mShortcutsAndWidgets.getChildCount();
1701 for (int i = 0; i < count; i++) {
1702 View child = mShortcutsAndWidgets.getChildAt(i);
Sebastian Francod4682992022-10-05 13:03:09 -05001703 CellLayoutLayoutParams
1704 lp = (CellLayoutLayoutParams) child.getLayoutParams();
Sebastian Franco877088e2023-01-03 15:16:22 -07001705 if (lp.getTmpCellX() != lp.getCellX() || lp.getTmpCellY() != lp.getCellY()) {
1706 lp.setTmpCellX(lp.getCellX());
1707 lp.setTmpCellY(lp.getCellY());
1708 animateChildToPosition(child, lp.getCellX(), lp.getCellY(),
1709 REORDER_ANIMATION_DURATION, 0, false, false);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001710 }
Adam Cohen19f37922012-03-21 11:59:11 -07001711 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001712 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07001713 }
Adam Cohen19f37922012-03-21 11:59:11 -07001714 }
1715
Adam Cohenbebf0422012-04-11 18:06:28 -07001716 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
1717 View dragView, int[] direction, boolean commit) {
1718 int[] pixelXY = new int[2];
1719 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
1720
1721 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001722 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Sebastian Francof153d912022-04-22 16:15:27 -05001723 spanX, spanY, direction, dragView, true, new ItemConfiguration());
Adam Cohenbebf0422012-04-11 18:06:28 -07001724
1725 setUseTempCoords(true);
1726 if (swapSolution != null && swapSolution.isSolution) {
1727 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
1728 // committing anything or animating anything as we just want to determine if a solution
1729 // exists
1730 copySolutionToTempState(swapSolution, dragView);
1731 setItemPlacementDirty(true);
1732 animateItemsToSolution(swapSolution, dragView, commit);
1733
1734 if (commit) {
Sunny Goyal711c5962021-06-23 12:36:18 -07001735 commitTempPlacement(null);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001736 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07001737 setItemPlacementDirty(false);
1738 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001739 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
Adam Cohen65086992020-02-19 08:40:49 -08001740 ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07001741 }
1742 mShortcutsAndWidgets.requestLayout();
1743 }
1744 return swapSolution.isSolution;
1745 }
1746
Sebastian Franco9cab1c32022-10-25 17:28:54 -07001747 /**
1748 * Find a vacant area that will fit the given bounds nearest the requested
1749 * cell location, and will also weigh in a suggested direction vector of the
1750 * desired location. This method computers distance based on unit grid distances,
1751 * not pixel distances.
1752 *
1753 * @param cellX The X cell nearest to which you want to search for a vacant area.
1754 * @param cellY The Y cell nearest which you want to search for a vacant area.
1755 * @param spanX Horizontal span of the object.
1756 * @param spanY Vertical span of the object.
1757 * @param direction The favored direction in which the views should move from x, y
1758 * @param occupied The array which represents which cells in the CellLayout are occupied
1759 * @param blockOccupied The array which represents which cells in the specified block (cellX,
1760 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
1761 * @param result Array in which to place the result, or null (in which case a new array will
1762 * be allocated)
1763 * @return The X, Y cell of a vacant area that can contain this object,
1764 * nearest the requested location.
1765 */
1766 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
1767 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
1768 // Keep track of best-scoring drop area
1769 final int[] bestXY = result != null ? result : new int[2];
1770 float bestDistance = Float.MAX_VALUE;
1771 int bestDirectionScore = Integer.MIN_VALUE;
Adam Cohen482ed822012-03-02 14:15:13 -08001772
Sebastian Franco9cab1c32022-10-25 17:28:54 -07001773 final int countX = mCountX;
1774 final int countY = mCountY;
1775
1776 for (int y = 0; y < countY - (spanY - 1); y++) {
1777 inner:
1778 for (int x = 0; x < countX - (spanX - 1); x++) {
1779 // First, let's see if this thing fits anywhere
1780 for (int i = 0; i < spanX; i++) {
1781 for (int j = 0; j < spanY; j++) {
1782 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
1783 continue inner;
1784 }
1785 }
1786 }
1787
1788 float distance = (float) Math.hypot(x - cellX, y - cellY);
1789 int[] curDirection = mTmpPoint;
1790 computeDirectionVector(x - cellX, y - cellY, curDirection);
1791 // The direction score is just the dot product of the two candidate direction
1792 // and that passed in.
1793 int curDirectionScore = direction[0] * curDirection[0] +
1794 direction[1] * curDirection[1];
1795 if (Float.compare(distance, bestDistance) < 0 ||
1796 (Float.compare(distance, bestDistance) == 0
1797 && curDirectionScore > bestDirectionScore)) {
1798 bestDistance = distance;
1799 bestDirectionScore = curDirectionScore;
1800 bestXY[0] = x;
1801 bestXY[1] = y;
1802 }
Adam Cohen19f37922012-03-21 11:59:11 -07001803 }
Adam Cohen19f37922012-03-21 11:59:11 -07001804 }
1805
Sebastian Franco9cab1c32022-10-25 17:28:54 -07001806 // Return -1, -1 if no suitable location found
1807 if (bestDistance == Float.MAX_VALUE) {
1808 bestXY[0] = -1;
1809 bestXY[1] = -1;
Sebastian Franco53a15a42022-10-25 17:28:54 -07001810 }
Sebastian Franco9cab1c32022-10-25 17:28:54 -07001811 return bestXY;
1812 }
1813
1814 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
1815 int[] direction, ItemConfiguration currentState) {
1816 CellAndSpan c = currentState.map.get(v);
1817 boolean success = false;
1818 mTmpOccupied.markCells(c, false);
1819 mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
1820
1821 findNearestArea(c.cellX, c.cellY, c.spanX, c.spanY, direction,
1822 mTmpOccupied.cells, null, mTempLocation);
1823
1824 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
1825 c.cellX = mTempLocation[0];
1826 c.cellY = mTempLocation[1];
1827 success = true;
1828 }
1829 mTmpOccupied.markCells(c, true);
1830 return success;
1831 }
1832
1833 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1834 int[] direction, View dragView, ItemConfiguration currentState) {
1835
1836 ViewCluster cluster = new ViewCluster(views, currentState);
1837 Rect clusterRect = cluster.getBoundingRect();
1838 int whichEdge;
1839 int pushDistance;
1840 boolean fail = false;
1841
1842 // Determine the edge of the cluster that will be leading the push and how far
1843 // the cluster must be shifted.
1844 if (direction[0] < 0) {
1845 whichEdge = ViewCluster.LEFT;
1846 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
1847 } else if (direction[0] > 0) {
1848 whichEdge = ViewCluster.RIGHT;
1849 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1850 } else if (direction[1] < 0) {
1851 whichEdge = ViewCluster.TOP;
1852 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1853 } else {
1854 whichEdge = ViewCluster.BOTTOM;
1855 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
1856 }
1857
1858 // Break early for invalid push distance.
1859 if (pushDistance <= 0) {
1860 return false;
1861 }
1862
1863 // Mark the occupied state as false for the group of views we want to move.
1864 for (View v: views) {
1865 CellAndSpan c = currentState.map.get(v);
1866 mTmpOccupied.markCells(c, false);
1867 }
1868
1869 // We save the current configuration -- if we fail to find a solution we will revert
1870 // to the initial state. The process of finding a solution modifies the configuration
1871 // in place, hence the need for revert in the failure case.
1872 currentState.save();
1873
1874 // The pushing algorithm is simplified by considering the views in the order in which
1875 // they would be pushed by the cluster. For example, if the cluster is leading with its
1876 // left edge, we consider sort the views by their right edge, from right to left.
1877 cluster.sortConfigurationForEdgePush(whichEdge);
1878
1879 while (pushDistance > 0 && !fail) {
1880 for (View v: currentState.sortedViews) {
1881 // For each view that isn't in the cluster, we see if the leading edge of the
1882 // cluster is contacting the edge of that view. If so, we add that view to the
1883 // cluster.
1884 if (!cluster.views.contains(v) && v != dragView) {
1885 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1886 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) v.getLayoutParams();
1887 if (!lp.canReorder) {
1888 // The push solution includes the all apps button, this is not viable.
1889 fail = true;
1890 break;
1891 }
1892 cluster.addView(v);
1893 CellAndSpan c = currentState.map.get(v);
1894
1895 // Adding view to cluster, mark it as not occupied.
1896 mTmpOccupied.markCells(c, false);
1897 }
1898 }
1899 }
1900 pushDistance--;
1901
1902 // The cluster has been completed, now we move the whole thing over in the appropriate
1903 // direction.
1904 cluster.shift(whichEdge, 1);
1905 }
1906
1907 boolean foundSolution = false;
1908 clusterRect = cluster.getBoundingRect();
1909
1910 // Due to the nature of the algorithm, the only check required to verify a valid solution
1911 // is to ensure that completed shifted cluster lies completely within the cell layout.
1912 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1913 clusterRect.bottom <= mCountY) {
1914 foundSolution = true;
1915 } else {
1916 currentState.restore();
1917 }
1918
1919 // In either case, we set the occupied array as marked for the location of the views
1920 for (View v: cluster.views) {
1921 CellAndSpan c = currentState.map.get(v);
1922 mTmpOccupied.markCells(c, true);
1923 }
1924
1925 return foundSolution;
1926 }
1927
1928 /**
1929 * This helper class defines a cluster of views. It helps with defining complex edges
1930 * of the cluster and determining how those edges interact with other views. The edges
1931 * essentially define a fine-grained boundary around the cluster of views -- like a more
1932 * precise version of a bounding box.
1933 */
1934 private class ViewCluster {
1935 final static int LEFT = 1 << 0;
1936 final static int TOP = 1 << 1;
1937 final static int RIGHT = 1 << 2;
1938 final static int BOTTOM = 1 << 3;
1939
1940 final ArrayList<View> views;
1941 final ItemConfiguration config;
1942 final Rect boundingRect = new Rect();
1943
1944 final int[] leftEdge = new int[mCountY];
1945 final int[] rightEdge = new int[mCountY];
1946 final int[] topEdge = new int[mCountX];
1947 final int[] bottomEdge = new int[mCountX];
1948 int dirtyEdges;
1949 boolean boundingRectDirty;
1950
1951 @SuppressWarnings("unchecked")
1952 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1953 this.views = (ArrayList<View>) views.clone();
1954 this.config = config;
1955 resetEdges();
1956 }
1957
1958 void resetEdges() {
1959 for (int i = 0; i < mCountX; i++) {
1960 topEdge[i] = -1;
1961 bottomEdge[i] = -1;
1962 }
1963 for (int i = 0; i < mCountY; i++) {
1964 leftEdge[i] = -1;
1965 rightEdge[i] = -1;
1966 }
1967 dirtyEdges = LEFT | TOP | RIGHT | BOTTOM;
1968 boundingRectDirty = true;
1969 }
1970
1971 void computeEdge(int which) {
1972 int count = views.size();
1973 for (int i = 0; i < count; i++) {
1974 CellAndSpan cs = config.map.get(views.get(i));
1975 switch (which) {
1976 case LEFT:
1977 int left = cs.cellX;
1978 for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) {
1979 if (left < leftEdge[j] || leftEdge[j] < 0) {
1980 leftEdge[j] = left;
1981 }
1982 }
1983 break;
1984 case RIGHT:
1985 int right = cs.cellX + cs.spanX;
1986 for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) {
1987 if (right > rightEdge[j]) {
1988 rightEdge[j] = right;
1989 }
1990 }
1991 break;
1992 case TOP:
1993 int top = cs.cellY;
1994 for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) {
1995 if (top < topEdge[j] || topEdge[j] < 0) {
1996 topEdge[j] = top;
1997 }
1998 }
1999 break;
2000 case BOTTOM:
2001 int bottom = cs.cellY + cs.spanY;
2002 for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) {
2003 if (bottom > bottomEdge[j]) {
2004 bottomEdge[j] = bottom;
2005 }
2006 }
2007 break;
2008 }
2009 }
2010 }
2011
2012 boolean isViewTouchingEdge(View v, int whichEdge) {
2013 CellAndSpan cs = config.map.get(v);
2014
2015 if ((dirtyEdges & whichEdge) == whichEdge) {
2016 computeEdge(whichEdge);
2017 dirtyEdges &= ~whichEdge;
2018 }
2019
2020 switch (whichEdge) {
2021 case LEFT:
2022 for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) {
2023 if (leftEdge[i] == cs.cellX + cs.spanX) {
2024 return true;
2025 }
2026 }
2027 break;
2028 case RIGHT:
2029 for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) {
2030 if (rightEdge[i] == cs.cellX) {
2031 return true;
2032 }
2033 }
2034 break;
2035 case TOP:
2036 for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) {
2037 if (topEdge[i] == cs.cellY + cs.spanY) {
2038 return true;
2039 }
2040 }
2041 break;
2042 case BOTTOM:
2043 for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) {
2044 if (bottomEdge[i] == cs.cellY) {
2045 return true;
2046 }
2047 }
2048 break;
2049 }
2050 return false;
2051 }
2052
2053 void shift(int whichEdge, int delta) {
2054 for (View v: views) {
2055 CellAndSpan c = config.map.get(v);
2056 switch (whichEdge) {
2057 case LEFT:
2058 c.cellX -= delta;
2059 break;
2060 case RIGHT:
2061 c.cellX += delta;
2062 break;
2063 case TOP:
2064 c.cellY -= delta;
2065 break;
2066 case BOTTOM:
2067 default:
2068 c.cellY += delta;
2069 break;
2070 }
2071 }
2072 resetEdges();
2073 }
2074
2075 public void addView(View v) {
2076 views.add(v);
2077 resetEdges();
2078 }
2079
2080 public Rect getBoundingRect() {
2081 if (boundingRectDirty) {
2082 config.getBoundingRectForViews(views, boundingRect);
2083 }
2084 return boundingRect;
2085 }
2086
2087 final PositionComparator comparator = new PositionComparator();
2088 class PositionComparator implements Comparator<View> {
2089 int whichEdge = 0;
2090 public int compare(View left, View right) {
2091 CellAndSpan l = config.map.get(left);
2092 CellAndSpan r = config.map.get(right);
2093 switch (whichEdge) {
2094 case LEFT:
2095 return (r.cellX + r.spanX) - (l.cellX + l.spanX);
2096 case RIGHT:
2097 return l.cellX - r.cellX;
2098 case TOP:
2099 return (r.cellY + r.spanY) - (l.cellY + l.spanY);
2100 case BOTTOM:
2101 default:
2102 return l.cellY - r.cellY;
2103 }
2104 }
2105 }
2106
2107 public void sortConfigurationForEdgePush(int edge) {
2108 comparator.whichEdge = edge;
2109 Collections.sort(config.sortedViews, comparator);
2110 }
2111 }
2112
2113 // This method tries to find a reordering solution which satisfies the push mechanic by trying
2114 // to push items in each of the cardinal directions, in an order based on the direction vector
2115 // passed.
2116 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
2117 int[] direction, View ignoreView, ItemConfiguration solution) {
2118 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
2119 // If the direction vector has two non-zero components, we try pushing
2120 // separately in each of the components.
2121 int temp = direction[1];
2122 direction[1] = 0;
2123
2124 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2125 ignoreView, solution)) {
2126 return true;
2127 }
2128 direction[1] = temp;
2129 temp = direction[0];
2130 direction[0] = 0;
2131
2132 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2133 ignoreView, solution)) {
2134 return true;
2135 }
2136 // Revert the direction
2137 direction[0] = temp;
2138
2139 // Now we try pushing in each component of the opposite direction
2140 direction[0] *= -1;
2141 direction[1] *= -1;
2142 temp = direction[1];
2143 direction[1] = 0;
2144 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2145 ignoreView, solution)) {
2146 return true;
2147 }
2148
2149 direction[1] = temp;
2150 temp = direction[0];
2151 direction[0] = 0;
2152 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2153 ignoreView, solution)) {
2154 return true;
2155 }
2156 // revert the direction
2157 direction[0] = temp;
2158 direction[0] *= -1;
2159 direction[1] *= -1;
2160
2161 } else {
2162 // If the direction vector has a single non-zero component, we push first in the
2163 // direction of the vector
2164 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2165 ignoreView, solution)) {
2166 return true;
2167 }
2168 // Then we try the opposite direction
2169 direction[0] *= -1;
2170 direction[1] *= -1;
2171 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2172 ignoreView, solution)) {
2173 return true;
2174 }
2175 // Switch the direction back
2176 direction[0] *= -1;
2177 direction[1] *= -1;
2178
2179 // If we have failed to find a push solution with the above, then we try
2180 // to find a solution by pushing along the perpendicular axis.
2181
2182 // Swap the components
2183 int temp = direction[1];
2184 direction[1] = direction[0];
2185 direction[0] = temp;
2186 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2187 ignoreView, solution)) {
2188 return true;
2189 }
2190
2191 // Then we try the opposite direction
2192 direction[0] *= -1;
2193 direction[1] *= -1;
2194 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2195 ignoreView, solution)) {
2196 return true;
2197 }
2198 // Switch the direction back
2199 direction[0] *= -1;
2200 direction[1] *= -1;
2201
2202 // Swap the components back
2203 temp = direction[1];
2204 direction[1] = direction[0];
2205 direction[0] = temp;
2206 }
2207 return false;
2208 }
2209
2210 /*
2211 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
2212 * the provided point and the provided cell
2213 */
2214 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
2215 double angle = Math.atan(deltaY / deltaX);
2216
2217 result[0] = 0;
2218 result[1] = 0;
2219 if (Math.abs(Math.cos(angle)) > 0.5f) {
2220 result[0] = (int) Math.signum(deltaX);
2221 }
2222 if (Math.abs(Math.sin(angle)) > 0.5f) {
2223 result[1] = (int) Math.signum(deltaY);
2224 }
2225 }
2226
2227 /* This seems like it should be obvious and straight-forward, but when the direction vector
2228 needs to match with the notion of the dragView pushing other views, we have to employ
2229 a slightly more subtle notion of the direction vector. The question is what two points is
2230 the vector between? The center of the dragView and its desired destination? Not quite, as
2231 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2232 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2233 or right, which helps make pushing feel right.
2234 */
Sebastian Francoe4c03452022-12-27 14:50:02 -06002235 public void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002236 int spanY, View dragView, int[] resultDirection) {
2237
2238 //TODO(adamcohen) b/151776141 use the items visual center for the direction vector
2239 int[] targetDestination = new int[2];
2240
Sebastián Francof9a6ac22022-11-15 22:56:37 +00002241 findNearestAreaIgnoreOccupied(dragViewCenterX, dragViewCenterY, spanX, spanY,
2242 targetDestination);
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002243 Rect dragRect = new Rect();
2244 cellToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2245 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2246
2247 Rect dropRegionRect = new Rect();
2248 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2249 dragView, dropRegionRect, mIntersectingViews);
2250
2251 int dropRegionSpanX = dropRegionRect.width();
2252 int dropRegionSpanY = dropRegionRect.height();
2253
2254 cellToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2255 dropRegionRect.height(), dropRegionRect);
2256
2257 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2258 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2259
2260 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2261 deltaX = 0;
2262 }
2263 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2264 deltaY = 0;
2265 }
2266
2267 if (deltaX == 0 && deltaY == 0) {
2268 // No idea what to do, give a random direction.
2269 resultDirection[0] = 1;
2270 resultDirection[1] = 0;
2271 } else {
2272 computeDirectionVector(deltaX, deltaY, resultDirection);
2273 }
2274 }
2275
2276 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
2277 int[] direction, View dragView, ItemConfiguration currentState) {
2278 if (views.size() == 0) return true;
2279
2280 boolean success = false;
2281 Rect boundingRect = new Rect();
2282 // We construct a rect which represents the entire group of views passed in
2283 currentState.getBoundingRectForViews(views, boundingRect);
2284
2285 // Mark the occupied state as false for the group of views we want to move.
2286 for (View v: views) {
2287 CellAndSpan c = currentState.map.get(v);
2288 mTmpOccupied.markCells(c, false);
2289 }
2290
2291 GridOccupancy blockOccupied = new GridOccupancy(boundingRect.width(), boundingRect.height());
2292 int top = boundingRect.top;
2293 int left = boundingRect.left;
2294 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
2295 // for interlocking.
2296 for (View v: views) {
2297 CellAndSpan c = currentState.map.get(v);
2298 blockOccupied.markCells(c.cellX - left, c.cellY - top, c.spanX, c.spanY, true);
2299 }
2300
2301 mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
2302
2303 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
2304 boundingRect.height(), direction,
2305 mTmpOccupied.cells, blockOccupied.cells, mTempLocation);
2306
2307 // If we successfully found a location by pushing the block of views, we commit it
2308 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
2309 int deltaX = mTempLocation[0] - boundingRect.left;
2310 int deltaY = mTempLocation[1] - boundingRect.top;
2311 for (View v: views) {
2312 CellAndSpan c = currentState.map.get(v);
2313 c.cellX += deltaX;
2314 c.cellY += deltaY;
2315 }
2316 success = true;
2317 }
2318
2319 // In either case, we set the occupied array as marked for the location of the views
2320 for (View v: views) {
2321 CellAndSpan c = currentState.map.get(v);
2322 mTmpOccupied.markCells(c, true);
2323 }
2324 return success;
2325 }
2326
Sebastian Francoe4c03452022-12-27 14:50:02 -06002327 public boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002328 View ignoreView, ItemConfiguration solution) {
2329 // Return early if get invalid cell positions
2330 if (cellX < 0 || cellY < 0) return false;
2331
2332 mIntersectingViews.clear();
2333 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2334
2335 // Mark the desired location of the view currently being dragged.
2336 if (ignoreView != null) {
2337 CellAndSpan c = solution.map.get(ignoreView);
2338 if (c != null) {
2339 c.cellX = cellX;
2340 c.cellY = cellY;
2341 }
2342 }
2343 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2344 Rect r1 = new Rect();
2345 for (View child: solution.map.keySet()) {
2346 if (child == ignoreView) continue;
2347 CellAndSpan c = solution.map.get(child);
2348 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
2349 r1.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2350 if (Rect.intersects(r0, r1)) {
2351 if (!lp.canReorder) {
2352 return false;
2353 }
2354 mIntersectingViews.add(child);
2355 }
2356 }
2357
2358 solution.intersectingViews = new ArrayList<>(mIntersectingViews);
2359
2360 // First we try to find a solution which respects the push mechanic. That is,
2361 // we try to find a solution such that no displaced item travels through another item
2362 // without also displacing that item.
2363 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
2364 solution)) {
2365 return true;
2366 }
2367
2368 // Next we try moving the views as a block, but without requiring the push mechanic.
2369 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
2370 solution)) {
2371 return true;
2372 }
2373
2374 // Ok, they couldn't move as a block, let's move them individually
2375 for (View v : mIntersectingViews) {
2376 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
2377 return false;
2378 }
2379 }
2380 return true;
2381 }
2382
Sebastian Francoe4c03452022-12-27 14:50:02 -06002383 public ReorderAlgorithm createReorderAlgorithm() {
2384 return new ReorderAlgorithm(this);
2385 }
2386
Sebastian Franco09589322022-11-02 15:25:58 -07002387 protected ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
2388 int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
2389 ItemConfiguration solution) {
Sebastian Francoe4c03452022-12-27 14:50:02 -06002390 return createReorderAlgorithm().findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
2391 spanX, spanY, direction, dragView, decX, solution);
Sebastian Franco09589322022-11-02 15:25:58 -07002392 }
2393
Sebastian Francoe4c03452022-12-27 14:50:02 -06002394 public void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002395 int childCount = mShortcutsAndWidgets.getChildCount();
2396 for (int i = 0; i < childCount; i++) {
2397 View child = mShortcutsAndWidgets.getChildAt(i);
2398 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
2399 CellAndSpan c;
2400 if (temp) {
Sebastian Franco877088e2023-01-03 15:16:22 -07002401 c = new CellAndSpan(lp.getTmpCellX(), lp.getTmpCellY(), lp.cellHSpan, lp.cellVSpan);
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002402 } else {
Sebastian Franco877088e2023-01-03 15:16:22 -07002403 c = new CellAndSpan(lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan);
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002404 }
2405 solution.add(child, c);
2406 }
Sebastian Franco53a15a42022-10-25 17:28:54 -07002407 }
2408
2409 /**
Sebastian Franco53a15a42022-10-25 17:28:54 -07002410 * When the user drags an Item in the workspace sometimes we need to move the items already in
2411 * the workspace to make space for the new item, this function return a solution for that
2412 * reorder.
2413 *
2414 * @param pixelX X coordinate in the screen of the dragView in pixels
2415 * @param pixelY Y coordinate in the screen of the dragView in pixels
2416 * @param minSpanX minimum horizontal span the item can be shrunk to
2417 * @param minSpanY minimum vertical span the item can be shrunk to
2418 * @param spanX occupied horizontal span
2419 * @param spanY occupied vertical span
2420 * @param dragView the view of the item being draged
2421 * @return returns a solution for the given parameters, the solution contains all the icons and
2422 * the locations they should be in the given solution.
2423 */
2424 public ItemConfiguration calculateReorder(int pixelX, int pixelY, int minSpanX, int minSpanY,
2425 int spanX, int spanY, View dragView) {
Sebastian Francoe4c03452022-12-27 14:50:02 -06002426 return createReorderAlgorithm().calculateReorder(pixelX, pixelY, minSpanX, minSpanY,
2427 spanX, spanY, dragView);
Sebastian Franco53a15a42022-10-25 17:28:54 -07002428 }
Adam Cohen482ed822012-03-02 14:15:13 -08002429
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002430 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
2431 View dragView, int[] result, int[] resultSpan, int mode) {
2432 if (resultSpan == null) {
2433 resultSpan = new int[]{-1, -1};
2434 }
2435 if (result == null) {
2436 result = new int[]{-1, -1};
2437 }
Sebastian Franco5d990ee2022-11-01 16:08:24 -07002438
2439 ItemConfiguration finalSolution = null;
2440 // We want the solution to match the animation of the preview and to match the drop so we
2441 // only recalculate in mode MODE_SHOW_REORDER_HINT because that the first one to run in the
2442 // reorder cycle.
2443 if (mode == MODE_SHOW_REORDER_HINT || mPreviousSolution == null) {
2444 finalSolution = calculateReorder(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
2445 dragView);
2446 mPreviousSolution = finalSolution;
2447 } else {
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002448 finalSolution = mPreviousSolution;
2449 // We reset this vector after drop
2450 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2451 mPreviousSolution = null;
2452 }
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002453 }
2454
2455 if (finalSolution == null || !finalSolution.isSolution) {
2456 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2457 } else {
2458 result[0] = finalSolution.cellX;
2459 result[1] = finalSolution.cellY;
2460 resultSpan[0] = finalSolution.spanX;
2461 resultSpan[1] = finalSolution.spanY;
Sebastian Franco9c743272022-11-15 15:03:25 -08002462 performReorder(finalSolution, dragView, mode);
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002463 }
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002464 return result;
2465 }
2466
Sebastian Franco53a15a42022-10-25 17:28:54 -07002467 /**
2468 * Animates and submits in the DB the given ItemConfiguration depending of the mode.
2469 *
2470 * @param solution represents widgets on the screen which the Workspace will animate to and
2471 * would be submitted to the database.
2472 * @param dragView view which is being dragged over the workspace that trigger the reorder
2473 * @param mode depending on the mode different animations would be played and depending on the
2474 * mode the solution would be submitted or not the database.
2475 * The possible modes are {@link MODE_SHOW_REORDER_HINT}, {@link MODE_DRAG_OVER},
2476 * {@link MODE_ON_DROP}, {@link MODE_ON_DROP_EXTERNAL}, {@link MODE_ACCEPT_DROP}
2477 * defined in {@link CellLayout}.
2478 */
Sebastian Francoe4c03452022-12-27 14:50:02 -06002479 public void performReorder(ItemConfiguration solution, View dragView, int mode) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002480 if (mode == MODE_SHOW_REORDER_HINT) {
Sebastian Franco53a15a42022-10-25 17:28:54 -07002481 beginOrAdjustReorderPreviewAnimations(solution, dragView,
2482 ReorderPreviewAnimation.MODE_HINT);
2483 return;
2484 }
2485 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2486 // committing anything or animating anything as we just want to determine if a solution
2487 // exists
2488 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2489 if (!DESTRUCTIVE_REORDER) {
2490 setUseTempCoords(true);
2491 }
2492
2493 if (!DESTRUCTIVE_REORDER) {
2494 copySolutionToTempState(solution, dragView);
2495 }
2496 setItemPlacementDirty(true);
2497 animateItemsToSolution(solution, dragView, mode == MODE_ON_DROP);
2498
2499 if (!DESTRUCTIVE_REORDER
2500 && (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
2501 // Since the temp solution didn't update dragView, don't commit it either
2502 commitTempPlacement(dragView);
2503 completeAndClearReorderPreviewAnimations();
2504 setItemPlacementDirty(false);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002505 } else {
Sebastian Franco53a15a42022-10-25 17:28:54 -07002506 beginOrAdjustReorderPreviewAnimations(solution, dragView,
2507 ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002508 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002509 }
2510
Sebastian Franco53a15a42022-10-25 17:28:54 -07002511 if (mode == MODE_ON_DROP && !DESTRUCTIVE_REORDER) {
Adam Cohen482ed822012-03-02 14:15:13 -08002512 setUseTempCoords(false);
2513 }
Adam Cohen482ed822012-03-02 14:15:13 -08002514
Michael Jurkaa52570f2012-03-20 03:18:20 -07002515 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002516 }
2517
Adam Cohen19f37922012-03-21 11:59:11 -07002518 void setItemPlacementDirty(boolean dirty) {
2519 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002520 }
Adam Cohen19f37922012-03-21 11:59:11 -07002521 boolean isItemPlacementDirty() {
2522 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002523 }
2524
Sebastian Francoe4c03452022-12-27 14:50:02 -06002525 /**
2526 * Represents the solution to a reorder of items in the Workspace.
2527 */
2528 public static class ItemConfiguration extends CellAndSpan {
2529 public final ArrayMap<View, CellAndSpan> map = new ArrayMap<>();
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07002530 private final ArrayMap<View, CellAndSpan> savedMap = new ArrayMap<>();
Sebastian Francoe4c03452022-12-27 14:50:02 -06002531 public final ArrayList<View> sortedViews = new ArrayList<>();
2532 public ArrayList<View> intersectingViews;
2533 public boolean isSolution = false;
Adam Cohen482ed822012-03-02 14:15:13 -08002534
Sebastian Francoe4c03452022-12-27 14:50:02 -06002535 public void save() {
Adam Cohenf3900c22012-11-16 18:28:11 -08002536 // Copy current state into savedMap
2537 for (View v: map.keySet()) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002538 savedMap.get(v).copyFrom(map.get(v));
Adam Cohenf3900c22012-11-16 18:28:11 -08002539 }
2540 }
2541
Sebastian Francoe4c03452022-12-27 14:50:02 -06002542 public void restore() {
Adam Cohenf3900c22012-11-16 18:28:11 -08002543 // Restore current state from savedMap
2544 for (View v: savedMap.keySet()) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002545 map.get(v).copyFrom(savedMap.get(v));
Adam Cohenf3900c22012-11-16 18:28:11 -08002546 }
2547 }
2548
Sebastian Francoe4c03452022-12-27 14:50:02 -06002549 public void add(View v, CellAndSpan cs) {
Adam Cohenf3900c22012-11-16 18:28:11 -08002550 map.put(v, cs);
2551 savedMap.put(v, new CellAndSpan());
2552 sortedViews.add(v);
2553 }
2554
Sebastian Francoe4c03452022-12-27 14:50:02 -06002555 public int area() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002556 return spanX * spanY;
Adam Cohenf3900c22012-11-16 18:28:11 -08002557 }
2558
Sebastian Francoe4c03452022-12-27 14:50:02 -06002559 public void getBoundingRectForViews(ArrayList<View> views, Rect outRect) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002560 boolean first = true;
2561 for (View v: views) {
2562 CellAndSpan c = map.get(v);
2563 if (first) {
2564 outRect.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2565 first = false;
2566 } else {
2567 outRect.union(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2568 }
2569 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002570 }
Adam Cohen482ed822012-03-02 14:15:13 -08002571 }
2572
Adam Cohendf035382011-04-11 17:22:04 -07002573 /**
Adam Cohendf035382011-04-11 17:22:04 -07002574 * Find a starting cell position that will fit the given bounds nearest the requested
2575 * cell location. Uses Euclidean distance to score multiple vacant areas.
2576 *
2577 * @param pixelX The X location at which you want to search for a vacant area.
2578 * @param pixelY The Y location at which you want to search for a vacant area.
2579 * @param spanX Horizontal span of the object.
2580 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07002581 * @param result Previously returned value to possibly recycle.
2582 * @return The X, Y cell of a vacant area that can contain this object,
2583 * nearest the requested location.
2584 */
Sebastián Francof9a6ac22022-11-15 22:56:37 +00002585 public int[] findNearestAreaIgnoreOccupied(int pixelX, int pixelY, int spanX, int spanY,
2586 int[] result) {
Sebastian Francob57c0b22022-06-28 13:54:35 -07002587 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, true, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002588 }
2589
Michael Jurka0280c3b2010-09-17 15:00:07 -07002590 boolean existsEmptyCell() {
2591 return findCellForSpan(null, 1, 1);
2592 }
2593
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002594 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002595 * Finds the upper-left coordinate of the first rectangle in the grid that can
2596 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2597 * then this method will only return coordinates for rectangles that contain the cell
2598 * (intersectX, intersectY)
2599 *
2600 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2601 * can be found.
2602 * @param spanX The horizontal span of the cell we want to find.
2603 * @param spanY The vertical span of the cell we want to find.
2604 *
2605 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002606 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002607 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002608 if (cellXY == null) {
2609 cellXY = new int[2];
Michael Jurka0280c3b2010-09-17 15:00:07 -07002610 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002611 return mOccupied.findVacantCell(cellXY, spanX, spanY);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002612 }
2613
2614 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002615 * A drag event has begun over this layout.
2616 * It may have begun over this layout (in which case onDragChild is called first),
2617 * or it may have begun on another layout.
2618 */
2619 void onDragEnter() {
Winson Chungc07918d2011-07-01 15:35:26 -07002620 mDragging = true;
Sebastian Franco5aa71ce2022-12-14 12:13:19 -06002621 mPreviousSolution = null;
Winson Chungc07918d2011-07-01 15:35:26 -07002622 }
2623
2624 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002625 * Called when drag has left this CellLayout or has been completed (successfully or not)
2626 */
2627 void onDragExit() {
Joe Onorato4be866d2010-10-10 11:26:02 -07002628 // This can actually be called when we aren't in a drag, e.g. when adding a new
2629 // item to this layout via the customize drawer.
2630 // Guard against that case.
2631 if (mDragging) {
2632 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002633 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002634
2635 // Invalidate the drag data
Sebastian Franco5aa71ce2022-12-14 12:13:19 -06002636 mPreviousSolution = null;
Adam Cohend41fbf52012-02-16 23:53:59 -08002637 mDragCell[0] = mDragCell[1] = -1;
Adam Cohenf7ca3b42021-02-22 11:03:58 -08002638 mDragCellSpan[0] = mDragCellSpan[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002639 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2640 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002641 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002642 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002643 }
2644
2645 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002646 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002647 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002648 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002649 *
2650 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002651 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002652 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002653 if (child != null) {
Sebastian Francod4682992022-10-05 13:03:09 -05002654 CellLayoutLayoutParams
2655 lp = (CellLayoutLayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002656 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002657 child.requestLayout();
Tony Wickham1cdb6d02015-09-17 11:08:27 -07002658 markCellsAsOccupiedForView(child);
Romain Guyd94533d2009-08-17 10:01:15 -07002659 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002660 }
2661
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002662 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002663 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002664 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002665 * @param cellX X coordinate of upper left corner expressed as a cell position
2666 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002667 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002668 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002669 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002670 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002671 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002672 final int cellWidth = mCellWidth;
2673 final int cellHeight = mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -07002674
Pierre Barbier de Reuille1b8bbb62021-05-19 22:45:16 +01002675 // We observe a shift of 1 pixel on the x coordinate compared to the actual cell coordinates
2676 final int hStartPadding = getPaddingLeft()
2677 + (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Winson Chung4b825dcd2011-06-19 12:41:22 -07002678 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002679
Thales Lima78d00ad2021-09-30 11:29:06 +01002680 int x = hStartPadding + (cellX * mBorderSpace.x) + (cellX * cellWidth);
2681 int y = vStartPadding + (cellY * mBorderSpace.y) + (cellY * cellHeight);
Jon Miranda228877d2021-02-09 11:05:00 -05002682
Thales Lima78d00ad2021-09-30 11:29:06 +01002683 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * mBorderSpace.x);
2684 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * mBorderSpace.y);
Winson Chungaafa03c2010-06-11 17:34:16 -07002685
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002686 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002687 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002688
Adam Cohend4844c32011-02-18 19:25:06 -08002689 public void markCellsAsOccupiedForView(View view) {
Sebastian Francof153d912022-04-22 16:15:27 -05002690 if (view instanceof LauncherAppWidgetHostView
2691 && view.getTag() instanceof LauncherAppWidgetInfo) {
2692 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
Sunny Goyal669b71f2023-01-27 14:37:07 -08002693 CellPos pos = mActivity.getCellPosMapper().mapModelToPresenter(info);
2694 mOccupied.markCells(pos.cellX, pos.cellY, info.spanX, info.spanY, true);
Sebastian Francof153d912022-04-22 16:15:27 -05002695 return;
2696 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002697 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Sebastian Francod4682992022-10-05 13:03:09 -05002698 CellLayoutLayoutParams
2699 lp = (CellLayoutLayoutParams) view.getLayoutParams();
Sebastian Franco877088e2023-01-03 15:16:22 -07002700 mOccupied.markCells(lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002701 }
2702
Adam Cohend4844c32011-02-18 19:25:06 -08002703 public void markCellsAsUnoccupiedForView(View view) {
Sebastian Francof153d912022-04-22 16:15:27 -05002704 if (view instanceof LauncherAppWidgetHostView
2705 && view.getTag() instanceof LauncherAppWidgetInfo) {
2706 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
Sunny Goyal669b71f2023-01-27 14:37:07 -08002707 CellPos pos = mActivity.getCellPosMapper().mapModelToPresenter(info);
2708 mOccupied.markCells(pos.cellX, pos.cellY, info.spanX, info.spanY, false);
Sebastian Francof153d912022-04-22 16:15:27 -05002709 return;
2710 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002711 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Sebastian Francod4682992022-10-05 13:03:09 -05002712 CellLayoutLayoutParams
2713 lp = (CellLayoutLayoutParams) view.getLayoutParams();
Sebastian Franco877088e2023-01-03 15:16:22 -07002714 mOccupied.markCells(lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan, false);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002715 }
2716
Adam Cohen2801caf2011-05-13 20:57:39 -07002717 public int getDesiredWidth() {
Jon Miranda228877d2021-02-09 11:05:00 -05002718 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth)
Thales Lima78d00ad2021-09-30 11:29:06 +01002719 + ((mCountX - 1) * mBorderSpace.x);
Adam Cohen2801caf2011-05-13 20:57:39 -07002720 }
2721
2722 public int getDesiredHeight() {
Jon Miranda228877d2021-02-09 11:05:00 -05002723 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight)
Thales Lima78d00ad2021-09-30 11:29:06 +01002724 + ((mCountY - 1) * mBorderSpace.y);
Adam Cohen2801caf2011-05-13 20:57:39 -07002725 }
2726
Michael Jurka66d72172011-04-12 16:29:25 -07002727 public boolean isOccupied(int x, int y) {
2728 if (x < mCountX && y < mCountY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002729 return mOccupied.cells[x][y];
Michael Jurka66d72172011-04-12 16:29:25 -07002730 } else {
2731 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2732 }
2733 }
2734
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002735 @Override
2736 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
Sebastian Francod4682992022-10-05 13:03:09 -05002737 return new CellLayoutLayoutParams(getContext(), attrs);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002738 }
2739
2740 @Override
2741 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
Sebastian Francod4682992022-10-05 13:03:09 -05002742 return p instanceof CellLayoutLayoutParams;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002743 }
2744
2745 @Override
2746 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
Sebastian Francod4682992022-10-05 13:03:09 -05002747 return new CellLayoutLayoutParams(p);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002748 }
2749
Michael Jurka0280c3b2010-09-17 15:00:07 -07002750 // This class stores info for two purposes:
2751 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2752 // its spanX, spanY, and the screen it is on
2753 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2754 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2755 // the CellLayout that was long clicked
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002756 public static final class CellInfo extends CellAndSpan {
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07002757 public final View cell;
Sunny Goyalefb7e842018-10-04 15:11:00 -07002758 final int screenId;
2759 final int container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002760
Sunny Goyal669b71f2023-01-27 14:37:07 -08002761 public CellInfo(View v, ItemInfo info, CellPos cellPos) {
2762 cellX = cellPos.cellX;
2763 cellY = cellPos.cellY;
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002764 spanX = info.spanX;
2765 spanY = info.spanY;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002766 cell = v;
Sunny Goyal669b71f2023-01-27 14:37:07 -08002767 screenId = cellPos.screenId;
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002768 container = info.container;
2769 }
2770
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002771 @Override
2772 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002773 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2774 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002775 }
2776 }
Michael Jurkad771c962011-08-09 15:00:48 -07002777
Tony Wickham86930612015-09-09 13:50:40 -07002778 /**
Samuel Fufa1e2d0042019-11-18 17:12:46 -08002779 * A Delegated cell Drawing for drawing on CellLayout
2780 */
2781 public abstract static class DelegatedCellDrawing {
2782 public int mDelegateCellX;
2783 public int mDelegateCellY;
2784
2785 /**
2786 * Draw under CellLayout
2787 */
2788 public abstract void drawUnderItem(Canvas canvas);
2789
2790 /**
2791 * Draw over CellLayout
2792 */
2793 public abstract void drawOverItem(Canvas canvas);
2794 }
2795
2796 /**
Tony Wickham86930612015-09-09 13:50:40 -07002797 * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing
2798 * if necessary).
2799 */
2800 public boolean hasReorderSolution(ItemInfo itemInfo) {
2801 int[] cellPoint = new int[2];
2802 // Check for a solution starting at every cell.
2803 for (int cellX = 0; cellX < getCountX(); cellX++) {
2804 for (int cellY = 0; cellY < getCountY(); cellY++) {
2805 cellToPoint(cellX, cellY, cellPoint);
2806 if (findReorderSolution(cellPoint[0], cellPoint[1], itemInfo.minSpanX,
2807 itemInfo.minSpanY, itemInfo.spanX, itemInfo.spanY, mDirectionVector, null,
2808 true, new ItemConfiguration()).isSolution) {
2809 return true;
2810 }
2811 }
2812 }
2813 return false;
2814 }
2815
Samuel Fufaa4211432020-02-25 18:47:54 -08002816 /**
2817 * Finds solution to accept hotseat migration to cell layout. commits solution if commitConfig
2818 */
2819 public boolean makeSpaceForHotseatMigration(boolean commitConfig) {
Samuel Fufaa4211432020-02-25 18:47:54 -08002820 int[] cellPoint = new int[2];
2821 int[] directionVector = new int[]{0, -1};
2822 cellToPoint(0, mCountY, cellPoint);
2823 ItemConfiguration configuration = new ItemConfiguration();
2824 if (findReorderSolution(cellPoint[0], cellPoint[1], mCountX, 1, mCountX, 1,
2825 directionVector, null, false, configuration).isSolution) {
2826 if (commitConfig) {
2827 copySolutionToTempState(configuration, null);
Sunny Goyal711c5962021-06-23 12:36:18 -07002828 commitTempPlacement(null);
Samuel Fufa82bbdac2020-03-09 18:24:47 -07002829 // undo marking cells occupied since there is actually nothing being placed yet.
2830 mOccupied.markCells(0, mCountY - 1, mCountX, 1, false);
Samuel Fufaa4211432020-02-25 18:47:54 -08002831 }
2832 return true;
2833 }
2834 return false;
2835 }
2836
Samuel Fufa82bbdac2020-03-09 18:24:47 -07002837 /**
2838 * returns a copy of cell layout's grid occupancy
2839 */
2840 public GridOccupancy cloneGridOccupancy() {
2841 GridOccupancy occupancy = new GridOccupancy(mCountX, mCountY);
2842 mOccupied.copyTo(occupancy);
2843 return occupancy;
2844 }
2845
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002846 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002847 return mOccupied.isRegionVacant(x, y, spanX, spanY);
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002848 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002849}