blob: e66d441912872afaf48e53be553a3befb81788ee [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 Goyal3d706ad2017-03-06 16:56:39 -080065import com.android.launcher3.config.FeatureFlags;
Tony Wickham0ac045f2021-11-03 13:17:02 -070066import com.android.launcher3.dragndrop.DraggableView;
Jon Mirandaa0233f72017-06-22 18:34:45 -070067import com.android.launcher3.folder.PreviewBackground;
Sunny Goyale396abf2020-04-06 15:11:17 -070068import com.android.launcher3.model.data.ItemInfo;
Sebastian Francof153d912022-04-22 16:15:27 -050069import com.android.launcher3.model.data.LauncherAppWidgetInfo;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -070070import com.android.launcher3.util.CellAndSpan;
71import com.android.launcher3.util.GridOccupancy;
Sunny Goyale2fd14b2015-08-27 17:45:46 -070072import com.android.launcher3.util.ParcelableSparseArray;
Sunny Goyal9b29ca52017-02-17 10:39:44 -080073import com.android.launcher3.util.Themes;
Adam Cohen091440a2015-03-18 14:16:05 -070074import com.android.launcher3.util.Thunk;
Sunny Goyalab770a12018-11-14 15:17:26 -080075import com.android.launcher3.views.ActivityContext;
Steven Ng32427202021-04-19 18:12:12 +010076import com.android.launcher3.widget.LauncherAppWidgetHostView;
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -070077
Sunny Goyalc13403c2016-11-18 23:44:48 -080078import java.lang.annotation.Retention;
79import java.lang.annotation.RetentionPolicy;
Adam Cohen69ce2e52011-07-03 19:25:21 -070080import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070081import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080082import java.util.Collections;
83import java.util.Comparator;
Adam Cohend41fbf52012-02-16 23:53:59 -080084import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070085
Sunny Goyalc4d32012020-04-03 17:10:11 -070086public class CellLayout extends ViewGroup {
Tony Wickhama0628cc2015-10-14 15:23:04 -070087 private static final String TAG = "CellLayout";
88 private static final boolean LOGD = false;
Winson Chungaafa03c2010-06-11 17:34:16 -070089
Sunny Goyalab770a12018-11-14 15:17:26 -080090 protected final ActivityContext mActivity;
Sunny Goyal4ffec482016-02-09 11:28:52 -080091 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070092 @Thunk int mCellWidth;
Sunny Goyal4ffec482016-02-09 11:28:52 -080093 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070094 @Thunk int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070095 private int mFixedCellWidth;
96 private int mFixedCellHeight;
Jon Miranda228877d2021-02-09 11:05:00 -050097 @ViewDebug.ExportedProperty(category = "launcher")
Thales Lima8cd020b2022-03-15 20:15:14 +000098 private Point mBorderSpace;
Winson Chungaafa03c2010-06-11 17:34:16 -070099
Sunny Goyal4ffec482016-02-09 11:28:52 -0800100 @ViewDebug.ExportedProperty(category = "launcher")
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700101 private int mCountX;
Sunny Goyal4ffec482016-02-09 11:28:52 -0800102 @ViewDebug.ExportedProperty(category = "launcher")
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700103 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800104
Adam Cohen917e3882013-10-31 15:03:35 -0700105 private boolean mDropPending = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800106
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700107 // These are temporary variables to prevent having to allocate a new object just to
108 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Adam Cohen091440a2015-03-18 14:16:05 -0700109 @Thunk final int[] mTmpPoint = new int[2];
Sunny Goyal2805e632015-05-20 15:35:32 -0700110 @Thunk final int[] mTempLocation = new int[2];
Adam Cohend9162062020-03-24 16:35:35 -0700111 final PointF mTmpPointF = new PointF();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700112
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700113 private GridOccupancy mOccupied;
114 private GridOccupancy mTmpOccupied;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800115
Michael Jurkadee05892010-07-27 10:01:56 -0700116 private OnTouchListener mInterceptTouchListener;
117
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800118 private final ArrayList<DelegatedCellDrawing> mDelegatedCellDrawings = new ArrayList<>();
Jon Mirandaa0233f72017-06-22 18:34:45 -0700119 final PreviewBackground mFolderLeaveBehind = new PreviewBackground();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700120
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800121 private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active };
Sunny Goyale15e2a82017-12-15 13:05:42 -0800122 private static final int[] BACKGROUND_STATE_DEFAULT = EMPTY_STATE_SET;
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800123 private final Drawable mBackground;
Sunny Goyal2805e632015-05-20 15:35:32 -0700124
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700125 // These values allow a fixed measurement to be set on the CellLayout.
126 private int mFixedWidth = -1;
127 private int mFixedHeight = -1;
128
Michael Jurka33945b22010-12-21 18:19:38 -0800129 // If we're actively dragging something over this screen, mIsDragOverlapping is true
130 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700131
Winson Chung150fbab2010-09-29 17:14:26 -0700132 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700133 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Sebastian Francod4682992022-10-05 13:03:09 -0500134 @Thunk final CellLayoutLayoutParams[] mDragOutlines = new CellLayoutLayoutParams[4];
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700135 @Thunk final float[] mDragOutlineAlphas = new float[mDragOutlines.length];
136 private final InterruptibleInOutAnimator[] mDragOutlineAnims =
Joe Onorato4be866d2010-10-10 11:26:02 -0700137 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700138
139 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700140 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700141 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700142
Sebastian Francod4682992022-10-05 13:03:09 -0500143 @Thunk final ArrayMap<CellLayoutLayoutParams, Animator> mReorderAnimators = new ArrayMap<>();
Adam Cohend9162062020-03-24 16:35:35 -0700144 @Thunk final ArrayMap<Reorderable, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
Adam Cohen19f37922012-03-21 11:59:11 -0700145
146 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700147
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800148 // Used to visualize the grid and drop locations
149 private boolean mVisualizeCells = false;
150 private boolean mVisualizeDropLocation = true;
151 private RectF mVisualizeGridRect = new RectF();
152 private Paint mVisualizeGridPaint = new Paint();
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800153 private int mGridVisualizationRoundingRadius;
154 private float mGridAlpha = 0f;
155 private int mGridColor = 0;
156 private float mSpringLoadedProgress = 0f;
157 private float mScrollProgress = 0f;
158
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700159 // When a drag operation is in progress, holds the nearest cell to the touch point
160 private final int[] mDragCell = new int[2];
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800161 private final int[] mDragCellSpan = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800162
Joe Onorato4be866d2010-10-10 11:26:02 -0700163 private boolean mDragging = false;
164
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700165 private final TimeInterpolator mEaseOutInterpolator;
166 private final ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700167
Sunny Goyalc13403c2016-11-18 23:44:48 -0800168 @Retention(RetentionPolicy.SOURCE)
169 @IntDef({WORKSPACE, HOTSEAT, FOLDER})
170 public @interface ContainerType{}
171 public static final int WORKSPACE = 0;
172 public static final int HOTSEAT = 1;
173 public static final int FOLDER = 2;
174
175 @ContainerType private final int mContainerType;
176
Jon Mirandab28c4fc2017-06-20 10:58:36 -0700177 private final float mChildScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800178
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800179 public static final int MODE_SHOW_REORDER_HINT = 0;
180 public static final int MODE_DRAG_OVER = 1;
181 public static final int MODE_ON_DROP = 2;
182 public static final int MODE_ON_DROP_EXTERNAL = 3;
183 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700184 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800185 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
186
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800187 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700188 private static final int REORDER_ANIMATION_DURATION = 150;
Sunny Goyalc13403c2016-11-18 23:44:48 -0800189 @Thunk final float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700190
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700191 private final ArrayList<View> mIntersectingViews = new ArrayList<>();
192 private final Rect mOccupiedRect = new Rect();
193 private final int[] mDirectionVector = new int[2];
Steven Ng30dd1d62021-03-15 21:45:49 +0000194
Sebastian Franco53a15a42022-10-25 17:28:54 -0700195 ItemConfiguration mPreviousSolution = null;
Adam Cohenb209e632012-03-27 17:09:36 -0700196 private static final int INVALID_DIRECTION = -100;
Adam Cohen482ed822012-03-02 14:15:13 -0800197
Sunny Goyal2805e632015-05-20 15:35:32 -0700198 private final Rect mTempRect = new Rect();
Jonathan Miranda21930da2021-05-03 18:44:13 +0000199 private final RectF mTempRectF = new RectF();
Jon Miranda88b7f6a2021-05-03 16:49:53 -0700200 private final float[] mTmpFloatArray = new float[4];
Winson Chung3a6e7f32013-10-09 15:50:52 -0700201
Sunny Goyal73b5a272019-12-09 14:55:56 -0800202 private static final Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700203
Adam Cohenc9735cf2015-01-23 16:11:55 -0800204 // Related to accessible drag and drop
Adam Cohen6e7c37a2020-06-25 19:22:37 -0700205 DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800206
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800207
208 public static final FloatProperty<CellLayout> SPRING_LOADED_PROGRESS =
209 new FloatProperty<CellLayout>("spring_loaded_progress") {
210 @Override
211 public Float get(CellLayout cl) {
212 return cl.getSpringLoadedProgress();
213 }
214
215 @Override
216 public void setValue(CellLayout cl, float progress) {
217 cl.setSpringLoadedProgress(progress);
218 }
219 };
220
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800221 public CellLayout(Context context) {
222 this(context, null);
223 }
224
225 public CellLayout(Context context, AttributeSet attrs) {
226 this(context, attrs, 0);
227 }
228
229 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
230 super(context, attrs, defStyle);
Sunny Goyalc13403c2016-11-18 23:44:48 -0800231 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
232 mContainerType = a.getInteger(R.styleable.CellLayout_containerType, WORKSPACE);
233 a.recycle();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700234
235 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
236 // the user where a dragged item will land when dropped.
237 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800238 setClipToPadding(false);
Sunny Goyalab770a12018-11-14 15:17:26 -0800239 mActivity = ActivityContext.lookupContext(context);
Steven Ngcc505b82021-03-18 23:04:35 +0000240 DeviceProfile deviceProfile = mActivity.getDeviceProfile();
Michael Jurkaa63c4522010-08-19 13:52:27 -0700241
Thales Lima8cd020b2022-03-15 20:15:14 +0000242 resetCellSizeInternal(deviceProfile);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700243
Steven Ngcc505b82021-03-18 23:04:35 +0000244 mCountX = deviceProfile.inv.numColumns;
245 mCountY = deviceProfile.inv.numRows;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700246 mOccupied = new GridOccupancy(mCountX, mCountY);
247 mTmpOccupied = new GridOccupancy(mCountX, mCountY);
248
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800249 mFolderLeaveBehind.mDelegateCellX = -1;
250 mFolderLeaveBehind.mDelegateCellY = -1;
Adam Cohenefca0272016-02-24 19:19:06 -0800251
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800252 setAlwaysDrawnWithCacheEnabled(false);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700253
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800254 Resources res = getResources();
255
256 mBackground = getContext().getDrawable(R.drawable.bg_celllayout);
Sunny Goyal2805e632015-05-20 15:35:32 -0700257 mBackground.setCallback(this);
Sunny Goyalaeb16432017-10-16 11:46:41 -0700258 mBackground.setAlpha(0);
Michael Jurka33945b22010-12-21 18:19:38 -0800259
Yogisha Dixitc0ac1dd2021-05-29 00:26:25 +0100260 mGridColor = Themes.getAttrColor(getContext(), R.attr.workspaceAccentColor);
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800261 mGridVisualizationRoundingRadius =
262 res.getDimensionPixelSize(R.dimen.grid_visualization_rounding_radius);
Steven Ngcc505b82021-03-18 23:04:35 +0000263 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * deviceProfile.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700264
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700265 // Initialize the data structures used for the drag visualization.
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -0700266 mEaseOutInterpolator = Interpolators.DEACCEL_2_5; // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700267 mDragCell[0] = mDragCell[1] = -1;
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800268 mDragCellSpan[0] = mDragCellSpan[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700269 for (int i = 0; i < mDragOutlines.length; i++) {
Sebastian Francod4682992022-10-05 13:03:09 -0500270 mDragOutlines[i] = new CellLayoutLayoutParams(0, 0, 0, 0);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700271 }
Mario Bertschler54ba6012017-06-08 10:53:53 -0700272 mDragOutlinePaint.setColor(Themes.getAttrColor(context, R.attr.workspaceTextColor));
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700273
274 // When dragging things around the home screens, we show a green outline of
275 // where the item will land. The outlines gradually fade out, leaving a trail
276 // behind the drag path.
277 // Set up all the animations that are used to implement this fading.
278 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700279 final float fromAlphaValue = 0;
280 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700281
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700282 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700283
284 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700285 final InterruptibleInOutAnimator anim =
Sebastian Francof153d912022-04-22 16:15:27 -0500286 new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700287 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700288 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700289 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700290 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700291 // If an animation is started and then stopped very quickly, we can still
292 // get spurious updates we've cleared the tag. Guard against this.
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800293 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
294 CellLayout.this.invalidate();
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700295 }
296 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700297 // The animation holds a reference to the drag outline bitmap as long is it's
298 // running. This way the bitmap can be GCed when the animations are complete.
Joe Onorato4be866d2010-10-10 11:26:02 -0700299 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700300 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700301
Sunny Goyalc13403c2016-11-18 23:44:48 -0800302 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType);
Jon Miranda228877d2021-02-09 11:05:00 -0500303 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
Thales Lima78d00ad2021-09-30 11:29:06 +0100304 mBorderSpace);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700305 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700306 }
307
Sunny Goyal9b180102020-03-11 10:02:29 -0700308 /**
309 * Sets or clears a delegate used for accessible drag and drop
310 */
311 public void setDragAndDropAccessibilityDelegate(DragAndDropAccessibilityDelegate delegate) {
312 setOnClickListener(delegate);
Sunny Goyal9b180102020-03-11 10:02:29 -0700313 ViewCompat.setAccessibilityDelegate(this, delegate);
314
Adam Cohen6e7c37a2020-06-25 19:22:37 -0700315 mTouchHelper = delegate;
316 int accessibilityFlag = mTouchHelper != null
Sunny Goyal9b180102020-03-11 10:02:29 -0700317 ? IMPORTANT_FOR_ACCESSIBILITY_YES : IMPORTANT_FOR_ACCESSIBILITY_NO;
318 setImportantForAccessibility(accessibilityFlag);
319 getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
Adam Cohenc9735cf2015-01-23 16:11:55 -0800320
Sunny Goyal384b5782021-02-09 22:50:02 -0800321 // ExploreByTouchHelper sets focusability. Clear it when the delegate is cleared.
322 setFocusable(delegate != null);
Adam Cohenc9735cf2015-01-23 16:11:55 -0800323 // Invalidate the accessibility hierarchy
324 if (getParent() != null) {
325 getParent().notifySubtreeAccessibilityStateChanged(
326 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
327 }
328 }
329
Sunny Goyala4647b62021-02-02 13:45:34 -0800330 /**
331 * Returns the currently set accessibility delegate
332 */
333 public DragAndDropAccessibilityDelegate getDragAndDropAccessibilityDelegate() {
334 return mTouchHelper;
335 }
336
Adam Cohenc9735cf2015-01-23 16:11:55 -0800337 @Override
Adam Cohen6e7c37a2020-06-25 19:22:37 -0700338 public boolean dispatchHoverEvent(MotionEvent event) {
339 // Always attempt to dispatch hover events to accessibility first.
340 if (mTouchHelper != null && mTouchHelper.dispatchHoverEvent(event)) {
341 return true;
342 }
343 return super.dispatchHoverEvent(event);
344 }
345
346 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800347 public boolean onInterceptTouchEvent(MotionEvent ev) {
Winson Chungf9935182020-10-23 09:26:44 -0700348 return mTouchHelper != null
349 || (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev));
Adam Cohenc9735cf2015-01-23 16:11:55 -0800350 }
351
Chris Craik01f2d7f2013-10-01 14:41:56 -0700352 public void enableHardwareLayer(boolean hasLayer) {
353 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700354 }
355
vadimt04f356f2019-02-14 18:46:36 -0800356 public boolean isHardwareLayerEnabled() {
357 return mShortcutsAndWidgets.getLayerType() == LAYER_TYPE_HARDWARE;
358 }
359
Thales Lima8cd020b2022-03-15 20:15:14 +0000360 /**
361 * Change sizes of cells
362 *
363 * @param width the new width of the cells
364 * @param height the new height of the cells
365 */
Winson Chung5f8afe62013-08-12 16:19:28 -0700366 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700367 mFixedCellWidth = mCellWidth = width;
368 mFixedCellHeight = mCellHeight = height;
Jon Miranda228877d2021-02-09 11:05:00 -0500369 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
Thales Lima78d00ad2021-09-30 11:29:06 +0100370 mBorderSpace);
Winson Chung5f8afe62013-08-12 16:19:28 -0700371 }
372
Thales Lima8cd020b2022-03-15 20:15:14 +0000373 private void resetCellSizeInternal(DeviceProfile deviceProfile) {
374 switch (mContainerType) {
375 case FOLDER:
376 mBorderSpace = new Point(deviceProfile.folderCellLayoutBorderSpacePx);
377 break;
378 case HOTSEAT:
379 mBorderSpace = new Point(deviceProfile.hotseatBorderSpace,
380 deviceProfile.hotseatBorderSpace);
381 break;
382 case WORKSPACE:
383 default:
384 mBorderSpace = new Point(deviceProfile.cellLayoutBorderSpacePx);
385 break;
386 }
387
388 mCellWidth = mCellHeight = -1;
389 mFixedCellWidth = mFixedCellHeight = -1;
390 }
391
392 /**
393 * Reset the cell sizes and border space
394 */
395 public void resetCellSize(DeviceProfile deviceProfile) {
396 resetCellSizeInternal(deviceProfile);
397 requestLayout();
398 }
399
Adam Cohen2801caf2011-05-13 20:57:39 -0700400 public void setGridSize(int x, int y) {
401 mCountX = x;
402 mCountY = y;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700403 mOccupied = new GridOccupancy(mCountX, mCountY);
404 mTmpOccupied = new GridOccupancy(mCountX, mCountY);
Adam Cohen7fbec102012-03-27 12:42:19 -0700405 mTempRectStack.clear();
Jon Miranda228877d2021-02-09 11:05:00 -0500406 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
Thales Lima78d00ad2021-09-30 11:29:06 +0100407 mBorderSpace);
Adam Cohen76fc0852011-06-17 13:26:23 -0700408 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700409 }
410
Adam Cohen2374abf2013-04-16 14:56:57 -0700411 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
412 public void setInvertIfRtl(boolean invert) {
413 mShortcutsAndWidgets.setInvertIfRtl(invert);
414 }
415
Adam Cohen917e3882013-10-31 15:03:35 -0700416 public void setDropPending(boolean pending) {
417 mDropPending = pending;
418 }
419
420 public boolean isDropPending() {
421 return mDropPending;
422 }
423
Adam Cohenc50438c2014-08-19 17:43:05 -0700424 void setIsDragOverlapping(boolean isDragOverlapping) {
425 if (mIsDragOverlapping != isDragOverlapping) {
426 mIsDragOverlapping = isDragOverlapping;
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800427 mBackground.setState(mIsDragOverlapping
428 ? BACKGROUND_STATE_ACTIVE : BACKGROUND_STATE_DEFAULT);
Adam Cohenc50438c2014-08-19 17:43:05 -0700429 invalidate();
430 }
431 }
432
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700433 @Override
434 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700435 ParcelableSparseArray jail = getJailedArray(container);
436 super.dispatchSaveInstanceState(jail);
437 container.put(R.id.cell_layout_jail_id, jail);
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700438 }
439
440 @Override
441 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700442 super.dispatchRestoreInstanceState(getJailedArray(container));
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700443 }
444
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700445 /**
446 * Wrap the SparseArray in another Parcelable so that the item ids do not conflict with our
447 * our internal resource ids
448 */
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700449 private ParcelableSparseArray getJailedArray(SparseArray<Parcelable> container) {
450 final Parcelable parcelable = container.get(R.id.cell_layout_jail_id);
451 return parcelable instanceof ParcelableSparseArray ?
452 (ParcelableSparseArray) parcelable : new ParcelableSparseArray();
453 }
454
Tony Wickham0f97b782015-12-02 17:55:07 -0800455 public boolean getIsDragOverlapping() {
456 return mIsDragOverlapping;
457 }
458
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700459 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700460 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700461 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
462 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
463 // When we're small, we are either drawn normally or in the "accepts drops" state (during
464 // a drag). However, we also drag the mini hover background *over* one of those two
465 // backgrounds
Sunny Goyalaeb16432017-10-16 11:46:41 -0700466 if (mBackground.getAlpha() > 0) {
Sunny Goyal2805e632015-05-20 15:35:32 -0700467 mBackground.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700468 }
Romain Guya6abce82009-11-10 02:54:41 -0800469
Adam Cohen482ed822012-03-02 14:15:13 -0800470 if (DEBUG_VISUALIZE_OCCUPIED) {
Tony Wickham0ac045f2021-11-03 13:17:02 -0700471 Rect cellBounds = new Rect();
472 // Will contain the bounds of the cell including spacing between cells.
473 Rect cellBoundsWithSpacing = new Rect();
Tony Wickham12784902021-11-03 14:02:10 -0700474 int[] targetCell = new int[2];
Tony Wickham0ac045f2021-11-03 13:17:02 -0700475 int[] cellCenter = new int[2];
476 Paint debugPaint = new Paint();
477 debugPaint.setStrokeWidth(Utilities.dpToPx(1));
478 for (int x = 0; x < mCountX; x++) {
479 for (int y = 0; y < mCountY; y++) {
480 if (!mOccupied.cells[x][y]) {
481 continue;
Adam Cohen482ed822012-03-02 14:15:13 -0800482 }
Tony Wickham12784902021-11-03 14:02:10 -0700483 targetCell[0] = x;
484 targetCell[1] = y;
Tony Wickham0ac045f2021-11-03 13:17:02 -0700485
Tony Wickham12784902021-11-03 14:02:10 -0700486 boolean canCreateFolder = canCreateFolder(getChildAt(x, y));
Tony Wickham0ac045f2021-11-03 13:17:02 -0700487 cellToRect(x, y, 1, 1, cellBounds);
488 cellBoundsWithSpacing.set(cellBounds);
489 cellBoundsWithSpacing.inset(-mBorderSpace.x / 2, -mBorderSpace.y / 2);
Tony Wickham3cfa5ed2021-11-03 13:20:43 -0700490 getWorkspaceCellVisualCenter(x, y, cellCenter);
Tony Wickham0ac045f2021-11-03 13:17:02 -0700491
492 canvas.save();
493 canvas.clipRect(cellBoundsWithSpacing);
494
495 // Draw reorder drag target.
496 debugPaint.setColor(Color.RED);
Sebastian Franco6e1024e2022-07-29 13:46:49 -0700497 canvas.drawCircle(cellCenter[0], cellCenter[1],
498 getReorderRadius(targetCell, 1, 1), debugPaint);
Tony Wickham0ac045f2021-11-03 13:17:02 -0700499
500 // Draw folder creation drag target.
501 if (canCreateFolder) {
502 debugPaint.setColor(Color.GREEN);
503 canvas.drawCircle(cellCenter[0], cellCenter[1],
Tony Wickham12784902021-11-03 14:02:10 -0700504 getFolderCreationRadius(targetCell), debugPaint);
Tony Wickham0ac045f2021-11-03 13:17:02 -0700505 }
506
507 canvas.restore();
Adam Cohen482ed822012-03-02 14:15:13 -0800508 }
509 }
510 }
511
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800512 for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
513 DelegatedCellDrawing cellDrawing = mDelegatedCellDrawings.get(i);
514 cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation);
Adam Cohenefca0272016-02-24 19:19:06 -0800515 canvas.save();
516 canvas.translate(mTempLocation[0], mTempLocation[1]);
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800517 cellDrawing.drawUnderItem(canvas);
Adam Cohenefca0272016-02-24 19:19:06 -0800518 canvas.restore();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700519 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700520
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800521 if (mFolderLeaveBehind.mDelegateCellX >= 0 && mFolderLeaveBehind.mDelegateCellY >= 0) {
522 cellToPoint(mFolderLeaveBehind.mDelegateCellX,
523 mFolderLeaveBehind.mDelegateCellY, mTempLocation);
Adam Cohenefca0272016-02-24 19:19:06 -0800524 canvas.save();
525 canvas.translate(mTempLocation[0], mTempLocation[1]);
Sunny Goyal19b93b72017-02-19 20:21:37 -0800526 mFolderLeaveBehind.drawLeaveBehind(canvas);
Adam Cohenefca0272016-02-24 19:19:06 -0800527 canvas.restore();
Adam Cohenc51934b2011-07-26 21:07:43 -0700528 }
Adam Cohen65086992020-02-19 08:40:49 -0800529
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800530 if (mVisualizeCells || mVisualizeDropLocation) {
Adam Cohen65086992020-02-19 08:40:49 -0800531 visualizeGrid(canvas);
532 }
533 }
534
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800535 /**
Tony Wickham12784902021-11-03 14:02:10 -0700536 * Returns whether dropping an icon on the given View can create (or add to) a folder.
537 */
538 private boolean canCreateFolder(View child) {
539 return child instanceof DraggableView
540 && ((DraggableView) child).getViewType() == DRAGGABLE_ICON;
541 }
542
543 /**
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800544 * Indicates the progress of the Workspace entering the SpringLoaded state; allows the
545 * CellLayout to update various visuals for this state.
546 *
547 * @param progress
548 */
549 public void setSpringLoadedProgress(float progress) {
550 if (Float.compare(progress, mSpringLoadedProgress) != 0) {
551 mSpringLoadedProgress = progress;
Federico Baron1028a722022-10-13 14:09:38 -0700552 if (!SHOW_HOME_GARDENING.get()) {
553 updateBgAlpha();
554 }
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800555 setGridAlpha(progress);
556 }
557 }
558
559 /**
560 * See setSpringLoadedProgress
561 * @return progress
562 */
563 public float getSpringLoadedProgress() {
564 return mSpringLoadedProgress;
565 }
566
567 private void updateBgAlpha() {
Adam Cohen0c4d2782021-04-29 15:56:13 -0700568 mBackground.setAlpha((int) (mSpringLoadedProgress * 255));
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800569 }
570
571 /**
572 * Set the progress of this page's scroll
573 *
574 * @param progress 0 if the screen is centered, +/-1 if it is to the right / left respectively
575 */
576 public void setScrollProgress(float progress) {
577 if (Float.compare(Math.abs(progress), mScrollProgress) != 0) {
578 mScrollProgress = Math.abs(progress);
Federico Baron1028a722022-10-13 14:09:38 -0700579 if (!SHOW_HOME_GARDENING.get()) {
580 updateBgAlpha();
581 }
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800582 }
583 }
584
585 private void setGridAlpha(float gridAlpha) {
586 if (Float.compare(gridAlpha, mGridAlpha) != 0) {
587 mGridAlpha = gridAlpha;
588 invalidate();
589 }
590 }
591
Adam Cohen65086992020-02-19 08:40:49 -0800592 protected void visualizeGrid(Canvas canvas) {
Adam Cohen0c4d2782021-04-29 15:56:13 -0700593 DeviceProfile dp = mActivity.getDeviceProfile();
Alex Chau51da2192022-05-20 13:32:10 +0100594 int paddingX = Math.min((mCellWidth - dp.iconSizePx) / 2, dp.gridVisualizationPaddingX);
595 int paddingY = Math.min((mCellHeight - dp.iconSizePx) / 2, dp.gridVisualizationPaddingY);
Adam Cohen0c4d2782021-04-29 15:56:13 -0700596 mVisualizeGridRect.set(paddingX, paddingY,
597 mCellWidth - paddingX,
598 mCellHeight - paddingY);
Adam Cohen65086992020-02-19 08:40:49 -0800599
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800600 mVisualizeGridPaint.setStrokeWidth(8);
601 int paintAlpha = (int) (120 * mGridAlpha);
602 mVisualizeGridPaint.setColor(ColorUtils.setAlphaComponent(mGridColor, paintAlpha));
Adam Cohen65086992020-02-19 08:40:49 -0800603
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800604 if (mVisualizeCells) {
605 for (int i = 0; i < mCountX; i++) {
606 for (int j = 0; j < mCountY; j++) {
Thales Lima78d00ad2021-09-30 11:29:06 +0100607 int transX = i * mCellWidth + (i * mBorderSpace.x) + getPaddingLeft()
Adam Cohen0c4d2782021-04-29 15:56:13 -0700608 + paddingX;
Thales Lima78d00ad2021-09-30 11:29:06 +0100609 int transY = j * mCellHeight + (j * mBorderSpace.y) + getPaddingTop()
Adam Cohen0c4d2782021-04-29 15:56:13 -0700610 + paddingY;
Adam Cohen65086992020-02-19 08:40:49 -0800611
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800612 mVisualizeGridRect.offsetTo(transX, transY);
613 mVisualizeGridPaint.setStyle(Paint.Style.FILL);
614 canvas.drawRoundRect(mVisualizeGridRect, mGridVisualizationRoundingRadius,
615 mGridVisualizationRoundingRadius, mVisualizeGridPaint);
616 }
617 }
618 }
Adam Cohen65086992020-02-19 08:40:49 -0800619
Federico Baron1028a722022-10-13 14:09:38 -0700620 if (mVisualizeDropLocation && !SHOW_HOME_GARDENING.get()) {
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800621 for (int i = 0; i < mDragOutlines.length; i++) {
622 final float alpha = mDragOutlineAlphas[i];
623 if (alpha <= 0) continue;
Adam Cohen65086992020-02-19 08:40:49 -0800624
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800625 mVisualizeGridPaint.setAlpha(255);
626 int x = mDragOutlines[i].cellX;
627 int y = mDragOutlines[i].cellY;
628 int spanX = mDragOutlines[i].cellHSpan;
629 int spanY = mDragOutlines[i].cellVSpan;
630
Adam Cohened82e0d2021-07-21 17:24:12 -0700631 // TODO b/194414754 clean this up, reconcile with cellToRect
Adam Cohen0c4d2782021-04-29 15:56:13 -0700632 mVisualizeGridRect.set(paddingX, paddingY,
Thales Lima78d00ad2021-09-30 11:29:06 +0100633 mCellWidth * spanX + mBorderSpace.x * (spanX - 1) - paddingX,
634 mCellHeight * spanY + mBorderSpace.y * (spanY - 1) - paddingY);
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800635
Thales Lima78d00ad2021-09-30 11:29:06 +0100636 int transX = x * mCellWidth + (x * mBorderSpace.x)
Adam Cohen0c4d2782021-04-29 15:56:13 -0700637 + getPaddingLeft() + paddingX;
Thales Lima78d00ad2021-09-30 11:29:06 +0100638 int transY = y * mCellHeight + (y * mBorderSpace.y)
Adam Cohen0c4d2782021-04-29 15:56:13 -0700639 + getPaddingTop() + paddingY;
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800640
641 mVisualizeGridRect.offsetTo(transX, transY);
Adam Cohen65086992020-02-19 08:40:49 -0800642
643 mVisualizeGridPaint.setStyle(Paint.Style.STROKE);
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800644 mVisualizeGridPaint.setColor(Color.argb((int) (alpha),
645 Color.red(mGridColor), Color.green(mGridColor), Color.blue(mGridColor)));
Adam Cohen65086992020-02-19 08:40:49 -0800646
Adam Cohenf7ca3b42021-02-22 11:03:58 -0800647 canvas.drawRoundRect(mVisualizeGridRect, mGridVisualizationRoundingRadius,
648 mGridVisualizationRoundingRadius, mVisualizeGridPaint);
Adam Cohen65086992020-02-19 08:40:49 -0800649 }
650 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700651 }
652
Adam Cohenefca0272016-02-24 19:19:06 -0800653 @Override
654 protected void dispatchDraw(Canvas canvas) {
655 super.dispatchDraw(canvas);
656
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800657 for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
658 DelegatedCellDrawing bg = mDelegatedCellDrawings.get(i);
659 cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation);
660 canvas.save();
661 canvas.translate(mTempLocation[0], mTempLocation[1]);
662 bg.drawOverItem(canvas);
663 canvas.restore();
Adam Cohenefca0272016-02-24 19:19:06 -0800664 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700665 }
666
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800667 /**
668 * Add Delegated cell drawing
669 */
670 public void addDelegatedCellDrawing(DelegatedCellDrawing bg) {
671 mDelegatedCellDrawings.add(bg);
Adam Cohenefca0272016-02-24 19:19:06 -0800672 }
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800673
674 /**
675 * Remove item from DelegatedCellDrawings
676 */
677 public void removeDelegatedCellDrawing(DelegatedCellDrawing bg) {
678 mDelegatedCellDrawings.remove(bg);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700679 }
680
Adam Cohenc51934b2011-07-26 21:07:43 -0700681 public void setFolderLeaveBehindCell(int x, int y) {
Adam Cohenefca0272016-02-24 19:19:06 -0800682 View child = getChildAt(x, y);
Sunny Goyalab770a12018-11-14 15:17:26 -0800683 mFolderLeaveBehind.setup(getContext(), mActivity, null,
Adam Cohenefca0272016-02-24 19:19:06 -0800684 child.getMeasuredWidth(), child.getPaddingTop());
685
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800686 mFolderLeaveBehind.mDelegateCellX = x;
687 mFolderLeaveBehind.mDelegateCellY = y;
Adam Cohenc51934b2011-07-26 21:07:43 -0700688 invalidate();
689 }
690
691 public void clearFolderLeaveBehind() {
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800692 mFolderLeaveBehind.mDelegateCellX = -1;
693 mFolderLeaveBehind.mDelegateCellY = -1;
Adam Cohenc51934b2011-07-26 21:07:43 -0700694 invalidate();
695 }
696
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700697 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700698 public boolean shouldDelayChildPressedState() {
699 return false;
700 }
701
Adam Cohen1462de32012-07-24 22:34:36 -0700702 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700703 try {
704 dispatchRestoreInstanceState(states);
705 } catch (IllegalArgumentException ex) {
Zak Cohen3eeb41d2020-02-14 14:15:13 -0800706 if (FeatureFlags.IS_STUDIO_BUILD) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700707 throw ex;
708 }
709 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
710 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
711 }
Adam Cohen1462de32012-07-24 22:34:36 -0700712 }
713
Michael Jurkae6235dd2011-10-04 15:02:05 -0700714 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700715 public void cancelLongPress() {
716 super.cancelLongPress();
717
718 // Cancel long press for all children
719 final int count = getChildCount();
720 for (int i = 0; i < count; i++) {
721 final View child = getChildAt(i);
722 child.cancelLongPress();
723 }
724 }
725
Michael Jurkadee05892010-07-27 10:01:56 -0700726 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
727 mInterceptTouchListener = listener;
728 }
729
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800730 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700731 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800732 }
733
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800734 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700735 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800736 }
737
Sunny Goyalc13403c2016-11-18 23:44:48 -0800738 public boolean acceptsWidget() {
739 return mContainerType == WORKSPACE;
Sunny Goyale9b651e2015-04-24 11:44:51 -0700740 }
741
Sebastian Francod4682992022-10-05 13:03:09 -0500742 /**
743 * Adds the given view to the CellLayout
744 *
745 * @param child view to add.
746 * @param index index of the CellLayout children where to add the view.
747 * @param childId id of the view.
748 * @param params represent the logic of the view on the CellLayout.
749 * @param markCells if the occupied cells should be marked or not
750 * @return if adding the view was successful
751 */
752 public boolean addViewToCellLayout(View child, int index, int childId,
753 CellLayoutLayoutParams params, boolean markCells) {
754 final CellLayoutLayoutParams lp = params;
Winson Chungaafa03c2010-06-11 17:34:16 -0700755
Andrew Flynnde38e422012-05-08 11:22:15 -0700756 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800757 if (child instanceof BubbleTextView) {
758 BubbleTextView bubbleChild = (BubbleTextView) child;
Jon Mirandaf1eae802017-10-04 11:23:33 -0700759 bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800760 }
761
Sunny Goyalc13403c2016-11-18 23:44:48 -0800762 child.setScaleX(mChildScale);
763 child.setScaleY(mChildScale);
Adam Cohen307fe232012-08-16 17:55:58 -0700764
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800765 // Generate an id for each view, this assumes we have at most 256x256 cells
766 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700767 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700768 // If the horizontal or vertical span is set to -1, it is taken to
769 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700770 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
771 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800772
Winson Chungaafa03c2010-06-11 17:34:16 -0700773 child.setId(childId);
Tony Wickhama0628cc2015-10-14 15:23:04 -0700774 if (LOGD) {
775 Log.d(TAG, "Adding view to ShortcutsAndWidgetsContainer: " + child);
776 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700777 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700778
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700779 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700780
Winson Chungaafa03c2010-06-11 17:34:16 -0700781 return true;
782 }
783 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800784 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700785
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800786 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700787 public void removeAllViews() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700788 mOccupied.clear();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700789 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700790 }
791
792 @Override
793 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700794 if (mShortcutsAndWidgets.getChildCount() > 0) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700795 mOccupied.clear();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700796 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700797 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700798 }
799
800 @Override
801 public void removeView(View view) {
802 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700803 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700804 }
805
806 @Override
807 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700808 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
809 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700810 }
811
812 @Override
813 public void removeViewInLayout(View view) {
814 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700815 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700816 }
817
818 @Override
819 public void removeViews(int start, int count) {
820 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700821 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700822 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700823 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700824 }
825
826 @Override
827 public void removeViewsInLayout(int start, int count) {
828 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700829 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700830 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700831 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800832 }
833
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700834 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700835 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800836 * @param x X coordinate of the point
837 * @param y Y coordinate of the point
838 * @param result Array of 2 ints to hold the x and y coordinate of the cell
839 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700840 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700841 final int hStartPadding = getPaddingLeft();
842 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800843
Sebastian Francob57c0b22022-06-28 13:54:35 -0700844 result[0] = (x - hStartPadding) / (mCellWidth + mBorderSpace.x);
845 result[1] = (y - vStartPadding) / (mCellHeight + mBorderSpace.y);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800846
Adam Cohend22015c2010-07-26 22:02:18 -0700847 final int xAxis = mCountX;
848 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800849
850 if (result[0] < 0) result[0] = 0;
851 if (result[0] >= xAxis) result[0] = xAxis - 1;
852 if (result[1] < 0) result[1] = 0;
853 if (result[1] >= yAxis) result[1] = yAxis - 1;
854 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700855
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800856 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800857 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700858 *
859 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800860 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700861 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800862 * @param result Array of 2 ints to hold the x and y coordinate of the point
863 */
864 void cellToPoint(int cellX, int cellY, int[] result) {
Jon Miranda228877d2021-02-09 11:05:00 -0500865 cellToRect(cellX, cellY, 1, 1, mTempRect);
866 result[0] = mTempRect.left;
867 result[1] = mTempRect.top;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800868 }
869
Adam Cohene3e27a82011-04-15 12:07:39 -0700870 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800871 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700872 *
873 * @param cellX X coordinate of the cell
874 * @param cellY Y coordinate of the cell
875 *
876 * @param result Array of 2 ints to hold the x and y coordinate of the point
877 */
878 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700879 regionToCenterPoint(cellX, cellY, 1, 1, result);
880 }
881
882 /**
Tony Wickham0ac045f2021-11-03 13:17:02 -0700883 * Given a cell coordinate and span return the point that represents the center of the region
Adam Cohen47a876d2012-03-19 13:21:41 -0700884 *
885 * @param cellX X coordinate of the cell
886 * @param cellY Y coordinate of the cell
887 *
888 * @param result Array of 2 ints to hold the x and y coordinate of the point
889 */
890 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Jon Miranda228877d2021-02-09 11:05:00 -0500891 cellToRect(cellX, cellY, spanX, spanY, mTempRect);
892 result[0] = mTempRect.centerX();
893 result[1] = mTempRect.centerY();
Adam Cohen19f37922012-03-21 11:59:11 -0700894 }
895
Tony Wickham3cfa5ed2021-11-03 13:20:43 -0700896 /**
897 * Returns the distance between the given coordinate and the visual center of the given cell.
898 */
899 public float getDistanceFromWorkspaceCellVisualCenter(float x, float y, int[] cell) {
900 getWorkspaceCellVisualCenter(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700901 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800902 }
903
Tony Wickham3cfa5ed2021-11-03 13:20:43 -0700904 private void getWorkspaceCellVisualCenter(int cellX, int cellY, int[] outPoint) {
905 View child = getChildAt(cellX, cellY);
906 if (child instanceof DraggableView) {
907 DraggableView draggableChild = (DraggableView) child;
908 if (draggableChild.getViewType() == DRAGGABLE_ICON) {
909 cellToPoint(cellX, cellY, outPoint);
910 draggableChild.getWorkspaceVisualDragBounds(mTempRect);
911 mTempRect.offset(outPoint[0], outPoint[1]);
912 outPoint[0] = mTempRect.centerX();
913 outPoint[1] = mTempRect.centerY();
914 return;
915 }
916 }
917 cellToCenterPoint(cellX, cellY, outPoint);
918 }
919
Tony Wickham0ac045f2021-11-03 13:17:02 -0700920 /**
921 * Returns the max distance from the center of a cell that can accept a drop to create a folder.
922 */
Tony Wickham12784902021-11-03 14:02:10 -0700923 public float getFolderCreationRadius(int[] targetCell) {
Tony Wickham0ac045f2021-11-03 13:17:02 -0700924 DeviceProfile grid = mActivity.getDeviceProfile();
Tony Wickham12784902021-11-03 14:02:10 -0700925 float iconVisibleRadius = ICON_VISIBLE_AREA_FACTOR * grid.iconSizePx / 2;
926 // Halfway between reorder radius and icon.
Sebastian Franco6e1024e2022-07-29 13:46:49 -0700927 return (getReorderRadius(targetCell, 1, 1) + iconVisibleRadius) / 2;
Tony Wickham12784902021-11-03 14:02:10 -0700928 }
929
930 /**
931 * Returns the max distance from the center of a cell that will start to reorder on drag over.
932 */
Sebastian Franco6e1024e2022-07-29 13:46:49 -0700933 public float getReorderRadius(int[] targetCell, int spanX, int spanY) {
Tony Wickham12784902021-11-03 14:02:10 -0700934 int[] centerPoint = mTmpPoint;
935 getWorkspaceCellVisualCenter(targetCell[0], targetCell[1], centerPoint);
936
937 Rect cellBoundsWithSpacing = mTempRect;
Sebastian Franco6e1024e2022-07-29 13:46:49 -0700938 cellToRect(targetCell[0], targetCell[1], spanX, spanY, cellBoundsWithSpacing);
Tony Wickham12784902021-11-03 14:02:10 -0700939 cellBoundsWithSpacing.inset(-mBorderSpace.x / 2, -mBorderSpace.y / 2);
940
Sebastian Francoc8392ea2022-10-28 16:38:37 -0700941 if (canCreateFolder(getChildAt(targetCell[0], targetCell[1])) && spanX == 1 && spanY == 1) {
Tony Wickham12784902021-11-03 14:02:10 -0700942 // Take only the circle in the smaller dimension, to ensure we don't start reordering
943 // too soon before accepting a folder drop.
944 int minRadius = centerPoint[0] - cellBoundsWithSpacing.left;
945 minRadius = Math.min(minRadius, centerPoint[1] - cellBoundsWithSpacing.top);
946 minRadius = Math.min(minRadius, cellBoundsWithSpacing.right - centerPoint[0]);
947 minRadius = Math.min(minRadius, cellBoundsWithSpacing.bottom - centerPoint[1]);
948 return minRadius;
949 }
950 // Take up the entire cell, including space between this cell and the adjacent ones.
Sebastian Francoc8392ea2022-10-28 16:38:37 -0700951 // Multiply by span to scale radius
952 return (float) Math.hypot(spanX * cellBoundsWithSpacing.width() / 2f,
953 spanY * cellBoundsWithSpacing.height() / 2f);
Tony Wickham0ac045f2021-11-03 13:17:02 -0700954 }
955
Adam Cohenf9c184a2016-01-15 16:47:43 -0800956 public int getCellWidth() {
Romain Guy84f296c2009-11-04 15:00:44 -0800957 return mCellWidth;
958 }
959
Sunny Goyal0b754e52017-08-07 07:42:45 -0700960 public int getCellHeight() {
Romain Guy84f296c2009-11-04 15:00:44 -0800961 return mCellHeight;
962 }
963
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700964 public void setFixedSize(int width, int height) {
965 mFixedWidth = width;
966 mFixedHeight = height;
967 }
968
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800969 @Override
970 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800971 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800972 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700973 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
974 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700975 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
976 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Sunny Goyalae6e3182019-04-30 12:04:37 -0700977
Winson Chung11a1a532013-09-13 11:14:45 -0700978 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Thales Lima78d00ad2021-09-30 11:29:06 +0100979 int cw = DeviceProfile.calculateCellWidth(childWidthSize, mBorderSpace.x,
Jon Miranda228877d2021-02-09 11:05:00 -0500980 mCountX);
Thales Lima78d00ad2021-09-30 11:29:06 +0100981 int ch = DeviceProfile.calculateCellHeight(childHeightSize, mBorderSpace.y,
Jon Miranda228877d2021-02-09 11:05:00 -0500982 mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700983 if (cw != mCellWidth || ch != mCellHeight) {
984 mCellWidth = cw;
985 mCellHeight = ch;
Jon Miranda228877d2021-02-09 11:05:00 -0500986 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
Thales Lima78d00ad2021-09-30 11:29:06 +0100987 mBorderSpace);
Winson Chung11a1a532013-09-13 11:14:45 -0700988 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700989 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700990
Winson Chung2d75f122013-09-23 16:53:31 -0700991 int newWidth = childWidthSize;
992 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700993 if (mFixedWidth > 0 && mFixedHeight > 0) {
994 newWidth = mFixedWidth;
995 newHeight = mFixedHeight;
996 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800997 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
998 }
999
Sunny Goyal4fe5a372015-05-14 19:55:10 -07001000 mShortcutsAndWidgets.measure(
1001 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
1002 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
1003
1004 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
1005 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -07001006 if (mFixedWidth > 0 && mFixedHeight > 0) {
1007 setMeasuredDimension(maxWidth, maxHeight);
1008 } else {
1009 setMeasuredDimension(widthSize, heightSize);
1010 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001011 }
1012
1013 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -07001014 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Tony Wickham26b01422015-11-10 14:44:32 -08001015 int left = getPaddingLeft();
Sunny Goyal7ce471b2017-08-02 03:37:39 -07001016 left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Sunny Goyal7c786f72016-06-01 14:08:21 -07001017 int right = r - l - getPaddingRight();
Sunny Goyal7ce471b2017-08-02 03:37:39 -07001018 right -= (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Sunny Goyal7c786f72016-06-01 14:08:21 -07001019
Winson Chung38848ca2013-10-08 12:03:44 -07001020 int top = getPaddingTop();
Sunny Goyal7c786f72016-06-01 14:08:21 -07001021 int bottom = b - t - getPaddingBottom();
Sunny Goyal4fe5a372015-05-14 19:55:10 -07001022
Sunny Goyal7c786f72016-06-01 14:08:21 -07001023 // Expand the background drawing bounds by the padding baked into the background drawable
1024 mBackground.getPadding(mTempRect);
1025 mBackground.setBounds(
Jon Miranda28032002017-07-13 16:18:56 -07001026 left - mTempRect.left - getPaddingLeft(),
1027 top - mTempRect.top - getPaddingTop(),
1028 right + mTempRect.right + getPaddingRight(),
1029 bottom + mTempRect.bottom + getPaddingBottom());
Sunny Goyalae6e3182019-04-30 12:04:37 -07001030
Sunny Goyalc4d32012020-04-03 17:10:11 -07001031 mShortcutsAndWidgets.layout(left, top, right, bottom);
Sunny Goyal7c786f72016-06-01 14:08:21 -07001032 }
1033
Tony Wickhama501d492015-11-03 18:05:01 -08001034 /**
1035 * Returns the amount of space left over after subtracting padding and cells. This space will be
1036 * 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 -05001037 * width in {@link DeviceProfile#calculateCellWidth(int, int, int)}.
Tony Wickhama501d492015-11-03 18:05:01 -08001038 */
1039 public int getUnusedHorizontalSpace() {
Jon Miranda228877d2021-02-09 11:05:00 -05001040 return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth)
Thales Lima78d00ad2021-09-30 11:29:06 +01001041 - ((mCountX - 1) * mBorderSpace.x);
Tony Wickhama501d492015-11-03 18:05:01 -08001042 }
1043
Sunny Goyal2805e632015-05-20 15:35:32 -07001044 @Override
1045 protected boolean verifyDrawable(Drawable who) {
Sunny Goyal7ce471b2017-08-02 03:37:39 -07001046 return super.verifyDrawable(who) || (who == mBackground);
Sunny Goyal2805e632015-05-20 15:35:32 -07001047 }
1048
Michael Jurkaa52570f2012-03-20 03:18:20 -07001049 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -07001050 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -07001051 }
1052
Jon Miranda228877d2021-02-09 11:05:00 -05001053 public View getChildAt(int cellX, int cellY) {
1054 return mShortcutsAndWidgets.getChildAt(cellX, cellY);
Patrick Dubroy440c3602010-07-13 17:50:32 -07001055 }
1056
Adam Cohen76fc0852011-06-17 13:26:23 -07001057 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -08001058 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001059 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -08001060
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001061 if (clc.indexOfChild(child) != -1 && (child instanceof Reorderable)) {
Sebastian Francod4682992022-10-05 13:03:09 -05001062 final CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001063 final ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001064 final Reorderable item = (Reorderable) child;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001065
1066 // We cancel any existing animations
1067 if (mReorderAnimators.containsKey(lp)) {
1068 mReorderAnimators.get(lp).cancel();
1069 mReorderAnimators.remove(lp);
1070 }
1071
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001072
Adam Cohen482ed822012-03-02 14:15:13 -08001073 if (adjustOccupied) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001074 GridOccupancy occupied = permanent ? mOccupied : mTmpOccupied;
1075 occupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
1076 occupied.markCells(cellX, cellY, lp.cellHSpan, lp.cellVSpan, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001077 }
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001078
1079 // Compute the new x and y position based on the new cellX and cellY
1080 // We leverage the actual layout logic in the layout params and hence need to modify
1081 // state and revert that state.
1082 final int oldX = lp.x;
1083 final int oldY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001084 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001085 if (permanent) {
1086 lp.cellX = info.cellX = cellX;
1087 lp.cellY = info.cellY = cellY;
1088 } else {
1089 lp.tmpCellX = cellX;
1090 lp.tmpCellY = cellY;
1091 }
Jon Mirandae96798e2016-12-07 12:10:44 -08001092 clc.setupLp(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001093 final int newX = lp.x;
1094 final int newY = lp.y;
Adam Cohen76fc0852011-06-17 13:26:23 -07001095 lp.x = oldX;
1096 lp.y = oldY;
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001097 lp.isLockedToGrid = false;
1098 // End compute new x and y
1099
1100 item.getReorderPreviewOffset(mTmpPointF);
1101 final float initPreviewOffsetX = mTmpPointF.x;
1102 final float initPreviewOffsetY = mTmpPointF.y;
1103 final float finalPreviewOffsetX = newX - oldX;
1104 final float finalPreviewOffsetY = newY - oldY;
1105
Adam Cohen76fc0852011-06-17 13:26:23 -07001106
Adam Cohen482ed822012-03-02 14:15:13 -08001107 // Exit early if we're not actually moving the view
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001108 if (finalPreviewOffsetX == 0 && finalPreviewOffsetY == 0
1109 && initPreviewOffsetX == 0 && initPreviewOffsetY == 0) {
Adam Cohen482ed822012-03-02 14:15:13 -08001110 lp.isLockedToGrid = true;
1111 return true;
1112 }
1113
Sunny Goyal849c6a22018-08-08 16:33:46 -07001114 ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001115 va.setDuration(duration);
1116 mReorderAnimators.put(lp, va);
1117
1118 va.addUpdateListener(new AnimatorUpdateListener() {
1119 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001120 public void onAnimationUpdate(ValueAnimator animation) {
Jon Mirandae96798e2016-12-07 12:10:44 -08001121 float r = (Float) animation.getAnimatedValue();
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001122 float x = (1 - r) * initPreviewOffsetX + r * finalPreviewOffsetX;
1123 float y = (1 - r) * initPreviewOffsetY + r * finalPreviewOffsetY;
1124 item.setReorderPreviewOffset(x, y);
Adam Cohenbfbfd262011-06-13 16:55:12 -07001125 }
1126 });
Adam Cohen482ed822012-03-02 14:15:13 -08001127 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001128 boolean cancelled = false;
1129 public void onAnimationEnd(Animator animation) {
1130 // If the animation was cancelled, it means that another animation
1131 // has interrupted this one, and we don't want to lock the item into
1132 // place just yet.
1133 if (!cancelled) {
1134 lp.isLockedToGrid = true;
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001135 item.setReorderPreviewOffset(0, 0);
Adam Cohen482ed822012-03-02 14:15:13 -08001136 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001137 }
1138 if (mReorderAnimators.containsKey(lp)) {
1139 mReorderAnimators.remove(lp);
1140 }
1141 }
1142 public void onAnimationCancel(Animator animation) {
1143 cancelled = true;
1144 }
1145 });
Adam Cohen482ed822012-03-02 14:15:13 -08001146 va.setStartDelay(delay);
1147 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001148 return true;
1149 }
1150 return false;
1151 }
1152
Adam Cohenf7ca3b42021-02-22 11:03:58 -08001153 void visualizeDropLocation(int cellX, int cellY, int spanX, int spanY,
1154 DropTarget.DragObject dragObject) {
1155 if (mDragCell[0] != cellX || mDragCell[1] != cellY || mDragCellSpan[0] != spanX
1156 || mDragCellSpan[1] != spanY) {
Adam Cohen482ed822012-03-02 14:15:13 -08001157 mDragCell[0] = cellX;
1158 mDragCell[1] = cellY;
Adam Cohenf7ca3b42021-02-22 11:03:58 -08001159 mDragCellSpan[0] = spanX;
1160 mDragCellSpan[1] = spanY;
Steven Ng30dd1d62021-03-15 21:45:49 +00001161
Steven Nga999e222021-04-19 18:17:15 +01001162 // Apply color extraction on a widget when dragging.
1163 applyColorExtractionOnWidget(dragObject, mDragCell, spanX, spanY);
1164
Joe Onorato4be866d2010-10-10 11:26:02 -07001165 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001166 mDragOutlineAnims[oldIndex].animateOut();
1167 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Sunny Goyal106bf642015-07-16 12:18:06 -07001168
Sebastian Francod4682992022-10-05 13:03:09 -05001169 CellLayoutLayoutParams cell = mDragOutlines[mDragOutlineCurrent];
Adam Cohenf7ca3b42021-02-22 11:03:58 -08001170 cell.cellX = cellX;
1171 cell.cellY = cellY;
1172 cell.cellHSpan = spanX;
1173 cell.cellVSpan = spanY;
Adam Cohen65086992020-02-19 08:40:49 -08001174
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001175 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Adam Cohenf7ca3b42021-02-22 11:03:58 -08001176 invalidate();
Sunny Goyale78e3d72015-09-24 11:23:31 -07001177
1178 if (dragObject.stateAnnouncer != null) {
Sunny Goyalc13403c2016-11-18 23:44:48 -08001179 dragObject.stateAnnouncer.announce(getItemMoveDescription(cellX, cellY));
Sunny Goyale78e3d72015-09-24 11:23:31 -07001180 }
Adam Cohenf7ca3b42021-02-22 11:03:58 -08001181
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001182 }
1183 }
1184
Steven Ng30dd1d62021-03-15 21:45:49 +00001185 /** Applies the local color extraction to a dragging widget object. */
Steven Nga999e222021-04-19 18:17:15 +01001186 private void applyColorExtractionOnWidget(DropTarget.DragObject dragObject, int[] targetCell,
1187 int spanX, int spanY) {
Steven Ng30dd1d62021-03-15 21:45:49 +00001188 // Apply local extracted color if the DragView is an AppWidgetHostViewDrawable.
Steven Ng32427202021-04-19 18:12:12 +01001189 View view = dragObject.dragView.getContentView();
1190 if (view instanceof LauncherAppWidgetHostView) {
Andras Kloczl2dacbee2022-02-21 16:53:28 +00001191 int screenId = getWorkspace().getIdForScreen(this);
Steven Ng30dd1d62021-03-15 21:45:49 +00001192 cellToRect(targetCell[0], targetCell[1], spanX, spanY, mTempRect);
Jonathan Miranda21930da2021-05-03 18:44:13 +00001193
Sunny Goyal69a8eec2021-07-22 10:02:50 -07001194 ((LauncherAppWidgetHostView) view).handleDrag(mTempRect, this, screenId);
Steven Ng30dd1d62021-03-15 21:45:49 +00001195 }
1196 }
1197
Sunny Goyal726bee72018-03-05 12:54:24 -08001198 @SuppressLint("StringFormatMatches")
Sunny Goyalc13403c2016-11-18 23:44:48 -08001199 public String getItemMoveDescription(int cellX, int cellY) {
1200 if (mContainerType == HOTSEAT) {
1201 return getContext().getString(R.string.move_to_hotseat_position,
1202 Math.max(cellX, cellY) + 1);
1203 } else {
Shikha Malhotraf78da1b2022-04-11 10:23:18 +00001204 Workspace<?> workspace = getWorkspace();
Andras Kloczl2dacbee2022-02-21 16:53:28 +00001205 int row = cellY + 1;
1206 int col = workspace.mIsRtl ? mCountX - cellX : cellX + 1;
1207 int panelCount = workspace.getPanelCount();
Sebastian Franco930531f2022-06-16 16:49:11 -07001208 int screenId = workspace.getIdForScreen(this);
1209 int pageIndex = workspace.getPageIndexForScreenId(screenId);
Andras Kloczl2dacbee2022-02-21 16:53:28 +00001210 if (panelCount > 1) {
1211 // Increment the column if the target is on the right side of a two panel home
Andras Kloczl2dacbee2022-02-21 16:53:28 +00001212 col += (pageIndex % panelCount) * mCountX;
1213 }
Sebastian Franco930531f2022-06-16 16:49:11 -07001214 return getContext().getString(R.string.move_to_empty_cell_description, row, col,
1215 workspace.getPageDescription(pageIndex));
Sunny Goyalc13403c2016-11-18 23:44:48 -08001216 }
1217 }
1218
Shikha Malhotraf78da1b2022-04-11 10:23:18 +00001219 private Workspace<?> getWorkspace() {
Andras Kloczl2dacbee2022-02-21 16:53:28 +00001220 return Launcher.cast(mActivity).getWorkspace();
1221 }
1222
Adam Cohene0310962011-04-18 16:15:31 -07001223 public void clearDragOutlines() {
1224 final int oldIndex = mDragOutlineCurrent;
1225 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001226 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001227 }
1228
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001229 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001230 * Find a vacant area that will fit the given bounds nearest the requested
1231 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001232 *
Romain Guy51afc022009-05-04 18:03:43 -07001233 * @param pixelX The X location at which you want to search for a vacant area.
1234 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001235 * @param minSpanX The minimum horizontal span required
1236 * @param minSpanY The minimum vertical span required
1237 * @param spanX Horizontal span of the object.
1238 * @param spanY Vertical span of the object.
1239 * @param result Array in which to place the result, or null (in which case a new array will
1240 * be allocated)
1241 * @return The X, Y cell of a vacant area that can contain this object,
1242 * nearest the requested location.
1243 */
1244 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1245 int spanY, int[] result, int[] resultSpan) {
Sebastian Francob57c0b22022-06-28 13:54:35 -07001246 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, false,
Adam Cohend41fbf52012-02-16 23:53:59 -08001247 result, resultSpan);
1248 }
1249
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001250 private final Stack<Rect> mTempRectStack = new Stack<>();
Adam Cohend41fbf52012-02-16 23:53:59 -08001251 private void lazyInitTempRectStack() {
1252 if (mTempRectStack.isEmpty()) {
1253 for (int i = 0; i < mCountX * mCountY; i++) {
1254 mTempRectStack.push(new Rect());
1255 }
1256 }
1257 }
Adam Cohen482ed822012-03-02 14:15:13 -08001258
Adam Cohend41fbf52012-02-16 23:53:59 -08001259 private void recycleTempRects(Stack<Rect> used) {
1260 while (!used.isEmpty()) {
1261 mTempRectStack.push(used.pop());
1262 }
1263 }
1264
1265 /**
1266 * Find a vacant area that will fit the given bounds nearest the requested
1267 * cell location. Uses Euclidean distance to score multiple vacant areas.
Sebastian Francob57c0b22022-06-28 13:54:35 -07001268 * @param relativeXPos The X location relative to the Cell layout at which you want to search
1269 * for a vacant area.
1270 * @param relativeYPos The Y location relative to the Cell layout at which you want to search
1271 * for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001272 * @param minSpanX The minimum horizontal span required
1273 * @param minSpanY The minimum vertical span required
1274 * @param spanX Horizontal span of the object.
1275 * @param spanY Vertical span of the object.
1276 * @param ignoreOccupied If true, the result can be an occupied cell
1277 * @param result Array in which to place the result, or null (in which case a new array will
1278 * be allocated)
1279 * @return The X, Y cell of a vacant area that can contain this object,
1280 * nearest the requested location.
1281 */
Sebastian Francob57c0b22022-06-28 13:54:35 -07001282 private int[] findNearestArea(int relativeXPos, int relativeYPos, int minSpanX, int minSpanY,
1283 int spanX, int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001284 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001285
Sebastian Francob57c0b22022-06-28 13:54:35 -07001286 // For items with a spanX / spanY > 1, the passed in point (relativeXPos, relativeYPos)
1287 // corresponds to the center of the item, but we are searching based on the top-left cell,
1288 // so we translate the point over to correspond to the top-left.
1289 relativeXPos = (int) (relativeXPos - (mCellWidth + mBorderSpace.x) * (spanX - 1) / 2f);
1290 relativeYPos = (int) (relativeYPos - (mCellHeight + mBorderSpace.y) * (spanY - 1) / 2f);
Adam Cohene3e27a82011-04-15 12:07:39 -07001291
Jeff Sharkey70864282009-04-07 21:08:40 -07001292 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001293 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001294 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001295 final Rect bestRect = new Rect(-1, -1, -1, -1);
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001296 final Stack<Rect> validRegions = new Stack<>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001297
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001298 final int countX = mCountX;
1299 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001300
Adam Cohend41fbf52012-02-16 23:53:59 -08001301 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1302 spanX < minSpanX || spanY < minSpanY) {
1303 return bestXY;
1304 }
1305
1306 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001307 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001308 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1309 int ySize = -1;
1310 int xSize = -1;
Sebastian Francob57c0b22022-06-28 13:54:35 -07001311 if (!ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001312 // First, let's see if this thing fits anywhere
1313 for (int i = 0; i < minSpanX; i++) {
1314 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001315 if (mOccupied.cells[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001316 continue inner;
1317 }
Michael Jurkac28de512010-08-13 11:27:44 -07001318 }
1319 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001320 xSize = minSpanX;
1321 ySize = minSpanY;
1322
1323 // We know that the item will fit at _some_ acceptable size, now let's see
1324 // how big we can make it. We'll alternate between incrementing x and y spans
1325 // until we hit a limit.
1326 boolean incX = true;
1327 boolean hitMaxX = xSize >= spanX;
1328 boolean hitMaxY = ySize >= spanY;
1329 while (!(hitMaxX && hitMaxY)) {
1330 if (incX && !hitMaxX) {
1331 for (int j = 0; j < ySize; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001332 if (x + xSize > countX -1 || mOccupied.cells[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001333 // We can't move out horizontally
1334 hitMaxX = true;
1335 }
1336 }
1337 if (!hitMaxX) {
1338 xSize++;
1339 }
1340 } else if (!hitMaxY) {
1341 for (int i = 0; i < xSize; i++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001342 if (y + ySize > countY - 1 || mOccupied.cells[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001343 // We can't move out vertically
1344 hitMaxY = true;
1345 }
1346 }
1347 if (!hitMaxY) {
1348 ySize++;
1349 }
1350 }
1351 hitMaxX |= xSize >= spanX;
1352 hitMaxY |= ySize >= spanY;
1353 incX = !incX;
1354 }
1355 incX = true;
1356 hitMaxX = xSize >= spanX;
1357 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001358 }
Sunny Goyal2805e632015-05-20 15:35:32 -07001359 final int[] cellXY = mTmpPoint;
Adam Cohene3e27a82011-04-15 12:07:39 -07001360 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001361
Adam Cohend41fbf52012-02-16 23:53:59 -08001362 // We verify that the current rect is not a sub-rect of any of our previous
1363 // candidates. In this case, the current rect is disqualified in favour of the
1364 // containing rect.
1365 Rect currentRect = mTempRectStack.pop();
1366 currentRect.set(x, y, x + xSize, y + ySize);
1367 boolean contained = false;
1368 for (Rect r : validRegions) {
1369 if (r.contains(currentRect)) {
1370 contained = true;
1371 break;
1372 }
1373 }
1374 validRegions.push(currentRect);
Sebastian Francob57c0b22022-06-28 13:54:35 -07001375 double distance = Math.hypot(cellXY[0] - relativeXPos, cellXY[1] - relativeYPos);
Adam Cohen482ed822012-03-02 14:15:13 -08001376
Adam Cohend41fbf52012-02-16 23:53:59 -08001377 if ((distance <= bestDistance && !contained) ||
1378 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001379 bestDistance = distance;
1380 bestXY[0] = x;
1381 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001382 if (resultSpan != null) {
1383 resultSpan[0] = xSize;
1384 resultSpan[1] = ySize;
1385 }
1386 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001387 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001388 }
1389 }
1390
Adam Cohenc0dcf592011-06-01 15:30:43 -07001391 // Return -1, -1 if no suitable location found
1392 if (bestDistance == Double.MAX_VALUE) {
1393 bestXY[0] = -1;
1394 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001395 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001396 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001397 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001398 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001399
Adam Cohen482ed822012-03-02 14:15:13 -08001400 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001401 mTmpOccupied.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001402
Michael Jurkaa52570f2012-03-20 03:18:20 -07001403 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001404 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001405 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001406 if (child == dragView) continue;
Sebastian Francod4682992022-10-05 13:03:09 -05001407 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001408 CellAndSpan c = solution.map.get(child);
1409 if (c != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001410 lp.tmpCellX = c.cellX;
1411 lp.tmpCellY = c.cellY;
Adam Cohen8baab352012-03-20 17:39:21 -07001412 lp.cellHSpan = c.spanX;
1413 lp.cellVSpan = c.spanY;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001414 mTmpOccupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001415 }
1416 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001417 mTmpOccupied.markCells(solution, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001418 }
1419
1420 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
1421 commitDragView) {
1422
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001423 GridOccupancy occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
1424 occupied.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001425
Michael Jurkaa52570f2012-03-20 03:18:20 -07001426 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001427 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001428 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001429 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001430 CellAndSpan c = solution.map.get(child);
1431 if (c != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001432 animateChildToPosition(child, c.cellX, c.cellY, REORDER_ANIMATION_DURATION, 0,
Adam Cohen19f37922012-03-21 11:59:11 -07001433 DESTRUCTIVE_REORDER, false);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001434 occupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001435 }
1436 }
1437 if (commitDragView) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001438 occupied.markCells(solution, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001439 }
1440 }
1441
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001442
1443 // This method starts or changes the reorder preview animations
1444 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
Adam Cohen65086992020-02-19 08:40:49 -08001445 View dragView, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07001446 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07001447 for (int i = 0; i < childCount; i++) {
1448 View child = mShortcutsAndWidgets.getChildAt(i);
1449 if (child == dragView) continue;
1450 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001451 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
1452 != null && !solution.intersectingViews.contains(child);
1453
Adam Cohend9162062020-03-24 16:35:35 -07001454
Sebastian Francod4682992022-10-05 13:03:09 -05001455 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
Adam Cohend9162062020-03-24 16:35:35 -07001456 if (c != null && !skip && (child instanceof Reorderable)) {
1457 ReorderPreviewAnimation rha = new ReorderPreviewAnimation((Reorderable) child,
1458 mode, lp.cellX, lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07001459 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07001460 }
1461 }
1462 }
1463
Sunny Goyal849c6a22018-08-08 16:33:46 -07001464 private static final Property<ReorderPreviewAnimation, Float> ANIMATION_PROGRESS =
1465 new Property<ReorderPreviewAnimation, Float>(float.class, "animationProgress") {
1466 @Override
1467 public Float get(ReorderPreviewAnimation anim) {
1468 return anim.animationProgress;
1469 }
1470
1471 @Override
1472 public void set(ReorderPreviewAnimation anim, Float progress) {
1473 anim.setAnimationProgress(progress);
1474 }
1475 };
1476
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001477 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07001478 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001479 class ReorderPreviewAnimation {
Adam Cohend9162062020-03-24 16:35:35 -07001480 final Reorderable child;
Adam Cohend024f982012-05-23 18:26:45 -07001481 float finalDeltaX;
1482 float finalDeltaY;
1483 float initDeltaX;
1484 float initDeltaY;
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001485 final float finalScale;
Adam Cohend024f982012-05-23 18:26:45 -07001486 float initScale;
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001487 final int mode;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001488 boolean repeating = false;
1489 private static final int PREVIEW_DURATION = 300;
1490 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
1491
Jon Miranda21266912016-12-19 14:12:05 -08001492 private static final float CHILD_DIVIDEND = 4.0f;
1493
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001494 public static final int MODE_HINT = 0;
1495 public static final int MODE_PREVIEW = 1;
1496
Sunny Goyal849c6a22018-08-08 16:33:46 -07001497 float animationProgress = 0;
Sunny Goyalf0b6db72018-08-13 16:10:14 -07001498 ValueAnimator a;
Adam Cohen19f37922012-03-21 11:59:11 -07001499
Adam Cohend9162062020-03-24 16:35:35 -07001500 public ReorderPreviewAnimation(Reorderable child, int mode, int cellX0, int cellY0,
1501 int cellX1, int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07001502 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
1503 final int x0 = mTmpPoint[0];
1504 final int y0 = mTmpPoint[1];
1505 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
1506 final int x1 = mTmpPoint[0];
1507 final int y1 = mTmpPoint[1];
1508 final int dX = x1 - x0;
1509 final int dY = y1 - y0;
Jon Miranda21266912016-12-19 14:12:05 -08001510
1511 this.child = child;
1512 this.mode = mode;
Adam Cohend9162062020-03-24 16:35:35 -07001513 finalDeltaX = 0;
1514 finalDeltaY = 0;
Adam Cohen65086992020-02-19 08:40:49 -08001515
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001516 child.getReorderBounceOffset(mTmpPointF);
Adam Cohend9162062020-03-24 16:35:35 -07001517 initDeltaX = mTmpPointF.x;
1518 initDeltaY = mTmpPointF.y;
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001519 initScale = child.getReorderBounceScale();
Adam Cohend9162062020-03-24 16:35:35 -07001520 finalScale = mChildScale - (CHILD_DIVIDEND / child.getView().getWidth()) * initScale;
1521
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001522 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07001523 if (dX == dY && dX == 0) {
1524 } else {
1525 if (dY == 0) {
Adam Cohend9162062020-03-24 16:35:35 -07001526 finalDeltaX = -dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07001527 } else if (dX == 0) {
Adam Cohend9162062020-03-24 16:35:35 -07001528 finalDeltaY = -dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07001529 } else {
1530 double angle = Math.atan( (float) (dY) / dX);
Adam Cohend9162062020-03-24 16:35:35 -07001531 finalDeltaX = (int) (-dir * Math.signum(dX)
1532 * Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
1533 finalDeltaY = (int) (-dir * Math.signum(dY)
1534 * Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07001535 }
1536 }
Jon Miranda21266912016-12-19 14:12:05 -08001537 }
1538
Adam Cohend9162062020-03-24 16:35:35 -07001539 void setInitialAnimationValuesToBaseline() {
1540 initScale = mChildScale;
1541 initDeltaX = 0;
1542 initDeltaY = 0;
Adam Cohen19f37922012-03-21 11:59:11 -07001543 }
1544
Adam Cohend024f982012-05-23 18:26:45 -07001545 void animate() {
Adam Cohend9162062020-03-24 16:35:35 -07001546 boolean noMovement = (finalDeltaX == 0) && (finalDeltaY == 0);
Jon Miranda21266912016-12-19 14:12:05 -08001547
Adam Cohen19f37922012-03-21 11:59:11 -07001548 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001549 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohen19f37922012-03-21 11:59:11 -07001550 mShakeAnimators.remove(child);
Adam Cohend9162062020-03-24 16:35:35 -07001551
Jon Miranda21266912016-12-19 14:12:05 -08001552 if (noMovement) {
Adam Cohend9162062020-03-24 16:35:35 -07001553 // A previous animation for this item exists, and no new animation will exist.
1554 // Finish the old animation smoothly.
1555 oldAnimation.finishAnimation();
Adam Cohene7587d22012-05-24 18:50:02 -07001556 return;
Adam Cohend9162062020-03-24 16:35:35 -07001557 } else {
1558 // A previous animation for this item exists, and a new one will exist. Stop
1559 // the old animation in its tracks, and proceed with the new one.
1560 oldAnimation.cancel();
Adam Cohene7587d22012-05-24 18:50:02 -07001561 }
Adam Cohen19f37922012-03-21 11:59:11 -07001562 }
Jon Miranda21266912016-12-19 14:12:05 -08001563 if (noMovement) {
Adam Cohen19f37922012-03-21 11:59:11 -07001564 return;
1565 }
Adam Cohend9162062020-03-24 16:35:35 -07001566
Sunny Goyal849c6a22018-08-08 16:33:46 -07001567 ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS, 0, 1);
Adam Cohene7587d22012-05-24 18:50:02 -07001568 a = va;
Tony Wickham9e0702f2015-09-02 14:45:39 -07001569
1570 // Animations are disabled in power save mode, causing the repeated animation to jump
1571 // spastically between beginning and end states. Since this looks bad, we don't repeat
1572 // the animation in power save mode.
Sunny Goyaleaf7a952020-07-29 16:54:20 -07001573 if (areAnimatorsEnabled()) {
Tony Wickham9e0702f2015-09-02 14:45:39 -07001574 va.setRepeatMode(ValueAnimator.REVERSE);
1575 va.setRepeatCount(ValueAnimator.INFINITE);
1576 }
1577
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001578 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07001579 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07001580 va.addListener(new AnimatorListenerAdapter() {
1581 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07001582 // We make sure to end only after a full period
Adam Cohend9162062020-03-24 16:35:35 -07001583 setInitialAnimationValuesToBaseline();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001584 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07001585 }
1586 });
Adam Cohen19f37922012-03-21 11:59:11 -07001587 mShakeAnimators.put(child, this);
1588 va.start();
1589 }
1590
Sunny Goyal849c6a22018-08-08 16:33:46 -07001591 private void setAnimationProgress(float progress) {
1592 animationProgress = progress;
1593 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : animationProgress;
1594 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
1595 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001596 child.setReorderBounceOffset(x, y);
Sunny Goyal849c6a22018-08-08 16:33:46 -07001597 float s = animationProgress * finalScale + (1 - animationProgress) * initScale;
Adam Cohen1d13c0b2020-04-21 16:29:12 -07001598 child.setReorderBounceScale(s);
Sunny Goyal849c6a22018-08-08 16:33:46 -07001599 }
1600
Adam Cohend024f982012-05-23 18:26:45 -07001601 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07001602 if (a != null) {
1603 a.cancel();
1604 }
Adam Cohen19f37922012-03-21 11:59:11 -07001605 }
Adam Cohene7587d22012-05-24 18:50:02 -07001606
Adam Cohend9162062020-03-24 16:35:35 -07001607 /**
1608 * Smoothly returns the item to its baseline position / scale
1609 */
1610 @Thunk void finishAnimation() {
Adam Cohene7587d22012-05-24 18:50:02 -07001611 if (a != null) {
1612 a.cancel();
1613 }
Brandon Keely50e6e562012-05-08 16:28:49 -07001614
Adam Cohend9162062020-03-24 16:35:35 -07001615 setInitialAnimationValuesToBaseline();
1616 ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS,
1617 animationProgress, 0);
1618 a = va;
Sunny Goyalf0b6db72018-08-13 16:10:14 -07001619 a.setInterpolator(DEACCEL_1_5);
Adam Cohend9162062020-03-24 16:35:35 -07001620 a.setDuration(REORDER_ANIMATION_DURATION);
Sunny Goyal5d2fc322015-07-06 22:52:49 -07001621 a.start();
Brandon Keely50e6e562012-05-08 16:28:49 -07001622 }
Adam Cohen19f37922012-03-21 11:59:11 -07001623 }
1624
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001625 private void completeAndClearReorderPreviewAnimations() {
1626 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Adam Cohend9162062020-03-24 16:35:35 -07001627 a.finishAnimation();
Adam Cohen19f37922012-03-21 11:59:11 -07001628 }
1629 mShakeAnimators.clear();
1630 }
1631
Sunny Goyal711c5962021-06-23 12:36:18 -07001632 private void commitTempPlacement(View dragView) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001633 mTmpOccupied.copyTo(mOccupied);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07001634
Andras Kloczl2dacbee2022-02-21 16:53:28 +00001635 int screenId = getWorkspace().getIdForScreen(this);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07001636 int container = Favorites.CONTAINER_DESKTOP;
1637
Sunny Goyalc13403c2016-11-18 23:44:48 -08001638 if (mContainerType == HOTSEAT) {
Sunny Goyalaa8ef112015-06-12 20:04:41 -07001639 screenId = -1;
1640 container = Favorites.CONTAINER_HOTSEAT;
1641 }
1642
Michael Jurkaa52570f2012-03-20 03:18:20 -07001643 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001644 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07001645 View child = mShortcutsAndWidgets.getChildAt(i);
Sebastian Francod4682992022-10-05 13:03:09 -05001646 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
Adam Cohenea889a22012-03-27 16:45:39 -07001647 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07001648 // We do a null check here because the item info can be null in the case of the
1649 // AllApps button in the hotseat.
Sunny Goyal711c5962021-06-23 12:36:18 -07001650 if (info != null && child != dragView) {
Sunny Goyalaa8ef112015-06-12 20:04:41 -07001651 final boolean requiresDbUpdate = (info.cellX != lp.tmpCellX
1652 || info.cellY != lp.tmpCellY || info.spanX != lp.cellHSpan
1653 || info.spanY != lp.cellVSpan);
1654
Adam Cohen2acce882012-03-28 19:03:19 -07001655 info.cellX = lp.cellX = lp.tmpCellX;
1656 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07001657 info.spanX = lp.cellHSpan;
1658 info.spanY = lp.cellVSpan;
Sunny Goyalaa8ef112015-06-12 20:04:41 -07001659
1660 if (requiresDbUpdate) {
Sunny Goyalab770a12018-11-14 15:17:26 -08001661 Launcher.cast(mActivity).getModelWriter().modifyItemInDatabase(info, container,
1662 screenId, info.cellX, info.cellY, info.spanX, info.spanY);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07001663 }
Adam Cohen2acce882012-03-28 19:03:19 -07001664 }
Adam Cohen482ed822012-03-02 14:15:13 -08001665 }
1666 }
1667
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001668 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001669 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001670 for (int i = 0; i < childCount; i++) {
Sebastian Francod4682992022-10-05 13:03:09 -05001671 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) mShortcutsAndWidgets.getChildAt(
1672 i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08001673 lp.useTmpCoords = useTempCoords;
1674 }
1675 }
1676
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001677 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001678 int spanX, int spanY, View dragView, ItemConfiguration solution) {
1679 int[] result = new int[2];
1680 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001681 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08001682 resultSpan);
1683 if (result[0] >= 0 && result[1] >= 0) {
1684 copyCurrentStateToSolution(solution, false);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001685 solution.cellX = result[0];
1686 solution.cellY = result[1];
1687 solution.spanX = resultSpan[0];
1688 solution.spanY = resultSpan[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001689 solution.isSolution = true;
1690 } else {
1691 solution.isSolution = false;
1692 }
1693 return solution;
1694 }
1695
Adam Cohen19f37922012-03-21 11:59:11 -07001696 // For a given cell and span, fetch the set of views intersecting the region.
1697 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
1698 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
1699 if (boundingRect != null) {
1700 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
1701 }
1702 intersectingViews.clear();
1703 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1704 Rect r1 = new Rect();
1705 final int count = mShortcutsAndWidgets.getChildCount();
1706 for (int i = 0; i < count; i++) {
1707 View child = mShortcutsAndWidgets.getChildAt(i);
1708 if (child == dragView) continue;
Sebastian Francod4682992022-10-05 13:03:09 -05001709 CellLayoutLayoutParams
1710 lp = (CellLayoutLayoutParams) child.getLayoutParams();
Adam Cohen19f37922012-03-21 11:59:11 -07001711 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
1712 if (Rect.intersects(r0, r1)) {
1713 mIntersectingViews.add(child);
1714 if (boundingRect != null) {
1715 boundingRect.union(r1);
1716 }
1717 }
1718 }
1719 }
1720
1721 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
1722 View dragView, int[] result) {
1723 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1724 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
1725 mIntersectingViews);
1726 return !mIntersectingViews.isEmpty();
1727 }
1728
1729 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001730 completeAndClearReorderPreviewAnimations();
1731 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
1732 final int count = mShortcutsAndWidgets.getChildCount();
1733 for (int i = 0; i < count; i++) {
1734 View child = mShortcutsAndWidgets.getChildAt(i);
Sebastian Francod4682992022-10-05 13:03:09 -05001735 CellLayoutLayoutParams
1736 lp = (CellLayoutLayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001737 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
1738 lp.tmpCellX = lp.cellX;
1739 lp.tmpCellY = lp.cellY;
1740 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
1741 0, false, false);
1742 }
Adam Cohen19f37922012-03-21 11:59:11 -07001743 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001744 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07001745 }
Adam Cohen19f37922012-03-21 11:59:11 -07001746 }
1747
Adam Cohenbebf0422012-04-11 18:06:28 -07001748 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
1749 View dragView, int[] direction, boolean commit) {
1750 int[] pixelXY = new int[2];
1751 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
1752
1753 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001754 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Sebastian Francof153d912022-04-22 16:15:27 -05001755 spanX, spanY, direction, dragView, true, new ItemConfiguration());
Adam Cohenbebf0422012-04-11 18:06:28 -07001756
1757 setUseTempCoords(true);
1758 if (swapSolution != null && swapSolution.isSolution) {
1759 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
1760 // committing anything or animating anything as we just want to determine if a solution
1761 // exists
1762 copySolutionToTempState(swapSolution, dragView);
1763 setItemPlacementDirty(true);
1764 animateItemsToSolution(swapSolution, dragView, commit);
1765
1766 if (commit) {
Sunny Goyal711c5962021-06-23 12:36:18 -07001767 commitTempPlacement(null);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001768 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07001769 setItemPlacementDirty(false);
1770 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001771 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
Adam Cohen65086992020-02-19 08:40:49 -08001772 ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07001773 }
1774 mShortcutsAndWidgets.requestLayout();
1775 }
1776 return swapSolution.isSolution;
1777 }
1778
Sebastian Franco9cab1c32022-10-25 17:28:54 -07001779 /**
1780 * Find a vacant area that will fit the given bounds nearest the requested
1781 * cell location, and will also weigh in a suggested direction vector of the
1782 * desired location. This method computers distance based on unit grid distances,
1783 * not pixel distances.
1784 *
1785 * @param cellX The X cell nearest to which you want to search for a vacant area.
1786 * @param cellY The Y cell nearest which you want to search for a vacant area.
1787 * @param spanX Horizontal span of the object.
1788 * @param spanY Vertical span of the object.
1789 * @param direction The favored direction in which the views should move from x, y
1790 * @param occupied The array which represents which cells in the CellLayout are occupied
1791 * @param blockOccupied The array which represents which cells in the specified block (cellX,
1792 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
1793 * @param result Array in which to place the result, or null (in which case a new array will
1794 * be allocated)
1795 * @return The X, Y cell of a vacant area that can contain this object,
1796 * nearest the requested location.
1797 */
1798 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
1799 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
1800 // Keep track of best-scoring drop area
1801 final int[] bestXY = result != null ? result : new int[2];
1802 float bestDistance = Float.MAX_VALUE;
1803 int bestDirectionScore = Integer.MIN_VALUE;
Adam Cohen482ed822012-03-02 14:15:13 -08001804
Sebastian Franco9cab1c32022-10-25 17:28:54 -07001805 final int countX = mCountX;
1806 final int countY = mCountY;
1807
1808 for (int y = 0; y < countY - (spanY - 1); y++) {
1809 inner:
1810 for (int x = 0; x < countX - (spanX - 1); x++) {
1811 // First, let's see if this thing fits anywhere
1812 for (int i = 0; i < spanX; i++) {
1813 for (int j = 0; j < spanY; j++) {
1814 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
1815 continue inner;
1816 }
1817 }
1818 }
1819
1820 float distance = (float) Math.hypot(x - cellX, y - cellY);
1821 int[] curDirection = mTmpPoint;
1822 computeDirectionVector(x - cellX, y - cellY, curDirection);
1823 // The direction score is just the dot product of the two candidate direction
1824 // and that passed in.
1825 int curDirectionScore = direction[0] * curDirection[0] +
1826 direction[1] * curDirection[1];
1827 if (Float.compare(distance, bestDistance) < 0 ||
1828 (Float.compare(distance, bestDistance) == 0
1829 && curDirectionScore > bestDirectionScore)) {
1830 bestDistance = distance;
1831 bestDirectionScore = curDirectionScore;
1832 bestXY[0] = x;
1833 bestXY[1] = y;
1834 }
Adam Cohen19f37922012-03-21 11:59:11 -07001835 }
Adam Cohen19f37922012-03-21 11:59:11 -07001836 }
1837
Sebastian Franco9cab1c32022-10-25 17:28:54 -07001838 // Return -1, -1 if no suitable location found
1839 if (bestDistance == Float.MAX_VALUE) {
1840 bestXY[0] = -1;
1841 bestXY[1] = -1;
Sebastian Franco53a15a42022-10-25 17:28:54 -07001842 }
Sebastian Franco9cab1c32022-10-25 17:28:54 -07001843 return bestXY;
1844 }
1845
1846 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
1847 int[] direction, ItemConfiguration currentState) {
1848 CellAndSpan c = currentState.map.get(v);
1849 boolean success = false;
1850 mTmpOccupied.markCells(c, false);
1851 mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
1852
1853 findNearestArea(c.cellX, c.cellY, c.spanX, c.spanY, direction,
1854 mTmpOccupied.cells, null, mTempLocation);
1855
1856 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
1857 c.cellX = mTempLocation[0];
1858 c.cellY = mTempLocation[1];
1859 success = true;
1860 }
1861 mTmpOccupied.markCells(c, true);
1862 return success;
1863 }
1864
1865 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1866 int[] direction, View dragView, ItemConfiguration currentState) {
1867
1868 ViewCluster cluster = new ViewCluster(views, currentState);
1869 Rect clusterRect = cluster.getBoundingRect();
1870 int whichEdge;
1871 int pushDistance;
1872 boolean fail = false;
1873
1874 // Determine the edge of the cluster that will be leading the push and how far
1875 // the cluster must be shifted.
1876 if (direction[0] < 0) {
1877 whichEdge = ViewCluster.LEFT;
1878 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
1879 } else if (direction[0] > 0) {
1880 whichEdge = ViewCluster.RIGHT;
1881 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1882 } else if (direction[1] < 0) {
1883 whichEdge = ViewCluster.TOP;
1884 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1885 } else {
1886 whichEdge = ViewCluster.BOTTOM;
1887 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
1888 }
1889
1890 // Break early for invalid push distance.
1891 if (pushDistance <= 0) {
1892 return false;
1893 }
1894
1895 // Mark the occupied state as false for the group of views we want to move.
1896 for (View v: views) {
1897 CellAndSpan c = currentState.map.get(v);
1898 mTmpOccupied.markCells(c, false);
1899 }
1900
1901 // We save the current configuration -- if we fail to find a solution we will revert
1902 // to the initial state. The process of finding a solution modifies the configuration
1903 // in place, hence the need for revert in the failure case.
1904 currentState.save();
1905
1906 // The pushing algorithm is simplified by considering the views in the order in which
1907 // they would be pushed by the cluster. For example, if the cluster is leading with its
1908 // left edge, we consider sort the views by their right edge, from right to left.
1909 cluster.sortConfigurationForEdgePush(whichEdge);
1910
1911 while (pushDistance > 0 && !fail) {
1912 for (View v: currentState.sortedViews) {
1913 // For each view that isn't in the cluster, we see if the leading edge of the
1914 // cluster is contacting the edge of that view. If so, we add that view to the
1915 // cluster.
1916 if (!cluster.views.contains(v) && v != dragView) {
1917 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1918 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) v.getLayoutParams();
1919 if (!lp.canReorder) {
1920 // The push solution includes the all apps button, this is not viable.
1921 fail = true;
1922 break;
1923 }
1924 cluster.addView(v);
1925 CellAndSpan c = currentState.map.get(v);
1926
1927 // Adding view to cluster, mark it as not occupied.
1928 mTmpOccupied.markCells(c, false);
1929 }
1930 }
1931 }
1932 pushDistance--;
1933
1934 // The cluster has been completed, now we move the whole thing over in the appropriate
1935 // direction.
1936 cluster.shift(whichEdge, 1);
1937 }
1938
1939 boolean foundSolution = false;
1940 clusterRect = cluster.getBoundingRect();
1941
1942 // Due to the nature of the algorithm, the only check required to verify a valid solution
1943 // is to ensure that completed shifted cluster lies completely within the cell layout.
1944 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1945 clusterRect.bottom <= mCountY) {
1946 foundSolution = true;
1947 } else {
1948 currentState.restore();
1949 }
1950
1951 // In either case, we set the occupied array as marked for the location of the views
1952 for (View v: cluster.views) {
1953 CellAndSpan c = currentState.map.get(v);
1954 mTmpOccupied.markCells(c, true);
1955 }
1956
1957 return foundSolution;
1958 }
1959
1960 /**
1961 * This helper class defines a cluster of views. It helps with defining complex edges
1962 * of the cluster and determining how those edges interact with other views. The edges
1963 * essentially define a fine-grained boundary around the cluster of views -- like a more
1964 * precise version of a bounding box.
1965 */
1966 private class ViewCluster {
1967 final static int LEFT = 1 << 0;
1968 final static int TOP = 1 << 1;
1969 final static int RIGHT = 1 << 2;
1970 final static int BOTTOM = 1 << 3;
1971
1972 final ArrayList<View> views;
1973 final ItemConfiguration config;
1974 final Rect boundingRect = new Rect();
1975
1976 final int[] leftEdge = new int[mCountY];
1977 final int[] rightEdge = new int[mCountY];
1978 final int[] topEdge = new int[mCountX];
1979 final int[] bottomEdge = new int[mCountX];
1980 int dirtyEdges;
1981 boolean boundingRectDirty;
1982
1983 @SuppressWarnings("unchecked")
1984 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1985 this.views = (ArrayList<View>) views.clone();
1986 this.config = config;
1987 resetEdges();
1988 }
1989
1990 void resetEdges() {
1991 for (int i = 0; i < mCountX; i++) {
1992 topEdge[i] = -1;
1993 bottomEdge[i] = -1;
1994 }
1995 for (int i = 0; i < mCountY; i++) {
1996 leftEdge[i] = -1;
1997 rightEdge[i] = -1;
1998 }
1999 dirtyEdges = LEFT | TOP | RIGHT | BOTTOM;
2000 boundingRectDirty = true;
2001 }
2002
2003 void computeEdge(int which) {
2004 int count = views.size();
2005 for (int i = 0; i < count; i++) {
2006 CellAndSpan cs = config.map.get(views.get(i));
2007 switch (which) {
2008 case LEFT:
2009 int left = cs.cellX;
2010 for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) {
2011 if (left < leftEdge[j] || leftEdge[j] < 0) {
2012 leftEdge[j] = left;
2013 }
2014 }
2015 break;
2016 case RIGHT:
2017 int right = cs.cellX + cs.spanX;
2018 for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) {
2019 if (right > rightEdge[j]) {
2020 rightEdge[j] = right;
2021 }
2022 }
2023 break;
2024 case TOP:
2025 int top = cs.cellY;
2026 for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) {
2027 if (top < topEdge[j] || topEdge[j] < 0) {
2028 topEdge[j] = top;
2029 }
2030 }
2031 break;
2032 case BOTTOM:
2033 int bottom = cs.cellY + cs.spanY;
2034 for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) {
2035 if (bottom > bottomEdge[j]) {
2036 bottomEdge[j] = bottom;
2037 }
2038 }
2039 break;
2040 }
2041 }
2042 }
2043
2044 boolean isViewTouchingEdge(View v, int whichEdge) {
2045 CellAndSpan cs = config.map.get(v);
2046
2047 if ((dirtyEdges & whichEdge) == whichEdge) {
2048 computeEdge(whichEdge);
2049 dirtyEdges &= ~whichEdge;
2050 }
2051
2052 switch (whichEdge) {
2053 case LEFT:
2054 for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) {
2055 if (leftEdge[i] == cs.cellX + cs.spanX) {
2056 return true;
2057 }
2058 }
2059 break;
2060 case RIGHT:
2061 for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) {
2062 if (rightEdge[i] == cs.cellX) {
2063 return true;
2064 }
2065 }
2066 break;
2067 case TOP:
2068 for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) {
2069 if (topEdge[i] == cs.cellY + cs.spanY) {
2070 return true;
2071 }
2072 }
2073 break;
2074 case BOTTOM:
2075 for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) {
2076 if (bottomEdge[i] == cs.cellY) {
2077 return true;
2078 }
2079 }
2080 break;
2081 }
2082 return false;
2083 }
2084
2085 void shift(int whichEdge, int delta) {
2086 for (View v: views) {
2087 CellAndSpan c = config.map.get(v);
2088 switch (whichEdge) {
2089 case LEFT:
2090 c.cellX -= delta;
2091 break;
2092 case RIGHT:
2093 c.cellX += delta;
2094 break;
2095 case TOP:
2096 c.cellY -= delta;
2097 break;
2098 case BOTTOM:
2099 default:
2100 c.cellY += delta;
2101 break;
2102 }
2103 }
2104 resetEdges();
2105 }
2106
2107 public void addView(View v) {
2108 views.add(v);
2109 resetEdges();
2110 }
2111
2112 public Rect getBoundingRect() {
2113 if (boundingRectDirty) {
2114 config.getBoundingRectForViews(views, boundingRect);
2115 }
2116 return boundingRect;
2117 }
2118
2119 final PositionComparator comparator = new PositionComparator();
2120 class PositionComparator implements Comparator<View> {
2121 int whichEdge = 0;
2122 public int compare(View left, View right) {
2123 CellAndSpan l = config.map.get(left);
2124 CellAndSpan r = config.map.get(right);
2125 switch (whichEdge) {
2126 case LEFT:
2127 return (r.cellX + r.spanX) - (l.cellX + l.spanX);
2128 case RIGHT:
2129 return l.cellX - r.cellX;
2130 case TOP:
2131 return (r.cellY + r.spanY) - (l.cellY + l.spanY);
2132 case BOTTOM:
2133 default:
2134 return l.cellY - r.cellY;
2135 }
2136 }
2137 }
2138
2139 public void sortConfigurationForEdgePush(int edge) {
2140 comparator.whichEdge = edge;
2141 Collections.sort(config.sortedViews, comparator);
2142 }
2143 }
2144
2145 // This method tries to find a reordering solution which satisfies the push mechanic by trying
2146 // to push items in each of the cardinal directions, in an order based on the direction vector
2147 // passed.
2148 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
2149 int[] direction, View ignoreView, ItemConfiguration solution) {
2150 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
2151 // If the direction vector has two non-zero components, we try pushing
2152 // separately in each of the components.
2153 int temp = direction[1];
2154 direction[1] = 0;
2155
2156 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2157 ignoreView, solution)) {
2158 return true;
2159 }
2160 direction[1] = temp;
2161 temp = direction[0];
2162 direction[0] = 0;
2163
2164 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2165 ignoreView, solution)) {
2166 return true;
2167 }
2168 // Revert the direction
2169 direction[0] = temp;
2170
2171 // Now we try pushing in each component of the opposite direction
2172 direction[0] *= -1;
2173 direction[1] *= -1;
2174 temp = direction[1];
2175 direction[1] = 0;
2176 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2177 ignoreView, solution)) {
2178 return true;
2179 }
2180
2181 direction[1] = temp;
2182 temp = direction[0];
2183 direction[0] = 0;
2184 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2185 ignoreView, solution)) {
2186 return true;
2187 }
2188 // revert the direction
2189 direction[0] = temp;
2190 direction[0] *= -1;
2191 direction[1] *= -1;
2192
2193 } else {
2194 // If the direction vector has a single non-zero component, we push first in the
2195 // direction of the vector
2196 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2197 ignoreView, solution)) {
2198 return true;
2199 }
2200 // Then we try the opposite direction
2201 direction[0] *= -1;
2202 direction[1] *= -1;
2203 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2204 ignoreView, solution)) {
2205 return true;
2206 }
2207 // Switch the direction back
2208 direction[0] *= -1;
2209 direction[1] *= -1;
2210
2211 // If we have failed to find a push solution with the above, then we try
2212 // to find a solution by pushing along the perpendicular axis.
2213
2214 // Swap the components
2215 int temp = direction[1];
2216 direction[1] = direction[0];
2217 direction[0] = temp;
2218 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2219 ignoreView, solution)) {
2220 return true;
2221 }
2222
2223 // Then we try the opposite direction
2224 direction[0] *= -1;
2225 direction[1] *= -1;
2226 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2227 ignoreView, solution)) {
2228 return true;
2229 }
2230 // Switch the direction back
2231 direction[0] *= -1;
2232 direction[1] *= -1;
2233
2234 // Swap the components back
2235 temp = direction[1];
2236 direction[1] = direction[0];
2237 direction[0] = temp;
2238 }
2239 return false;
2240 }
2241
2242 /*
2243 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
2244 * the provided point and the provided cell
2245 */
2246 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
2247 double angle = Math.atan(deltaY / deltaX);
2248
2249 result[0] = 0;
2250 result[1] = 0;
2251 if (Math.abs(Math.cos(angle)) > 0.5f) {
2252 result[0] = (int) Math.signum(deltaX);
2253 }
2254 if (Math.abs(Math.sin(angle)) > 0.5f) {
2255 result[1] = (int) Math.signum(deltaY);
2256 }
2257 }
2258
2259 /* This seems like it should be obvious and straight-forward, but when the direction vector
2260 needs to match with the notion of the dragView pushing other views, we have to employ
2261 a slightly more subtle notion of the direction vector. The question is what two points is
2262 the vector between? The center of the dragView and its desired destination? Not quite, as
2263 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2264 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2265 or right, which helps make pushing feel right.
2266 */
2267 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2268 int spanY, View dragView, int[] resultDirection) {
2269
2270 //TODO(adamcohen) b/151776141 use the items visual center for the direction vector
2271 int[] targetDestination = new int[2];
2272
2273 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2274 Rect dragRect = new Rect();
2275 cellToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2276 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2277
2278 Rect dropRegionRect = new Rect();
2279 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2280 dragView, dropRegionRect, mIntersectingViews);
2281
2282 int dropRegionSpanX = dropRegionRect.width();
2283 int dropRegionSpanY = dropRegionRect.height();
2284
2285 cellToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2286 dropRegionRect.height(), dropRegionRect);
2287
2288 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2289 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2290
2291 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2292 deltaX = 0;
2293 }
2294 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2295 deltaY = 0;
2296 }
2297
2298 if (deltaX == 0 && deltaY == 0) {
2299 // No idea what to do, give a random direction.
2300 resultDirection[0] = 1;
2301 resultDirection[1] = 0;
2302 } else {
2303 computeDirectionVector(deltaX, deltaY, resultDirection);
2304 }
2305 }
2306
2307 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
2308 int[] direction, View dragView, ItemConfiguration currentState) {
2309 if (views.size() == 0) return true;
2310
2311 boolean success = false;
2312 Rect boundingRect = new Rect();
2313 // We construct a rect which represents the entire group of views passed in
2314 currentState.getBoundingRectForViews(views, boundingRect);
2315
2316 // Mark the occupied state as false for the group of views we want to move.
2317 for (View v: views) {
2318 CellAndSpan c = currentState.map.get(v);
2319 mTmpOccupied.markCells(c, false);
2320 }
2321
2322 GridOccupancy blockOccupied = new GridOccupancy(boundingRect.width(), boundingRect.height());
2323 int top = boundingRect.top;
2324 int left = boundingRect.left;
2325 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
2326 // for interlocking.
2327 for (View v: views) {
2328 CellAndSpan c = currentState.map.get(v);
2329 blockOccupied.markCells(c.cellX - left, c.cellY - top, c.spanX, c.spanY, true);
2330 }
2331
2332 mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
2333
2334 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
2335 boundingRect.height(), direction,
2336 mTmpOccupied.cells, blockOccupied.cells, mTempLocation);
2337
2338 // If we successfully found a location by pushing the block of views, we commit it
2339 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
2340 int deltaX = mTempLocation[0] - boundingRect.left;
2341 int deltaY = mTempLocation[1] - boundingRect.top;
2342 for (View v: views) {
2343 CellAndSpan c = currentState.map.get(v);
2344 c.cellX += deltaX;
2345 c.cellY += deltaY;
2346 }
2347 success = true;
2348 }
2349
2350 // In either case, we set the occupied array as marked for the location of the views
2351 for (View v: views) {
2352 CellAndSpan c = currentState.map.get(v);
2353 mTmpOccupied.markCells(c, true);
2354 }
2355 return success;
2356 }
2357
2358 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
2359 View ignoreView, ItemConfiguration solution) {
2360 // Return early if get invalid cell positions
2361 if (cellX < 0 || cellY < 0) return false;
2362
2363 mIntersectingViews.clear();
2364 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2365
2366 // Mark the desired location of the view currently being dragged.
2367 if (ignoreView != null) {
2368 CellAndSpan c = solution.map.get(ignoreView);
2369 if (c != null) {
2370 c.cellX = cellX;
2371 c.cellY = cellY;
2372 }
2373 }
2374 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2375 Rect r1 = new Rect();
2376 for (View child: solution.map.keySet()) {
2377 if (child == ignoreView) continue;
2378 CellAndSpan c = solution.map.get(child);
2379 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
2380 r1.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2381 if (Rect.intersects(r0, r1)) {
2382 if (!lp.canReorder) {
2383 return false;
2384 }
2385 mIntersectingViews.add(child);
2386 }
2387 }
2388
2389 solution.intersectingViews = new ArrayList<>(mIntersectingViews);
2390
2391 // First we try to find a solution which respects the push mechanic. That is,
2392 // we try to find a solution such that no displaced item travels through another item
2393 // without also displacing that item.
2394 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
2395 solution)) {
2396 return true;
2397 }
2398
2399 // Next we try moving the views as a block, but without requiring the push mechanic.
2400 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
2401 solution)) {
2402 return true;
2403 }
2404
2405 // Ok, they couldn't move as a block, let's move them individually
2406 for (View v : mIntersectingViews) {
2407 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
2408 return false;
2409 }
2410 }
2411 return true;
2412 }
2413
2414 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
2415 int spanX, int spanY, int[] direction, View dragView, boolean decX,
2416 ItemConfiguration solution) {
2417 // Copy the current state into the solution. This solution will be manipulated as necessary.
2418 copyCurrentStateToSolution(solution, false);
2419 // Copy the current occupied array into the temporary occupied array. This array will be
2420 // manipulated as necessary to find a solution.
2421 mOccupied.copyTo(mTmpOccupied);
2422
2423 // We find the nearest cell into which we would place the dragged item, assuming there's
2424 // nothing in its way.
2425 int result[] = new int[2];
2426 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2427
2428 boolean success;
2429 // First we try the exact nearest position of the item being dragged,
2430 // we will then want to try to move this around to other neighbouring positions
2431 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
2432 solution);
2433
2434 if (!success) {
2435 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
2436 // x, then 1 in y etc.
2437 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
2438 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
2439 direction, dragView, false, solution);
2440 } else if (spanY > minSpanY) {
2441 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
2442 direction, dragView, true, solution);
2443 }
2444 solution.isSolution = false;
2445 } else {
2446 solution.isSolution = true;
2447 solution.cellX = result[0];
2448 solution.cellY = result[1];
2449 solution.spanX = spanX;
2450 solution.spanY = spanY;
2451 }
2452 return solution;
2453 }
2454
2455 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
2456 int childCount = mShortcutsAndWidgets.getChildCount();
2457 for (int i = 0; i < childCount; i++) {
2458 View child = mShortcutsAndWidgets.getChildAt(i);
2459 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
2460 CellAndSpan c;
2461 if (temp) {
2462 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
2463 } else {
2464 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
2465 }
2466 solution.add(child, c);
2467 }
Sebastian Franco53a15a42022-10-25 17:28:54 -07002468 }
2469
2470 /**
2471 * Returns a "reorder" where we simply drop the item in the closest empty space, without moving
2472 * any other item in the way.
2473 *
2474 * @param pixelX X coordinate in pixels in the screen
2475 * @param pixelY Y coordinate in pixels in the screen
2476 * @param spanX horizontal cell span
2477 * @param spanY vertical cell span
2478 * @return the configuration that represents the found reorder
2479 */
2480 public ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int spanX,
2481 int spanY) {
2482 int[] result = new int[2];
2483 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2484 ItemConfiguration solution = new ItemConfiguration();
2485 copyCurrentStateToSolution(solution, false);
2486 solution.isSolution = result[0] != -1;
2487 if (!solution.isSolution) {
2488 return solution;
2489 }
2490 solution.cellX = result[0];
2491 solution.cellY = result[1];
2492 solution.spanX = spanX;
2493 solution.spanY = spanY;
2494 return solution;
2495 }
2496
2497 /**
2498 * When the user drags an Item in the workspace sometimes we need to move the items already in
2499 * the workspace to make space for the new item, this function return a solution for that
2500 * reorder.
2501 *
2502 * @param pixelX X coordinate in the screen of the dragView in pixels
2503 * @param pixelY Y coordinate in the screen of the dragView in pixels
2504 * @param minSpanX minimum horizontal span the item can be shrunk to
2505 * @param minSpanY minimum vertical span the item can be shrunk to
2506 * @param spanX occupied horizontal span
2507 * @param spanY occupied vertical span
2508 * @param dragView the view of the item being draged
2509 * @return returns a solution for the given parameters, the solution contains all the icons and
2510 * the locations they should be in the given solution.
2511 */
2512 public ItemConfiguration calculateReorder(int pixelX, int pixelY, int minSpanX, int minSpanY,
2513 int spanX, int spanY, View dragView) {
2514 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2515
2516 ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder(pixelX, pixelY, spanX,
2517 spanY);
2518
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002519 // Find a solution involving pushing / displacing any items in the way
2520 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Sebastian Francof153d912022-04-22 16:15:27 -05002521 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
Adam Cohen482ed822012-03-02 14:15:13 -08002522
2523 // We attempt the approach which doesn't shuffle views at all
2524 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2525 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2526
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002527 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2528 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002529 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
Sebastian Franco53a15a42022-10-25 17:28:54 -07002530 return swapSolution;
Adam Cohen482ed822012-03-02 14:15:13 -08002531 } else if (noShuffleSolution.isSolution) {
Sebastian Franco53a15a42022-10-25 17:28:54 -07002532 return noShuffleSolution;
2533 } else if (closestSpaceSolution.isSolution) {
2534 return closestSpaceSolution;
Adam Cohen482ed822012-03-02 14:15:13 -08002535 }
Sebastian Franco53a15a42022-10-25 17:28:54 -07002536 return null;
2537 }
Adam Cohen482ed822012-03-02 14:15:13 -08002538
Sebastian Franco9cab1c32022-10-25 17:28:54 -07002539 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
2540 View dragView, int[] result, int[] resultSpan, int mode) {
2541 if (resultSpan == null) {
2542 resultSpan = new int[]{-1, -1};
2543 }
2544 if (result == null) {
2545 result = new int[]{-1, -1};
2546 }
2547 ItemConfiguration finalSolution;
2548 // When we are checking drop validity or actually dropping, we don't recompute the
2549 // direction vector, since we want the solution to match the preview, and it's possible
2550 // that the exact position of the item has changed to result in a new reordering outcome.
2551 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2552 && mPreviousSolution != null) {
2553 finalSolution = mPreviousSolution;
2554 // We reset this vector after drop
2555 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2556 mPreviousSolution = null;
2557 }
2558 } else {
2559 finalSolution = calculateReorder(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
2560 dragView);
2561 mPreviousSolution = finalSolution;
2562 }
2563
2564 if (finalSolution == null || !finalSolution.isSolution) {
2565 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2566 } else {
2567 result[0] = finalSolution.cellX;
2568 result[1] = finalSolution.cellY;
2569 resultSpan[0] = finalSolution.spanX;
2570 resultSpan[1] = finalSolution.spanY;
2571 }
2572 performReorder(finalSolution, dragView, mode);
2573 return result;
2574 }
2575
Sebastian Franco53a15a42022-10-25 17:28:54 -07002576 /**
2577 * Animates and submits in the DB the given ItemConfiguration depending of the mode.
2578 *
2579 * @param solution represents widgets on the screen which the Workspace will animate to and
2580 * would be submitted to the database.
2581 * @param dragView view which is being dragged over the workspace that trigger the reorder
2582 * @param mode depending on the mode different animations would be played and depending on the
2583 * mode the solution would be submitted or not the database.
2584 * The possible modes are {@link MODE_SHOW_REORDER_HINT}, {@link MODE_DRAG_OVER},
2585 * {@link MODE_ON_DROP}, {@link MODE_ON_DROP_EXTERNAL}, {@link MODE_ACCEPT_DROP}
2586 * defined in {@link CellLayout}.
2587 */
2588 void performReorder(ItemConfiguration solution, View dragView, int mode) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002589 if (mode == MODE_SHOW_REORDER_HINT) {
Sebastian Franco53a15a42022-10-25 17:28:54 -07002590 beginOrAdjustReorderPreviewAnimations(solution, dragView,
2591 ReorderPreviewAnimation.MODE_HINT);
2592 return;
2593 }
2594 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2595 // committing anything or animating anything as we just want to determine if a solution
2596 // exists
2597 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2598 if (!DESTRUCTIVE_REORDER) {
2599 setUseTempCoords(true);
2600 }
2601
2602 if (!DESTRUCTIVE_REORDER) {
2603 copySolutionToTempState(solution, dragView);
2604 }
2605 setItemPlacementDirty(true);
2606 animateItemsToSolution(solution, dragView, mode == MODE_ON_DROP);
2607
2608 if (!DESTRUCTIVE_REORDER
2609 && (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
2610 // Since the temp solution didn't update dragView, don't commit it either
2611 commitTempPlacement(dragView);
2612 completeAndClearReorderPreviewAnimations();
2613 setItemPlacementDirty(false);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002614 } else {
Sebastian Franco53a15a42022-10-25 17:28:54 -07002615 beginOrAdjustReorderPreviewAnimations(solution, dragView,
2616 ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002617 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002618 }
2619
Sebastian Franco53a15a42022-10-25 17:28:54 -07002620 if (mode == MODE_ON_DROP && !DESTRUCTIVE_REORDER) {
Adam Cohen482ed822012-03-02 14:15:13 -08002621 setUseTempCoords(false);
2622 }
Adam Cohen482ed822012-03-02 14:15:13 -08002623
Michael Jurkaa52570f2012-03-20 03:18:20 -07002624 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002625 }
2626
Adam Cohen19f37922012-03-21 11:59:11 -07002627 void setItemPlacementDirty(boolean dirty) {
2628 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002629 }
Adam Cohen19f37922012-03-21 11:59:11 -07002630 boolean isItemPlacementDirty() {
2631 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002632 }
2633
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002634 private static class ItemConfiguration extends CellAndSpan {
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07002635 final ArrayMap<View, CellAndSpan> map = new ArrayMap<>();
2636 private final ArrayMap<View, CellAndSpan> savedMap = new ArrayMap<>();
2637 final ArrayList<View> sortedViews = new ArrayList<>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002638 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002639 boolean isSolution = false;
Adam Cohen482ed822012-03-02 14:15:13 -08002640
Adam Cohenf3900c22012-11-16 18:28:11 -08002641 void save() {
2642 // Copy current state into savedMap
2643 for (View v: map.keySet()) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002644 savedMap.get(v).copyFrom(map.get(v));
Adam Cohenf3900c22012-11-16 18:28:11 -08002645 }
2646 }
2647
2648 void restore() {
2649 // Restore current state from savedMap
2650 for (View v: savedMap.keySet()) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002651 map.get(v).copyFrom(savedMap.get(v));
Adam Cohenf3900c22012-11-16 18:28:11 -08002652 }
2653 }
2654
2655 void add(View v, CellAndSpan cs) {
2656 map.put(v, cs);
2657 savedMap.put(v, new CellAndSpan());
2658 sortedViews.add(v);
2659 }
2660
Adam Cohen482ed822012-03-02 14:15:13 -08002661 int area() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002662 return spanX * spanY;
Adam Cohenf3900c22012-11-16 18:28:11 -08002663 }
2664
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002665 void getBoundingRectForViews(ArrayList<View> views, Rect outRect) {
2666 boolean first = true;
2667 for (View v: views) {
2668 CellAndSpan c = map.get(v);
2669 if (first) {
2670 outRect.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2671 first = false;
2672 } else {
2673 outRect.union(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2674 }
2675 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002676 }
Adam Cohen482ed822012-03-02 14:15:13 -08002677 }
2678
Adam Cohendf035382011-04-11 17:22:04 -07002679 /**
Adam Cohendf035382011-04-11 17:22:04 -07002680 * Find a starting cell position that will fit the given bounds nearest the requested
2681 * cell location. Uses Euclidean distance to score multiple vacant areas.
2682 *
2683 * @param pixelX The X location at which you want to search for a vacant area.
2684 * @param pixelY The Y location at which you want to search for a vacant area.
2685 * @param spanX Horizontal span of the object.
2686 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07002687 * @param result Previously returned value to possibly recycle.
2688 * @return The X, Y cell of a vacant area that can contain this object,
2689 * nearest the requested location.
2690 */
Adam Cohenf9c184a2016-01-15 16:47:43 -08002691 public int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
Sebastian Francob57c0b22022-06-28 13:54:35 -07002692 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, true, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002693 }
2694
Michael Jurka0280c3b2010-09-17 15:00:07 -07002695 boolean existsEmptyCell() {
2696 return findCellForSpan(null, 1, 1);
2697 }
2698
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002699 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002700 * Finds the upper-left coordinate of the first rectangle in the grid that can
2701 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2702 * then this method will only return coordinates for rectangles that contain the cell
2703 * (intersectX, intersectY)
2704 *
2705 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2706 * can be found.
2707 * @param spanX The horizontal span of the cell we want to find.
2708 * @param spanY The vertical span of the cell we want to find.
2709 *
2710 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002711 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002712 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002713 if (cellXY == null) {
2714 cellXY = new int[2];
Michael Jurka0280c3b2010-09-17 15:00:07 -07002715 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002716 return mOccupied.findVacantCell(cellXY, spanX, spanY);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002717 }
2718
2719 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002720 * A drag event has begun over this layout.
2721 * It may have begun over this layout (in which case onDragChild is called first),
2722 * or it may have begun on another layout.
2723 */
2724 void onDragEnter() {
Winson Chungc07918d2011-07-01 15:35:26 -07002725 mDragging = true;
2726 }
2727
2728 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002729 * Called when drag has left this CellLayout or has been completed (successfully or not)
2730 */
2731 void onDragExit() {
Joe Onorato4be866d2010-10-10 11:26:02 -07002732 // This can actually be called when we aren't in a drag, e.g. when adding a new
2733 // item to this layout via the customize drawer.
2734 // Guard against that case.
2735 if (mDragging) {
2736 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002737 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002738
2739 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002740 mDragCell[0] = mDragCell[1] = -1;
Adam Cohenf7ca3b42021-02-22 11:03:58 -08002741 mDragCellSpan[0] = mDragCellSpan[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002742 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2743 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002744 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002745 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002746 }
2747
2748 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002749 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002750 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002751 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002752 *
2753 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002754 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002755 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002756 if (child != null) {
Sebastian Francod4682992022-10-05 13:03:09 -05002757 CellLayoutLayoutParams
2758 lp = (CellLayoutLayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002759 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002760 child.requestLayout();
Tony Wickham1cdb6d02015-09-17 11:08:27 -07002761 markCellsAsOccupiedForView(child);
Romain Guyd94533d2009-08-17 10:01:15 -07002762 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002763 }
2764
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002765 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002766 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002767 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002768 * @param cellX X coordinate of upper left corner expressed as a cell position
2769 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002770 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002771 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002772 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002773 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002774 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002775 final int cellWidth = mCellWidth;
2776 final int cellHeight = mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -07002777
Pierre Barbier de Reuille1b8bbb62021-05-19 22:45:16 +01002778 // We observe a shift of 1 pixel on the x coordinate compared to the actual cell coordinates
2779 final int hStartPadding = getPaddingLeft()
2780 + (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Winson Chung4b825dcd2011-06-19 12:41:22 -07002781 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002782
Thales Lima78d00ad2021-09-30 11:29:06 +01002783 int x = hStartPadding + (cellX * mBorderSpace.x) + (cellX * cellWidth);
2784 int y = vStartPadding + (cellY * mBorderSpace.y) + (cellY * cellHeight);
Jon Miranda228877d2021-02-09 11:05:00 -05002785
Thales Lima78d00ad2021-09-30 11:29:06 +01002786 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * mBorderSpace.x);
2787 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * mBorderSpace.y);
Winson Chungaafa03c2010-06-11 17:34:16 -07002788
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002789 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002790 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002791
Adam Cohend4844c32011-02-18 19:25:06 -08002792 public void markCellsAsOccupiedForView(View view) {
Sebastian Francof153d912022-04-22 16:15:27 -05002793 if (view instanceof LauncherAppWidgetHostView
2794 && view.getTag() instanceof LauncherAppWidgetInfo) {
2795 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
2796 mOccupied.markCells(info.cellX, info.cellY, info.spanX, info.spanY, true);
2797 return;
2798 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002799 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Sebastian Francod4682992022-10-05 13:03:09 -05002800 CellLayoutLayoutParams
2801 lp = (CellLayoutLayoutParams) view.getLayoutParams();
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002802 mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002803 }
2804
Adam Cohend4844c32011-02-18 19:25:06 -08002805 public void markCellsAsUnoccupiedForView(View view) {
Sebastian Francof153d912022-04-22 16:15:27 -05002806 if (view instanceof LauncherAppWidgetHostView
2807 && view.getTag() instanceof LauncherAppWidgetInfo) {
2808 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
2809 mOccupied.markCells(info.cellX, info.cellY, info.spanX, info.spanY, false);
2810 return;
2811 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002812 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Sebastian Francod4682992022-10-05 13:03:09 -05002813 CellLayoutLayoutParams
2814 lp = (CellLayoutLayoutParams) view.getLayoutParams();
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002815 mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002816 }
2817
Adam Cohen2801caf2011-05-13 20:57:39 -07002818 public int getDesiredWidth() {
Jon Miranda228877d2021-02-09 11:05:00 -05002819 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth)
Thales Lima78d00ad2021-09-30 11:29:06 +01002820 + ((mCountX - 1) * mBorderSpace.x);
Adam Cohen2801caf2011-05-13 20:57:39 -07002821 }
2822
2823 public int getDesiredHeight() {
Jon Miranda228877d2021-02-09 11:05:00 -05002824 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight)
Thales Lima78d00ad2021-09-30 11:29:06 +01002825 + ((mCountY - 1) * mBorderSpace.y);
Adam Cohen2801caf2011-05-13 20:57:39 -07002826 }
2827
Michael Jurka66d72172011-04-12 16:29:25 -07002828 public boolean isOccupied(int x, int y) {
2829 if (x < mCountX && y < mCountY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002830 return mOccupied.cells[x][y];
Michael Jurka66d72172011-04-12 16:29:25 -07002831 } else {
2832 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2833 }
2834 }
2835
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002836 @Override
2837 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
Sebastian Francod4682992022-10-05 13:03:09 -05002838 return new CellLayoutLayoutParams(getContext(), attrs);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002839 }
2840
2841 @Override
2842 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
Sebastian Francod4682992022-10-05 13:03:09 -05002843 return p instanceof CellLayoutLayoutParams;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002844 }
2845
2846 @Override
2847 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
Sebastian Francod4682992022-10-05 13:03:09 -05002848 return new CellLayoutLayoutParams(p);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002849 }
2850
Michael Jurka0280c3b2010-09-17 15:00:07 -07002851 // This class stores info for two purposes:
2852 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2853 // its spanX, spanY, and the screen it is on
2854 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2855 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2856 // the CellLayout that was long clicked
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002857 public static final class CellInfo extends CellAndSpan {
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07002858 public final View cell;
Sunny Goyalefb7e842018-10-04 15:11:00 -07002859 final int screenId;
2860 final int container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002861
Sunny Goyal83a8f042015-05-19 12:52:12 -07002862 public CellInfo(View v, ItemInfo info) {
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002863 cellX = info.cellX;
2864 cellY = info.cellY;
2865 spanX = info.spanX;
2866 spanY = info.spanY;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002867 cell = v;
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002868 screenId = info.screenId;
2869 container = info.container;
2870 }
2871
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002872 @Override
2873 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002874 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2875 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002876 }
2877 }
Michael Jurkad771c962011-08-09 15:00:48 -07002878
Tony Wickham86930612015-09-09 13:50:40 -07002879 /**
Samuel Fufa1e2d0042019-11-18 17:12:46 -08002880 * A Delegated cell Drawing for drawing on CellLayout
2881 */
2882 public abstract static class DelegatedCellDrawing {
2883 public int mDelegateCellX;
2884 public int mDelegateCellY;
2885
2886 /**
2887 * Draw under CellLayout
2888 */
2889 public abstract void drawUnderItem(Canvas canvas);
2890
2891 /**
2892 * Draw over CellLayout
2893 */
2894 public abstract void drawOverItem(Canvas canvas);
2895 }
2896
2897 /**
Tony Wickham86930612015-09-09 13:50:40 -07002898 * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing
2899 * if necessary).
2900 */
2901 public boolean hasReorderSolution(ItemInfo itemInfo) {
2902 int[] cellPoint = new int[2];
2903 // Check for a solution starting at every cell.
2904 for (int cellX = 0; cellX < getCountX(); cellX++) {
2905 for (int cellY = 0; cellY < getCountY(); cellY++) {
2906 cellToPoint(cellX, cellY, cellPoint);
2907 if (findReorderSolution(cellPoint[0], cellPoint[1], itemInfo.minSpanX,
2908 itemInfo.minSpanY, itemInfo.spanX, itemInfo.spanY, mDirectionVector, null,
2909 true, new ItemConfiguration()).isSolution) {
2910 return true;
2911 }
2912 }
2913 }
2914 return false;
2915 }
2916
Samuel Fufaa4211432020-02-25 18:47:54 -08002917 /**
2918 * Finds solution to accept hotseat migration to cell layout. commits solution if commitConfig
2919 */
2920 public boolean makeSpaceForHotseatMigration(boolean commitConfig) {
Samuel Fufaa4211432020-02-25 18:47:54 -08002921 int[] cellPoint = new int[2];
2922 int[] directionVector = new int[]{0, -1};
2923 cellToPoint(0, mCountY, cellPoint);
2924 ItemConfiguration configuration = new ItemConfiguration();
2925 if (findReorderSolution(cellPoint[0], cellPoint[1], mCountX, 1, mCountX, 1,
2926 directionVector, null, false, configuration).isSolution) {
2927 if (commitConfig) {
2928 copySolutionToTempState(configuration, null);
Sunny Goyal711c5962021-06-23 12:36:18 -07002929 commitTempPlacement(null);
Samuel Fufa82bbdac2020-03-09 18:24:47 -07002930 // undo marking cells occupied since there is actually nothing being placed yet.
2931 mOccupied.markCells(0, mCountY - 1, mCountX, 1, false);
Samuel Fufaa4211432020-02-25 18:47:54 -08002932 }
2933 return true;
2934 }
2935 return false;
2936 }
2937
Samuel Fufa82bbdac2020-03-09 18:24:47 -07002938 /**
2939 * returns a copy of cell layout's grid occupancy
2940 */
2941 public GridOccupancy cloneGridOccupancy() {
2942 GridOccupancy occupancy = new GridOccupancy(mCountX, mCountY);
2943 mOccupied.copyTo(occupancy);
2944 return occupancy;
2945 }
2946
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002947 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002948 return mOccupied.isRegionVacant(x, y, spanX, spanY);
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002949 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002950}