blob: d388ebcb9d5cd3881853a3e044a7f6f20f2c17a2 [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 Goyalf0b6db72018-08-13 16:10:14 -070025
Joe Onorato4be866d2010-10-10 11:26:02 -070026import android.animation.Animator;
Michael Jurka629758f2012-06-14 16:18:21 -070027import android.animation.AnimatorListenerAdapter;
Sunny Goyal849c6a22018-08-08 16:33:46 -070028import android.animation.ObjectAnimator;
Chet Haase00397b12010-10-07 11:13:10 -070029import android.animation.TimeInterpolator;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070030import android.animation.ValueAnimator;
31import android.animation.ValueAnimator.AnimatorUpdateListener;
Sunny Goyal726bee72018-03-05 12:54:24 -080032import android.annotation.SuppressLint;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080033import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040034import android.content.res.Resources;
Sunny Goyalc13403c2016-11-18 23:44:48 -080035import android.content.res.TypedArray;
Winson Chungaafa03c2010-06-11 17:34:16 -070036import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080037import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070038import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070039import android.graphics.Point;
Adam Cohend9162062020-03-24 16:35:35 -070040import android.graphics.PointF;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080041import android.graphics.Rect;
Adam Cohenf7ca3b42021-02-22 11:03:58 -080042import android.graphics.RectF;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070043import android.graphics.drawable.Drawable;
Adam Cohen1462de32012-07-24 22:34:36 -070044import android.os.Parcelable;
Rajeev Kumar9962dbe2017-06-12 12:16:20 -070045import android.util.ArrayMap;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080046import android.util.AttributeSet;
Adam Cohenf7ca3b42021-02-22 11:03:58 -080047import android.util.FloatProperty;
Joe Onorato4be866d2010-10-10 11:26:02 -070048import android.util.Log;
Sunny Goyal849c6a22018-08-08 16:33:46 -070049import android.util.Property;
Adam Cohen1462de32012-07-24 22:34:36 -070050import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080051import android.view.MotionEvent;
52import android.view.View;
53import android.view.ViewDebug;
54import android.view.ViewGroup;
Adam Cohenc9735cf2015-01-23 16:11:55 -080055import android.view.accessibility.AccessibilityEvent;
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -070056
vadimt04f356f2019-02-14 18:46:36 -080057import androidx.annotation.IntDef;
Adam Cohenf7ca3b42021-02-22 11:03:58 -080058import androidx.core.graphics.ColorUtils;
vadimt04f356f2019-02-14 18:46:36 -080059import androidx.core.view.ViewCompat;
60
Sunny Goyalaa8ef112015-06-12 20:04:41 -070061import com.android.launcher3.LauncherSettings.Favorites;
Sunny Goyale9b651e2015-04-24 11:44:51 -070062import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -070063import com.android.launcher3.anim.Interpolators;
Sebastian Francod4682992022-10-05 13:03:09 -050064import com.android.launcher3.celllayout.CellLayoutLayoutParams;
Sunny Goyal669b71f2023-01-27 14:37:07 -080065import com.android.launcher3.celllayout.CellPosMapper.CellPos;
Sunny Goyal3d706ad2017-03-06 16:56:39 -080066import com.android.launcher3.config.FeatureFlags;
Tony Wickham0ac045f2021-11-03 13:17:02 -070067import com.android.launcher3.dragndrop.DraggableView;
Jon Mirandaa0233f72017-06-22 18:34:45 -070068import com.android.launcher3.folder.PreviewBackground;
Sunny Goyale396abf2020-04-06 15:11:17 -070069import com.android.launcher3.model.data.ItemInfo;
Sebastian Francof153d912022-04-22 16:15:27 -050070import com.android.launcher3.model.data.LauncherAppWidgetInfo;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -070071import com.android.launcher3.util.CellAndSpan;
72import com.android.launcher3.util.GridOccupancy;
Sunny Goyale2fd14b2015-08-27 17:45:46 -070073import com.android.launcher3.util.ParcelableSparseArray;
Sunny Goyal9b29ca52017-02-17 10:39:44 -080074import com.android.launcher3.util.Themes;
Adam Cohen091440a2015-03-18 14:16:05 -070075import com.android.launcher3.util.Thunk;
Sunny Goyalab770a12018-11-14 15:17:26 -080076import com.android.launcher3.views.ActivityContext;
Steven Ng32427202021-04-19 18:12:12 +010077import com.android.launcher3.widget.LauncherAppWidgetHostView;
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -070078
Sunny Goyalc13403c2016-11-18 23:44:48 -080079import java.lang.annotation.Retention;
80import java.lang.annotation.RetentionPolicy;
Adam Cohen69ce2e52011-07-03 19:25:21 -070081import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070082import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080083import java.util.Collections;
84import java.util.Comparator;
Adam Cohend41fbf52012-02-16 23:53:59 -080085import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070086
Sunny Goyalc4d32012020-04-03 17:10:11 -070087public class CellLayout extends ViewGroup {
Tony Wickhama0628cc2015-10-14 15:23:04 -070088 private static final String TAG = "CellLayout";
89 private static final boolean LOGD = false;
Winson Chungaafa03c2010-06-11 17:34:16 -070090
Sunny Goyalab770a12018-11-14 15:17:26 -080091 protected final ActivityContext mActivity;
Sunny Goyal4ffec482016-02-09 11:28:52 -080092 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070093 @Thunk int mCellWidth;
Sunny Goyal4ffec482016-02-09 11:28:52 -080094 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070095 @Thunk int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070096 private int mFixedCellWidth;
97 private int mFixedCellHeight;
Jon Miranda228877d2021-02-09 11:05:00 -050098 @ViewDebug.ExportedProperty(category = "launcher")
Thales Lima8cd020b2022-03-15 20:15:14 +000099 private Point mBorderSpace;
Winson Chungaafa03c2010-06-11 17:34:16 -0700100
Sunny Goyal4ffec482016-02-09 11:28:52 -0800101 @ViewDebug.ExportedProperty(category = "launcher")
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700102 private int mCountX;
Sunny Goyal4ffec482016-02-09 11:28:52 -0800103 @ViewDebug.ExportedProperty(category = "launcher")
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700104 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800105
Adam Cohen917e3882013-10-31 15:03:35 -0700106 private boolean mDropPending = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800107
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700108 // These are temporary variables to prevent having to allocate a new object just to
109 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Adam Cohen091440a2015-03-18 14:16:05 -0700110 @Thunk final int[] mTmpPoint = new int[2];
Sunny Goyal2805e632015-05-20 15:35:32 -0700111 @Thunk final int[] mTempLocation = new int[2];
Adam Cohend9162062020-03-24 16:35:35 -0700112 final PointF mTmpPointF = new PointF();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700113
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700114 private GridOccupancy mOccupied;
115 private GridOccupancy mTmpOccupied;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800116
Michael Jurkadee05892010-07-27 10:01:56 -0700117 private OnTouchListener mInterceptTouchListener;
118
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800119 private final ArrayList<DelegatedCellDrawing> mDelegatedCellDrawings = new ArrayList<>();
Jon Mirandaa0233f72017-06-22 18:34:45 -0700120 final PreviewBackground mFolderLeaveBehind = new PreviewBackground();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700121
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800122 private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active };
Sunny Goyale15e2a82017-12-15 13:05:42 -0800123 private static final int[] BACKGROUND_STATE_DEFAULT = EMPTY_STATE_SET;
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800124 private final Drawable mBackground;
Sunny Goyal2805e632015-05-20 15:35:32 -0700125
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700126 // These values allow a fixed measurement to be set on the CellLayout.
127 private int mFixedWidth = -1;
128 private int mFixedHeight = -1;
129
Michael Jurka33945b22010-12-21 18:19:38 -0800130 // If we're actively dragging something over this screen, mIsDragOverlapping is true
131 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700132
Winson Chung150fbab2010-09-29 17:14:26 -0700133 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700134 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Sebastian Francod4682992022-10-05 13:03:09 -0500135 @Thunk final CellLayoutLayoutParams[] mDragOutlines = new CellLayoutLayoutParams[4];
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700136 @Thunk final float[] mDragOutlineAlphas = new float[mDragOutlines.length];
137 private final InterruptibleInOutAnimator[] mDragOutlineAnims =
Joe Onorato4be866d2010-10-10 11:26:02 -0700138 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700139
140 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700141 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700142 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700143
Sebastian Francod4682992022-10-05 13:03:09 -0500144 @Thunk final ArrayMap<CellLayoutLayoutParams, Animator> mReorderAnimators = new ArrayMap<>();
Adam Cohend9162062020-03-24 16:35:35 -0700145 @Thunk final ArrayMap<Reorderable, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
Adam Cohen19f37922012-03-21 11:59:11 -0700146
147 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700148
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800149 // Used to visualize the grid and drop locations
150 private boolean mVisualizeCells = false;
151 private boolean mVisualizeDropLocation = true;
152 private RectF mVisualizeGridRect = new RectF();
153 private Paint mVisualizeGridPaint = new Paint();
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800154 private int mGridVisualizationRoundingRadius;
155 private float mGridAlpha = 0f;
156 private int mGridColor = 0;
157 private float mSpringLoadedProgress = 0f;
158 private float mScrollProgress = 0f;
159
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700160 // When a drag operation is in progress, holds the nearest cell to the touch point
161 private final int[] mDragCell = new int[2];
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800162 private final int[] mDragCellSpan = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800163
Joe Onorato4be866d2010-10-10 11:26:02 -0700164 private boolean mDragging = false;
165
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700166 private final TimeInterpolator mEaseOutInterpolator;
167 private final ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700168
Sunny Goyalc13403c2016-11-18 23:44:48 -0800169 @Retention(RetentionPolicy.SOURCE)
170 @IntDef({WORKSPACE, HOTSEAT, FOLDER})
171 public @interface ContainerType{}
172 public static final int WORKSPACE = 0;
173 public static final int HOTSEAT = 1;
174 public static final int FOLDER = 2;
175
176 @ContainerType private final int mContainerType;
177
Jon Mirandab28c4fc2017-06-20 10:58:36 -0700178 private final float mChildScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800179
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800180 public static final int MODE_SHOW_REORDER_HINT = 0;
181 public static final int MODE_DRAG_OVER = 1;
182 public static final int MODE_ON_DROP = 2;
183 public static final int MODE_ON_DROP_EXTERNAL = 3;
184 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700185 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800186 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
187
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800188 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700189 private static final int REORDER_ANIMATION_DURATION = 150;
Sunny Goyalc13403c2016-11-18 23:44:48 -0800190 @Thunk final float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700191
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700192 private final ArrayList<View> mIntersectingViews = new ArrayList<>();
193 private final Rect mOccupiedRect = new Rect();
194 private final int[] mDirectionVector = new int[2];
Steven Ng30dd1d62021-03-15 21:45:49 +0000195
Sebastian Franco53a15a42022-10-25 17:28:54 -0700196 ItemConfiguration mPreviousSolution = null;
Adam Cohenb209e632012-03-27 17:09:36 -0700197 private static final int INVALID_DIRECTION = -100;
Adam Cohen482ed822012-03-02 14:15:13 -0800198
Sunny Goyal2805e632015-05-20 15:35:32 -0700199 private final Rect mTempRect = new Rect();
Jonathan Miranda21930da2021-05-03 18:44:13 +0000200 private final RectF mTempRectF = new RectF();
Jon Miranda88b7f6a2021-05-03 16:49:53 -0700201 private final float[] mTmpFloatArray = new float[4];
Winson Chung3a6e7f32013-10-09 15:50:52 -0700202
Sunny Goyal73b5a272019-12-09 14:55:56 -0800203 private static final Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700204
Adam Cohenc9735cf2015-01-23 16:11:55 -0800205 // Related to accessible drag and drop
Adam Cohen6e7c37a2020-06-25 19:22:37 -0700206 DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800207
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800208
209 public static final FloatProperty<CellLayout> SPRING_LOADED_PROGRESS =
210 new FloatProperty<CellLayout>("spring_loaded_progress") {
211 @Override
212 public Float get(CellLayout cl) {
213 return cl.getSpringLoadedProgress();
214 }
215
216 @Override
217 public void setValue(CellLayout cl, float progress) {
218 cl.setSpringLoadedProgress(progress);
219 }
220 };
221
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800222 public CellLayout(Context context) {
223 this(context, null);
224 }
225
226 public CellLayout(Context context, AttributeSet attrs) {
227 this(context, attrs, 0);
228 }
229
230 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
231 super(context, attrs, defStyle);
Sunny Goyalc13403c2016-11-18 23:44:48 -0800232 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
233 mContainerType = a.getInteger(R.styleable.CellLayout_containerType, WORKSPACE);
234 a.recycle();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700235
236 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
237 // the user where a dragged item will land when dropped.
238 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800239 setClipToPadding(false);
Sunny Goyalab770a12018-11-14 15:17:26 -0800240 mActivity = ActivityContext.lookupContext(context);
Steven Ngcc505b82021-03-18 23:04:35 +0000241 DeviceProfile deviceProfile = mActivity.getDeviceProfile();
Michael Jurkaa63c4522010-08-19 13:52:27 -0700242
Thales Lima8cd020b2022-03-15 20:15:14 +0000243 resetCellSizeInternal(deviceProfile);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700244
Steven Ngcc505b82021-03-18 23:04:35 +0000245 mCountX = deviceProfile.inv.numColumns;
246 mCountY = deviceProfile.inv.numRows;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700247 mOccupied = new GridOccupancy(mCountX, mCountY);
248 mTmpOccupied = new GridOccupancy(mCountX, mCountY);
249
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800250 mFolderLeaveBehind.mDelegateCellX = -1;
251 mFolderLeaveBehind.mDelegateCellY = -1;
Adam Cohenefca0272016-02-24 19:19:06 -0800252
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800253 setAlwaysDrawnWithCacheEnabled(false);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700254
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800255 Resources res = getResources();
256
257 mBackground = getContext().getDrawable(R.drawable.bg_celllayout);
Sunny Goyal2805e632015-05-20 15:35:32 -0700258 mBackground.setCallback(this);
Sunny Goyalaeb16432017-10-16 11:46:41 -0700259 mBackground.setAlpha(0);
Michael Jurka33945b22010-12-21 18:19:38 -0800260
Yogisha Dixitc0ac1dd2021-05-29 00:26:25 +0100261 mGridColor = Themes.getAttrColor(getContext(), R.attr.workspaceAccentColor);
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800262 mGridVisualizationRoundingRadius =
263 res.getDimensionPixelSize(R.dimen.grid_visualization_rounding_radius);
Steven Ngcc505b82021-03-18 23:04:35 +0000264 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * deviceProfile.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700265
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700266 // Initialize the data structures used for the drag visualization.
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -0700267 mEaseOutInterpolator = Interpolators.DEACCEL_2_5; // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700268 mDragCell[0] = mDragCell[1] = -1;
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800269 mDragCellSpan[0] = mDragCellSpan[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700270 for (int i = 0; i < mDragOutlines.length; i++) {
Sunny Goyal669b71f2023-01-27 14:37:07 -0800271 mDragOutlines[i] = new CellLayoutLayoutParams(0, 0, 0, 0);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700272 }
Mario Bertschler54ba6012017-06-08 10:53:53 -0700273 mDragOutlinePaint.setColor(Themes.getAttrColor(context, R.attr.workspaceTextColor));
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700274
275 // When dragging things around the home screens, we show a green outline of
276 // where the item will land. The outlines gradually fade out, leaving a trail
277 // behind the drag path.
278 // Set up all the animations that are used to implement this fading.
279 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700280 final float fromAlphaValue = 0;
281 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700282
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700283 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700284
285 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700286 final InterruptibleInOutAnimator anim =
Sebastian Francof153d912022-04-22 16:15:27 -0500287 new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700288 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700289 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700290 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700291 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700292 // If an animation is started and then stopped very quickly, we can still
293 // get spurious updates we've cleared the tag. Guard against this.
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800294 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
295 CellLayout.this.invalidate();
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700296 }
297 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700298 // The animation holds a reference to the drag outline bitmap as long is it's
299 // running. This way the bitmap can be GCed when the animations are complete.
Joe Onorato4be866d2010-10-10 11:26:02 -0700300 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700301 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700302
Sunny Goyalc13403c2016-11-18 23:44:48 -0800303 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType);
Jon Miranda228877d2021-02-09 11:05:00 -0500304 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
Thales Lima78d00ad2021-09-30 11:29:06 +0100305 mBorderSpace);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700306 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700307 }
308
Sunny Goyal9b180102020-03-11 10:02:29 -0700309 /**
310 * Sets or clears a delegate used for accessible drag and drop
311 */
312 public void setDragAndDropAccessibilityDelegate(DragAndDropAccessibilityDelegate delegate) {
313 setOnClickListener(delegate);
Sunny Goyal9b180102020-03-11 10:02:29 -0700314 ViewCompat.setAccessibilityDelegate(this, delegate);
315
Adam Cohen6e7c37a2020-06-25 19:22:37 -0700316 mTouchHelper = delegate;
317 int accessibilityFlag = mTouchHelper != null
Sunny Goyal9b180102020-03-11 10:02:29 -0700318 ? IMPORTANT_FOR_ACCESSIBILITY_YES : IMPORTANT_FOR_ACCESSIBILITY_NO;
319 setImportantForAccessibility(accessibilityFlag);
320 getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
Adam Cohenc9735cf2015-01-23 16:11:55 -0800321
Sunny Goyal384b5782021-02-09 22:50:02 -0800322 // ExploreByTouchHelper sets focusability. Clear it when the delegate is cleared.
323 setFocusable(delegate != null);
Adam Cohenc9735cf2015-01-23 16:11:55 -0800324 // Invalidate the accessibility hierarchy
325 if (getParent() != null) {
326 getParent().notifySubtreeAccessibilityStateChanged(
327 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
328 }
329 }
330
Sunny Goyala4647b62021-02-02 13:45:34 -0800331 /**
332 * Returns the currently set accessibility delegate
333 */
334 public DragAndDropAccessibilityDelegate getDragAndDropAccessibilityDelegate() {
335 return mTouchHelper;
336 }
337
Adam Cohenc9735cf2015-01-23 16:11:55 -0800338 @Override
Adam Cohen6e7c37a2020-06-25 19:22:37 -0700339 public boolean dispatchHoverEvent(MotionEvent event) {
340 // Always attempt to dispatch hover events to accessibility first.
341 if (mTouchHelper != null && mTouchHelper.dispatchHoverEvent(event)) {
342 return true;
343 }
344 return super.dispatchHoverEvent(event);
345 }
346
347 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800348 public boolean onInterceptTouchEvent(MotionEvent ev) {
Winson Chungf9935182020-10-23 09:26:44 -0700349 return mTouchHelper != null
350 || (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev));
Adam Cohenc9735cf2015-01-23 16:11:55 -0800351 }
352
Chris Craik01f2d7f2013-10-01 14:41:56 -0700353 public void enableHardwareLayer(boolean hasLayer) {
354 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700355 }
356
vadimt04f356f2019-02-14 18:46:36 -0800357 public boolean isHardwareLayerEnabled() {
358 return mShortcutsAndWidgets.getLayerType() == LAYER_TYPE_HARDWARE;
359 }
360
Thales Lima8cd020b2022-03-15 20:15:14 +0000361 /**
362 * Change sizes of cells
363 *
364 * @param width the new width of the cells
365 * @param height the new height of the cells
366 */
Winson Chung5f8afe62013-08-12 16:19:28 -0700367 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700368 mFixedCellWidth = mCellWidth = width;
369 mFixedCellHeight = mCellHeight = height;
Jon Miranda228877d2021-02-09 11:05:00 -0500370 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
Thales Lima78d00ad2021-09-30 11:29:06 +0100371 mBorderSpace);
Winson Chung5f8afe62013-08-12 16:19:28 -0700372 }
373
Thales Lima8cd020b2022-03-15 20:15:14 +0000374 private void resetCellSizeInternal(DeviceProfile deviceProfile) {
375 switch (mContainerType) {
376 case FOLDER:
Thales Limab35faed2022-09-05 16:30:01 -0300377 mBorderSpace = new Point(deviceProfile.folderCellLayoutBorderSpacePx,
378 deviceProfile.folderCellLayoutBorderSpacePx);
Thales Lima8cd020b2022-03-15 20:15:14 +0000379 break;
380 case HOTSEAT:
381 mBorderSpace = new Point(deviceProfile.hotseatBorderSpace,
382 deviceProfile.hotseatBorderSpace);
383 break;
384 case WORKSPACE:
385 default:
386 mBorderSpace = new Point(deviceProfile.cellLayoutBorderSpacePx);
387 break;
388 }
389
390 mCellWidth = mCellHeight = -1;
391 mFixedCellWidth = mFixedCellHeight = -1;
392 }
393
394 /**
395 * Reset the cell sizes and border space
396 */
397 public void resetCellSize(DeviceProfile deviceProfile) {
398 resetCellSizeInternal(deviceProfile);
399 requestLayout();
400 }
401
Adam Cohen2801caf2011-05-13 20:57:39 -0700402 public void setGridSize(int x, int y) {
403 mCountX = x;
404 mCountY = y;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700405 mOccupied = new GridOccupancy(mCountX, mCountY);
406 mTmpOccupied = new GridOccupancy(mCountX, mCountY);
Jon Miranda228877d2021-02-09 11:05:00 -0500407 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
Thales Lima78d00ad2021-09-30 11:29:06 +0100408 mBorderSpace);
Adam Cohen76fc0852011-06-17 13:26:23 -0700409 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700410 }
411
Adam Cohen2374abf2013-04-16 14:56:57 -0700412 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
413 public void setInvertIfRtl(boolean invert) {
414 mShortcutsAndWidgets.setInvertIfRtl(invert);
415 }
416
Adam Cohen917e3882013-10-31 15:03:35 -0700417 public void setDropPending(boolean pending) {
418 mDropPending = pending;
419 }
420
421 public boolean isDropPending() {
422 return mDropPending;
423 }
424
Adam Cohenc50438c2014-08-19 17:43:05 -0700425 void setIsDragOverlapping(boolean isDragOverlapping) {
426 if (mIsDragOverlapping != isDragOverlapping) {
427 mIsDragOverlapping = isDragOverlapping;
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800428 mBackground.setState(mIsDragOverlapping
429 ? BACKGROUND_STATE_ACTIVE : BACKGROUND_STATE_DEFAULT);
Adam Cohenc50438c2014-08-19 17:43:05 -0700430 invalidate();
431 }
432 }
433
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700434 @Override
435 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700436 ParcelableSparseArray jail = getJailedArray(container);
437 super.dispatchSaveInstanceState(jail);
438 container.put(R.id.cell_layout_jail_id, jail);
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700439 }
440
441 @Override
442 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700443 super.dispatchRestoreInstanceState(getJailedArray(container));
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700444 }
445
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700446 /**
447 * Wrap the SparseArray in another Parcelable so that the item ids do not conflict with our
448 * our internal resource ids
449 */
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700450 private ParcelableSparseArray getJailedArray(SparseArray<Parcelable> container) {
451 final Parcelable parcelable = container.get(R.id.cell_layout_jail_id);
452 return parcelable instanceof ParcelableSparseArray ?
453 (ParcelableSparseArray) parcelable : new ParcelableSparseArray();
454 }
455
Tony Wickham0f97b782015-12-02 17:55:07 -0800456 public boolean getIsDragOverlapping() {
457 return mIsDragOverlapping;
458 }
459
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700460 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700461 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700462 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
463 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
464 // When we're small, we are either drawn normally or in the "accepts drops" state (during
465 // a drag). However, we also drag the mini hover background *over* one of those two
466 // backgrounds
Sunny Goyalaeb16432017-10-16 11:46:41 -0700467 if (mBackground.getAlpha() > 0) {
Sunny Goyal2805e632015-05-20 15:35:32 -0700468 mBackground.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700469 }
Romain Guya6abce82009-11-10 02:54:41 -0800470
Adam Cohen482ed822012-03-02 14:15:13 -0800471 if (DEBUG_VISUALIZE_OCCUPIED) {
Tony Wickham0ac045f2021-11-03 13:17:02 -0700472 Rect cellBounds = new Rect();
473 // Will contain the bounds of the cell including spacing between cells.
474 Rect cellBoundsWithSpacing = new Rect();
Tony Wickham12784902021-11-03 14:02:10 -0700475 int[] targetCell = new int[2];
Tony Wickham0ac045f2021-11-03 13:17:02 -0700476 int[] cellCenter = new int[2];
477 Paint debugPaint = new Paint();
478 debugPaint.setStrokeWidth(Utilities.dpToPx(1));
479 for (int x = 0; x < mCountX; x++) {
480 for (int y = 0; y < mCountY; y++) {
481 if (!mOccupied.cells[x][y]) {
482 continue;
Adam Cohen482ed822012-03-02 14:15:13 -0800483 }
Tony Wickham12784902021-11-03 14:02:10 -0700484 targetCell[0] = x;
485 targetCell[1] = y;
Tony Wickham0ac045f2021-11-03 13:17:02 -0700486
Tony Wickham12784902021-11-03 14:02:10 -0700487 boolean canCreateFolder = canCreateFolder(getChildAt(x, y));
Tony Wickham0ac045f2021-11-03 13:17:02 -0700488 cellToRect(x, y, 1, 1, cellBounds);
489 cellBoundsWithSpacing.set(cellBounds);
490 cellBoundsWithSpacing.inset(-mBorderSpace.x / 2, -mBorderSpace.y / 2);
Tony Wickham3cfa5ed2021-11-03 13:20:43 -0700491 getWorkspaceCellVisualCenter(x, y, cellCenter);
Tony Wickham0ac045f2021-11-03 13:17:02 -0700492
493 canvas.save();
494 canvas.clipRect(cellBoundsWithSpacing);
495
496 // Draw reorder drag target.
497 debugPaint.setColor(Color.RED);
Sebastian Franco6e1024e2022-07-29 13:46:49 -0700498 canvas.drawCircle(cellCenter[0], cellCenter[1],
499 getReorderRadius(targetCell, 1, 1), debugPaint);
Tony Wickham0ac045f2021-11-03 13:17:02 -0700500
501 // Draw folder creation drag target.
502 if (canCreateFolder) {
503 debugPaint.setColor(Color.GREEN);
504 canvas.drawCircle(cellCenter[0], cellCenter[1],
Tony Wickham12784902021-11-03 14:02:10 -0700505 getFolderCreationRadius(targetCell), debugPaint);
Tony Wickham0ac045f2021-11-03 13:17:02 -0700506 }
507
508 canvas.restore();
Adam Cohen482ed822012-03-02 14:15:13 -0800509 }
510 }
511 }
512
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800513 for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
514 DelegatedCellDrawing cellDrawing = mDelegatedCellDrawings.get(i);
515 cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation);
Adam Cohenefca0272016-02-24 19:19:06 -0800516 canvas.save();
517 canvas.translate(mTempLocation[0], mTempLocation[1]);
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800518 cellDrawing.drawUnderItem(canvas);
Adam Cohenefca0272016-02-24 19:19:06 -0800519 canvas.restore();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700520 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700521
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800522 if (mFolderLeaveBehind.mDelegateCellX >= 0 && mFolderLeaveBehind.mDelegateCellY >= 0) {
523 cellToPoint(mFolderLeaveBehind.mDelegateCellX,
524 mFolderLeaveBehind.mDelegateCellY, mTempLocation);
Adam Cohenefca0272016-02-24 19:19:06 -0800525 canvas.save();
526 canvas.translate(mTempLocation[0], mTempLocation[1]);
Sunny Goyal19b93b72017-02-19 20:21:37 -0800527 mFolderLeaveBehind.drawLeaveBehind(canvas);
Adam Cohenefca0272016-02-24 19:19:06 -0800528 canvas.restore();
Adam Cohenc51934b2011-07-26 21:07:43 -0700529 }
Adam Cohen65086992020-02-19 08:40:49 -0800530
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800531 if (mVisualizeCells || mVisualizeDropLocation) {
Adam Cohen65086992020-02-19 08:40:49 -0800532 visualizeGrid(canvas);
533 }
534 }
535
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800536 /**
Tony Wickham12784902021-11-03 14:02:10 -0700537 * Returns whether dropping an icon on the given View can create (or add to) a folder.
538 */
539 private boolean canCreateFolder(View child) {
540 return child instanceof DraggableView
541 && ((DraggableView) child).getViewType() == DRAGGABLE_ICON;
542 }
543
544 /**
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800545 * Indicates the progress of the Workspace entering the SpringLoaded state; allows the
546 * CellLayout to update various visuals for this state.
547 *
548 * @param progress
549 */
550 public void setSpringLoadedProgress(float progress) {
551 if (Float.compare(progress, mSpringLoadedProgress) != 0) {
552 mSpringLoadedProgress = progress;
Federico Baron1028a722022-10-13 14:09:38 -0700553 if (!SHOW_HOME_GARDENING.get()) {
554 updateBgAlpha();
555 }
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800556 setGridAlpha(progress);
557 }
558 }
559
560 /**
561 * See setSpringLoadedProgress
562 * @return progress
563 */
564 public float getSpringLoadedProgress() {
565 return mSpringLoadedProgress;
566 }
567
568 private void updateBgAlpha() {
Adam Cohen0c4d2782021-04-29 15:56:13 -0700569 mBackground.setAlpha((int) (mSpringLoadedProgress * 255));
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800570 }
571
572 /**
573 * Set the progress of this page's scroll
574 *
575 * @param progress 0 if the screen is centered, +/-1 if it is to the right / left respectively
576 */
577 public void setScrollProgress(float progress) {
578 if (Float.compare(Math.abs(progress), mScrollProgress) != 0) {
579 mScrollProgress = Math.abs(progress);
Federico Baron1028a722022-10-13 14:09:38 -0700580 if (!SHOW_HOME_GARDENING.get()) {
581 updateBgAlpha();
582 }
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800583 }
584 }
585
586 private void setGridAlpha(float gridAlpha) {
587 if (Float.compare(gridAlpha, mGridAlpha) != 0) {
588 mGridAlpha = gridAlpha;
589 invalidate();
590 }
591 }
592
Adam Cohen65086992020-02-19 08:40:49 -0800593 protected void visualizeGrid(Canvas canvas) {
Adam Cohen0c4d2782021-04-29 15:56:13 -0700594 DeviceProfile dp = mActivity.getDeviceProfile();
Alex Chau51da2192022-05-20 13:32:10 +0100595 int paddingX = Math.min((mCellWidth - dp.iconSizePx) / 2, dp.gridVisualizationPaddingX);
596 int paddingY = Math.min((mCellHeight - dp.iconSizePx) / 2, dp.gridVisualizationPaddingY);
Adam Cohen0c4d2782021-04-29 15:56:13 -0700597 mVisualizeGridRect.set(paddingX, paddingY,
598 mCellWidth - paddingX,
599 mCellHeight - paddingY);
Adam Cohen65086992020-02-19 08:40:49 -0800600
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800601 mVisualizeGridPaint.setStrokeWidth(8);
602 int paintAlpha = (int) (120 * mGridAlpha);
603 mVisualizeGridPaint.setColor(ColorUtils.setAlphaComponent(mGridColor, paintAlpha));
Adam Cohen65086992020-02-19 08:40:49 -0800604
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800605 if (mVisualizeCells) {
606 for (int i = 0; i < mCountX; i++) {
607 for (int j = 0; j < mCountY; j++) {
Thales Lima78d00ad2021-09-30 11:29:06 +0100608 int transX = i * mCellWidth + (i * mBorderSpace.x) + getPaddingLeft()
Adam Cohen0c4d2782021-04-29 15:56:13 -0700609 + paddingX;
Thales Lima78d00ad2021-09-30 11:29:06 +0100610 int transY = j * mCellHeight + (j * mBorderSpace.y) + getPaddingTop()
Adam Cohen0c4d2782021-04-29 15:56:13 -0700611 + paddingY;
Adam Cohen65086992020-02-19 08:40:49 -0800612
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800613 mVisualizeGridRect.offsetTo(transX, transY);
614 mVisualizeGridPaint.setStyle(Paint.Style.FILL);
615 canvas.drawRoundRect(mVisualizeGridRect, mGridVisualizationRoundingRadius,
616 mGridVisualizationRoundingRadius, mVisualizeGridPaint);
617 }
618 }
619 }
Adam Cohen65086992020-02-19 08:40:49 -0800620
Federico Baron1028a722022-10-13 14:09:38 -0700621 if (mVisualizeDropLocation && !SHOW_HOME_GARDENING.get()) {
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800622 for (int i = 0; i < mDragOutlines.length; i++) {
623 final float alpha = mDragOutlineAlphas[i];
624 if (alpha <= 0) continue;
Adam Cohen65086992020-02-19 08:40:49 -0800625
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800626 mVisualizeGridPaint.setAlpha(255);
Sebastian Franco877088e2023-01-03 15:16:22 -0700627 int x = mDragOutlines[i].getCellX();
628 int y = mDragOutlines[i].getCellY();
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800629 int spanX = mDragOutlines[i].cellHSpan;
630 int spanY = mDragOutlines[i].cellVSpan;
631
Adam Cohened82e0d2021-07-21 17:24:12 -0700632 // TODO b/194414754 clean this up, reconcile with cellToRect
Adam Cohen0c4d2782021-04-29 15:56:13 -0700633 mVisualizeGridRect.set(paddingX, paddingY,
Thales Lima78d00ad2021-09-30 11:29:06 +0100634 mCellWidth * spanX + mBorderSpace.x * (spanX - 1) - paddingX,
635 mCellHeight * spanY + mBorderSpace.y * (spanY - 1) - paddingY);
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800636
Thales Lima78d00ad2021-09-30 11:29:06 +0100637 int transX = x * mCellWidth + (x * mBorderSpace.x)
Adam Cohen0c4d2782021-04-29 15:56:13 -0700638 + getPaddingLeft() + paddingX;
Thales Lima78d00ad2021-09-30 11:29:06 +0100639 int transY = y * mCellHeight + (y * mBorderSpace.y)
Adam Cohen0c4d2782021-04-29 15:56:13 -0700640 + getPaddingTop() + paddingY;
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800641
642 mVisualizeGridRect.offsetTo(transX, transY);
Adam Cohen65086992020-02-19 08:40:49 -0800643
644 mVisualizeGridPaint.setStyle(Paint.Style.STROKE);
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800645 mVisualizeGridPaint.setColor(Color.argb((int) (alpha),
646 Color.red(mGridColor), Color.green(mGridColor), Color.blue(mGridColor)));
Adam Cohen65086992020-02-19 08:40:49 -0800647
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800648 canvas.drawRoundRect(mVisualizeGridRect, mGridVisualizationRoundingRadius,
649 mGridVisualizationRoundingRadius, mVisualizeGridPaint);
Adam Cohen65086992020-02-19 08:40:49 -0800650 }
651 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700652 }
653
Adam Cohenefca0272016-02-24 19:19:06 -0800654 @Override
655 protected void dispatchDraw(Canvas canvas) {
656 super.dispatchDraw(canvas);
657
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800658 for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
659 DelegatedCellDrawing bg = mDelegatedCellDrawings.get(i);
660 cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation);
661 canvas.save();
662 canvas.translate(mTempLocation[0], mTempLocation[1]);
663 bg.drawOverItem(canvas);
664 canvas.restore();
Adam Cohenefca0272016-02-24 19:19:06 -0800665 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700666 }
667
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800668 /**
669 * Add Delegated cell drawing
670 */
671 public void addDelegatedCellDrawing(DelegatedCellDrawing bg) {
672 mDelegatedCellDrawings.add(bg);
Adam Cohenefca0272016-02-24 19:19:06 -0800673 }
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800674
675 /**
676 * Remove item from DelegatedCellDrawings
677 */
678 public void removeDelegatedCellDrawing(DelegatedCellDrawing bg) {
679 mDelegatedCellDrawings.remove(bg);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700680 }
681
Adam Cohenc51934b2011-07-26 21:07:43 -0700682 public void setFolderLeaveBehindCell(int x, int y) {
Adam Cohenefca0272016-02-24 19:19:06 -0800683 View child = getChildAt(x, y);
Sunny Goyalab770a12018-11-14 15:17:26 -0800684 mFolderLeaveBehind.setup(getContext(), mActivity, null,
Adam Cohenefca0272016-02-24 19:19:06 -0800685 child.getMeasuredWidth(), child.getPaddingTop());
686
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800687 mFolderLeaveBehind.mDelegateCellX = x;
688 mFolderLeaveBehind.mDelegateCellY = y;
Adam Cohenc51934b2011-07-26 21:07:43 -0700689 invalidate();
690 }
691
692 public void clearFolderLeaveBehind() {
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800693 mFolderLeaveBehind.mDelegateCellX = -1;
694 mFolderLeaveBehind.mDelegateCellY = -1;
Adam Cohenc51934b2011-07-26 21:07:43 -0700695 invalidate();
696 }
697
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700698 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700699 public boolean shouldDelayChildPressedState() {
700 return false;
701 }
702
Adam Cohen1462de32012-07-24 22:34:36 -0700703 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700704 try {
705 dispatchRestoreInstanceState(states);
706 } catch (IllegalArgumentException ex) {
Zak Cohen3eeb41d2020-02-14 14:15:13 -0800707 if (FeatureFlags.IS_STUDIO_BUILD) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700708 throw ex;
709 }
710 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
711 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
712 }
Adam Cohen1462de32012-07-24 22:34:36 -0700713 }
714
Michael Jurkae6235dd2011-10-04 15:02:05 -0700715 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700716 public void cancelLongPress() {
717 super.cancelLongPress();
718
719 // Cancel long press for all children
720 final int count = getChildCount();
721 for (int i = 0; i < count; i++) {
722 final View child = getChildAt(i);
723 child.cancelLongPress();
724 }
725 }
726
Michael Jurkadee05892010-07-27 10:01:56 -0700727 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
728 mInterceptTouchListener = listener;
729 }
730
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800731 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700732 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800733 }
734
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800735 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700736 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800737 }
738
Sunny Goyalc13403c2016-11-18 23:44:48 -0800739 public boolean acceptsWidget() {
740 return mContainerType == WORKSPACE;
Sunny Goyale9b651e2015-04-24 11:44:51 -0700741 }
742
Sebastian Francod4682992022-10-05 13:03:09 -0500743 /**
744 * Adds the given view to the CellLayout
745 *
746 * @param child view to add.
747 * @param index index of the CellLayout children where to add the view.
748 * @param childId id of the view.
749 * @param params represent the logic of the view on the CellLayout.
750 * @param markCells if the occupied cells should be marked or not
751 * @return if adding the view was successful
752 */
753 public boolean addViewToCellLayout(View child, int index, int childId,
754 CellLayoutLayoutParams params, boolean markCells) {
755 final CellLayoutLayoutParams lp = params;
Winson Chungaafa03c2010-06-11 17:34:16 -0700756
Andrew Flynnde38e422012-05-08 11:22:15 -0700757 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800758 if (child instanceof BubbleTextView) {
759 BubbleTextView bubbleChild = (BubbleTextView) child;
Jon Mirandaf1eae802017-10-04 11:23:33 -0700760 bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800761 }
762
Sunny Goyalc13403c2016-11-18 23:44:48 -0800763 child.setScaleX(mChildScale);
764 child.setScaleY(mChildScale);
Adam Cohen307fe232012-08-16 17:55:58 -0700765
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800766 // Generate an id for each view, this assumes we have at most 256x256 cells
767 // per workspace screen
Sebastian Franco877088e2023-01-03 15:16:22 -0700768 if (lp.getCellX() >= 0 && lp.getCellX() <= mCountX - 1
769 && lp.getCellY() >= 0 && lp.getCellY() <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700770 // If the horizontal or vertical span is set to -1, it is taken to
771 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700772 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
773 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800774
Winson Chungaafa03c2010-06-11 17:34:16 -0700775 child.setId(childId);
Tony Wickhama0628cc2015-10-14 15:23:04 -0700776 if (LOGD) {
777 Log.d(TAG, "Adding view to ShortcutsAndWidgetsContainer: " + child);
778 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700779 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700780
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700781 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700782
Winson Chungaafa03c2010-06-11 17:34:16 -0700783 return true;
784 }
785 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800786 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700787
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800788 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700789 public void removeAllViews() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700790 mOccupied.clear();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700791 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700792 }
793
794 @Override
795 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700796 if (mShortcutsAndWidgets.getChildCount() > 0) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700797 mOccupied.clear();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700798 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700799 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700800 }
801
802 @Override
803 public void removeView(View view) {
804 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700805 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700806 }
807
808 @Override
809 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700810 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
811 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700812 }
813
814 @Override
815 public void removeViewInLayout(View view) {
816 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700817 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700818 }
819
820 @Override
821 public void removeViews(int start, int count) {
822 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700823 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700824 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700825 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700826 }
827
828 @Override
829 public void removeViewsInLayout(int start, int count) {
830 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700831 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700832 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700833 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800834 }
835
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700836 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700837 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800838 * @param x X coordinate of the point
839 * @param y Y coordinate of the point
840 * @param result Array of 2 ints to hold the x and y coordinate of the cell
841 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700842 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700843 final int hStartPadding = getPaddingLeft();
844 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800845
Sebastian Francob57c0b22022-06-28 13:54:35 -0700846 result[0] = (x - hStartPadding) / (mCellWidth + mBorderSpace.x);
847 result[1] = (y - vStartPadding) / (mCellHeight + mBorderSpace.y);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800848
Adam Cohend22015c2010-07-26 22:02:18 -0700849 final int xAxis = mCountX;
850 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800851
852 if (result[0] < 0) result[0] = 0;
853 if (result[0] >= xAxis) result[0] = xAxis - 1;
854 if (result[1] < 0) result[1] = 0;
855 if (result[1] >= yAxis) result[1] = yAxis - 1;
856 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700857
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800858 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800859 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700860 *
861 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800862 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700863 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800864 * @param result Array of 2 ints to hold the x and y coordinate of the point
865 */
866 void cellToPoint(int cellX, int cellY, int[] result) {
Jon Miranda228877d2021-02-09 11:05:00 -0500867 cellToRect(cellX, cellY, 1, 1, mTempRect);
868 result[0] = mTempRect.left;
869 result[1] = mTempRect.top;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800870 }
871
Adam Cohene3e27a82011-04-15 12:07:39 -0700872 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800873 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700874 *
875 * @param cellX X coordinate of the cell
876 * @param cellY Y coordinate of the cell
877 *
878 * @param result Array of 2 ints to hold the x and y coordinate of the point
879 */
880 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700881 regionToCenterPoint(cellX, cellY, 1, 1, result);
882 }
883
884 /**
Tony Wickham0ac045f2021-11-03 13:17:02 -0700885 * Given a cell coordinate and span return the point that represents the center of the region
Adam Cohen47a876d2012-03-19 13:21:41 -0700886 *
887 * @param cellX X coordinate of the cell
888 * @param cellY Y coordinate of the cell
889 *
890 * @param result Array of 2 ints to hold the x and y coordinate of the point
891 */
892 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Jon Miranda228877d2021-02-09 11:05:00 -0500893 cellToRect(cellX, cellY, spanX, spanY, mTempRect);
894 result[0] = mTempRect.centerX();
895 result[1] = mTempRect.centerY();
Adam Cohen19f37922012-03-21 11:59:11 -0700896 }
897
Tony Wickham3cfa5ed2021-11-03 13:20:43 -0700898 /**
899 * Returns the distance between the given coordinate and the visual center of the given cell.
900 */
901 public float getDistanceFromWorkspaceCellVisualCenter(float x, float y, int[] cell) {
902 getWorkspaceCellVisualCenter(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700903 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800904 }
905
Tony Wickham3cfa5ed2021-11-03 13:20:43 -0700906 private void getWorkspaceCellVisualCenter(int cellX, int cellY, int[] outPoint) {
907 View child = getChildAt(cellX, cellY);
908 if (child instanceof DraggableView) {
909 DraggableView draggableChild = (DraggableView) child;
910 if (draggableChild.getViewType() == DRAGGABLE_ICON) {
911 cellToPoint(cellX, cellY, outPoint);
912 draggableChild.getWorkspaceVisualDragBounds(mTempRect);
913 mTempRect.offset(outPoint[0], outPoint[1]);
914 outPoint[0] = mTempRect.centerX();
915 outPoint[1] = mTempRect.centerY();
916 return;
917 }
918 }
919 cellToCenterPoint(cellX, cellY, outPoint);
920 }
921
Tony Wickham0ac045f2021-11-03 13:17:02 -0700922 /**
923 * Returns the max distance from the center of a cell that can accept a drop to create a folder.
924 */
Tony Wickham12784902021-11-03 14:02:10 -0700925 public float getFolderCreationRadius(int[] targetCell) {
Tony Wickham0ac045f2021-11-03 13:17:02 -0700926 DeviceProfile grid = mActivity.getDeviceProfile();
Tony Wickham12784902021-11-03 14:02:10 -0700927 float iconVisibleRadius = ICON_VISIBLE_AREA_FACTOR * grid.iconSizePx / 2;
928 // Halfway between reorder radius and icon.
Sebastian Franco6e1024e2022-07-29 13:46:49 -0700929 return (getReorderRadius(targetCell, 1, 1) + iconVisibleRadius) / 2;
Tony Wickham12784902021-11-03 14:02:10 -0700930 }
931
932 /**
933 * Returns the max distance from the center of a cell that will start to reorder on drag over.
934 */
Sebastian Franco6e1024e2022-07-29 13:46:49 -0700935 public float getReorderRadius(int[] targetCell, int spanX, int spanY) {
Tony Wickham12784902021-11-03 14:02:10 -0700936 int[] centerPoint = mTmpPoint;
937 getWorkspaceCellVisualCenter(targetCell[0], targetCell[1], centerPoint);
938
939 Rect cellBoundsWithSpacing = mTempRect;
Sebastian Franco6e1024e2022-07-29 13:46:49 -0700940 cellToRect(targetCell[0], targetCell[1], spanX, spanY, cellBoundsWithSpacing);
Tony Wickham12784902021-11-03 14:02:10 -0700941 cellBoundsWithSpacing.inset(-mBorderSpace.x / 2, -mBorderSpace.y / 2);
942
Sebastian Francoc8392ea2022-10-28 16:38:37 -0700943 if (canCreateFolder(getChildAt(targetCell[0], targetCell[1])) && spanX == 1 && spanY == 1) {
Tony Wickham12784902021-11-03 14:02:10 -0700944 // Take only the circle in the smaller dimension, to ensure we don't start reordering
945 // too soon before accepting a folder drop.
946 int minRadius = centerPoint[0] - cellBoundsWithSpacing.left;
947 minRadius = Math.min(minRadius, centerPoint[1] - cellBoundsWithSpacing.top);
948 minRadius = Math.min(minRadius, cellBoundsWithSpacing.right - centerPoint[0]);
949 minRadius = Math.min(minRadius, cellBoundsWithSpacing.bottom - centerPoint[1]);
950 return minRadius;
951 }
952 // Take up the entire cell, including space between this cell and the adjacent ones.
Sebastian Francoc8392ea2022-10-28 16:38:37 -0700953 // Multiply by span to scale radius
954 return (float) Math.hypot(spanX * cellBoundsWithSpacing.width() / 2f,
955 spanY * cellBoundsWithSpacing.height() / 2f);
Tony Wickham0ac045f2021-11-03 13:17:02 -0700956 }
957
Adam Cohenf9c184a2016-01-15 16:47:43 -0800958 public int getCellWidth() {
Romain Guy84f296c2009-11-04 15:00:44 -0800959 return mCellWidth;
960 }
961
Sunny Goyal0b754e52017-08-07 07:42:45 -0700962 public int getCellHeight() {
Romain Guy84f296c2009-11-04 15:00:44 -0800963 return mCellHeight;
964 }
965
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700966 public void setFixedSize(int width, int height) {
967 mFixedWidth = width;
968 mFixedHeight = height;
969 }
970
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800971 @Override
972 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800973 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800974 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700975 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
976 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700977 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
978 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Sunny Goyalae6e3182019-04-30 12:04:37 -0700979
Winson Chung11a1a532013-09-13 11:14:45 -0700980 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Thales Lima78d00ad2021-09-30 11:29:06 +0100981 int cw = DeviceProfile.calculateCellWidth(childWidthSize, mBorderSpace.x,
Jon Miranda228877d2021-02-09 11:05:00 -0500982 mCountX);
Thales Lima78d00ad2021-09-30 11:29:06 +0100983 int ch = DeviceProfile.calculateCellHeight(childHeightSize, mBorderSpace.y,
Jon Miranda228877d2021-02-09 11:05:00 -0500984 mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700985 if (cw != mCellWidth || ch != mCellHeight) {
986 mCellWidth = cw;
987 mCellHeight = ch;
Jon Miranda228877d2021-02-09 11:05:00 -0500988 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
Thales Lima78d00ad2021-09-30 11:29:06 +0100989 mBorderSpace);
Winson Chung11a1a532013-09-13 11:14:45 -0700990 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700991 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700992
Winson Chung2d75f122013-09-23 16:53:31 -0700993 int newWidth = childWidthSize;
994 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700995 if (mFixedWidth > 0 && mFixedHeight > 0) {
996 newWidth = mFixedWidth;
997 newHeight = mFixedHeight;
998 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800999 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
1000 }
1001
Sunny Goyal4fe5a372015-05-14 19:55:10 -07001002 mShortcutsAndWidgets.measure(
1003 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
1004 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
1005
1006 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
1007 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -07001008 if (mFixedWidth > 0 && mFixedHeight > 0) {
1009 setMeasuredDimension(maxWidth, maxHeight);
1010 } else {
1011 setMeasuredDimension(widthSize, heightSize);
1012 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001013 }
1014
1015 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -07001016 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Tony Wickham26b01422015-11-10 14:44:32 -08001017 int left = getPaddingLeft();
Sunny Goyal7ce471b2017-08-02 03:37:39 -07001018 left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Sunny Goyal7c786f72016-06-01 14:08:21 -07001019 int right = r - l - getPaddingRight();
Sunny Goyal7ce471b2017-08-02 03:37:39 -07001020 right -= (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Sunny Goyal7c786f72016-06-01 14:08:21 -07001021
Winson Chung38848ca2013-10-08 12:03:44 -07001022 int top = getPaddingTop();
Sunny Goyal7c786f72016-06-01 14:08:21 -07001023 int bottom = b - t - getPaddingBottom();
Sunny Goyal4fe5a372015-05-14 19:55:10 -07001024
Sunny Goyal7c786f72016-06-01 14:08:21 -07001025 // Expand the background drawing bounds by the padding baked into the background drawable
1026 mBackground.getPadding(mTempRect);
1027 mBackground.setBounds(
Jon Miranda28032002017-07-13 16:18:56 -07001028 left - mTempRect.left - getPaddingLeft(),
1029 top - mTempRect.top - getPaddingTop(),
1030 right + mTempRect.right + getPaddingRight(),
1031 bottom + mTempRect.bottom + getPaddingBottom());
Sunny Goyalae6e3182019-04-30 12:04:37 -07001032
Sunny Goyalc4d32012020-04-03 17:10:11 -07001033 mShortcutsAndWidgets.layout(left, top, right, bottom);
Sunny Goyal7c786f72016-06-01 14:08:21 -07001034 }
1035
Tony Wickhama501d492015-11-03 18:05:01 -08001036 /**
1037 * Returns the amount of space left over after subtracting padding and cells. This space will be
1038 * 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 -05001039 * width in {@link DeviceProfile#calculateCellWidth(int, int, int)}.
Tony Wickhama501d492015-11-03 18:05:01 -08001040 */
1041 public int getUnusedHorizontalSpace() {
Jon Miranda228877d2021-02-09 11:05:00 -05001042 return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth)
Thales Lima78d00ad2021-09-30 11:29:06 +01001043 - ((mCountX - 1) * mBorderSpace.x);
Tony Wickhama501d492015-11-03 18:05:01 -08001044 }
1045
Sunny Goyal2805e632015-05-20 15:35:32 -07001046 @Override
1047 protected boolean verifyDrawable(Drawable who) {
Sunny Goyal7ce471b2017-08-02 03:37:39 -07001048 return super.verifyDrawable(who) || (who == mBackground);
Sunny Goyal2805e632015-05-20 15:35:32 -07001049 }
1050
Michael Jurkaa52570f2012-03-20 03:18:20 -07001051 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -07001052 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -07001053 }
1054
Jon Miranda228877d2021-02-09 11:05:00 -05001055 public View getChildAt(int cellX, int cellY) {
1056 return mShortcutsAndWidgets.getChildAt(cellX, cellY);
Patrick Dubroy440c3602010-07-13 17:50:32 -07001057 }
1058
Adam Cohen76fc0852011-06-17 13:26:23 -07001059 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -08001060 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001061 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -08001062
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001063 if (clc.indexOfChild(child) != -1 && (child instanceof Reorderable)) {
Sebastian Francod4682992022-10-05 13:03:09 -05001064 final CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001065 final ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001066 final Reorderable item = (Reorderable) child;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001067
1068 // We cancel any existing animations
1069 if (mReorderAnimators.containsKey(lp)) {
1070 mReorderAnimators.get(lp).cancel();
1071 mReorderAnimators.remove(lp);
1072 }
1073
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001074
Adam Cohen482ed822012-03-02 14:15:13 -08001075 if (adjustOccupied) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001076 GridOccupancy occupied = permanent ? mOccupied : mTmpOccupied;
Sebastian Franco877088e2023-01-03 15:16:22 -07001077 occupied.markCells(lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan, false);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001078 occupied.markCells(cellX, cellY, lp.cellHSpan, lp.cellVSpan, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001079 }
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001080
1081 // Compute the new x and y position based on the new cellX and cellY
1082 // We leverage the actual layout logic in the layout params and hence need to modify
1083 // state and revert that state.
1084 final int oldX = lp.x;
1085 final int oldY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001086 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001087 if (permanent) {
Sunny Goyal669b71f2023-01-27 14:37:07 -08001088 lp.setCellX(cellX);
1089 lp.setCellY(cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001090 } else {
Sebastian Franco877088e2023-01-03 15:16:22 -07001091 lp.setTmpCellX(cellX);
1092 lp.setTmpCellY(cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001093 }
Jon Mirandae96798e2016-12-07 12:10:44 -08001094 clc.setupLp(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001095 final int newX = lp.x;
1096 final int newY = lp.y;
Adam Cohen76fc0852011-06-17 13:26:23 -07001097 lp.x = oldX;
1098 lp.y = oldY;
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001099 lp.isLockedToGrid = false;
1100 // End compute new x and y
1101
1102 item.getReorderPreviewOffset(mTmpPointF);
1103 final float initPreviewOffsetX = mTmpPointF.x;
1104 final float initPreviewOffsetY = mTmpPointF.y;
1105 final float finalPreviewOffsetX = newX - oldX;
1106 final float finalPreviewOffsetY = newY - oldY;
1107
Adam Cohen76fc0852011-06-17 13:26:23 -07001108
Adam Cohen482ed822012-03-02 14:15:13 -08001109 // Exit early if we're not actually moving the view
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001110 if (finalPreviewOffsetX == 0 && finalPreviewOffsetY == 0
1111 && initPreviewOffsetX == 0 && initPreviewOffsetY == 0) {
Adam Cohen482ed822012-03-02 14:15:13 -08001112 lp.isLockedToGrid = true;
1113 return true;
1114 }
1115
Sunny Goyal849c6a22018-08-08 16:33:46 -07001116 ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001117 va.setDuration(duration);
1118 mReorderAnimators.put(lp, va);
1119
1120 va.addUpdateListener(new AnimatorUpdateListener() {
1121 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001122 public void onAnimationUpdate(ValueAnimator animation) {
Jon Mirandae96798e2016-12-07 12:10:44 -08001123 float r = (Float) animation.getAnimatedValue();
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001124 float x = (1 - r) * initPreviewOffsetX + r * finalPreviewOffsetX;
1125 float y = (1 - r) * initPreviewOffsetY + r * finalPreviewOffsetY;
1126 item.setReorderPreviewOffset(x, y);
Adam Cohenbfbfd262011-06-13 16:55:12 -07001127 }
1128 });
Adam Cohen482ed822012-03-02 14:15:13 -08001129 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001130 boolean cancelled = false;
1131 public void onAnimationEnd(Animator animation) {
1132 // If the animation was cancelled, it means that another animation
1133 // has interrupted this one, and we don't want to lock the item into
1134 // place just yet.
1135 if (!cancelled) {
1136 lp.isLockedToGrid = true;
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001137 item.setReorderPreviewOffset(0, 0);
Adam Cohen482ed822012-03-02 14:15:13 -08001138 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001139 }
1140 if (mReorderAnimators.containsKey(lp)) {
1141 mReorderAnimators.remove(lp);
1142 }
1143 }
1144 public void onAnimationCancel(Animator animation) {
1145 cancelled = true;
1146 }
1147 });
Adam Cohen482ed822012-03-02 14:15:13 -08001148 va.setStartDelay(delay);
1149 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001150 return true;
1151 }
1152 return false;
1153 }
1154
Adam Cohenf7ca3b42021-02-22 11:03:58 -08001155 void visualizeDropLocation(int cellX, int cellY, int spanX, int spanY,
1156 DropTarget.DragObject dragObject) {
1157 if (mDragCell[0] != cellX || mDragCell[1] != cellY || mDragCellSpan[0] != spanX
1158 || mDragCellSpan[1] != spanY) {
Adam Cohen482ed822012-03-02 14:15:13 -08001159 mDragCell[0] = cellX;
1160 mDragCell[1] = cellY;
Adam Cohenf7ca3b42021-02-22 11:03:58 -08001161 mDragCellSpan[0] = spanX;
1162 mDragCellSpan[1] = spanY;
Steven Ng30dd1d62021-03-15 21:45:49 +00001163
Steven Nga999e222021-04-19 18:17:15 +01001164 // Apply color extraction on a widget when dragging.
1165 applyColorExtractionOnWidget(dragObject, mDragCell, spanX, spanY);
1166
Joe Onorato4be866d2010-10-10 11:26:02 -07001167 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001168 mDragOutlineAnims[oldIndex].animateOut();
1169 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Sunny Goyal106bf642015-07-16 12:18:06 -07001170
Sebastian Francod4682992022-10-05 13:03:09 -05001171 CellLayoutLayoutParams cell = mDragOutlines[mDragOutlineCurrent];
Sebastian Franco877088e2023-01-03 15:16:22 -07001172 cell.setCellX(cellX);
1173 cell.setCellY(cellY);
Adam Cohenf7ca3b42021-02-22 11:03:58 -08001174 cell.cellHSpan = spanX;
1175 cell.cellVSpan = spanY;
Adam Cohen65086992020-02-19 08:40:49 -08001176
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001177 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Adam Cohenf7ca3b42021-02-22 11:03:58 -08001178 invalidate();
Sunny Goyale78e3d72015-09-24 11:23:31 -07001179
1180 if (dragObject.stateAnnouncer != null) {
Sunny Goyalc13403c2016-11-18 23:44:48 -08001181 dragObject.stateAnnouncer.announce(getItemMoveDescription(cellX, cellY));
Sunny Goyale78e3d72015-09-24 11:23:31 -07001182 }
Adam Cohenf7ca3b42021-02-22 11:03:58 -08001183
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001184 }
1185 }
1186
Steven Ng30dd1d62021-03-15 21:45:49 +00001187 /** Applies the local color extraction to a dragging widget object. */
Steven Nga999e222021-04-19 18:17:15 +01001188 private void applyColorExtractionOnWidget(DropTarget.DragObject dragObject, int[] targetCell,
1189 int spanX, int spanY) {
Steven Ng30dd1d62021-03-15 21:45:49 +00001190 // Apply local extracted color if the DragView is an AppWidgetHostViewDrawable.
Steven Ng32427202021-04-19 18:12:12 +01001191 View view = dragObject.dragView.getContentView();
1192 if (view instanceof LauncherAppWidgetHostView) {
Andras Kloczl2dacbee2022-02-21 16:53:28 +00001193 int screenId = getWorkspace().getIdForScreen(this);
Steven Ng30dd1d62021-03-15 21:45:49 +00001194 cellToRect(targetCell[0], targetCell[1], spanX, spanY, mTempRect);
Jonathan Miranda21930da2021-05-03 18:44:13 +00001195
Sunny Goyal69a8eec2021-07-22 10:02:50 -07001196 ((LauncherAppWidgetHostView) view).handleDrag(mTempRect, this, screenId);
Steven Ng30dd1d62021-03-15 21:45:49 +00001197 }
1198 }
1199
Sunny Goyal726bee72018-03-05 12:54:24 -08001200 @SuppressLint("StringFormatMatches")
Sunny Goyalc13403c2016-11-18 23:44:48 -08001201 public String getItemMoveDescription(int cellX, int cellY) {
1202 if (mContainerType == HOTSEAT) {
1203 return getContext().getString(R.string.move_to_hotseat_position,
1204 Math.max(cellX, cellY) + 1);
1205 } else {
Shikha Malhotraf78da1b2022-04-11 10:23:18 +00001206 Workspace<?> workspace = getWorkspace();
Andras Kloczl2dacbee2022-02-21 16:53:28 +00001207 int row = cellY + 1;
1208 int col = workspace.mIsRtl ? mCountX - cellX : cellX + 1;
1209 int panelCount = workspace.getPanelCount();
Sebastian Franco930531f2022-06-16 16:49:11 -07001210 int screenId = workspace.getIdForScreen(this);
1211 int pageIndex = workspace.getPageIndexForScreenId(screenId);
Andras Kloczl2dacbee2022-02-21 16:53:28 +00001212 if (panelCount > 1) {
1213 // Increment the column if the target is on the right side of a two panel home
Andras Kloczl2dacbee2022-02-21 16:53:28 +00001214 col += (pageIndex % panelCount) * mCountX;
1215 }
Sebastian Franco930531f2022-06-16 16:49:11 -07001216 return getContext().getString(R.string.move_to_empty_cell_description, row, col,
1217 workspace.getPageDescription(pageIndex));
Sunny Goyalc13403c2016-11-18 23:44:48 -08001218 }
1219 }
1220
Shikha Malhotraf78da1b2022-04-11 10:23:18 +00001221 private Workspace<?> getWorkspace() {
Andras Kloczl2dacbee2022-02-21 16:53:28 +00001222 return Launcher.cast(mActivity).getWorkspace();
1223 }
1224
Adam Cohene0310962011-04-18 16:15:31 -07001225 public void clearDragOutlines() {
1226 final int oldIndex = mDragOutlineCurrent;
1227 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001228 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001229 }
1230
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001231 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001232 * Find a vacant area that will fit the given bounds nearest the requested
1233 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001234 *
Romain Guy51afc022009-05-04 18:03:43 -07001235 * @param pixelX The X location at which you want to search for a vacant area.
1236 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001237 * @param minSpanX The minimum horizontal span required
1238 * @param minSpanY The minimum vertical span required
1239 * @param spanX Horizontal span of the object.
1240 * @param spanY Vertical span of the object.
1241 * @param result Array in which to place the result, or null (in which case a new array will
1242 * be allocated)
1243 * @return The X, Y cell of a vacant area that can contain this object,
1244 * nearest the requested location.
1245 */
1246 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1247 int spanY, int[] result, int[] resultSpan) {
Sebastian Francob57c0b22022-06-28 13:54:35 -07001248 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, false,
Adam Cohend41fbf52012-02-16 23:53:59 -08001249 result, resultSpan);
1250 }
1251
Adam Cohend41fbf52012-02-16 23:53:59 -08001252 /**
1253 * Find a vacant area that will fit the given bounds nearest the requested
1254 * cell location. Uses Euclidean distance to score multiple vacant areas.
Sebastian Francob57c0b22022-06-28 13:54:35 -07001255 * @param relativeXPos The X location relative to the Cell layout at which you want to search
1256 * for a vacant area.
1257 * @param relativeYPos The Y location relative to the Cell layout at which you want to search
1258 * for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001259 * @param minSpanX The minimum horizontal span required
1260 * @param minSpanY The minimum vertical span required
1261 * @param spanX Horizontal span of the object.
1262 * @param spanY Vertical span of the object.
1263 * @param ignoreOccupied If true, the result can be an occupied cell
1264 * @param result Array in which to place the result, or null (in which case a new array will
1265 * be allocated)
1266 * @return The X, Y cell of a vacant area that can contain this object,
1267 * nearest the requested location.
1268 */
Sebastian Francob57c0b22022-06-28 13:54:35 -07001269 private int[] findNearestArea(int relativeXPos, int relativeYPos, int minSpanX, int minSpanY,
1270 int spanX, int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Sebastian Francob57c0b22022-06-28 13:54:35 -07001271 // For items with a spanX / spanY > 1, the passed in point (relativeXPos, relativeYPos)
1272 // corresponds to the center of the item, but we are searching based on the top-left cell,
1273 // so we translate the point over to correspond to the top-left.
1274 relativeXPos = (int) (relativeXPos - (mCellWidth + mBorderSpace.x) * (spanX - 1) / 2f);
1275 relativeYPos = (int) (relativeYPos - (mCellHeight + mBorderSpace.y) * (spanY - 1) / 2f);
Adam Cohene3e27a82011-04-15 12:07:39 -07001276
Jeff Sharkey70864282009-04-07 21:08:40 -07001277 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001278 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001279 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001280 final Rect bestRect = new Rect(-1, -1, -1, -1);
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001281 final Stack<Rect> validRegions = new Stack<>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001282
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001283 final int countX = mCountX;
1284 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001285
Adam Cohend41fbf52012-02-16 23:53:59 -08001286 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1287 spanX < minSpanX || spanY < minSpanY) {
1288 return bestXY;
1289 }
1290
1291 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001292 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001293 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1294 int ySize = -1;
1295 int xSize = -1;
Sebastian Francob57c0b22022-06-28 13:54:35 -07001296 if (!ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001297 // First, let's see if this thing fits anywhere
1298 for (int i = 0; i < minSpanX; i++) {
1299 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001300 if (mOccupied.cells[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001301 continue inner;
1302 }
Michael Jurkac28de512010-08-13 11:27:44 -07001303 }
1304 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001305 xSize = minSpanX;
1306 ySize = minSpanY;
1307
1308 // We know that the item will fit at _some_ acceptable size, now let's see
1309 // how big we can make it. We'll alternate between incrementing x and y spans
1310 // until we hit a limit.
1311 boolean incX = true;
1312 boolean hitMaxX = xSize >= spanX;
1313 boolean hitMaxY = ySize >= spanY;
1314 while (!(hitMaxX && hitMaxY)) {
1315 if (incX && !hitMaxX) {
1316 for (int j = 0; j < ySize; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001317 if (x + xSize > countX -1 || mOccupied.cells[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001318 // We can't move out horizontally
1319 hitMaxX = true;
1320 }
1321 }
1322 if (!hitMaxX) {
1323 xSize++;
1324 }
1325 } else if (!hitMaxY) {
1326 for (int i = 0; i < xSize; i++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001327 if (y + ySize > countY - 1 || mOccupied.cells[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001328 // We can't move out vertically
1329 hitMaxY = true;
1330 }
1331 }
1332 if (!hitMaxY) {
1333 ySize++;
1334 }
1335 }
1336 hitMaxX |= xSize >= spanX;
1337 hitMaxY |= ySize >= spanY;
1338 incX = !incX;
1339 }
Michael Jurkac28de512010-08-13 11:27:44 -07001340 }
Sunny Goyal2805e632015-05-20 15:35:32 -07001341 final int[] cellXY = mTmpPoint;
Adam Cohene3e27a82011-04-15 12:07:39 -07001342 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001343
Adam Cohend41fbf52012-02-16 23:53:59 -08001344 // We verify that the current rect is not a sub-rect of any of our previous
1345 // candidates. In this case, the current rect is disqualified in favour of the
1346 // containing rect.
Sebastian Franco4a922672022-10-27 16:42:24 -07001347 Rect currentRect = new Rect(x, y, x + xSize, y + ySize);
Adam Cohend41fbf52012-02-16 23:53:59 -08001348 boolean contained = false;
1349 for (Rect r : validRegions) {
1350 if (r.contains(currentRect)) {
1351 contained = true;
1352 break;
1353 }
1354 }
1355 validRegions.push(currentRect);
Sebastian Francob57c0b22022-06-28 13:54:35 -07001356 double distance = Math.hypot(cellXY[0] - relativeXPos, cellXY[1] - relativeYPos);
Adam Cohen482ed822012-03-02 14:15:13 -08001357
Adam Cohend41fbf52012-02-16 23:53:59 -08001358 if ((distance <= bestDistance && !contained) ||
1359 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001360 bestDistance = distance;
1361 bestXY[0] = x;
1362 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001363 if (resultSpan != null) {
1364 resultSpan[0] = xSize;
1365 resultSpan[1] = ySize;
1366 }
1367 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001368 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001369 }
1370 }
1371
Adam Cohenc0dcf592011-06-01 15:30:43 -07001372 // Return -1, -1 if no suitable location found
1373 if (bestDistance == Double.MAX_VALUE) {
1374 bestXY[0] = -1;
1375 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001376 }
Adam Cohenc0dcf592011-06-01 15:30:43 -07001377 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001378 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001379
Adam Cohen482ed822012-03-02 14:15:13 -08001380 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001381 mTmpOccupied.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001382
Michael Jurkaa52570f2012-03-20 03:18:20 -07001383 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001384 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001385 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001386 if (child == dragView) continue;
Sebastian Francod4682992022-10-05 13:03:09 -05001387 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001388 CellAndSpan c = solution.map.get(child);
1389 if (c != null) {
Sebastian Franco877088e2023-01-03 15:16:22 -07001390 lp.setTmpCellX(c.cellX);
1391 lp.setTmpCellY(c.cellY);
Adam Cohen8baab352012-03-20 17:39:21 -07001392 lp.cellHSpan = c.spanX;
1393 lp.cellVSpan = c.spanY;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001394 mTmpOccupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001395 }
1396 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001397 mTmpOccupied.markCells(solution, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001398 }
1399
1400 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
1401 commitDragView) {
1402
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001403 GridOccupancy occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
1404 occupied.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001405
Michael Jurkaa52570f2012-03-20 03:18:20 -07001406 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001407 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001408 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001409 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001410 CellAndSpan c = solution.map.get(child);
1411 if (c != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001412 animateChildToPosition(child, c.cellX, c.cellY, REORDER_ANIMATION_DURATION, 0,
Adam Cohen19f37922012-03-21 11:59:11 -07001413 DESTRUCTIVE_REORDER, false);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001414 occupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001415 }
1416 }
1417 if (commitDragView) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001418 occupied.markCells(solution, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001419 }
1420 }
1421
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001422
1423 // This method starts or changes the reorder preview animations
1424 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
Adam Cohen65086992020-02-19 08:40:49 -08001425 View dragView, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07001426 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07001427 for (int i = 0; i < childCount; i++) {
1428 View child = mShortcutsAndWidgets.getChildAt(i);
1429 if (child == dragView) continue;
1430 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001431 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
1432 != null && !solution.intersectingViews.contains(child);
1433
Adam Cohend9162062020-03-24 16:35:35 -07001434
Sebastian Francod4682992022-10-05 13:03:09 -05001435 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
Adam Cohend9162062020-03-24 16:35:35 -07001436 if (c != null && !skip && (child instanceof Reorderable)) {
1437 ReorderPreviewAnimation rha = new ReorderPreviewAnimation((Reorderable) child,
Sebastian Franco877088e2023-01-03 15:16:22 -07001438 mode, lp.getCellX(), lp.getCellY(), c.cellX, c.cellY, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07001439 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07001440 }
1441 }
1442 }
1443
Sunny Goyal849c6a22018-08-08 16:33:46 -07001444 private static final Property<ReorderPreviewAnimation, Float> ANIMATION_PROGRESS =
1445 new Property<ReorderPreviewAnimation, Float>(float.class, "animationProgress") {
1446 @Override
1447 public Float get(ReorderPreviewAnimation anim) {
1448 return anim.animationProgress;
1449 }
1450
1451 @Override
1452 public void set(ReorderPreviewAnimation anim, Float progress) {
1453 anim.setAnimationProgress(progress);
1454 }
1455 };
1456
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001457 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07001458 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001459 class ReorderPreviewAnimation {
Adam Cohend9162062020-03-24 16:35:35 -07001460 final Reorderable child;
Adam Cohend024f982012-05-23 18:26:45 -07001461 float finalDeltaX;
1462 float finalDeltaY;
1463 float initDeltaX;
1464 float initDeltaY;
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001465 final float finalScale;
Adam Cohend024f982012-05-23 18:26:45 -07001466 float initScale;
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001467 final int mode;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001468 boolean repeating = false;
1469 private static final int PREVIEW_DURATION = 300;
1470 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
1471
Jon Miranda21266912016-12-19 14:12:05 -08001472 private static final float CHILD_DIVIDEND = 4.0f;
1473
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001474 public static final int MODE_HINT = 0;
1475 public static final int MODE_PREVIEW = 1;
1476
Sunny Goyal849c6a22018-08-08 16:33:46 -07001477 float animationProgress = 0;
Sunny Goyalf0b6db72018-08-13 16:10:14 -07001478 ValueAnimator a;
Adam Cohen19f37922012-03-21 11:59:11 -07001479
Adam Cohend9162062020-03-24 16:35:35 -07001480 public ReorderPreviewAnimation(Reorderable child, int mode, int cellX0, int cellY0,
1481 int cellX1, int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07001482 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
1483 final int x0 = mTmpPoint[0];
1484 final int y0 = mTmpPoint[1];
1485 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
1486 final int x1 = mTmpPoint[0];
1487 final int y1 = mTmpPoint[1];
1488 final int dX = x1 - x0;
1489 final int dY = y1 - y0;
Jon Miranda21266912016-12-19 14:12:05 -08001490
1491 this.child = child;
1492 this.mode = mode;
Adam Cohend9162062020-03-24 16:35:35 -07001493 finalDeltaX = 0;
1494 finalDeltaY = 0;
Adam Cohen65086992020-02-19 08:40:49 -08001495
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001496 child.getReorderBounceOffset(mTmpPointF);
Adam Cohend9162062020-03-24 16:35:35 -07001497 initDeltaX = mTmpPointF.x;
1498 initDeltaY = mTmpPointF.y;
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001499 initScale = child.getReorderBounceScale();
Adam Cohend9162062020-03-24 16:35:35 -07001500 finalScale = mChildScale - (CHILD_DIVIDEND / child.getView().getWidth()) * initScale;
1501
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001502 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07001503 if (dX == dY && dX == 0) {
1504 } else {
1505 if (dY == 0) {
Adam Cohend9162062020-03-24 16:35:35 -07001506 finalDeltaX = -dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07001507 } else if (dX == 0) {
Adam Cohend9162062020-03-24 16:35:35 -07001508 finalDeltaY = -dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07001509 } else {
1510 double angle = Math.atan( (float) (dY) / dX);
Adam Cohend9162062020-03-24 16:35:35 -07001511 finalDeltaX = (int) (-dir * Math.signum(dX)
1512 * Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
1513 finalDeltaY = (int) (-dir * Math.signum(dY)
1514 * Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07001515 }
1516 }
Jon Miranda21266912016-12-19 14:12:05 -08001517 }
1518
Adam Cohend9162062020-03-24 16:35:35 -07001519 void setInitialAnimationValuesToBaseline() {
1520 initScale = mChildScale;
1521 initDeltaX = 0;
1522 initDeltaY = 0;
Adam Cohen19f37922012-03-21 11:59:11 -07001523 }
1524
Adam Cohend024f982012-05-23 18:26:45 -07001525 void animate() {
Adam Cohend9162062020-03-24 16:35:35 -07001526 boolean noMovement = (finalDeltaX == 0) && (finalDeltaY == 0);
Jon Miranda21266912016-12-19 14:12:05 -08001527
Adam Cohen19f37922012-03-21 11:59:11 -07001528 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001529 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohen19f37922012-03-21 11:59:11 -07001530 mShakeAnimators.remove(child);
Adam Cohend9162062020-03-24 16:35:35 -07001531
Jon Miranda21266912016-12-19 14:12:05 -08001532 if (noMovement) {
Adam Cohend9162062020-03-24 16:35:35 -07001533 // A previous animation for this item exists, and no new animation will exist.
1534 // Finish the old animation smoothly.
1535 oldAnimation.finishAnimation();
Adam Cohene7587d22012-05-24 18:50:02 -07001536 return;
Adam Cohend9162062020-03-24 16:35:35 -07001537 } else {
1538 // A previous animation for this item exists, and a new one will exist. Stop
1539 // the old animation in its tracks, and proceed with the new one.
1540 oldAnimation.cancel();
Adam Cohene7587d22012-05-24 18:50:02 -07001541 }
Adam Cohen19f37922012-03-21 11:59:11 -07001542 }
Jon Miranda21266912016-12-19 14:12:05 -08001543 if (noMovement) {
Adam Cohen19f37922012-03-21 11:59:11 -07001544 return;
1545 }
Adam Cohend9162062020-03-24 16:35:35 -07001546
Sunny Goyal849c6a22018-08-08 16:33:46 -07001547 ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS, 0, 1);
Adam Cohene7587d22012-05-24 18:50:02 -07001548 a = va;
Tony Wickham9e0702f2015-09-02 14:45:39 -07001549
1550 // Animations are disabled in power save mode, causing the repeated animation to jump
1551 // spastically between beginning and end states. Since this looks bad, we don't repeat
1552 // the animation in power save mode.
Sunny Goyaleaf7a952020-07-29 16:54:20 -07001553 if (areAnimatorsEnabled()) {
Tony Wickham9e0702f2015-09-02 14:45:39 -07001554 va.setRepeatMode(ValueAnimator.REVERSE);
1555 va.setRepeatCount(ValueAnimator.INFINITE);
1556 }
1557
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001558 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07001559 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07001560 va.addListener(new AnimatorListenerAdapter() {
1561 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07001562 // We make sure to end only after a full period
Adam Cohend9162062020-03-24 16:35:35 -07001563 setInitialAnimationValuesToBaseline();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001564 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07001565 }
1566 });
Adam Cohen19f37922012-03-21 11:59:11 -07001567 mShakeAnimators.put(child, this);
1568 va.start();
1569 }
1570
Sunny Goyal849c6a22018-08-08 16:33:46 -07001571 private void setAnimationProgress(float progress) {
1572 animationProgress = progress;
1573 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : animationProgress;
1574 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
1575 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001576 child.setReorderBounceOffset(x, y);
Sunny Goyal849c6a22018-08-08 16:33:46 -07001577 float s = animationProgress * finalScale + (1 - animationProgress) * initScale;
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001578 child.setReorderBounceScale(s);
Sunny Goyal849c6a22018-08-08 16:33:46 -07001579 }
1580
Adam Cohend024f982012-05-23 18:26:45 -07001581 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07001582 if (a != null) {
1583 a.cancel();
1584 }
Adam Cohen19f37922012-03-21 11:59:11 -07001585 }
Adam Cohene7587d22012-05-24 18:50:02 -07001586
Adam Cohend9162062020-03-24 16:35:35 -07001587 /**
1588 * Smoothly returns the item to its baseline position / scale
1589 */
1590 @Thunk void finishAnimation() {
Adam Cohene7587d22012-05-24 18:50:02 -07001591 if (a != null) {
1592 a.cancel();
1593 }
Brandon Keely50e6e562012-05-08 16:28:49 -07001594
Adam Cohend9162062020-03-24 16:35:35 -07001595 setInitialAnimationValuesToBaseline();
1596 ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS,
1597 animationProgress, 0);
1598 a = va;
Sunny Goyalf0b6db72018-08-13 16:10:14 -07001599 a.setInterpolator(DEACCEL_1_5);
Adam Cohend9162062020-03-24 16:35:35 -07001600 a.setDuration(REORDER_ANIMATION_DURATION);
Sunny Goyal5d2fc322015-07-06 22:52:49 -07001601 a.start();
Brandon Keely50e6e562012-05-08 16:28:49 -07001602 }
Adam Cohen19f37922012-03-21 11:59:11 -07001603 }
1604
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001605 private void completeAndClearReorderPreviewAnimations() {
1606 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Adam Cohend9162062020-03-24 16:35:35 -07001607 a.finishAnimation();
Adam Cohen19f37922012-03-21 11:59:11 -07001608 }
1609 mShakeAnimators.clear();
1610 }
1611
Sunny Goyal711c5962021-06-23 12:36:18 -07001612 private void commitTempPlacement(View dragView) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001613 mTmpOccupied.copyTo(mOccupied);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07001614
Andras Kloczl2dacbee2022-02-21 16:53:28 +00001615 int screenId = getWorkspace().getIdForScreen(this);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07001616 int container = Favorites.CONTAINER_DESKTOP;
1617
Sunny Goyalc13403c2016-11-18 23:44:48 -08001618 if (mContainerType == HOTSEAT) {
Sunny Goyalaa8ef112015-06-12 20:04:41 -07001619 screenId = -1;
1620 container = Favorites.CONTAINER_HOTSEAT;
1621 }
1622
Michael Jurkaa52570f2012-03-20 03:18:20 -07001623 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001624 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07001625 View child = mShortcutsAndWidgets.getChildAt(i);
Sebastian Francod4682992022-10-05 13:03:09 -05001626 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
Adam Cohenea889a22012-03-27 16:45:39 -07001627 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07001628 // We do a null check here because the item info can be null in the case of the
1629 // AllApps button in the hotseat.
Sunny Goyal711c5962021-06-23 12:36:18 -07001630 if (info != null && child != dragView) {
Sunny Goyal669b71f2023-01-27 14:37:07 -08001631 CellPos presenterPos = mActivity.getCellPosMapper().mapModelToPresenter(info);
1632 final boolean requiresDbUpdate = (presenterPos.cellX != lp.getTmpCellX()
1633 || presenterPos.cellY != lp.getTmpCellY() || info.spanX != lp.cellHSpan
1634 || info.spanY != lp.cellVSpan || presenterPos.screenId != screenId);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07001635
Sebastian Franco877088e2023-01-03 15:16:22 -07001636 lp.setCellX(lp.getTmpCellX());
Sebastian Franco877088e2023-01-03 15:16:22 -07001637 lp.setCellY(lp.getTmpCellY());
Sunny Goyalaa8ef112015-06-12 20:04:41 -07001638 if (requiresDbUpdate) {
Sunny Goyalab770a12018-11-14 15:17:26 -08001639 Launcher.cast(mActivity).getModelWriter().modifyItemInDatabase(info, container,
Sunny Goyal669b71f2023-01-27 14:37:07 -08001640 screenId, lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07001641 }
Adam Cohen2acce882012-03-28 19:03:19 -07001642 }
Adam Cohen482ed822012-03-02 14:15:13 -08001643 }
1644 }
1645
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001646 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001647 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001648 for (int i = 0; i < childCount; i++) {
Sebastian Francod4682992022-10-05 13:03:09 -05001649 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) mShortcutsAndWidgets.getChildAt(
1650 i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08001651 lp.useTmpCoords = useTempCoords;
1652 }
1653 }
1654
Sebastián Francof9a6ac22022-11-15 22:56:37 +00001655 /**
1656 * Returns a "reorder" where we simply drop the item in the closest empty space, without moving
1657 * any other item in the way.
1658 *
1659 * @param pixelX X coordinate in pixels in the screen
1660 * @param pixelY Y coordinate in pixels in the screen
1661 * @param spanX horizontal cell span
1662 * @param spanY vertical cell span
1663 * @return the configuration that represents the found reorder
1664 */
1665 private ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX,
1666 int minSpanY, int spanX, int spanY) {
1667 ItemConfiguration solution = new ItemConfiguration();
Adam Cohen482ed822012-03-02 14:15:13 -08001668 int[] result = new int[2];
1669 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001670 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08001671 resultSpan);
1672 if (result[0] >= 0 && result[1] >= 0) {
1673 copyCurrentStateToSolution(solution, false);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001674 solution.cellX = result[0];
1675 solution.cellY = result[1];
1676 solution.spanX = resultSpan[0];
1677 solution.spanY = resultSpan[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001678 solution.isSolution = true;
1679 } else {
1680 solution.isSolution = false;
1681 }
1682 return solution;
1683 }
1684
Adam Cohen19f37922012-03-21 11:59:11 -07001685 // For a given cell and span, fetch the set of views intersecting the region.
1686 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
1687 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
1688 if (boundingRect != null) {
1689 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
1690 }
1691 intersectingViews.clear();
1692 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1693 Rect r1 = new Rect();
1694 final int count = mShortcutsAndWidgets.getChildCount();
1695 for (int i = 0; i < count; i++) {
1696 View child = mShortcutsAndWidgets.getChildAt(i);
1697 if (child == dragView) continue;
Sebastian Francod4682992022-10-05 13:03:09 -05001698 CellLayoutLayoutParams
1699 lp = (CellLayoutLayoutParams) child.getLayoutParams();
Sebastian Franco877088e2023-01-03 15:16:22 -07001700 r1.set(lp.getCellX(), lp.getCellY(), lp.getCellX() + lp.cellHSpan,
1701 lp.getCellY() + lp.cellVSpan);
Adam Cohen19f37922012-03-21 11:59:11 -07001702 if (Rect.intersects(r0, r1)) {
1703 mIntersectingViews.add(child);
1704 if (boundingRect != null) {
1705 boundingRect.union(r1);
1706 }
1707 }
1708 }
1709 }
1710
1711 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
1712 View dragView, int[] result) {
Sebastián Francof9a6ac22022-11-15 22:56:37 +00001713 result = findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY, result);
Adam Cohen19f37922012-03-21 11:59:11 -07001714 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
1715 mIntersectingViews);
1716 return !mIntersectingViews.isEmpty();
1717 }
1718
1719 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001720 completeAndClearReorderPreviewAnimations();
1721 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
1722 final int count = mShortcutsAndWidgets.getChildCount();
1723 for (int i = 0; i < count; i++) {
1724 View child = mShortcutsAndWidgets.getChildAt(i);
Sebastian Francod4682992022-10-05 13:03:09 -05001725 CellLayoutLayoutParams
1726 lp = (CellLayoutLayoutParams) child.getLayoutParams();
Sebastian Franco877088e2023-01-03 15:16:22 -07001727 if (lp.getTmpCellX() != lp.getCellX() || lp.getTmpCellY() != lp.getCellY()) {
1728 lp.setTmpCellX(lp.getCellX());
1729 lp.setTmpCellY(lp.getCellY());
1730 animateChildToPosition(child, lp.getCellX(), lp.getCellY(),
1731 REORDER_ANIMATION_DURATION, 0, false, false);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001732 }
Adam Cohen19f37922012-03-21 11:59:11 -07001733 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001734 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07001735 }
Adam Cohen19f37922012-03-21 11:59:11 -07001736 }
1737
Adam Cohenbebf0422012-04-11 18:06:28 -07001738 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
1739 View dragView, int[] direction, boolean commit) {
1740 int[] pixelXY = new int[2];
1741 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
1742
1743 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001744 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Sebastian Francof153d912022-04-22 16:15:27 -05001745 spanX, spanY, direction, dragView, true, new ItemConfiguration());
Adam Cohenbebf0422012-04-11 18:06:28 -07001746
1747 setUseTempCoords(true);
1748 if (swapSolution != null && swapSolution.isSolution) {
1749 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
1750 // committing anything or animating anything as we just want to determine if a solution
1751 // exists
1752 copySolutionToTempState(swapSolution, dragView);
1753 setItemPlacementDirty(true);
1754 animateItemsToSolution(swapSolution, dragView, commit);
1755
1756 if (commit) {
Sunny Goyal711c5962021-06-23 12:36:18 -07001757 commitTempPlacement(null);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001758 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07001759 setItemPlacementDirty(false);
1760 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001761 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
Adam Cohen65086992020-02-19 08:40:49 -08001762 ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07001763 }
1764 mShortcutsAndWidgets.requestLayout();
1765 }
1766 return swapSolution.isSolution;
1767 }
1768
Sebastian Franco9cab1c32022-10-25 17:28:54 -07001769 /**
1770 * Find a vacant area that will fit the given bounds nearest the requested
1771 * cell location, and will also weigh in a suggested direction vector of the
1772 * desired location. This method computers distance based on unit grid distances,
1773 * not pixel distances.
1774 *
1775 * @param cellX The X cell nearest to which you want to search for a vacant area.
1776 * @param cellY The Y cell nearest which you want to search for a vacant area.
1777 * @param spanX Horizontal span of the object.
1778 * @param spanY Vertical span of the object.
1779 * @param direction The favored direction in which the views should move from x, y
1780 * @param occupied The array which represents which cells in the CellLayout are occupied
1781 * @param blockOccupied The array which represents which cells in the specified block (cellX,
1782 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
1783 * @param result Array in which to place the result, or null (in which case a new array will
1784 * be allocated)
1785 * @return The X, Y cell of a vacant area that can contain this object,
1786 * nearest the requested location.
1787 */
1788 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
1789 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
1790 // Keep track of best-scoring drop area
1791 final int[] bestXY = result != null ? result : new int[2];
1792 float bestDistance = Float.MAX_VALUE;
1793 int bestDirectionScore = Integer.MIN_VALUE;
Adam Cohen482ed822012-03-02 14:15:13 -08001794
Sebastian Franco9cab1c32022-10-25 17:28:54 -07001795 final int countX = mCountX;
1796 final int countY = mCountY;
1797
1798 for (int y = 0; y < countY - (spanY - 1); y++) {
1799 inner:
1800 for (int x = 0; x < countX - (spanX - 1); x++) {
1801 // First, let's see if this thing fits anywhere
1802 for (int i = 0; i < spanX; i++) {
1803 for (int j = 0; j < spanY; j++) {
1804 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
1805 continue inner;
1806 }
1807 }
1808 }
1809
1810 float distance = (float) Math.hypot(x - cellX, y - cellY);
1811 int[] curDirection = mTmpPoint;
1812 computeDirectionVector(x - cellX, y - cellY, curDirection);
1813 // The direction score is just the dot product of the two candidate direction
1814 // and that passed in.
1815 int curDirectionScore = direction[0] * curDirection[0] +
1816 direction[1] * curDirection[1];
1817 if (Float.compare(distance, bestDistance) < 0 ||
1818 (Float.compare(distance, bestDistance) == 0
1819 && curDirectionScore > bestDirectionScore)) {
1820 bestDistance = distance;
1821 bestDirectionScore = curDirectionScore;
1822 bestXY[0] = x;
1823 bestXY[1] = y;
1824 }
Adam Cohen19f37922012-03-21 11:59:11 -07001825 }
Adam Cohen19f37922012-03-21 11:59:11 -07001826 }
1827
Sebastian Franco9cab1c32022-10-25 17:28:54 -07001828 // Return -1, -1 if no suitable location found
1829 if (bestDistance == Float.MAX_VALUE) {
1830 bestXY[0] = -1;
1831 bestXY[1] = -1;
Sebastian Franco53a15a42022-10-25 17:28:54 -07001832 }
Sebastian Franco9cab1c32022-10-25 17:28:54 -07001833 return bestXY;
1834 }
1835
1836 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
1837 int[] direction, ItemConfiguration currentState) {
1838 CellAndSpan c = currentState.map.get(v);
1839 boolean success = false;
1840 mTmpOccupied.markCells(c, false);
1841 mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
1842
1843 findNearestArea(c.cellX, c.cellY, c.spanX, c.spanY, direction,
1844 mTmpOccupied.cells, null, mTempLocation);
1845
1846 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
1847 c.cellX = mTempLocation[0];
1848 c.cellY = mTempLocation[1];
1849 success = true;
1850 }
1851 mTmpOccupied.markCells(c, true);
1852 return success;
1853 }
1854
1855 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1856 int[] direction, View dragView, ItemConfiguration currentState) {
1857
1858 ViewCluster cluster = new ViewCluster(views, currentState);
1859 Rect clusterRect = cluster.getBoundingRect();
1860 int whichEdge;
1861 int pushDistance;
1862 boolean fail = false;
1863
1864 // Determine the edge of the cluster that will be leading the push and how far
1865 // the cluster must be shifted.
1866 if (direction[0] < 0) {
1867 whichEdge = ViewCluster.LEFT;
1868 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
1869 } else if (direction[0] > 0) {
1870 whichEdge = ViewCluster.RIGHT;
1871 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1872 } else if (direction[1] < 0) {
1873 whichEdge = ViewCluster.TOP;
1874 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1875 } else {
1876 whichEdge = ViewCluster.BOTTOM;
1877 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
1878 }
1879
1880 // Break early for invalid push distance.
1881 if (pushDistance <= 0) {
1882 return false;
1883 }
1884
1885 // Mark the occupied state as false for the group of views we want to move.
1886 for (View v: views) {
1887 CellAndSpan c = currentState.map.get(v);
1888 mTmpOccupied.markCells(c, false);
1889 }
1890
1891 // We save the current configuration -- if we fail to find a solution we will revert
1892 // to the initial state. The process of finding a solution modifies the configuration
1893 // in place, hence the need for revert in the failure case.
1894 currentState.save();
1895
1896 // The pushing algorithm is simplified by considering the views in the order in which
1897 // they would be pushed by the cluster. For example, if the cluster is leading with its
1898 // left edge, we consider sort the views by their right edge, from right to left.
1899 cluster.sortConfigurationForEdgePush(whichEdge);
1900
1901 while (pushDistance > 0 && !fail) {
1902 for (View v: currentState.sortedViews) {
1903 // For each view that isn't in the cluster, we see if the leading edge of the
1904 // cluster is contacting the edge of that view. If so, we add that view to the
1905 // cluster.
1906 if (!cluster.views.contains(v) && v != dragView) {
1907 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1908 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) v.getLayoutParams();
1909 if (!lp.canReorder) {
1910 // The push solution includes the all apps button, this is not viable.
1911 fail = true;
1912 break;
1913 }
1914 cluster.addView(v);
1915 CellAndSpan c = currentState.map.get(v);
1916
1917 // Adding view to cluster, mark it as not occupied.
1918 mTmpOccupied.markCells(c, false);
1919 }
1920 }
1921 }
1922 pushDistance--;
1923
1924 // The cluster has been completed, now we move the whole thing over in the appropriate
1925 // direction.
1926 cluster.shift(whichEdge, 1);
1927 }
1928
1929 boolean foundSolution = false;
1930 clusterRect = cluster.getBoundingRect();
1931
1932 // Due to the nature of the algorithm, the only check required to verify a valid solution
1933 // is to ensure that completed shifted cluster lies completely within the cell layout.
1934 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1935 clusterRect.bottom <= mCountY) {
1936 foundSolution = true;
1937 } else {
1938 currentState.restore();
1939 }
1940
1941 // In either case, we set the occupied array as marked for the location of the views
1942 for (View v: cluster.views) {
1943 CellAndSpan c = currentState.map.get(v);
1944 mTmpOccupied.markCells(c, true);
1945 }
1946
1947 return foundSolution;
1948 }
1949
1950 /**
1951 * This helper class defines a cluster of views. It helps with defining complex edges
1952 * of the cluster and determining how those edges interact with other views. The edges
1953 * essentially define a fine-grained boundary around the cluster of views -- like a more
1954 * precise version of a bounding box.
1955 */
1956 private class ViewCluster {
1957 final static int LEFT = 1 << 0;
1958 final static int TOP = 1 << 1;
1959 final static int RIGHT = 1 << 2;
1960 final static int BOTTOM = 1 << 3;
1961
1962 final ArrayList<View> views;
1963 final ItemConfiguration config;
1964 final Rect boundingRect = new Rect();
1965
1966 final int[] leftEdge = new int[mCountY];
1967 final int[] rightEdge = new int[mCountY];
1968 final int[] topEdge = new int[mCountX];
1969 final int[] bottomEdge = new int[mCountX];
1970 int dirtyEdges;
1971 boolean boundingRectDirty;
1972
1973 @SuppressWarnings("unchecked")
1974 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1975 this.views = (ArrayList<View>) views.clone();
1976 this.config = config;
1977 resetEdges();
1978 }
1979
1980 void resetEdges() {
1981 for (int i = 0; i < mCountX; i++) {
1982 topEdge[i] = -1;
1983 bottomEdge[i] = -1;
1984 }
1985 for (int i = 0; i < mCountY; i++) {
1986 leftEdge[i] = -1;
1987 rightEdge[i] = -1;
1988 }
1989 dirtyEdges = LEFT | TOP | RIGHT | BOTTOM;
1990 boundingRectDirty = true;
1991 }
1992
1993 void computeEdge(int which) {
1994 int count = views.size();
1995 for (int i = 0; i < count; i++) {
1996 CellAndSpan cs = config.map.get(views.get(i));
1997 switch (which) {
1998 case LEFT:
1999 int left = cs.cellX;
2000 for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) {
2001 if (left < leftEdge[j] || leftEdge[j] < 0) {
2002 leftEdge[j] = left;
2003 }
2004 }
2005 break;
2006 case RIGHT:
2007 int right = cs.cellX + cs.spanX;
2008 for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) {
2009 if (right > rightEdge[j]) {
2010 rightEdge[j] = right;
2011 }
2012 }
2013 break;
2014 case TOP:
2015 int top = cs.cellY;
2016 for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) {
2017 if (top < topEdge[j] || topEdge[j] < 0) {
2018 topEdge[j] = top;
2019 }
2020 }
2021 break;
2022 case BOTTOM:
2023 int bottom = cs.cellY + cs.spanY;
2024 for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) {
2025 if (bottom > bottomEdge[j]) {
2026 bottomEdge[j] = bottom;
2027 }
2028 }
2029 break;
2030 }
2031 }
2032 }
2033
2034 boolean isViewTouchingEdge(View v, int whichEdge) {
2035 CellAndSpan cs = config.map.get(v);
2036
2037 if ((dirtyEdges & whichEdge) == whichEdge) {
2038 computeEdge(whichEdge);
2039 dirtyEdges &= ~whichEdge;
2040 }
2041
2042 switch (whichEdge) {
2043 case LEFT:
2044 for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) {
2045 if (leftEdge[i] == cs.cellX + cs.spanX) {
2046 return true;
2047 }
2048 }
2049 break;
2050 case RIGHT:
2051 for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) {
2052 if (rightEdge[i] == cs.cellX) {
2053 return true;
2054 }
2055 }
2056 break;
2057 case TOP:
2058 for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) {
2059 if (topEdge[i] == cs.cellY + cs.spanY) {
2060 return true;
2061 }
2062 }
2063 break;
2064 case BOTTOM:
2065 for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) {
2066 if (bottomEdge[i] == cs.cellY) {
2067 return true;
2068 }
2069 }
2070 break;
2071 }
2072 return false;
2073 }
2074
2075 void shift(int whichEdge, int delta) {
2076 for (View v: views) {
2077 CellAndSpan c = config.map.get(v);
2078 switch (whichEdge) {
2079 case LEFT:
2080 c.cellX -= delta;
2081 break;
2082 case RIGHT:
2083 c.cellX += delta;
2084 break;
2085 case TOP:
2086 c.cellY -= delta;
2087 break;
2088 case BOTTOM:
2089 default:
2090 c.cellY += delta;
2091 break;
2092 }
2093 }
2094 resetEdges();
2095 }
2096
2097 public void addView(View v) {
2098 views.add(v);
2099 resetEdges();
2100 }
2101
2102 public Rect getBoundingRect() {
2103 if (boundingRectDirty) {
2104 config.getBoundingRectForViews(views, boundingRect);
2105 }
2106 return boundingRect;
2107 }
2108
2109 final PositionComparator comparator = new PositionComparator();
2110 class PositionComparator implements Comparator<View> {
2111 int whichEdge = 0;
2112 public int compare(View left, View right) {
2113 CellAndSpan l = config.map.get(left);
2114 CellAndSpan r = config.map.get(right);
2115 switch (whichEdge) {
2116 case LEFT:
2117 return (r.cellX + r.spanX) - (l.cellX + l.spanX);
2118 case RIGHT:
2119 return l.cellX - r.cellX;
2120 case TOP:
2121 return (r.cellY + r.spanY) - (l.cellY + l.spanY);
2122 case BOTTOM:
2123 default:
2124 return l.cellY - r.cellY;
2125 }
2126 }
2127 }
2128
2129 public void sortConfigurationForEdgePush(int edge) {
2130 comparator.whichEdge = edge;
2131 Collections.sort(config.sortedViews, comparator);
2132 }
2133 }
2134
2135 // This method tries to find a reordering solution which satisfies the push mechanic by trying
2136 // to push items in each of the cardinal directions, in an order based on the direction vector
2137 // passed.
2138 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
2139 int[] direction, View ignoreView, ItemConfiguration solution) {
2140 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
2141 // If the direction vector has two non-zero components, we try pushing
2142 // separately in each of the components.
2143 int temp = direction[1];
2144 direction[1] = 0;
2145
2146 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2147 ignoreView, solution)) {
2148 return true;
2149 }
2150 direction[1] = temp;
2151 temp = direction[0];
2152 direction[0] = 0;
2153
2154 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2155 ignoreView, solution)) {
2156 return true;
2157 }
2158 // Revert the direction
2159 direction[0] = temp;
2160
2161 // Now we try pushing in each component of the opposite direction
2162 direction[0] *= -1;
2163 direction[1] *= -1;
2164 temp = direction[1];
2165 direction[1] = 0;
2166 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2167 ignoreView, solution)) {
2168 return true;
2169 }
2170
2171 direction[1] = temp;
2172 temp = direction[0];
2173 direction[0] = 0;
2174 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2175 ignoreView, solution)) {
2176 return true;
2177 }
2178 // revert the direction
2179 direction[0] = temp;
2180 direction[0] *= -1;
2181 direction[1] *= -1;
2182
2183 } else {
2184 // If the direction vector has a single non-zero component, we push first in the
2185 // direction of the vector
2186 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2187 ignoreView, solution)) {
2188 return true;
2189 }
2190 // Then we try the opposite direction
2191 direction[0] *= -1;
2192 direction[1] *= -1;
2193 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2194 ignoreView, solution)) {
2195 return true;
2196 }
2197 // Switch the direction back
2198 direction[0] *= -1;
2199 direction[1] *= -1;
2200
2201 // If we have failed to find a push solution with the above, then we try
2202 // to find a solution by pushing along the perpendicular axis.
2203
2204 // Swap the components
2205 int temp = direction[1];
2206 direction[1] = direction[0];
2207 direction[0] = temp;
2208 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2209 ignoreView, solution)) {
2210 return true;
2211 }
2212
2213 // Then we try the opposite direction
2214 direction[0] *= -1;
2215 direction[1] *= -1;
2216 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2217 ignoreView, solution)) {
2218 return true;
2219 }
2220 // Switch the direction back
2221 direction[0] *= -1;
2222 direction[1] *= -1;
2223
2224 // Swap the components back
2225 temp = direction[1];
2226 direction[1] = direction[0];
2227 direction[0] = temp;
2228 }
2229 return false;
2230 }
2231
2232 /*
2233 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
2234 * the provided point and the provided cell
2235 */
2236 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
2237 double angle = Math.atan(deltaY / deltaX);
2238
2239 result[0] = 0;
2240 result[1] = 0;
2241 if (Math.abs(Math.cos(angle)) > 0.5f) {
2242 result[0] = (int) Math.signum(deltaX);
2243 }
2244 if (Math.abs(Math.sin(angle)) > 0.5f) {
2245 result[1] = (int) Math.signum(deltaY);
2246 }
2247 }
2248
2249 /* This seems like it should be obvious and straight-forward, but when the direction vector
2250 needs to match with the notion of the dragView pushing other views, we have to employ
2251 a slightly more subtle notion of the direction vector. The question is what two points is
2252 the vector between? The center of the dragView and its desired destination? Not quite, as
2253 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2254 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2255 or right, which helps make pushing feel right.
2256 */
2257 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2258 int spanY, View dragView, int[] resultDirection) {
2259
2260 //TODO(adamcohen) b/151776141 use the items visual center for the direction vector
2261 int[] targetDestination = new int[2];
2262
Sebastián Francof9a6ac22022-11-15 22:56:37 +00002263 findNearestAreaIgnoreOccupied(dragViewCenterX, dragViewCenterY, spanX, spanY,
2264 targetDestination);
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002265 Rect dragRect = new Rect();
2266 cellToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2267 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2268
2269 Rect dropRegionRect = new Rect();
2270 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2271 dragView, dropRegionRect, mIntersectingViews);
2272
2273 int dropRegionSpanX = dropRegionRect.width();
2274 int dropRegionSpanY = dropRegionRect.height();
2275
2276 cellToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2277 dropRegionRect.height(), dropRegionRect);
2278
2279 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2280 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2281
2282 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2283 deltaX = 0;
2284 }
2285 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2286 deltaY = 0;
2287 }
2288
2289 if (deltaX == 0 && deltaY == 0) {
2290 // No idea what to do, give a random direction.
2291 resultDirection[0] = 1;
2292 resultDirection[1] = 0;
2293 } else {
2294 computeDirectionVector(deltaX, deltaY, resultDirection);
2295 }
2296 }
2297
2298 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
2299 int[] direction, View dragView, ItemConfiguration currentState) {
2300 if (views.size() == 0) return true;
2301
2302 boolean success = false;
2303 Rect boundingRect = new Rect();
2304 // We construct a rect which represents the entire group of views passed in
2305 currentState.getBoundingRectForViews(views, boundingRect);
2306
2307 // Mark the occupied state as false for the group of views we want to move.
2308 for (View v: views) {
2309 CellAndSpan c = currentState.map.get(v);
2310 mTmpOccupied.markCells(c, false);
2311 }
2312
2313 GridOccupancy blockOccupied = new GridOccupancy(boundingRect.width(), boundingRect.height());
2314 int top = boundingRect.top;
2315 int left = boundingRect.left;
2316 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
2317 // for interlocking.
2318 for (View v: views) {
2319 CellAndSpan c = currentState.map.get(v);
2320 blockOccupied.markCells(c.cellX - left, c.cellY - top, c.spanX, c.spanY, true);
2321 }
2322
2323 mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
2324
2325 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
2326 boundingRect.height(), direction,
2327 mTmpOccupied.cells, blockOccupied.cells, mTempLocation);
2328
2329 // If we successfully found a location by pushing the block of views, we commit it
2330 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
2331 int deltaX = mTempLocation[0] - boundingRect.left;
2332 int deltaY = mTempLocation[1] - boundingRect.top;
2333 for (View v: views) {
2334 CellAndSpan c = currentState.map.get(v);
2335 c.cellX += deltaX;
2336 c.cellY += deltaY;
2337 }
2338 success = true;
2339 }
2340
2341 // In either case, we set the occupied array as marked for the location of the views
2342 for (View v: views) {
2343 CellAndSpan c = currentState.map.get(v);
2344 mTmpOccupied.markCells(c, true);
2345 }
2346 return success;
2347 }
2348
2349 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
2350 View ignoreView, ItemConfiguration solution) {
2351 // Return early if get invalid cell positions
2352 if (cellX < 0 || cellY < 0) return false;
2353
2354 mIntersectingViews.clear();
2355 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2356
2357 // Mark the desired location of the view currently being dragged.
2358 if (ignoreView != null) {
2359 CellAndSpan c = solution.map.get(ignoreView);
2360 if (c != null) {
2361 c.cellX = cellX;
2362 c.cellY = cellY;
2363 }
2364 }
2365 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2366 Rect r1 = new Rect();
2367 for (View child: solution.map.keySet()) {
2368 if (child == ignoreView) continue;
2369 CellAndSpan c = solution.map.get(child);
2370 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
2371 r1.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2372 if (Rect.intersects(r0, r1)) {
2373 if (!lp.canReorder) {
2374 return false;
2375 }
2376 mIntersectingViews.add(child);
2377 }
2378 }
2379
2380 solution.intersectingViews = new ArrayList<>(mIntersectingViews);
2381
2382 // First we try to find a solution which respects the push mechanic. That is,
2383 // we try to find a solution such that no displaced item travels through another item
2384 // without also displacing that item.
2385 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
2386 solution)) {
2387 return true;
2388 }
2389
2390 // Next we try moving the views as a block, but without requiring the push mechanic.
2391 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
2392 solution)) {
2393 return true;
2394 }
2395
2396 // Ok, they couldn't move as a block, let's move them individually
2397 for (View v : mIntersectingViews) {
2398 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
2399 return false;
2400 }
2401 }
2402 return true;
2403 }
2404
2405 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
2406 int spanX, int spanY, int[] direction, View dragView, boolean decX,
2407 ItemConfiguration solution) {
2408 // Copy the current state into the solution. This solution will be manipulated as necessary.
2409 copyCurrentStateToSolution(solution, false);
2410 // Copy the current occupied array into the temporary occupied array. This array will be
2411 // manipulated as necessary to find a solution.
2412 mOccupied.copyTo(mTmpOccupied);
2413
2414 // We find the nearest cell into which we would place the dragged item, assuming there's
2415 // nothing in its way.
2416 int result[] = new int[2];
Sebastián Francof9a6ac22022-11-15 22:56:37 +00002417 result = findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY, result);
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002418
2419 boolean success;
2420 // First we try the exact nearest position of the item being dragged,
2421 // we will then want to try to move this around to other neighbouring positions
2422 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
2423 solution);
2424
2425 if (!success) {
2426 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
2427 // x, then 1 in y etc.
2428 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
2429 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
2430 direction, dragView, false, solution);
2431 } else if (spanY > minSpanY) {
2432 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
2433 direction, dragView, true, solution);
2434 }
2435 solution.isSolution = false;
2436 } else {
2437 solution.isSolution = true;
2438 solution.cellX = result[0];
2439 solution.cellY = result[1];
2440 solution.spanX = spanX;
2441 solution.spanY = spanY;
2442 }
2443 return solution;
2444 }
2445
2446 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
2447 int childCount = mShortcutsAndWidgets.getChildCount();
2448 for (int i = 0; i < childCount; i++) {
2449 View child = mShortcutsAndWidgets.getChildAt(i);
2450 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
2451 CellAndSpan c;
2452 if (temp) {
Sebastian Franco877088e2023-01-03 15:16:22 -07002453 c = new CellAndSpan(lp.getTmpCellX(), lp.getTmpCellY(), lp.cellHSpan, lp.cellVSpan);
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002454 } else {
Sebastian Franco877088e2023-01-03 15:16:22 -07002455 c = new CellAndSpan(lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan);
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002456 }
2457 solution.add(child, c);
2458 }
Sebastian Franco53a15a42022-10-25 17:28:54 -07002459 }
2460
2461 /**
Sebastián Francof9a6ac22022-11-15 22:56:37 +00002462 * Returns a "reorder" if there is empty space without rearranging anything.
Sebastian Franco53a15a42022-10-25 17:28:54 -07002463 *
2464 * @param pixelX X coordinate in pixels in the screen
2465 * @param pixelY Y coordinate in pixels in the screen
2466 * @param spanX horizontal cell span
2467 * @param spanY vertical cell span
Sebastián Francof9a6ac22022-11-15 22:56:37 +00002468 * @param dragView view being dragged in reorder
Sebastian Franco53a15a42022-10-25 17:28:54 -07002469 * @return the configuration that represents the found reorder
2470 */
Sebastián Francof9a6ac22022-11-15 22:56:37 +00002471 public ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX,
2472 int spanY, View dragView) {
Sebastian Franco53a15a42022-10-25 17:28:54 -07002473 int[] result = new int[2];
Sebastián Francof9a6ac22022-11-15 22:56:37 +00002474 if (isNearestDropLocationOccupied(pixelX, pixelY, spanX, spanY, dragView, result)) {
2475 result[0] = result[1] = -1;
2476 }
Sebastian Franco53a15a42022-10-25 17:28:54 -07002477 ItemConfiguration solution = new ItemConfiguration();
2478 copyCurrentStateToSolution(solution, false);
2479 solution.isSolution = result[0] != -1;
2480 if (!solution.isSolution) {
2481 return solution;
2482 }
2483 solution.cellX = result[0];
2484 solution.cellY = result[1];
2485 solution.spanX = spanX;
2486 solution.spanY = spanY;
2487 return solution;
2488 }
2489
2490 /**
2491 * When the user drags an Item in the workspace sometimes we need to move the items already in
2492 * the workspace to make space for the new item, this function return a solution for that
2493 * reorder.
2494 *
2495 * @param pixelX X coordinate in the screen of the dragView in pixels
2496 * @param pixelY Y coordinate in the screen of the dragView in pixels
2497 * @param minSpanX minimum horizontal span the item can be shrunk to
2498 * @param minSpanY minimum vertical span the item can be shrunk to
2499 * @param spanX occupied horizontal span
2500 * @param spanY occupied vertical span
2501 * @param dragView the view of the item being draged
2502 * @return returns a solution for the given parameters, the solution contains all the icons and
2503 * the locations they should be in the given solution.
2504 */
2505 public ItemConfiguration calculateReorder(int pixelX, int pixelY, int minSpanX, int minSpanY,
2506 int spanX, int spanY, View dragView) {
2507 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2508
Sebastián Francof9a6ac22022-11-15 22:56:37 +00002509 ItemConfiguration dropInPlaceSolution = dropInPlaceSolution(pixelX, pixelY, spanX, spanY,
2510 dragView);
Sebastian Franco53a15a42022-10-25 17:28:54 -07002511
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002512 // Find a solution involving pushing / displacing any items in the way
2513 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Sebastian Francof153d912022-04-22 16:15:27 -05002514 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
Adam Cohen482ed822012-03-02 14:15:13 -08002515
2516 // We attempt the approach which doesn't shuffle views at all
Sebastián Francof9a6ac22022-11-15 22:56:37 +00002517 ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder(pixelX, pixelY, minSpanX,
2518 minSpanY, spanX, spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08002519
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002520 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2521 // favor a solution in which the item is not resized, but
Sebastián Francof9a6ac22022-11-15 22:56:37 +00002522 if (swapSolution.isSolution && swapSolution.area() >= closestSpaceSolution.area()) {
Sebastian Franco53a15a42022-10-25 17:28:54 -07002523 return swapSolution;
Sebastian Franco53a15a42022-10-25 17:28:54 -07002524 } else if (closestSpaceSolution.isSolution) {
2525 return closestSpaceSolution;
Sebastián Francof9a6ac22022-11-15 22:56:37 +00002526 } else if (dropInPlaceSolution.isSolution) {
2527 return dropInPlaceSolution;
Adam Cohen482ed822012-03-02 14:15:13 -08002528 }
Sebastian Franco53a15a42022-10-25 17:28:54 -07002529 return null;
2530 }
Adam Cohen482ed822012-03-02 14:15:13 -08002531
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002532 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
2533 View dragView, int[] result, int[] resultSpan, int mode) {
2534 if (resultSpan == null) {
2535 resultSpan = new int[]{-1, -1};
2536 }
2537 if (result == null) {
2538 result = new int[]{-1, -1};
2539 }
Sebastian Franco5d990ee2022-11-01 16:08:24 -07002540
2541 ItemConfiguration finalSolution = null;
2542 // We want the solution to match the animation of the preview and to match the drop so we
2543 // only recalculate in mode MODE_SHOW_REORDER_HINT because that the first one to run in the
2544 // reorder cycle.
2545 if (mode == MODE_SHOW_REORDER_HINT || mPreviousSolution == null) {
2546 finalSolution = calculateReorder(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
2547 dragView);
2548 mPreviousSolution = finalSolution;
2549 } else {
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002550 finalSolution = mPreviousSolution;
2551 // We reset this vector after drop
2552 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2553 mPreviousSolution = null;
2554 }
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002555 }
2556
2557 if (finalSolution == null || !finalSolution.isSolution) {
2558 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2559 } else {
2560 result[0] = finalSolution.cellX;
2561 result[1] = finalSolution.cellY;
2562 resultSpan[0] = finalSolution.spanX;
2563 resultSpan[1] = finalSolution.spanY;
Sebastian Franco9c743272022-11-15 15:03:25 -08002564 performReorder(finalSolution, dragView, mode);
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002565 }
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002566 return result;
2567 }
2568
Sebastian Franco53a15a42022-10-25 17:28:54 -07002569 /**
2570 * Animates and submits in the DB the given ItemConfiguration depending of the mode.
2571 *
2572 * @param solution represents widgets on the screen which the Workspace will animate to and
2573 * would be submitted to the database.
2574 * @param dragView view which is being dragged over the workspace that trigger the reorder
2575 * @param mode depending on the mode different animations would be played and depending on the
2576 * mode the solution would be submitted or not the database.
2577 * The possible modes are {@link MODE_SHOW_REORDER_HINT}, {@link MODE_DRAG_OVER},
2578 * {@link MODE_ON_DROP}, {@link MODE_ON_DROP_EXTERNAL}, {@link MODE_ACCEPT_DROP}
2579 * defined in {@link CellLayout}.
2580 */
2581 void performReorder(ItemConfiguration solution, View dragView, int mode) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002582 if (mode == MODE_SHOW_REORDER_HINT) {
Sebastian Franco53a15a42022-10-25 17:28:54 -07002583 beginOrAdjustReorderPreviewAnimations(solution, dragView,
2584 ReorderPreviewAnimation.MODE_HINT);
2585 return;
2586 }
2587 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2588 // committing anything or animating anything as we just want to determine if a solution
2589 // exists
2590 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2591 if (!DESTRUCTIVE_REORDER) {
2592 setUseTempCoords(true);
2593 }
2594
2595 if (!DESTRUCTIVE_REORDER) {
2596 copySolutionToTempState(solution, dragView);
2597 }
2598 setItemPlacementDirty(true);
2599 animateItemsToSolution(solution, dragView, mode == MODE_ON_DROP);
2600
2601 if (!DESTRUCTIVE_REORDER
2602 && (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
2603 // Since the temp solution didn't update dragView, don't commit it either
2604 commitTempPlacement(dragView);
2605 completeAndClearReorderPreviewAnimations();
2606 setItemPlacementDirty(false);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002607 } else {
Sebastian Franco53a15a42022-10-25 17:28:54 -07002608 beginOrAdjustReorderPreviewAnimations(solution, dragView,
2609 ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002610 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002611 }
2612
Sebastian Franco53a15a42022-10-25 17:28:54 -07002613 if (mode == MODE_ON_DROP && !DESTRUCTIVE_REORDER) {
Adam Cohen482ed822012-03-02 14:15:13 -08002614 setUseTempCoords(false);
2615 }
Adam Cohen482ed822012-03-02 14:15:13 -08002616
Michael Jurkaa52570f2012-03-20 03:18:20 -07002617 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002618 }
2619
Adam Cohen19f37922012-03-21 11:59:11 -07002620 void setItemPlacementDirty(boolean dirty) {
2621 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002622 }
Adam Cohen19f37922012-03-21 11:59:11 -07002623 boolean isItemPlacementDirty() {
2624 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002625 }
2626
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002627 private static class ItemConfiguration extends CellAndSpan {
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07002628 final ArrayMap<View, CellAndSpan> map = new ArrayMap<>();
2629 private final ArrayMap<View, CellAndSpan> savedMap = new ArrayMap<>();
2630 final ArrayList<View> sortedViews = new ArrayList<>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002631 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002632 boolean isSolution = false;
Adam Cohen482ed822012-03-02 14:15:13 -08002633
Adam Cohenf3900c22012-11-16 18:28:11 -08002634 void save() {
2635 // Copy current state into savedMap
2636 for (View v: map.keySet()) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002637 savedMap.get(v).copyFrom(map.get(v));
Adam Cohenf3900c22012-11-16 18:28:11 -08002638 }
2639 }
2640
2641 void restore() {
2642 // Restore current state from savedMap
2643 for (View v: savedMap.keySet()) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002644 map.get(v).copyFrom(savedMap.get(v));
Adam Cohenf3900c22012-11-16 18:28:11 -08002645 }
2646 }
2647
2648 void add(View v, CellAndSpan cs) {
2649 map.put(v, cs);
2650 savedMap.put(v, new CellAndSpan());
2651 sortedViews.add(v);
2652 }
2653
Adam Cohen482ed822012-03-02 14:15:13 -08002654 int area() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002655 return spanX * spanY;
Adam Cohenf3900c22012-11-16 18:28:11 -08002656 }
2657
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002658 void getBoundingRectForViews(ArrayList<View> views, Rect outRect) {
2659 boolean first = true;
2660 for (View v: views) {
2661 CellAndSpan c = map.get(v);
2662 if (first) {
2663 outRect.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2664 first = false;
2665 } else {
2666 outRect.union(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2667 }
2668 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002669 }
Adam Cohen482ed822012-03-02 14:15:13 -08002670 }
2671
Adam Cohendf035382011-04-11 17:22:04 -07002672 /**
Adam Cohendf035382011-04-11 17:22:04 -07002673 * Find a starting cell position that will fit the given bounds nearest the requested
2674 * cell location. Uses Euclidean distance to score multiple vacant areas.
2675 *
2676 * @param pixelX The X location at which you want to search for a vacant area.
2677 * @param pixelY The Y location at which you want to search for a vacant area.
2678 * @param spanX Horizontal span of the object.
2679 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07002680 * @param result Previously returned value to possibly recycle.
2681 * @return The X, Y cell of a vacant area that can contain this object,
2682 * nearest the requested location.
2683 */
Sebastián Francof9a6ac22022-11-15 22:56:37 +00002684 public int[] findNearestAreaIgnoreOccupied(int pixelX, int pixelY, int spanX, int spanY,
2685 int[] result) {
Sebastian Francob57c0b22022-06-28 13:54:35 -07002686 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, true, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002687 }
2688
Michael Jurka0280c3b2010-09-17 15:00:07 -07002689 boolean existsEmptyCell() {
2690 return findCellForSpan(null, 1, 1);
2691 }
2692
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002693 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002694 * Finds the upper-left coordinate of the first rectangle in the grid that can
2695 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2696 * then this method will only return coordinates for rectangles that contain the cell
2697 * (intersectX, intersectY)
2698 *
2699 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2700 * can be found.
2701 * @param spanX The horizontal span of the cell we want to find.
2702 * @param spanY The vertical span of the cell we want to find.
2703 *
2704 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002705 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002706 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002707 if (cellXY == null) {
2708 cellXY = new int[2];
Michael Jurka0280c3b2010-09-17 15:00:07 -07002709 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002710 return mOccupied.findVacantCell(cellXY, spanX, spanY);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002711 }
2712
2713 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002714 * A drag event has begun over this layout.
2715 * It may have begun over this layout (in which case onDragChild is called first),
2716 * or it may have begun on another layout.
2717 */
2718 void onDragEnter() {
Winson Chungc07918d2011-07-01 15:35:26 -07002719 mDragging = true;
Sebastian Franco5aa71ce2022-12-14 12:13:19 -06002720 mPreviousSolution = null;
Winson Chungc07918d2011-07-01 15:35:26 -07002721 }
2722
2723 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002724 * Called when drag has left this CellLayout or has been completed (successfully or not)
2725 */
2726 void onDragExit() {
Joe Onorato4be866d2010-10-10 11:26:02 -07002727 // This can actually be called when we aren't in a drag, e.g. when adding a new
2728 // item to this layout via the customize drawer.
2729 // Guard against that case.
2730 if (mDragging) {
2731 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002732 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002733
2734 // Invalidate the drag data
Sebastian Franco5aa71ce2022-12-14 12:13:19 -06002735 mPreviousSolution = null;
Adam Cohend41fbf52012-02-16 23:53:59 -08002736 mDragCell[0] = mDragCell[1] = -1;
Adam Cohenf7ca3b42021-02-22 11:03:58 -08002737 mDragCellSpan[0] = mDragCellSpan[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002738 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2739 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002740 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002741 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002742 }
2743
2744 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002745 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002746 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002747 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002748 *
2749 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002750 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002751 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002752 if (child != null) {
Sebastian Francod4682992022-10-05 13:03:09 -05002753 CellLayoutLayoutParams
2754 lp = (CellLayoutLayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002755 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002756 child.requestLayout();
Tony Wickham1cdb6d02015-09-17 11:08:27 -07002757 markCellsAsOccupiedForView(child);
Romain Guyd94533d2009-08-17 10:01:15 -07002758 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002759 }
2760
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002761 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002762 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002763 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002764 * @param cellX X coordinate of upper left corner expressed as a cell position
2765 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002766 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002767 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002768 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002769 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002770 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002771 final int cellWidth = mCellWidth;
2772 final int cellHeight = mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -07002773
Pierre Barbier de Reuille1b8bbb62021-05-19 22:45:16 +01002774 // We observe a shift of 1 pixel on the x coordinate compared to the actual cell coordinates
2775 final int hStartPadding = getPaddingLeft()
2776 + (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Winson Chung4b825dcd2011-06-19 12:41:22 -07002777 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002778
Thales Lima78d00ad2021-09-30 11:29:06 +01002779 int x = hStartPadding + (cellX * mBorderSpace.x) + (cellX * cellWidth);
2780 int y = vStartPadding + (cellY * mBorderSpace.y) + (cellY * cellHeight);
Jon Miranda228877d2021-02-09 11:05:00 -05002781
Thales Lima78d00ad2021-09-30 11:29:06 +01002782 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * mBorderSpace.x);
2783 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * mBorderSpace.y);
Winson Chungaafa03c2010-06-11 17:34:16 -07002784
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002785 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002786 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002787
Adam Cohend4844c32011-02-18 19:25:06 -08002788 public void markCellsAsOccupiedForView(View view) {
Sebastian Francof153d912022-04-22 16:15:27 -05002789 if (view instanceof LauncherAppWidgetHostView
2790 && view.getTag() instanceof LauncherAppWidgetInfo) {
2791 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
Sunny Goyal669b71f2023-01-27 14:37:07 -08002792 CellPos pos = mActivity.getCellPosMapper().mapModelToPresenter(info);
2793 mOccupied.markCells(pos.cellX, pos.cellY, info.spanX, info.spanY, true);
Sebastian Francof153d912022-04-22 16:15:27 -05002794 return;
2795 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002796 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Sebastian Francod4682992022-10-05 13:03:09 -05002797 CellLayoutLayoutParams
2798 lp = (CellLayoutLayoutParams) view.getLayoutParams();
Sebastian Franco877088e2023-01-03 15:16:22 -07002799 mOccupied.markCells(lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002800 }
2801
Adam Cohend4844c32011-02-18 19:25:06 -08002802 public void markCellsAsUnoccupiedForView(View view) {
Sebastian Francof153d912022-04-22 16:15:27 -05002803 if (view instanceof LauncherAppWidgetHostView
2804 && view.getTag() instanceof LauncherAppWidgetInfo) {
2805 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
Sunny Goyal669b71f2023-01-27 14:37:07 -08002806 CellPos pos = mActivity.getCellPosMapper().mapModelToPresenter(info);
2807 mOccupied.markCells(pos.cellX, pos.cellY, info.spanX, info.spanY, false);
Sebastian Francof153d912022-04-22 16:15:27 -05002808 return;
2809 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002810 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Sebastian Francod4682992022-10-05 13:03:09 -05002811 CellLayoutLayoutParams
2812 lp = (CellLayoutLayoutParams) view.getLayoutParams();
Sebastian Franco877088e2023-01-03 15:16:22 -07002813 mOccupied.markCells(lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan, false);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002814 }
2815
Adam Cohen2801caf2011-05-13 20:57:39 -07002816 public int getDesiredWidth() {
Jon Miranda228877d2021-02-09 11:05:00 -05002817 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth)
Thales Lima78d00ad2021-09-30 11:29:06 +01002818 + ((mCountX - 1) * mBorderSpace.x);
Adam Cohen2801caf2011-05-13 20:57:39 -07002819 }
2820
2821 public int getDesiredHeight() {
Jon Miranda228877d2021-02-09 11:05:00 -05002822 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight)
Thales Lima78d00ad2021-09-30 11:29:06 +01002823 + ((mCountY - 1) * mBorderSpace.y);
Adam Cohen2801caf2011-05-13 20:57:39 -07002824 }
2825
Michael Jurka66d72172011-04-12 16:29:25 -07002826 public boolean isOccupied(int x, int y) {
2827 if (x < mCountX && y < mCountY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002828 return mOccupied.cells[x][y];
Michael Jurka66d72172011-04-12 16:29:25 -07002829 } else {
2830 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2831 }
2832 }
2833
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002834 @Override
2835 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
Sebastian Francod4682992022-10-05 13:03:09 -05002836 return new CellLayoutLayoutParams(getContext(), attrs);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002837 }
2838
2839 @Override
2840 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
Sebastian Francod4682992022-10-05 13:03:09 -05002841 return p instanceof CellLayoutLayoutParams;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002842 }
2843
2844 @Override
2845 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
Sebastian Francod4682992022-10-05 13:03:09 -05002846 return new CellLayoutLayoutParams(p);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002847 }
2848
Michael Jurka0280c3b2010-09-17 15:00:07 -07002849 // This class stores info for two purposes:
2850 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2851 // its spanX, spanY, and the screen it is on
2852 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2853 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2854 // the CellLayout that was long clicked
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002855 public static final class CellInfo extends CellAndSpan {
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07002856 public final View cell;
Sunny Goyalefb7e842018-10-04 15:11:00 -07002857 final int screenId;
2858 final int container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002859
Sunny Goyal669b71f2023-01-27 14:37:07 -08002860 public CellInfo(View v, ItemInfo info, CellPos cellPos) {
2861 cellX = cellPos.cellX;
2862 cellY = cellPos.cellY;
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002863 spanX = info.spanX;
2864 spanY = info.spanY;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002865 cell = v;
Sunny Goyal669b71f2023-01-27 14:37:07 -08002866 screenId = cellPos.screenId;
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002867 container = info.container;
2868 }
2869
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002870 @Override
2871 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002872 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2873 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002874 }
2875 }
Michael Jurkad771c962011-08-09 15:00:48 -07002876
Tony Wickham86930612015-09-09 13:50:40 -07002877 /**
Samuel Fufa1e2d0042019-11-18 17:12:46 -08002878 * A Delegated cell Drawing for drawing on CellLayout
2879 */
2880 public abstract static class DelegatedCellDrawing {
2881 public int mDelegateCellX;
2882 public int mDelegateCellY;
2883
2884 /**
2885 * Draw under CellLayout
2886 */
2887 public abstract void drawUnderItem(Canvas canvas);
2888
2889 /**
2890 * Draw over CellLayout
2891 */
2892 public abstract void drawOverItem(Canvas canvas);
2893 }
2894
2895 /**
Tony Wickham86930612015-09-09 13:50:40 -07002896 * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing
2897 * if necessary).
2898 */
2899 public boolean hasReorderSolution(ItemInfo itemInfo) {
2900 int[] cellPoint = new int[2];
2901 // Check for a solution starting at every cell.
2902 for (int cellX = 0; cellX < getCountX(); cellX++) {
2903 for (int cellY = 0; cellY < getCountY(); cellY++) {
2904 cellToPoint(cellX, cellY, cellPoint);
2905 if (findReorderSolution(cellPoint[0], cellPoint[1], itemInfo.minSpanX,
2906 itemInfo.minSpanY, itemInfo.spanX, itemInfo.spanY, mDirectionVector, null,
2907 true, new ItemConfiguration()).isSolution) {
2908 return true;
2909 }
2910 }
2911 }
2912 return false;
2913 }
2914
Samuel Fufaa4211432020-02-25 18:47:54 -08002915 /**
2916 * Finds solution to accept hotseat migration to cell layout. commits solution if commitConfig
2917 */
2918 public boolean makeSpaceForHotseatMigration(boolean commitConfig) {
Samuel Fufaa4211432020-02-25 18:47:54 -08002919 int[] cellPoint = new int[2];
2920 int[] directionVector = new int[]{0, -1};
2921 cellToPoint(0, mCountY, cellPoint);
2922 ItemConfiguration configuration = new ItemConfiguration();
2923 if (findReorderSolution(cellPoint[0], cellPoint[1], mCountX, 1, mCountX, 1,
2924 directionVector, null, false, configuration).isSolution) {
2925 if (commitConfig) {
2926 copySolutionToTempState(configuration, null);
Sunny Goyal711c5962021-06-23 12:36:18 -07002927 commitTempPlacement(null);
Samuel Fufa82bbdac2020-03-09 18:24:47 -07002928 // undo marking cells occupied since there is actually nothing being placed yet.
2929 mOccupied.markCells(0, mCountY - 1, mCountX, 1, false);
Samuel Fufaa4211432020-02-25 18:47:54 -08002930 }
2931 return true;
2932 }
2933 return false;
2934 }
2935
Samuel Fufa82bbdac2020-03-09 18:24:47 -07002936 /**
2937 * returns a copy of cell layout's grid occupancy
2938 */
2939 public GridOccupancy cloneGridOccupancy() {
2940 GridOccupancy occupancy = new GridOccupancy(mCountX, mCountY);
2941 mOccupied.copyTo(occupancy);
2942 return occupancy;
2943 }
2944
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002945 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002946 return mOccupied.isRegionVacant(x, y, spanX, spanY);
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002947 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002948}