blob: 9682d09cd172e13d7efaa7781c295636c4b32843 [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 Goyalf0b6db72018-08-13 16:10:14 -070019import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
20
Joe Onorato4be866d2010-10-10 11:26:02 -070021import android.animation.Animator;
Michael Jurka629758f2012-06-14 16:18:21 -070022import android.animation.AnimatorListenerAdapter;
Sunny Goyal849c6a22018-08-08 16:33:46 -070023import android.animation.ObjectAnimator;
Chet Haase00397b12010-10-07 11:13:10 -070024import android.animation.TimeInterpolator;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070025import android.animation.ValueAnimator;
26import android.animation.ValueAnimator.AnimatorUpdateListener;
Sunny Goyal726bee72018-03-05 12:54:24 -080027import android.annotation.SuppressLint;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080028import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040029import android.content.res.Resources;
Sunny Goyalc13403c2016-11-18 23:44:48 -080030import android.content.res.TypedArray;
Joe Onorato4be866d2010-10-10 11:26:02 -070031import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070032import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080033import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070034import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070035import android.graphics.Point;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080036import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080037import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070038import android.graphics.drawable.Drawable;
Adam Cohen1462de32012-07-24 22:34:36 -070039import android.os.Parcelable;
Rajeev Kumar9962dbe2017-06-12 12:16:20 -070040import android.util.ArrayMap;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080041import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070042import android.util.Log;
Sunny Goyal849c6a22018-08-08 16:33:46 -070043import android.util.Property;
Adam Cohen1462de32012-07-24 22:34:36 -070044import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080045import android.view.MotionEvent;
46import android.view.View;
47import android.view.ViewDebug;
48import android.view.ViewGroup;
Adam Cohenc9735cf2015-01-23 16:11:55 -080049import android.view.accessibility.AccessibilityEvent;
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -070050
vadimt04f356f2019-02-14 18:46:36 -080051import androidx.annotation.IntDef;
52import androidx.core.view.ViewCompat;
53
Sunny Goyalaa8ef112015-06-12 20:04:41 -070054import com.android.launcher3.LauncherSettings.Favorites;
Sunny Goyale9b651e2015-04-24 11:44:51 -070055import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -070056import com.android.launcher3.anim.Interpolators;
Sunny Goyal9e76f682017-02-13 12:13:43 -080057import com.android.launcher3.anim.PropertyListBuilder;
Sunny Goyal3d706ad2017-03-06 16:56:39 -080058import com.android.launcher3.config.FeatureFlags;
Adam Cohen65086992020-02-19 08:40:49 -080059import com.android.launcher3.dragndrop.DraggableView;
Jon Mirandaa0233f72017-06-22 18:34:45 -070060import com.android.launcher3.folder.PreviewBackground;
Sunny Goyal06e21a22016-08-11 16:02:02 -070061import com.android.launcher3.graphics.DragPreviewProvider;
Sunny Goyalae6e3182019-04-30 12:04:37 -070062import com.android.launcher3.graphics.RotationMode;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -070063import com.android.launcher3.util.CellAndSpan;
64import com.android.launcher3.util.GridOccupancy;
Sunny Goyale2fd14b2015-08-27 17:45:46 -070065import com.android.launcher3.util.ParcelableSparseArray;
Sunny Goyal9b29ca52017-02-17 10:39:44 -080066import com.android.launcher3.util.Themes;
Adam Cohen091440a2015-03-18 14:16:05 -070067import com.android.launcher3.util.Thunk;
Sunny Goyalab770a12018-11-14 15:17:26 -080068import com.android.launcher3.views.ActivityContext;
Sunny Goyalae6e3182019-04-30 12:04:37 -070069import com.android.launcher3.views.Transposable;
Sunny Goyal29947f02017-12-18 13:49:44 -080070import com.android.launcher3.widget.LauncherAppWidgetHostView;
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -070071
Sunny Goyalc13403c2016-11-18 23:44:48 -080072import java.lang.annotation.Retention;
73import java.lang.annotation.RetentionPolicy;
Adam Cohen69ce2e52011-07-03 19:25:21 -070074import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070075import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080076import java.util.Collections;
77import java.util.Comparator;
Adam Cohend41fbf52012-02-16 23:53:59 -080078import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070079
Sunny Goyalae6e3182019-04-30 12:04:37 -070080public class CellLayout extends ViewGroup implements Transposable {
Tony Wickhama0628cc2015-10-14 15:23:04 -070081 private static final String TAG = "CellLayout";
82 private static final boolean LOGD = false;
Winson Chungaafa03c2010-06-11 17:34:16 -070083
Sunny Goyalab770a12018-11-14 15:17:26 -080084 protected final ActivityContext mActivity;
Sunny Goyal4ffec482016-02-09 11:28:52 -080085 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070086 @Thunk int mCellWidth;
Sunny Goyal4ffec482016-02-09 11:28:52 -080087 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070088 @Thunk int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070089 private int mFixedCellWidth;
90 private int mFixedCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070091
Sunny Goyal4ffec482016-02-09 11:28:52 -080092 @ViewDebug.ExportedProperty(category = "launcher")
Sunny Goyalff4ba2d2016-04-02 14:12:34 -070093 private int mCountX;
Sunny Goyal4ffec482016-02-09 11:28:52 -080094 @ViewDebug.ExportedProperty(category = "launcher")
Sunny Goyalff4ba2d2016-04-02 14:12:34 -070095 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080096
Adam Cohen917e3882013-10-31 15:03:35 -070097 private boolean mDropPending = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080098
Patrick Dubroyde7658b2010-09-27 11:15:43 -070099 // These are temporary variables to prevent having to allocate a new object just to
100 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Adam Cohen091440a2015-03-18 14:16:05 -0700101 @Thunk final int[] mTmpPoint = new int[2];
Sunny Goyal2805e632015-05-20 15:35:32 -0700102 @Thunk final int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700103
Adam Cohen65086992020-02-19 08:40:49 -0800104 // Used to visualize / debug the Grid of the CellLayout
105 private static final boolean VISUALIZE_GRID = false;
106 private Rect mVisualizeGridRect = new Rect();
107 private Paint mVisualizeGridPaint = new Paint();
108
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700109 private GridOccupancy mOccupied;
110 private GridOccupancy mTmpOccupied;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800111
Michael Jurkadee05892010-07-27 10:01:56 -0700112 private OnTouchListener mInterceptTouchListener;
113
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800114 private final ArrayList<DelegatedCellDrawing> mDelegatedCellDrawings = new ArrayList<>();
Jon Mirandaa0233f72017-06-22 18:34:45 -0700115 final PreviewBackground mFolderLeaveBehind = new PreviewBackground();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700116
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800117 private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active };
Sunny Goyale15e2a82017-12-15 13:05:42 -0800118 private static final int[] BACKGROUND_STATE_DEFAULT = EMPTY_STATE_SET;
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800119 private final Drawable mBackground;
Sunny Goyal2805e632015-05-20 15:35:32 -0700120
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700121 // These values allow a fixed measurement to be set on the CellLayout.
122 private int mFixedWidth = -1;
123 private int mFixedHeight = -1;
124
Michael Jurka33945b22010-12-21 18:19:38 -0800125 // If we're actively dragging something over this screen, mIsDragOverlapping is true
126 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700127
Winson Chung150fbab2010-09-29 17:14:26 -0700128 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700129 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700130 @Thunk final Rect[] mDragOutlines = new Rect[4];
131 @Thunk final float[] mDragOutlineAlphas = new float[mDragOutlines.length];
132 private final InterruptibleInOutAnimator[] mDragOutlineAnims =
Joe Onorato4be866d2010-10-10 11:26:02 -0700133 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700134
135 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700136 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700137 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700138
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700139 @Thunk final ArrayMap<LayoutParams, Animator> mReorderAnimators = new ArrayMap<>();
140 @Thunk final ArrayMap<View, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
Adam Cohen19f37922012-03-21 11:59:11 -0700141
142 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700143
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700144 // When a drag operation is in progress, holds the nearest cell to the touch point
145 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800146
Joe Onorato4be866d2010-10-10 11:26:02 -0700147 private boolean mDragging = false;
148
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700149 private final TimeInterpolator mEaseOutInterpolator;
150 private final ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700151
Sunny Goyalc13403c2016-11-18 23:44:48 -0800152 @Retention(RetentionPolicy.SOURCE)
153 @IntDef({WORKSPACE, HOTSEAT, FOLDER})
154 public @interface ContainerType{}
155 public static final int WORKSPACE = 0;
156 public static final int HOTSEAT = 1;
157 public static final int FOLDER = 2;
158
159 @ContainerType private final int mContainerType;
160
Jon Mirandab28c4fc2017-06-20 10:58:36 -0700161 private final float mChildScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800162
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800163 public static final int MODE_SHOW_REORDER_HINT = 0;
164 public static final int MODE_DRAG_OVER = 1;
165 public static final int MODE_ON_DROP = 2;
166 public static final int MODE_ON_DROP_EXTERNAL = 3;
167 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700168 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800169 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
170
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800171 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700172 private static final int REORDER_ANIMATION_DURATION = 150;
Sunny Goyalc13403c2016-11-18 23:44:48 -0800173 @Thunk final float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700174
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700175 private final ArrayList<View> mIntersectingViews = new ArrayList<>();
176 private final Rect mOccupiedRect = new Rect();
177 private final int[] mDirectionVector = new int[2];
178 final int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700179 private static final int INVALID_DIRECTION = -100;
Adam Cohen482ed822012-03-02 14:15:13 -0800180
Sunny Goyal2805e632015-05-20 15:35:32 -0700181 private final Rect mTempRect = new Rect();
Winson Chung3a6e7f32013-10-09 15:50:52 -0700182
Sunny Goyal73b5a272019-12-09 14:55:56 -0800183 private static final Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700184
Adam Cohenc9735cf2015-01-23 16:11:55 -0800185 // Related to accessible drag and drop
Adam Cohenc9735cf2015-01-23 16:11:55 -0800186 private boolean mUseTouchHelper = false;
Sunny Goyalae6e3182019-04-30 12:04:37 -0700187 private RotationMode mRotationMode = RotationMode.NORMAL;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800188
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800189 public CellLayout(Context context) {
190 this(context, null);
191 }
192
193 public CellLayout(Context context, AttributeSet attrs) {
194 this(context, attrs, 0);
195 }
196
197 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
198 super(context, attrs, defStyle);
Sunny Goyalc13403c2016-11-18 23:44:48 -0800199 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
200 mContainerType = a.getInteger(R.styleable.CellLayout_containerType, WORKSPACE);
201 a.recycle();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700202
203 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
204 // the user where a dragged item will land when dropped.
205 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800206 setClipToPadding(false);
Sunny Goyalab770a12018-11-14 15:17:26 -0800207 mActivity = ActivityContext.lookupContext(context);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700208
Sunny Goyalae6e3182019-04-30 12:04:37 -0700209 DeviceProfile grid = mActivity.getWallpaperDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800210
Winson Chung11a1a532013-09-13 11:14:45 -0700211 mCellWidth = mCellHeight = -1;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800212 mFixedCellWidth = mFixedCellHeight = -1;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700213
214 mCountX = grid.inv.numColumns;
215 mCountY = grid.inv.numRows;
216 mOccupied = new GridOccupancy(mCountX, mCountY);
217 mTmpOccupied = new GridOccupancy(mCountX, mCountY);
218
Adam Cohen5b53f292012-03-29 14:30:35 -0700219 mPreviousReorderDirection[0] = INVALID_DIRECTION;
220 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800221
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800222 mFolderLeaveBehind.mDelegateCellX = -1;
223 mFolderLeaveBehind.mDelegateCellY = -1;
Adam Cohenefca0272016-02-24 19:19:06 -0800224
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800225 setAlwaysDrawnWithCacheEnabled(false);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700226 final Resources res = getResources();
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700227
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800228 mBackground = res.getDrawable(R.drawable.bg_celllayout);
Sunny Goyal2805e632015-05-20 15:35:32 -0700229 mBackground.setCallback(this);
Sunny Goyalaeb16432017-10-16 11:46:41 -0700230 mBackground.setAlpha(0);
Michael Jurka33945b22010-12-21 18:19:38 -0800231
Sunny Goyalc13403c2016-11-18 23:44:48 -0800232 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700233
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700234 // Initialize the data structures used for the drag visualization.
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -0700235 mEaseOutInterpolator = Interpolators.DEACCEL_2_5; // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700236 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700237 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800238 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700239 }
Mario Bertschler54ba6012017-06-08 10:53:53 -0700240 mDragOutlinePaint.setColor(Themes.getAttrColor(context, R.attr.workspaceTextColor));
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700241
242 // When dragging things around the home screens, we show a green outline of
243 // where the item will land. The outlines gradually fade out, leaving a trail
244 // behind the drag path.
245 // Set up all the animations that are used to implement this fading.
246 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700247 final float fromAlphaValue = 0;
248 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700249
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700250 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700251
252 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700253 final InterruptibleInOutAnimator anim =
Sunny Goyal849c6a22018-08-08 16:33:46 -0700254 new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700255 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700256 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700257 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700258 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700259 final Bitmap outline = (Bitmap)anim.getTag();
260
261 // If an animation is started and then stopped very quickly, we can still
262 // get spurious updates we've cleared the tag. Guard against this.
263 if (outline == null) {
Tony Wickhama0628cc2015-10-14 15:23:04 -0700264 if (LOGD) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700265 Object val = animation.getAnimatedValue();
266 Log.d(TAG, "anim " + thisIndex + " update: " + val +
267 ", isStopped " + anim.isStopped());
268 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700269 // Try to prevent it from continuing to run
270 animation.cancel();
271 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700272 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800273 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700274 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700275 }
276 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700277 // The animation holds a reference to the drag outline bitmap as long is it's
278 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700279 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700280 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700281 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700282 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700283 anim.setTag(null);
284 }
285 }
286 });
287 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700288 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700289
Sunny Goyalc13403c2016-11-18 23:44:48 -0800290 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType);
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530291 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700292 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700293 }
294
Sunny Goyal9b180102020-03-11 10:02:29 -0700295
296 /**
297 * Sets or clears a delegate used for accessible drag and drop
298 */
299 public void setDragAndDropAccessibilityDelegate(DragAndDropAccessibilityDelegate delegate) {
300 setOnClickListener(delegate);
301 setOnHoverListener(delegate);
302 ViewCompat.setAccessibilityDelegate(this, delegate);
303
304 mUseTouchHelper = delegate != null;
305 int accessibilityFlag = mUseTouchHelper
306 ? IMPORTANT_FOR_ACCESSIBILITY_YES : IMPORTANT_FOR_ACCESSIBILITY_NO;
307 setImportantForAccessibility(accessibilityFlag);
308 getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
Adam Cohenc9735cf2015-01-23 16:11:55 -0800309
310 // Invalidate the accessibility hierarchy
311 if (getParent() != null) {
312 getParent().notifySubtreeAccessibilityStateChanged(
313 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
314 }
315 }
316
Sunny Goyalae6e3182019-04-30 12:04:37 -0700317 public void setRotationMode(RotationMode mode) {
318 if (mRotationMode != mode) {
319 mRotationMode = mode;
320 requestLayout();
321 }
322 }
323
324 @Override
325 public RotationMode getRotationMode() {
326 return mRotationMode;
327 }
328
329 @Override
330 public void setPadding(int left, int top, int right, int bottom) {
331 mRotationMode.mapRect(left, top, right, bottom, mTempRect);
332 super.setPadding(mTempRect.left, mTempRect.top, mTempRect.right, mTempRect.bottom);
333 }
334
Adam Cohenc9735cf2015-01-23 16:11:55 -0800335 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800336 public boolean onInterceptTouchEvent(MotionEvent ev) {
337 if (mUseTouchHelper ||
338 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
339 return true;
340 }
341 return false;
342 }
343
Chris Craik01f2d7f2013-10-01 14:41:56 -0700344 public void enableHardwareLayer(boolean hasLayer) {
345 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700346 }
347
vadimt04f356f2019-02-14 18:46:36 -0800348 public boolean isHardwareLayerEnabled() {
349 return mShortcutsAndWidgets.getLayerType() == LAYER_TYPE_HARDWARE;
350 }
351
Winson Chung5f8afe62013-08-12 16:19:28 -0700352 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700353 mFixedCellWidth = mCellWidth = width;
354 mFixedCellHeight = mCellHeight = height;
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530355 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
Winson Chung5f8afe62013-08-12 16:19:28 -0700356 }
357
Adam Cohen2801caf2011-05-13 20:57:39 -0700358 public void setGridSize(int x, int y) {
359 mCountX = x;
360 mCountY = y;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700361 mOccupied = new GridOccupancy(mCountX, mCountY);
362 mTmpOccupied = new GridOccupancy(mCountX, mCountY);
Adam Cohen7fbec102012-03-27 12:42:19 -0700363 mTempRectStack.clear();
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530364 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700365 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700366 }
367
Adam Cohen2374abf2013-04-16 14:56:57 -0700368 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
369 public void setInvertIfRtl(boolean invert) {
370 mShortcutsAndWidgets.setInvertIfRtl(invert);
371 }
372
Adam Cohen917e3882013-10-31 15:03:35 -0700373 public void setDropPending(boolean pending) {
374 mDropPending = pending;
375 }
376
377 public boolean isDropPending() {
378 return mDropPending;
379 }
380
Adam Cohenc50438c2014-08-19 17:43:05 -0700381 void setIsDragOverlapping(boolean isDragOverlapping) {
382 if (mIsDragOverlapping != isDragOverlapping) {
383 mIsDragOverlapping = isDragOverlapping;
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800384 mBackground.setState(mIsDragOverlapping
385 ? BACKGROUND_STATE_ACTIVE : BACKGROUND_STATE_DEFAULT);
Adam Cohenc50438c2014-08-19 17:43:05 -0700386 invalidate();
387 }
388 }
389
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700390 @Override
391 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700392 ParcelableSparseArray jail = getJailedArray(container);
393 super.dispatchSaveInstanceState(jail);
394 container.put(R.id.cell_layout_jail_id, jail);
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700395 }
396
397 @Override
398 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700399 super.dispatchRestoreInstanceState(getJailedArray(container));
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700400 }
401
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700402 /**
403 * Wrap the SparseArray in another Parcelable so that the item ids do not conflict with our
404 * our internal resource ids
405 */
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700406 private ParcelableSparseArray getJailedArray(SparseArray<Parcelable> container) {
407 final Parcelable parcelable = container.get(R.id.cell_layout_jail_id);
408 return parcelable instanceof ParcelableSparseArray ?
409 (ParcelableSparseArray) parcelable : new ParcelableSparseArray();
410 }
411
Tony Wickham0f97b782015-12-02 17:55:07 -0800412 public boolean getIsDragOverlapping() {
413 return mIsDragOverlapping;
414 }
415
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700416 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700417 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700418 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
419 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
420 // When we're small, we are either drawn normally or in the "accepts drops" state (during
421 // a drag). However, we also drag the mini hover background *over* one of those two
422 // backgrounds
Sunny Goyalaeb16432017-10-16 11:46:41 -0700423 if (mBackground.getAlpha() > 0) {
Sunny Goyal2805e632015-05-20 15:35:32 -0700424 mBackground.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700425 }
Romain Guya6abce82009-11-10 02:54:41 -0800426
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700427 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700428 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700429 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700430 if (alpha > 0) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700431 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700432 paint.setAlpha((int)(alpha + .5f));
Sunny Goyal106bf642015-07-16 12:18:06 -0700433 canvas.drawBitmap(b, null, mDragOutlines[i], paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700434 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700435 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800436
Adam Cohen482ed822012-03-02 14:15:13 -0800437 if (DEBUG_VISUALIZE_OCCUPIED) {
438 int[] pt = new int[2];
439 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700440 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800441 for (int i = 0; i < mCountX; i++) {
442 for (int j = 0; j < mCountY; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700443 if (mOccupied.cells[i][j]) {
Adam Cohen482ed822012-03-02 14:15:13 -0800444 cellToPoint(i, j, pt);
445 canvas.save();
446 canvas.translate(pt[0], pt[1]);
447 cd.draw(canvas);
448 canvas.restore();
449 }
450 }
451 }
452 }
453
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800454 for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
455 DelegatedCellDrawing cellDrawing = mDelegatedCellDrawings.get(i);
456 cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation);
Adam Cohenefca0272016-02-24 19:19:06 -0800457 canvas.save();
458 canvas.translate(mTempLocation[0], mTempLocation[1]);
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800459 cellDrawing.drawUnderItem(canvas);
Adam Cohenefca0272016-02-24 19:19:06 -0800460 canvas.restore();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700461 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700462
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800463 if (mFolderLeaveBehind.mDelegateCellX >= 0 && mFolderLeaveBehind.mDelegateCellY >= 0) {
464 cellToPoint(mFolderLeaveBehind.mDelegateCellX,
465 mFolderLeaveBehind.mDelegateCellY, mTempLocation);
Adam Cohenefca0272016-02-24 19:19:06 -0800466 canvas.save();
467 canvas.translate(mTempLocation[0], mTempLocation[1]);
Sunny Goyal19b93b72017-02-19 20:21:37 -0800468 mFolderLeaveBehind.drawLeaveBehind(canvas);
Adam Cohenefca0272016-02-24 19:19:06 -0800469 canvas.restore();
Adam Cohenc51934b2011-07-26 21:07:43 -0700470 }
Adam Cohen65086992020-02-19 08:40:49 -0800471
472 if (VISUALIZE_GRID) {
473 visualizeGrid(canvas);
474 }
475 }
476
477 protected void visualizeGrid(Canvas canvas) {
478 mVisualizeGridRect.set(0, 0, mCellWidth, mCellHeight);
479 mVisualizeGridPaint.setStrokeWidth(4);
480
481 for (int i = 0; i < mCountX; i++) {
482 for (int j = 0; j < mCountY; j++) {
483 canvas.save();
484
485 int transX = i * mCellWidth;
486 int transY = j * mCellHeight;
487
488 canvas.translate(getPaddingLeft() + transX, getPaddingTop() + transY);
489
490 mVisualizeGridPaint.setStyle(Paint.Style.FILL);
491 mVisualizeGridPaint.setColor(Color.argb(80, 255, 100, 100));
492
493 canvas.drawRect(mVisualizeGridRect, mVisualizeGridPaint);
494
495 mVisualizeGridPaint.setStyle(Paint.Style.STROKE);
496 mVisualizeGridPaint.setColor(Color.argb(255, 255, 100, 100));
497
498 canvas.drawRect(mVisualizeGridRect, mVisualizeGridPaint);
499 canvas.restore();
500 }
501 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700502 }
503
Adam Cohenefca0272016-02-24 19:19:06 -0800504 @Override
505 protected void dispatchDraw(Canvas canvas) {
506 super.dispatchDraw(canvas);
507
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800508 for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
509 DelegatedCellDrawing bg = mDelegatedCellDrawings.get(i);
510 cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation);
511 canvas.save();
512 canvas.translate(mTempLocation[0], mTempLocation[1]);
513 bg.drawOverItem(canvas);
514 canvas.restore();
Adam Cohenefca0272016-02-24 19:19:06 -0800515 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700516 }
517
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800518 /**
519 * Add Delegated cell drawing
520 */
521 public void addDelegatedCellDrawing(DelegatedCellDrawing bg) {
522 mDelegatedCellDrawings.add(bg);
Adam Cohenefca0272016-02-24 19:19:06 -0800523 }
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800524
525 /**
526 * Remove item from DelegatedCellDrawings
527 */
528 public void removeDelegatedCellDrawing(DelegatedCellDrawing bg) {
529 mDelegatedCellDrawings.remove(bg);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700530 }
531
Adam Cohenc51934b2011-07-26 21:07:43 -0700532 public void setFolderLeaveBehindCell(int x, int y) {
Adam Cohenefca0272016-02-24 19:19:06 -0800533 View child = getChildAt(x, y);
Sunny Goyalab770a12018-11-14 15:17:26 -0800534 mFolderLeaveBehind.setup(getContext(), mActivity, null,
Adam Cohenefca0272016-02-24 19:19:06 -0800535 child.getMeasuredWidth(), child.getPaddingTop());
536
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800537 mFolderLeaveBehind.mDelegateCellX = x;
538 mFolderLeaveBehind.mDelegateCellY = y;
Adam Cohenc51934b2011-07-26 21:07:43 -0700539 invalidate();
540 }
541
542 public void clearFolderLeaveBehind() {
Samuel Fufa1e2d0042019-11-18 17:12:46 -0800543 mFolderLeaveBehind.mDelegateCellX = -1;
544 mFolderLeaveBehind.mDelegateCellY = -1;
Adam Cohenc51934b2011-07-26 21:07:43 -0700545 invalidate();
546 }
547
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700548 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700549 public boolean shouldDelayChildPressedState() {
550 return false;
551 }
552
Adam Cohen1462de32012-07-24 22:34:36 -0700553 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700554 try {
555 dispatchRestoreInstanceState(states);
556 } catch (IllegalArgumentException ex) {
Zak Cohen3eeb41d2020-02-14 14:15:13 -0800557 if (FeatureFlags.IS_STUDIO_BUILD) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700558 throw ex;
559 }
560 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
561 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
562 }
Adam Cohen1462de32012-07-24 22:34:36 -0700563 }
564
Michael Jurkae6235dd2011-10-04 15:02:05 -0700565 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700566 public void cancelLongPress() {
567 super.cancelLongPress();
568
569 // Cancel long press for all children
570 final int count = getChildCount();
571 for (int i = 0; i < count; i++) {
572 final View child = getChildAt(i);
573 child.cancelLongPress();
574 }
575 }
576
Michael Jurkadee05892010-07-27 10:01:56 -0700577 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
578 mInterceptTouchListener = listener;
579 }
580
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800581 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700582 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800583 }
584
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800585 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700586 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800587 }
588
Sunny Goyalc13403c2016-11-18 23:44:48 -0800589 public boolean acceptsWidget() {
590 return mContainerType == WORKSPACE;
Sunny Goyale9b651e2015-04-24 11:44:51 -0700591 }
592
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800593 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700594 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700595 final LayoutParams lp = params;
596
Andrew Flynnde38e422012-05-08 11:22:15 -0700597 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800598 if (child instanceof BubbleTextView) {
599 BubbleTextView bubbleChild = (BubbleTextView) child;
Jon Mirandaf1eae802017-10-04 11:23:33 -0700600 bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800601 }
602
Sunny Goyalc13403c2016-11-18 23:44:48 -0800603 child.setScaleX(mChildScale);
604 child.setScaleY(mChildScale);
Adam Cohen307fe232012-08-16 17:55:58 -0700605
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800606 // Generate an id for each view, this assumes we have at most 256x256 cells
607 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700608 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700609 // If the horizontal or vertical span is set to -1, it is taken to
610 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700611 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
612 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800613
Winson Chungaafa03c2010-06-11 17:34:16 -0700614 child.setId(childId);
Tony Wickhama0628cc2015-10-14 15:23:04 -0700615 if (LOGD) {
616 Log.d(TAG, "Adding view to ShortcutsAndWidgetsContainer: " + child);
617 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700618 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700619
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700620 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700621
Winson Chungaafa03c2010-06-11 17:34:16 -0700622 return true;
623 }
624 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800625 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700626
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800627 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700628 public void removeAllViews() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700629 mOccupied.clear();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700630 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700631 }
632
633 @Override
634 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700635 if (mShortcutsAndWidgets.getChildCount() > 0) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700636 mOccupied.clear();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700637 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700638 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700639 }
640
641 @Override
642 public void removeView(View view) {
643 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700644 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700645 }
646
647 @Override
648 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700649 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
650 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700651 }
652
653 @Override
654 public void removeViewInLayout(View view) {
655 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700656 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700657 }
658
659 @Override
660 public void removeViews(int start, int count) {
661 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700662 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700663 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700664 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700665 }
666
667 @Override
668 public void removeViewsInLayout(int start, int count) {
669 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700670 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700671 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700672 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800673 }
674
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700675 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700676 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800677 * @param x X coordinate of the point
678 * @param y Y coordinate of the point
679 * @param result Array of 2 ints to hold the x and y coordinate of the cell
680 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700681 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700682 final int hStartPadding = getPaddingLeft();
683 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800684
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530685 result[0] = (x - hStartPadding) / mCellWidth;
686 result[1] = (y - vStartPadding) / mCellHeight;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800687
Adam Cohend22015c2010-07-26 22:02:18 -0700688 final int xAxis = mCountX;
689 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800690
691 if (result[0] < 0) result[0] = 0;
692 if (result[0] >= xAxis) result[0] = xAxis - 1;
693 if (result[1] < 0) result[1] = 0;
694 if (result[1] >= yAxis) result[1] = yAxis - 1;
695 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700696
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800697 /**
698 * Given a point, return the cell that most closely encloses that point
699 * @param x X coordinate of the point
700 * @param y Y coordinate of the point
701 * @param result Array of 2 ints to hold the x and y coordinate of the cell
702 */
703 void pointToCellRounded(int x, int y, int[] result) {
704 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
705 }
706
707 /**
708 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700709 *
710 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800711 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700712 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800713 * @param result Array of 2 ints to hold the x and y coordinate of the point
714 */
715 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700716 final int hStartPadding = getPaddingLeft();
717 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800718
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530719 result[0] = hStartPadding + cellX * mCellWidth;
720 result[1] = vStartPadding + cellY * mCellHeight;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800721 }
722
Adam Cohene3e27a82011-04-15 12:07:39 -0700723 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800724 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700725 *
726 * @param cellX X coordinate of the cell
727 * @param cellY Y coordinate of the cell
728 *
729 * @param result Array of 2 ints to hold the x and y coordinate of the point
730 */
731 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700732 regionToCenterPoint(cellX, cellY, 1, 1, result);
733 }
734
735 /**
736 * Given a cell coordinate and span return the point that represents the center of the regio
737 *
738 * @param cellX X coordinate of the cell
739 * @param cellY Y coordinate of the cell
740 *
741 * @param result Array of 2 ints to hold the x and y coordinate of the point
742 */
743 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700744 final int hStartPadding = getPaddingLeft();
745 final int vStartPadding = getPaddingTop();
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530746 result[0] = hStartPadding + cellX * mCellWidth + (spanX * mCellWidth) / 2;
747 result[1] = vStartPadding + cellY * mCellHeight + (spanY * mCellHeight) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700748 }
749
Adam Cohen19f37922012-03-21 11:59:11 -0700750 /**
751 * Given a cell coordinate and span fills out a corresponding pixel rect
752 *
753 * @param cellX X coordinate of the cell
754 * @param cellY Y coordinate of the cell
755 * @param result Rect in which to write the result
756 */
757 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
758 final int hStartPadding = getPaddingLeft();
759 final int vStartPadding = getPaddingTop();
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530760 final int left = hStartPadding + cellX * mCellWidth;
761 final int top = vStartPadding + cellY * mCellHeight;
762 result.set(left, top, left + (spanX * mCellWidth), top + (spanY * mCellHeight));
Adam Cohen19f37922012-03-21 11:59:11 -0700763 }
764
Adam Cohen482ed822012-03-02 14:15:13 -0800765 public float getDistanceFromCell(float x, float y, int[] cell) {
766 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700767 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800768 }
769
Adam Cohenf9c184a2016-01-15 16:47:43 -0800770 public int getCellWidth() {
Romain Guy84f296c2009-11-04 15:00:44 -0800771 return mCellWidth;
772 }
773
Sunny Goyal0b754e52017-08-07 07:42:45 -0700774 public int getCellHeight() {
Romain Guy84f296c2009-11-04 15:00:44 -0800775 return mCellHeight;
776 }
777
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700778 public void setFixedSize(int width, int height) {
779 mFixedWidth = width;
780 mFixedHeight = height;
781 }
782
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800783 @Override
784 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800785 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800786 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700787 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
788 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700789 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
790 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Sunny Goyalae6e3182019-04-30 12:04:37 -0700791
792 mShortcutsAndWidgets.setRotation(mRotationMode.surfaceRotation);
793 if (mRotationMode.isTransposed) {
794 int tmp = childWidthSize;
795 childWidthSize = childHeightSize;
796 childHeightSize = tmp;
797 }
798
Winson Chung11a1a532013-09-13 11:14:45 -0700799 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Sunny Goyalc6205602015-05-21 20:46:33 -0700800 int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX);
801 int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700802 if (cw != mCellWidth || ch != mCellHeight) {
803 mCellWidth = cw;
804 mCellHeight = ch;
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530805 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700806 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700807 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700808
Winson Chung2d75f122013-09-23 16:53:31 -0700809 int newWidth = childWidthSize;
810 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700811 if (mFixedWidth > 0 && mFixedHeight > 0) {
812 newWidth = mFixedWidth;
813 newHeight = mFixedHeight;
814 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800815 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
816 }
817
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700818 mShortcutsAndWidgets.measure(
819 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
820 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
821
822 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
823 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -0700824 if (mFixedWidth > 0 && mFixedHeight > 0) {
825 setMeasuredDimension(maxWidth, maxHeight);
826 } else {
827 setMeasuredDimension(widthSize, heightSize);
828 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800829 }
830
831 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700832 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Tony Wickham26b01422015-11-10 14:44:32 -0800833 int left = getPaddingLeft();
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700834 left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Sunny Goyal7c786f72016-06-01 14:08:21 -0700835 int right = r - l - getPaddingRight();
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700836 right -= (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Sunny Goyal7c786f72016-06-01 14:08:21 -0700837
Winson Chung38848ca2013-10-08 12:03:44 -0700838 int top = getPaddingTop();
Sunny Goyal7c786f72016-06-01 14:08:21 -0700839 int bottom = b - t - getPaddingBottom();
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700840
Sunny Goyal7c786f72016-06-01 14:08:21 -0700841 // Expand the background drawing bounds by the padding baked into the background drawable
842 mBackground.getPadding(mTempRect);
843 mBackground.setBounds(
Jon Miranda28032002017-07-13 16:18:56 -0700844 left - mTempRect.left - getPaddingLeft(),
845 top - mTempRect.top - getPaddingTop(),
846 right + mTempRect.right + getPaddingRight(),
847 bottom + mTempRect.bottom + getPaddingBottom());
Sunny Goyalae6e3182019-04-30 12:04:37 -0700848
849 if (mRotationMode.isTransposed) {
850 int halfW = mShortcutsAndWidgets.getMeasuredWidth() / 2;
851 int halfH = mShortcutsAndWidgets.getMeasuredHeight() / 2;
852 int cX = (left + right) / 2;
853 int cY = (top + bottom) / 2;
854 mShortcutsAndWidgets.layout(cX - halfW, cY - halfH, cX + halfW, cY + halfH);
855 } else {
856 mShortcutsAndWidgets.layout(left, top, right, bottom);
857 }
Sunny Goyal7c786f72016-06-01 14:08:21 -0700858 }
859
Tony Wickhama501d492015-11-03 18:05:01 -0800860 /**
861 * Returns the amount of space left over after subtracting padding and cells. This space will be
862 * very small, a few pixels at most, and is a result of rounding down when calculating the cell
863 * width in {@link DeviceProfile#calculateCellWidth(int, int)}.
864 */
865 public int getUnusedHorizontalSpace() {
Sunny Goyal41d51a02019-05-14 09:44:34 -0700866 return (mRotationMode.isTransposed ? getMeasuredHeight() : getMeasuredWidth())
867 - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
Tony Wickhama501d492015-11-03 18:05:01 -0800868 }
869
Sunny Goyalaeb16432017-10-16 11:46:41 -0700870 public Drawable getScrimBackground() {
871 return mBackground;
Michael Jurkadee05892010-07-27 10:01:56 -0700872 }
873
Sunny Goyal2805e632015-05-20 15:35:32 -0700874 @Override
875 protected boolean verifyDrawable(Drawable who) {
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700876 return super.verifyDrawable(who) || (who == mBackground);
Sunny Goyal2805e632015-05-20 15:35:32 -0700877 }
878
Michael Jurkaa52570f2012-03-20 03:18:20 -0700879 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700880 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700881 }
882
Patrick Dubroy440c3602010-07-13 17:50:32 -0700883 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700884 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700885 }
886
Adam Cohen76fc0852011-06-17 13:26:23 -0700887 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800888 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700889 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800890
Adam Cohen19f37922012-03-21 11:59:11 -0700891 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700892 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
893 final ItemInfo info = (ItemInfo) child.getTag();
894
895 // We cancel any existing animations
896 if (mReorderAnimators.containsKey(lp)) {
897 mReorderAnimators.get(lp).cancel();
898 mReorderAnimators.remove(lp);
899 }
900
Adam Cohen482ed822012-03-02 14:15:13 -0800901 final int oldX = lp.x;
902 final int oldY = lp.y;
903 if (adjustOccupied) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700904 GridOccupancy occupied = permanent ? mOccupied : mTmpOccupied;
905 occupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
906 occupied.markCells(cellX, cellY, lp.cellHSpan, lp.cellVSpan, true);
Adam Cohen482ed822012-03-02 14:15:13 -0800907 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700908 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -0800909 if (permanent) {
910 lp.cellX = info.cellX = cellX;
911 lp.cellY = info.cellY = cellY;
912 } else {
913 lp.tmpCellX = cellX;
914 lp.tmpCellY = cellY;
915 }
Jon Mirandae96798e2016-12-07 12:10:44 -0800916 clc.setupLp(child);
Adam Cohenbfbfd262011-06-13 16:55:12 -0700917 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800918 final int newX = lp.x;
919 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700920
Adam Cohen76fc0852011-06-17 13:26:23 -0700921 lp.x = oldX;
922 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -0700923
Adam Cohen482ed822012-03-02 14:15:13 -0800924 // Exit early if we're not actually moving the view
925 if (oldX == newX && oldY == newY) {
926 lp.isLockedToGrid = true;
927 return true;
928 }
929
Sunny Goyal849c6a22018-08-08 16:33:46 -0700930 ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -0800931 va.setDuration(duration);
932 mReorderAnimators.put(lp, va);
933
934 va.addUpdateListener(new AnimatorUpdateListener() {
935 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -0700936 public void onAnimationUpdate(ValueAnimator animation) {
Jon Mirandae96798e2016-12-07 12:10:44 -0800937 float r = (Float) animation.getAnimatedValue();
Adam Cohen19f37922012-03-21 11:59:11 -0700938 lp.x = (int) ((1 - r) * oldX + r * newX);
939 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -0700940 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -0700941 }
942 });
Adam Cohen482ed822012-03-02 14:15:13 -0800943 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700944 boolean cancelled = false;
945 public void onAnimationEnd(Animator animation) {
946 // If the animation was cancelled, it means that another animation
947 // has interrupted this one, and we don't want to lock the item into
948 // place just yet.
949 if (!cancelled) {
950 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -0800951 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -0700952 }
953 if (mReorderAnimators.containsKey(lp)) {
954 mReorderAnimators.remove(lp);
955 }
956 }
957 public void onAnimationCancel(Animator animation) {
958 cancelled = true;
959 }
960 });
Adam Cohen482ed822012-03-02 14:15:13 -0800961 va.setStartDelay(delay);
962 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -0700963 return true;
964 }
965 return false;
966 }
967
Adam Cohen65086992020-02-19 08:40:49 -0800968 void visualizeDropLocation(DraggableView v, DragPreviewProvider outlineProvider, int cellX, int
969 cellY, int spanX, int spanY, boolean resize, DropTarget.DragObject dragObject) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -0700970 final int oldDragCellX = mDragCell[0];
971 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -0800972
Hyunyoung Song0de01172016-10-05 16:27:48 -0700973 if (outlineProvider == null || outlineProvider.generatedDragOutline == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -0700974 return;
975 }
976
Hyunyoung Song0de01172016-10-05 16:27:48 -0700977 Bitmap dragOutline = outlineProvider.generatedDragOutline;
Adam Cohen482ed822012-03-02 14:15:13 -0800978 if (cellX != oldDragCellX || cellY != oldDragCellY) {
979 mDragCell[0] = cellX;
980 mDragCell[1] = cellY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700981
Joe Onorato4be866d2010-10-10 11:26:02 -0700982 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -0700983 mDragOutlineAnims[oldIndex].animateOut();
984 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -0800985 Rect r = mDragOutlines[mDragOutlineCurrent];
Sunny Goyal106bf642015-07-16 12:18:06 -0700986
Adam Cohen65086992020-02-19 08:40:49 -0800987 cellToRect(cellX, cellY, spanX, spanY, r);
988 int left = r.left;
989 int top = r.top;
990
991 int width = dragOutline.getWidth();
992 int height = dragOutline.getHeight();
993
Adam Cohend41fbf52012-02-16 23:53:59 -0800994 if (resize) {
Adam Cohen65086992020-02-19 08:40:49 -0800995 width = r.width();
996 height = r.height();
Adam Cohend41fbf52012-02-16 23:53:59 -0800997 }
Winson Chung150fbab2010-09-29 17:14:26 -0700998
Adam Cohen65086992020-02-19 08:40:49 -0800999 if (v != null && v.getViewType() == DraggableView.DRAGGABLE_ICON) {
1000 left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
1001 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1002 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1003 top += cellPaddingY;
1004 }
1005
1006 r.set(left, top, left + width, top + height);
1007
Jon Miranda6f6a06a2016-12-15 11:24:18 -08001008 Utilities.scaleRectAboutCenter(r, mChildScale);
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001009 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1010 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Sunny Goyale78e3d72015-09-24 11:23:31 -07001011
1012 if (dragObject.stateAnnouncer != null) {
Sunny Goyalc13403c2016-11-18 23:44:48 -08001013 dragObject.stateAnnouncer.announce(getItemMoveDescription(cellX, cellY));
Sunny Goyale78e3d72015-09-24 11:23:31 -07001014 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001015 }
1016 }
1017
Sunny Goyal726bee72018-03-05 12:54:24 -08001018 @SuppressLint("StringFormatMatches")
Sunny Goyalc13403c2016-11-18 23:44:48 -08001019 public String getItemMoveDescription(int cellX, int cellY) {
1020 if (mContainerType == HOTSEAT) {
1021 return getContext().getString(R.string.move_to_hotseat_position,
1022 Math.max(cellX, cellY) + 1);
1023 } else {
1024 return getContext().getString(R.string.move_to_empty_cell,
1025 cellY + 1, cellX + 1);
1026 }
1027 }
1028
Adam Cohene0310962011-04-18 16:15:31 -07001029 public void clearDragOutlines() {
1030 final int oldIndex = mDragOutlineCurrent;
1031 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001032 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001033 }
1034
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001035 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001036 * Find a vacant area that will fit the given bounds nearest the requested
1037 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001038 *
Romain Guy51afc022009-05-04 18:03:43 -07001039 * @param pixelX The X location at which you want to search for a vacant area.
1040 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001041 * @param minSpanX The minimum horizontal span required
1042 * @param minSpanY The minimum vertical span required
1043 * @param spanX Horizontal span of the object.
1044 * @param spanY Vertical span of the object.
1045 * @param result Array in which to place the result, or null (in which case a new array will
1046 * be allocated)
1047 * @return The X, Y cell of a vacant area that can contain this object,
1048 * nearest the requested location.
1049 */
1050 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1051 int spanY, int[] result, int[] resultSpan) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001052 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
Adam Cohend41fbf52012-02-16 23:53:59 -08001053 result, resultSpan);
1054 }
1055
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001056 private final Stack<Rect> mTempRectStack = new Stack<>();
Adam Cohend41fbf52012-02-16 23:53:59 -08001057 private void lazyInitTempRectStack() {
1058 if (mTempRectStack.isEmpty()) {
1059 for (int i = 0; i < mCountX * mCountY; i++) {
1060 mTempRectStack.push(new Rect());
1061 }
1062 }
1063 }
Adam Cohen482ed822012-03-02 14:15:13 -08001064
Adam Cohend41fbf52012-02-16 23:53:59 -08001065 private void recycleTempRects(Stack<Rect> used) {
1066 while (!used.isEmpty()) {
1067 mTempRectStack.push(used.pop());
1068 }
1069 }
1070
1071 /**
1072 * Find a vacant area that will fit the given bounds nearest the requested
1073 * cell location. Uses Euclidean distance to score multiple vacant areas.
1074 *
1075 * @param pixelX The X location at which you want to search for a vacant area.
1076 * @param pixelY The Y location at which you want to search for a vacant area.
1077 * @param minSpanX The minimum horizontal span required
1078 * @param minSpanY The minimum vertical span required
1079 * @param spanX Horizontal span of the object.
1080 * @param spanY Vertical span of the object.
1081 * @param ignoreOccupied If true, the result can be an occupied cell
1082 * @param result Array in which to place the result, or null (in which case a new array will
1083 * be allocated)
1084 * @return The X, Y cell of a vacant area that can contain this object,
1085 * nearest the requested location.
1086 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001087 private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1088 int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001089 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001090
Adam Cohene3e27a82011-04-15 12:07:39 -07001091 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1092 // to the center of the item, but we are searching based on the top-left cell, so
1093 // we translate the point over to correspond to the top-left.
Sunny Goyalaa8a8712016-11-20 15:26:01 +05301094 pixelX -= mCellWidth * (spanX - 1) / 2f;
1095 pixelY -= mCellHeight * (spanY - 1) / 2f;
Adam Cohene3e27a82011-04-15 12:07:39 -07001096
Jeff Sharkey70864282009-04-07 21:08:40 -07001097 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001098 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001099 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001100 final Rect bestRect = new Rect(-1, -1, -1, -1);
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001101 final Stack<Rect> validRegions = new Stack<>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001102
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001103 final int countX = mCountX;
1104 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001105
Adam Cohend41fbf52012-02-16 23:53:59 -08001106 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1107 spanX < minSpanX || spanY < minSpanY) {
1108 return bestXY;
1109 }
1110
1111 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001112 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001113 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1114 int ySize = -1;
1115 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001116 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001117 // First, let's see if this thing fits anywhere
1118 for (int i = 0; i < minSpanX; i++) {
1119 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001120 if (mOccupied.cells[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001121 continue inner;
1122 }
Michael Jurkac28de512010-08-13 11:27:44 -07001123 }
1124 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001125 xSize = minSpanX;
1126 ySize = minSpanY;
1127
1128 // We know that the item will fit at _some_ acceptable size, now let's see
1129 // how big we can make it. We'll alternate between incrementing x and y spans
1130 // until we hit a limit.
1131 boolean incX = true;
1132 boolean hitMaxX = xSize >= spanX;
1133 boolean hitMaxY = ySize >= spanY;
1134 while (!(hitMaxX && hitMaxY)) {
1135 if (incX && !hitMaxX) {
1136 for (int j = 0; j < ySize; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001137 if (x + xSize > countX -1 || mOccupied.cells[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001138 // We can't move out horizontally
1139 hitMaxX = true;
1140 }
1141 }
1142 if (!hitMaxX) {
1143 xSize++;
1144 }
1145 } else if (!hitMaxY) {
1146 for (int i = 0; i < xSize; i++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001147 if (y + ySize > countY - 1 || mOccupied.cells[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001148 // We can't move out vertically
1149 hitMaxY = true;
1150 }
1151 }
1152 if (!hitMaxY) {
1153 ySize++;
1154 }
1155 }
1156 hitMaxX |= xSize >= spanX;
1157 hitMaxY |= ySize >= spanY;
1158 incX = !incX;
1159 }
1160 incX = true;
1161 hitMaxX = xSize >= spanX;
1162 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001163 }
Sunny Goyal2805e632015-05-20 15:35:32 -07001164 final int[] cellXY = mTmpPoint;
Adam Cohene3e27a82011-04-15 12:07:39 -07001165 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001166
Adam Cohend41fbf52012-02-16 23:53:59 -08001167 // We verify that the current rect is not a sub-rect of any of our previous
1168 // candidates. In this case, the current rect is disqualified in favour of the
1169 // containing rect.
1170 Rect currentRect = mTempRectStack.pop();
1171 currentRect.set(x, y, x + xSize, y + ySize);
1172 boolean contained = false;
1173 for (Rect r : validRegions) {
1174 if (r.contains(currentRect)) {
1175 contained = true;
1176 break;
1177 }
1178 }
1179 validRegions.push(currentRect);
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001180 double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY);
Adam Cohen482ed822012-03-02 14:15:13 -08001181
Adam Cohend41fbf52012-02-16 23:53:59 -08001182 if ((distance <= bestDistance && !contained) ||
1183 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001184 bestDistance = distance;
1185 bestXY[0] = x;
1186 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001187 if (resultSpan != null) {
1188 resultSpan[0] = xSize;
1189 resultSpan[1] = ySize;
1190 }
1191 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001192 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001193 }
1194 }
1195
Adam Cohenc0dcf592011-06-01 15:30:43 -07001196 // Return -1, -1 if no suitable location found
1197 if (bestDistance == Double.MAX_VALUE) {
1198 bestXY[0] = -1;
1199 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001200 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001201 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001202 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001203 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001204
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001205 /**
Adam Cohen482ed822012-03-02 14:15:13 -08001206 * Find a vacant area that will fit the given bounds nearest the requested
1207 * cell location, and will also weigh in a suggested direction vector of the
1208 * desired location. This method computers distance based on unit grid distances,
1209 * not pixel distances.
1210 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001211 * @param cellX The X cell nearest to which you want to search for a vacant area.
1212 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001213 * @param spanX Horizontal span of the object.
1214 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001215 * @param direction The favored direction in which the views should move from x, y
Sunny Goyal9eba1fd2015-10-16 08:58:57 -07001216 * @param occupied The array which represents which cells in the CellLayout are occupied
Adam Cohen47a876d2012-03-19 13:21:41 -07001217 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001218 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001219 * @param result Array in which to place the result, or null (in which case a new array will
1220 * be allocated)
1221 * @return The X, Y cell of a vacant area that can contain this object,
1222 * nearest the requested location.
1223 */
1224 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001225 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001226 // Keep track of best-scoring drop area
1227 final int[] bestXY = result != null ? result : new int[2];
1228 float bestDistance = Float.MAX_VALUE;
1229 int bestDirectionScore = Integer.MIN_VALUE;
1230
1231 final int countX = mCountX;
1232 final int countY = mCountY;
1233
1234 for (int y = 0; y < countY - (spanY - 1); y++) {
1235 inner:
1236 for (int x = 0; x < countX - (spanX - 1); x++) {
1237 // First, let's see if this thing fits anywhere
1238 for (int i = 0; i < spanX; i++) {
1239 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001240 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001241 continue inner;
1242 }
1243 }
1244 }
1245
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001246 float distance = (float) Math.hypot(x - cellX, y - cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001247 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001248 computeDirectionVector(x - cellX, y - cellY, curDirection);
1249 // The direction score is just the dot product of the two candidate direction
1250 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001251 int curDirectionScore = direction[0] * curDirection[0] +
1252 direction[1] * curDirection[1];
Sunny Goyal8f90dcf2016-08-18 15:01:11 -07001253 if (Float.compare(distance, bestDistance) < 0 ||
1254 (Float.compare(distance, bestDistance) == 0
1255 && curDirectionScore > bestDirectionScore)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001256 bestDistance = distance;
1257 bestDirectionScore = curDirectionScore;
1258 bestXY[0] = x;
1259 bestXY[1] = y;
1260 }
1261 }
1262 }
1263
1264 // Return -1, -1 if no suitable location found
1265 if (bestDistance == Float.MAX_VALUE) {
1266 bestXY[0] = -1;
1267 bestXY[1] = -1;
1268 }
1269 return bestXY;
1270 }
1271
1272 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001273 int[] direction, ItemConfiguration currentState) {
1274 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001275 boolean success = false;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001276 mTmpOccupied.markCells(c, false);
1277 mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001278
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001279 findNearestArea(c.cellX, c.cellY, c.spanX, c.spanY, direction,
1280 mTmpOccupied.cells, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001281
1282 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001283 c.cellX = mTempLocation[0];
1284 c.cellY = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001285 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001286 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001287 mTmpOccupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001288 return success;
1289 }
1290
Adam Cohenf3900c22012-11-16 18:28:11 -08001291 /**
1292 * This helper class defines a cluster of views. It helps with defining complex edges
1293 * of the cluster and determining how those edges interact with other views. The edges
1294 * essentially define a fine-grained boundary around the cluster of views -- like a more
1295 * precise version of a bounding box.
1296 */
1297 private class ViewCluster {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001298 final static int LEFT = 1 << 0;
1299 final static int TOP = 1 << 1;
1300 final static int RIGHT = 1 << 2;
1301 final static int BOTTOM = 1 << 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001302
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001303 final ArrayList<View> views;
1304 final ItemConfiguration config;
1305 final Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001306
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001307 final int[] leftEdge = new int[mCountY];
1308 final int[] rightEdge = new int[mCountY];
1309 final int[] topEdge = new int[mCountX];
1310 final int[] bottomEdge = new int[mCountX];
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001311 int dirtyEdges;
1312 boolean boundingRectDirty;
Adam Cohenf3900c22012-11-16 18:28:11 -08001313
1314 @SuppressWarnings("unchecked")
1315 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1316 this.views = (ArrayList<View>) views.clone();
1317 this.config = config;
1318 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001319 }
1320
Adam Cohenf3900c22012-11-16 18:28:11 -08001321 void resetEdges() {
1322 for (int i = 0; i < mCountX; i++) {
1323 topEdge[i] = -1;
1324 bottomEdge[i] = -1;
1325 }
1326 for (int i = 0; i < mCountY; i++) {
1327 leftEdge[i] = -1;
1328 rightEdge[i] = -1;
1329 }
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001330 dirtyEdges = LEFT | TOP | RIGHT | BOTTOM;
Adam Cohenf3900c22012-11-16 18:28:11 -08001331 boundingRectDirty = true;
1332 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001333
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001334 void computeEdge(int which) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001335 int count = views.size();
1336 for (int i = 0; i < count; i++) {
1337 CellAndSpan cs = config.map.get(views.get(i));
1338 switch (which) {
1339 case LEFT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001340 int left = cs.cellX;
1341 for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001342 if (left < leftEdge[j] || leftEdge[j] < 0) {
1343 leftEdge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001344 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001345 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001346 break;
1347 case RIGHT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001348 int right = cs.cellX + cs.spanX;
1349 for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001350 if (right > rightEdge[j]) {
1351 rightEdge[j] = right;
Adam Cohenf3900c22012-11-16 18:28:11 -08001352 }
1353 }
1354 break;
1355 case TOP:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001356 int top = cs.cellY;
1357 for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001358 if (top < topEdge[j] || topEdge[j] < 0) {
1359 topEdge[j] = top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001360 }
1361 }
1362 break;
1363 case BOTTOM:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001364 int bottom = cs.cellY + cs.spanY;
1365 for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001366 if (bottom > bottomEdge[j]) {
1367 bottomEdge[j] = bottom;
Adam Cohenf3900c22012-11-16 18:28:11 -08001368 }
1369 }
1370 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001371 }
1372 }
1373 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001374
1375 boolean isViewTouchingEdge(View v, int whichEdge) {
1376 CellAndSpan cs = config.map.get(v);
1377
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001378 if ((dirtyEdges & whichEdge) == whichEdge) {
1379 computeEdge(whichEdge);
1380 dirtyEdges &= ~whichEdge;
1381 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001382
1383 switch (whichEdge) {
1384 case LEFT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001385 for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) {
1386 if (leftEdge[i] == cs.cellX + cs.spanX) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001387 return true;
1388 }
1389 }
1390 break;
1391 case RIGHT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001392 for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) {
1393 if (rightEdge[i] == cs.cellX) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001394 return true;
1395 }
1396 }
1397 break;
1398 case TOP:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001399 for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) {
1400 if (topEdge[i] == cs.cellY + cs.spanY) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001401 return true;
1402 }
1403 }
1404 break;
1405 case BOTTOM:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001406 for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) {
1407 if (bottomEdge[i] == cs.cellY) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001408 return true;
1409 }
1410 }
1411 break;
1412 }
1413 return false;
1414 }
1415
1416 void shift(int whichEdge, int delta) {
1417 for (View v: views) {
1418 CellAndSpan c = config.map.get(v);
1419 switch (whichEdge) {
1420 case LEFT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001421 c.cellX -= delta;
Adam Cohenf3900c22012-11-16 18:28:11 -08001422 break;
1423 case RIGHT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001424 c.cellX += delta;
Adam Cohenf3900c22012-11-16 18:28:11 -08001425 break;
1426 case TOP:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001427 c.cellY -= delta;
Adam Cohenf3900c22012-11-16 18:28:11 -08001428 break;
1429 case BOTTOM:
1430 default:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001431 c.cellY += delta;
Adam Cohenf3900c22012-11-16 18:28:11 -08001432 break;
1433 }
1434 }
1435 resetEdges();
1436 }
1437
1438 public void addView(View v) {
1439 views.add(v);
1440 resetEdges();
1441 }
1442
1443 public Rect getBoundingRect() {
1444 if (boundingRectDirty) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001445 config.getBoundingRectForViews(views, boundingRect);
Adam Cohenf3900c22012-11-16 18:28:11 -08001446 }
1447 return boundingRect;
1448 }
1449
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001450 final PositionComparator comparator = new PositionComparator();
Adam Cohenf3900c22012-11-16 18:28:11 -08001451 class PositionComparator implements Comparator<View> {
1452 int whichEdge = 0;
1453 public int compare(View left, View right) {
1454 CellAndSpan l = config.map.get(left);
1455 CellAndSpan r = config.map.get(right);
1456 switch (whichEdge) {
1457 case LEFT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001458 return (r.cellX + r.spanX) - (l.cellX + l.spanX);
Adam Cohenf3900c22012-11-16 18:28:11 -08001459 case RIGHT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001460 return l.cellX - r.cellX;
Adam Cohenf3900c22012-11-16 18:28:11 -08001461 case TOP:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001462 return (r.cellY + r.spanY) - (l.cellY + l.spanY);
Adam Cohenf3900c22012-11-16 18:28:11 -08001463 case BOTTOM:
1464 default:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001465 return l.cellY - r.cellY;
Adam Cohenf3900c22012-11-16 18:28:11 -08001466 }
1467 }
1468 }
1469
1470 public void sortConfigurationForEdgePush(int edge) {
1471 comparator.whichEdge = edge;
1472 Collections.sort(config.sortedViews, comparator);
1473 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001474 }
1475
Adam Cohenf3900c22012-11-16 18:28:11 -08001476 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1477 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001478
Adam Cohenf3900c22012-11-16 18:28:11 -08001479 ViewCluster cluster = new ViewCluster(views, currentState);
1480 Rect clusterRect = cluster.getBoundingRect();
1481 int whichEdge;
1482 int pushDistance;
1483 boolean fail = false;
1484
1485 // Determine the edge of the cluster that will be leading the push and how far
1486 // the cluster must be shifted.
1487 if (direction[0] < 0) {
1488 whichEdge = ViewCluster.LEFT;
1489 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001490 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001491 whichEdge = ViewCluster.RIGHT;
1492 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1493 } else if (direction[1] < 0) {
1494 whichEdge = ViewCluster.TOP;
1495 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1496 } else {
1497 whichEdge = ViewCluster.BOTTOM;
1498 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001499 }
1500
Adam Cohenf3900c22012-11-16 18:28:11 -08001501 // Break early for invalid push distance.
1502 if (pushDistance <= 0) {
1503 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001504 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001505
1506 // Mark the occupied state as false for the group of views we want to move.
1507 for (View v: views) {
1508 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001509 mTmpOccupied.markCells(c, false);
Adam Cohenf3900c22012-11-16 18:28:11 -08001510 }
1511
1512 // We save the current configuration -- if we fail to find a solution we will revert
1513 // to the initial state. The process of finding a solution modifies the configuration
1514 // in place, hence the need for revert in the failure case.
1515 currentState.save();
1516
1517 // The pushing algorithm is simplified by considering the views in the order in which
1518 // they would be pushed by the cluster. For example, if the cluster is leading with its
1519 // left edge, we consider sort the views by their right edge, from right to left.
1520 cluster.sortConfigurationForEdgePush(whichEdge);
1521
1522 while (pushDistance > 0 && !fail) {
1523 for (View v: currentState.sortedViews) {
1524 // For each view that isn't in the cluster, we see if the leading edge of the
1525 // cluster is contacting the edge of that view. If so, we add that view to the
1526 // cluster.
1527 if (!cluster.views.contains(v) && v != dragView) {
1528 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1529 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1530 if (!lp.canReorder) {
1531 // The push solution includes the all apps button, this is not viable.
1532 fail = true;
1533 break;
1534 }
1535 cluster.addView(v);
1536 CellAndSpan c = currentState.map.get(v);
1537
1538 // Adding view to cluster, mark it as not occupied.
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001539 mTmpOccupied.markCells(c, false);
Adam Cohenf3900c22012-11-16 18:28:11 -08001540 }
1541 }
1542 }
1543 pushDistance--;
1544
1545 // The cluster has been completed, now we move the whole thing over in the appropriate
1546 // direction.
1547 cluster.shift(whichEdge, 1);
1548 }
1549
1550 boolean foundSolution = false;
1551 clusterRect = cluster.getBoundingRect();
1552
1553 // Due to the nature of the algorithm, the only check required to verify a valid solution
1554 // is to ensure that completed shifted cluster lies completely within the cell layout.
1555 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1556 clusterRect.bottom <= mCountY) {
1557 foundSolution = true;
1558 } else {
1559 currentState.restore();
1560 }
1561
1562 // In either case, we set the occupied array as marked for the location of the views
1563 for (View v: cluster.views) {
1564 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001565 mTmpOccupied.markCells(c, true);
Adam Cohenf3900c22012-11-16 18:28:11 -08001566 }
1567
1568 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001569 }
1570
Adam Cohen482ed822012-03-02 14:15:13 -08001571 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001572 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001573 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001574
Adam Cohen8baab352012-03-20 17:39:21 -07001575 boolean success = false;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001576 Rect boundingRect = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001577 // We construct a rect which represents the entire group of views passed in
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001578 currentState.getBoundingRectForViews(views, boundingRect);
Adam Cohen8baab352012-03-20 17:39:21 -07001579
Adam Cohen8baab352012-03-20 17:39:21 -07001580 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001581 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001582 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001583 mTmpOccupied.markCells(c, false);
Adam Cohen8baab352012-03-20 17:39:21 -07001584 }
1585
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001586 GridOccupancy blockOccupied = new GridOccupancy(boundingRect.width(), boundingRect.height());
Adam Cohen47a876d2012-03-19 13:21:41 -07001587 int top = boundingRect.top;
1588 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001589 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001590 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001591 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001592 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001593 blockOccupied.markCells(c.cellX - left, c.cellY - top, c.spanX, c.spanY, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001594 }
1595
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001596 mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001597
Adam Cohenf3900c22012-11-16 18:28:11 -08001598 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001599 boundingRect.height(), direction,
1600 mTmpOccupied.cells, blockOccupied.cells, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001601
Adam Cohen8baab352012-03-20 17:39:21 -07001602 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001603 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001604 int deltaX = mTempLocation[0] - boundingRect.left;
1605 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001606 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001607 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001608 c.cellX += deltaX;
1609 c.cellY += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001610 }
1611 success = true;
1612 }
Adam Cohen8baab352012-03-20 17:39:21 -07001613
1614 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001615 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001616 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001617 mTmpOccupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001618 }
1619 return success;
1620 }
1621
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001622 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1623 // to push items in each of the cardinal directions, in an order based on the direction vector
1624 // passed.
1625 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1626 int[] direction, View ignoreView, ItemConfiguration solution) {
1627 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001628 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001629 // separately in each of the components.
1630 int temp = direction[1];
1631 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001632
1633 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001634 ignoreView, solution)) {
1635 return true;
1636 }
1637 direction[1] = temp;
1638 temp = direction[0];
1639 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001640
1641 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001642 ignoreView, solution)) {
1643 return true;
1644 }
1645 // Revert the direction
1646 direction[0] = temp;
1647
1648 // Now we try pushing in each component of the opposite direction
1649 direction[0] *= -1;
1650 direction[1] *= -1;
1651 temp = direction[1];
1652 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001653 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001654 ignoreView, solution)) {
1655 return true;
1656 }
1657
1658 direction[1] = temp;
1659 temp = direction[0];
1660 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001661 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001662 ignoreView, solution)) {
1663 return true;
1664 }
1665 // revert the direction
1666 direction[0] = temp;
1667 direction[0] *= -1;
1668 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001669
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001670 } else {
1671 // If the direction vector has a single non-zero component, we push first in the
1672 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001673 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001674 ignoreView, solution)) {
1675 return true;
1676 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001677 // Then we try the opposite direction
1678 direction[0] *= -1;
1679 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001680 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001681 ignoreView, solution)) {
1682 return true;
1683 }
1684 // Switch the direction back
1685 direction[0] *= -1;
1686 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001687
1688 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001689 // to find a solution by pushing along the perpendicular axis.
1690
1691 // Swap the components
1692 int temp = direction[1];
1693 direction[1] = direction[0];
1694 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001695 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001696 ignoreView, solution)) {
1697 return true;
1698 }
1699
1700 // Then we try the opposite direction
1701 direction[0] *= -1;
1702 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001703 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001704 ignoreView, solution)) {
1705 return true;
1706 }
1707 // Switch the direction back
1708 direction[0] *= -1;
1709 direction[1] *= -1;
1710
1711 // Swap the components back
1712 temp = direction[1];
1713 direction[1] = direction[0];
1714 direction[0] = temp;
1715 }
1716 return false;
1717 }
1718
Adam Cohen482ed822012-03-02 14:15:13 -08001719 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001720 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001721 // Return early if get invalid cell positions
1722 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001723
Adam Cohen8baab352012-03-20 17:39:21 -07001724 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001725 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001726
Adam Cohen8baab352012-03-20 17:39:21 -07001727 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001728 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001729 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001730 if (c != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001731 c.cellX = cellX;
1732 c.cellY = cellY;
Adam Cohen19f37922012-03-21 11:59:11 -07001733 }
Adam Cohen482ed822012-03-02 14:15:13 -08001734 }
Adam Cohen482ed822012-03-02 14:15:13 -08001735 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1736 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001737 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001738 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001739 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001740 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001741 r1.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001742 if (Rect.intersects(r0, r1)) {
1743 if (!lp.canReorder) {
1744 return false;
1745 }
1746 mIntersectingViews.add(child);
1747 }
1748 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001749
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001750 solution.intersectingViews = new ArrayList<>(mIntersectingViews);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001751
Winson Chung5f8afe62013-08-12 16:19:28 -07001752 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001753 // we try to find a solution such that no displaced item travels through another item
1754 // without also displacing that item.
1755 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001756 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001757 return true;
1758 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001759
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001760 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08001761 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001762 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001763 return true;
1764 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001765
Adam Cohen482ed822012-03-02 14:15:13 -08001766 // Ok, they couldn't move as a block, let's move them individually
1767 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001768 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001769 return false;
1770 }
1771 }
1772 return true;
1773 }
1774
1775 /*
1776 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1777 * the provided point and the provided cell
1778 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001779 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Jon Mirandae96798e2016-12-07 12:10:44 -08001780 double angle = Math.atan(deltaY / deltaX);
Adam Cohen482ed822012-03-02 14:15:13 -08001781
1782 result[0] = 0;
1783 result[1] = 0;
1784 if (Math.abs(Math.cos(angle)) > 0.5f) {
1785 result[0] = (int) Math.signum(deltaX);
1786 }
1787 if (Math.abs(Math.sin(angle)) > 0.5f) {
1788 result[1] = (int) Math.signum(deltaY);
1789 }
1790 }
1791
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001792 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001793 int spanX, int spanY, int[] direction, View dragView, boolean decX,
1794 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001795 // Copy the current state into the solution. This solution will be manipulated as necessary.
1796 copyCurrentStateToSolution(solution, false);
1797 // Copy the current occupied array into the temporary occupied array. This array will be
1798 // manipulated as necessary to find a solution.
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001799 mOccupied.copyTo(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001800
1801 // We find the nearest cell into which we would place the dragged item, assuming there's
1802 // nothing in its way.
1803 int result[] = new int[2];
1804 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1805
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001806 boolean success;
Adam Cohen482ed822012-03-02 14:15:13 -08001807 // First we try the exact nearest position of the item being dragged,
1808 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001809 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1810 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001811
1812 if (!success) {
1813 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1814 // x, then 1 in y etc.
1815 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001816 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
1817 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001818 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001819 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
1820 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001821 }
1822 solution.isSolution = false;
1823 } else {
1824 solution.isSolution = true;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001825 solution.cellX = result[0];
1826 solution.cellY = result[1];
1827 solution.spanX = spanX;
1828 solution.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08001829 }
1830 return solution;
1831 }
1832
1833 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001834 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001835 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001836 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001837 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001838 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08001839 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07001840 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001841 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001842 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001843 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001844 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08001845 }
1846 }
1847
1848 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001849 mTmpOccupied.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001850
Michael Jurkaa52570f2012-03-20 03:18:20 -07001851 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001852 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001853 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001854 if (child == dragView) continue;
1855 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001856 CellAndSpan c = solution.map.get(child);
1857 if (c != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001858 lp.tmpCellX = c.cellX;
1859 lp.tmpCellY = c.cellY;
Adam Cohen8baab352012-03-20 17:39:21 -07001860 lp.cellHSpan = c.spanX;
1861 lp.cellVSpan = c.spanY;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001862 mTmpOccupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001863 }
1864 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001865 mTmpOccupied.markCells(solution, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001866 }
1867
1868 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
1869 commitDragView) {
1870
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001871 GridOccupancy occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
1872 occupied.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001873
Michael Jurkaa52570f2012-03-20 03:18:20 -07001874 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001875 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001876 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001877 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001878 CellAndSpan c = solution.map.get(child);
1879 if (c != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001880 animateChildToPosition(child, c.cellX, c.cellY, REORDER_ANIMATION_DURATION, 0,
Adam Cohen19f37922012-03-21 11:59:11 -07001881 DESTRUCTIVE_REORDER, false);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001882 occupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001883 }
1884 }
1885 if (commitDragView) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001886 occupied.markCells(solution, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001887 }
1888 }
1889
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001890
1891 // This method starts or changes the reorder preview animations
1892 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
Adam Cohen65086992020-02-19 08:40:49 -08001893 View dragView, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07001894 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07001895 for (int i = 0; i < childCount; i++) {
1896 View child = mShortcutsAndWidgets.getChildAt(i);
1897 if (child == dragView) continue;
1898 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001899 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
1900 != null && !solution.intersectingViews.contains(child);
1901
Adam Cohen19f37922012-03-21 11:59:11 -07001902 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001903 if (c != null && !skip) {
1904 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001905 lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07001906 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07001907 }
1908 }
1909 }
1910
Sunny Goyal849c6a22018-08-08 16:33:46 -07001911 private static final Property<ReorderPreviewAnimation, Float> ANIMATION_PROGRESS =
1912 new Property<ReorderPreviewAnimation, Float>(float.class, "animationProgress") {
1913 @Override
1914 public Float get(ReorderPreviewAnimation anim) {
1915 return anim.animationProgress;
1916 }
1917
1918 @Override
1919 public void set(ReorderPreviewAnimation anim, Float progress) {
1920 anim.setAnimationProgress(progress);
1921 }
1922 };
1923
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001924 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07001925 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001926 class ReorderPreviewAnimation {
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001927 final View child;
Adam Cohend024f982012-05-23 18:26:45 -07001928 float finalDeltaX;
1929 float finalDeltaY;
1930 float initDeltaX;
1931 float initDeltaY;
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001932 final float finalScale;
Adam Cohend024f982012-05-23 18:26:45 -07001933 float initScale;
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001934 final int mode;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001935 boolean repeating = false;
1936 private static final int PREVIEW_DURATION = 300;
1937 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
1938
Jon Miranda21266912016-12-19 14:12:05 -08001939 private static final float CHILD_DIVIDEND = 4.0f;
1940
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001941 public static final int MODE_HINT = 0;
1942 public static final int MODE_PREVIEW = 1;
1943
Sunny Goyal849c6a22018-08-08 16:33:46 -07001944 float animationProgress = 0;
Sunny Goyalf0b6db72018-08-13 16:10:14 -07001945 ValueAnimator a;
Adam Cohen19f37922012-03-21 11:59:11 -07001946
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001947 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
1948 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07001949 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
1950 final int x0 = mTmpPoint[0];
1951 final int y0 = mTmpPoint[1];
1952 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
1953 final int x1 = mTmpPoint[0];
1954 final int y1 = mTmpPoint[1];
1955 final int dX = x1 - x0;
1956 final int dY = y1 - y0;
Jon Miranda21266912016-12-19 14:12:05 -08001957
1958 this.child = child;
1959 this.mode = mode;
Adam Cohen65086992020-02-19 08:40:49 -08001960
1961 // TODO issue!
Jon Miranda21266912016-12-19 14:12:05 -08001962 setInitialAnimationValues(false);
1963 finalScale = (mChildScale - (CHILD_DIVIDEND / child.getWidth())) * initScale;
1964 finalDeltaX = initDeltaX;
1965 finalDeltaY = initDeltaY;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001966 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07001967 if (dX == dY && dX == 0) {
1968 } else {
1969 if (dY == 0) {
Jon Miranda21266912016-12-19 14:12:05 -08001970 finalDeltaX += - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07001971 } else if (dX == 0) {
Jon Miranda21266912016-12-19 14:12:05 -08001972 finalDeltaY += - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07001973 } else {
1974 double angle = Math.atan( (float) (dY) / dX);
Jon Miranda21266912016-12-19 14:12:05 -08001975 finalDeltaX += (int) (- dir * Math.signum(dX) *
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001976 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
Jon Miranda21266912016-12-19 14:12:05 -08001977 finalDeltaY += (int) (- dir * Math.signum(dY) *
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001978 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07001979 }
1980 }
Jon Miranda21266912016-12-19 14:12:05 -08001981 }
1982
1983 void setInitialAnimationValues(boolean restoreOriginalValues) {
1984 if (restoreOriginalValues) {
1985 if (child instanceof LauncherAppWidgetHostView) {
1986 LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
1987 initScale = lahv.getScaleToFit();
1988 initDeltaX = lahv.getTranslationForCentering().x;
1989 initDeltaY = lahv.getTranslationForCentering().y;
1990 } else {
1991 initScale = mChildScale;
1992 initDeltaX = 0;
1993 initDeltaY = 0;
1994 }
1995 } else {
1996 initScale = child.getScaleX();
1997 initDeltaX = child.getTranslationX();
1998 initDeltaY = child.getTranslationY();
1999 }
Adam Cohen19f37922012-03-21 11:59:11 -07002000 }
2001
Adam Cohend024f982012-05-23 18:26:45 -07002002 void animate() {
Jon Miranda21266912016-12-19 14:12:05 -08002003 boolean noMovement = (finalDeltaX == initDeltaX) && (finalDeltaY == initDeltaY);
2004
Adam Cohen19f37922012-03-21 11:59:11 -07002005 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002006 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002007 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002008 mShakeAnimators.remove(child);
Jon Miranda21266912016-12-19 14:12:05 -08002009 if (noMovement) {
Adam Cohene7587d22012-05-24 18:50:02 -07002010 completeAnimationImmediately();
2011 return;
2012 }
Adam Cohen19f37922012-03-21 11:59:11 -07002013 }
Jon Miranda21266912016-12-19 14:12:05 -08002014 if (noMovement) {
Adam Cohen19f37922012-03-21 11:59:11 -07002015 return;
2016 }
Sunny Goyal849c6a22018-08-08 16:33:46 -07002017 ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS, 0, 1);
Adam Cohene7587d22012-05-24 18:50:02 -07002018 a = va;
Tony Wickham9e0702f2015-09-02 14:45:39 -07002019
2020 // Animations are disabled in power save mode, causing the repeated animation to jump
2021 // spastically between beginning and end states. Since this looks bad, we don't repeat
2022 // the animation in power save mode.
Sunny Goyal7368fa42019-04-22 09:58:14 -07002023 if (Utilities.areAnimationsEnabled(getContext())) {
Tony Wickham9e0702f2015-09-02 14:45:39 -07002024 va.setRepeatMode(ValueAnimator.REVERSE);
2025 va.setRepeatCount(ValueAnimator.INFINITE);
2026 }
2027
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002028 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002029 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002030 va.addListener(new AnimatorListenerAdapter() {
2031 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002032 // We make sure to end only after a full period
Jon Miranda21266912016-12-19 14:12:05 -08002033 setInitialAnimationValues(true);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002034 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002035 }
2036 });
Adam Cohen19f37922012-03-21 11:59:11 -07002037 mShakeAnimators.put(child, this);
2038 va.start();
2039 }
2040
Sunny Goyal849c6a22018-08-08 16:33:46 -07002041 private void setAnimationProgress(float progress) {
2042 animationProgress = progress;
2043 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : animationProgress;
2044 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2045 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
2046 child.setTranslationX(x);
2047 child.setTranslationY(y);
2048 float s = animationProgress * finalScale + (1 - animationProgress) * initScale;
2049 child.setScaleX(s);
2050 child.setScaleY(s);
2051 }
2052
Adam Cohend024f982012-05-23 18:26:45 -07002053 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002054 if (a != null) {
2055 a.cancel();
2056 }
Adam Cohen19f37922012-03-21 11:59:11 -07002057 }
Adam Cohene7587d22012-05-24 18:50:02 -07002058
Adam Cohen091440a2015-03-18 14:16:05 -07002059 @Thunk void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002060 if (a != null) {
2061 a.cancel();
2062 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002063
Jon Miranda72dbd912017-01-04 14:03:07 -08002064 setInitialAnimationValues(true);
Sunny Goyalf0b6db72018-08-13 16:10:14 -07002065 a = new PropertyListBuilder()
2066 .scale(initScale)
2067 .translationX(initDeltaX)
2068 .translationY(initDeltaY)
2069 .build(child)
Sunny Goyal9e76f682017-02-13 12:13:43 -08002070 .setDuration(REORDER_ANIMATION_DURATION);
Sunny Goyalab770a12018-11-14 15:17:26 -08002071 Launcher.cast(mActivity).getDragController().addFirstFrameAnimationHelper(a);
Sunny Goyalf0b6db72018-08-13 16:10:14 -07002072 a.setInterpolator(DEACCEL_1_5);
Sunny Goyal5d2fc322015-07-06 22:52:49 -07002073 a.start();
Brandon Keely50e6e562012-05-08 16:28:49 -07002074 }
Adam Cohen19f37922012-03-21 11:59:11 -07002075 }
2076
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002077 private void completeAndClearReorderPreviewAnimations() {
2078 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002079 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002080 }
2081 mShakeAnimators.clear();
2082 }
2083
Adam Cohen482ed822012-03-02 14:15:13 -08002084 private void commitTempPlacement() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002085 mTmpOccupied.copyTo(mOccupied);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002086
Sunny Goyalab770a12018-11-14 15:17:26 -08002087 int screenId = Launcher.cast(mActivity).getWorkspace().getIdForScreen(this);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002088 int container = Favorites.CONTAINER_DESKTOP;
2089
Sunny Goyalc13403c2016-11-18 23:44:48 -08002090 if (mContainerType == HOTSEAT) {
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002091 screenId = -1;
2092 container = Favorites.CONTAINER_HOTSEAT;
2093 }
2094
Michael Jurkaa52570f2012-03-20 03:18:20 -07002095 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002096 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002097 View child = mShortcutsAndWidgets.getChildAt(i);
2098 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2099 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002100 // We do a null check here because the item info can be null in the case of the
2101 // AllApps button in the hotseat.
2102 if (info != null) {
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002103 final boolean requiresDbUpdate = (info.cellX != lp.tmpCellX
2104 || info.cellY != lp.tmpCellY || info.spanX != lp.cellHSpan
2105 || info.spanY != lp.cellVSpan);
2106
Adam Cohen2acce882012-03-28 19:03:19 -07002107 info.cellX = lp.cellX = lp.tmpCellX;
2108 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002109 info.spanX = lp.cellHSpan;
2110 info.spanY = lp.cellVSpan;
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002111
2112 if (requiresDbUpdate) {
Sunny Goyalab770a12018-11-14 15:17:26 -08002113 Launcher.cast(mActivity).getModelWriter().modifyItemInDatabase(info, container,
2114 screenId, info.cellX, info.cellY, info.spanX, info.spanY);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002115 }
Adam Cohen2acce882012-03-28 19:03:19 -07002116 }
Adam Cohen482ed822012-03-02 14:15:13 -08002117 }
2118 }
2119
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002120 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002121 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002122 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002123 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002124 lp.useTmpCoords = useTempCoords;
2125 }
2126 }
2127
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002128 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002129 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2130 int[] result = new int[2];
2131 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002132 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002133 resultSpan);
2134 if (result[0] >= 0 && result[1] >= 0) {
2135 copyCurrentStateToSolution(solution, false);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002136 solution.cellX = result[0];
2137 solution.cellY = result[1];
2138 solution.spanX = resultSpan[0];
2139 solution.spanY = resultSpan[1];
Adam Cohen482ed822012-03-02 14:15:13 -08002140 solution.isSolution = true;
2141 } else {
2142 solution.isSolution = false;
2143 }
2144 return solution;
2145 }
2146
Adam Cohen19f37922012-03-21 11:59:11 -07002147 /* This seems like it should be obvious and straight-forward, but when the direction vector
2148 needs to match with the notion of the dragView pushing other views, we have to employ
2149 a slightly more subtle notion of the direction vector. The question is what two points is
2150 the vector between? The center of the dragView and its desired destination? Not quite, as
2151 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2152 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2153 or right, which helps make pushing feel right.
2154 */
2155 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2156 int spanY, View dragView, int[] resultDirection) {
Adam Cohen65086992020-02-19 08:40:49 -08002157
2158 //TODO(adamcohen) b/151776141 use the items visual center for the direction vector
Adam Cohen19f37922012-03-21 11:59:11 -07002159 int[] targetDestination = new int[2];
2160
2161 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2162 Rect dragRect = new Rect();
2163 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2164 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2165
2166 Rect dropRegionRect = new Rect();
2167 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2168 dragView, dropRegionRect, mIntersectingViews);
2169
2170 int dropRegionSpanX = dropRegionRect.width();
2171 int dropRegionSpanY = dropRegionRect.height();
2172
2173 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2174 dropRegionRect.height(), dropRegionRect);
2175
2176 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2177 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2178
2179 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2180 deltaX = 0;
2181 }
2182 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2183 deltaY = 0;
2184 }
2185
2186 if (deltaX == 0 && deltaY == 0) {
2187 // No idea what to do, give a random direction.
2188 resultDirection[0] = 1;
2189 resultDirection[1] = 0;
2190 } else {
2191 computeDirectionVector(deltaX, deltaY, resultDirection);
2192 }
2193 }
2194
2195 // For a given cell and span, fetch the set of views intersecting the region.
2196 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2197 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2198 if (boundingRect != null) {
2199 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2200 }
2201 intersectingViews.clear();
2202 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2203 Rect r1 = new Rect();
2204 final int count = mShortcutsAndWidgets.getChildCount();
2205 for (int i = 0; i < count; i++) {
2206 View child = mShortcutsAndWidgets.getChildAt(i);
2207 if (child == dragView) continue;
2208 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2209 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2210 if (Rect.intersects(r0, r1)) {
2211 mIntersectingViews.add(child);
2212 if (boundingRect != null) {
2213 boundingRect.union(r1);
2214 }
2215 }
2216 }
2217 }
2218
2219 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2220 View dragView, int[] result) {
2221 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2222 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2223 mIntersectingViews);
2224 return !mIntersectingViews.isEmpty();
2225 }
2226
2227 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002228 completeAndClearReorderPreviewAnimations();
2229 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2230 final int count = mShortcutsAndWidgets.getChildCount();
2231 for (int i = 0; i < count; i++) {
2232 View child = mShortcutsAndWidgets.getChildAt(i);
2233 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2234 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2235 lp.tmpCellX = lp.cellX;
2236 lp.tmpCellY = lp.cellY;
2237 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2238 0, false, false);
2239 }
Adam Cohen19f37922012-03-21 11:59:11 -07002240 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002241 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002242 }
Adam Cohen19f37922012-03-21 11:59:11 -07002243 }
2244
Adam Cohenbebf0422012-04-11 18:06:28 -07002245 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2246 View dragView, int[] direction, boolean commit) {
2247 int[] pixelXY = new int[2];
2248 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2249
2250 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002251 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002252 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2253
2254 setUseTempCoords(true);
2255 if (swapSolution != null && swapSolution.isSolution) {
2256 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2257 // committing anything or animating anything as we just want to determine if a solution
2258 // exists
2259 copySolutionToTempState(swapSolution, dragView);
2260 setItemPlacementDirty(true);
2261 animateItemsToSolution(swapSolution, dragView, commit);
2262
2263 if (commit) {
2264 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002265 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002266 setItemPlacementDirty(false);
2267 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002268 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
Adam Cohen65086992020-02-19 08:40:49 -08002269 ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002270 }
2271 mShortcutsAndWidgets.requestLayout();
2272 }
2273 return swapSolution.isSolution;
2274 }
2275
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002276 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002277 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002278 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002279 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002280
2281 if (resultSpan == null) {
2282 resultSpan = new int[2];
2283 }
2284
Adam Cohen19f37922012-03-21 11:59:11 -07002285 // When we are checking drop validity or actually dropping, we don't recompute the
2286 // direction vector, since we want the solution to match the preview, and it's possible
2287 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002288 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2289 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002290 mDirectionVector[0] = mPreviousReorderDirection[0];
2291 mDirectionVector[1] = mPreviousReorderDirection[1];
2292 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002293 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2294 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2295 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002296 }
2297 } else {
2298 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2299 mPreviousReorderDirection[0] = mDirectionVector[0];
2300 mPreviousReorderDirection[1] = mDirectionVector[1];
2301 }
2302
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002303 // Find a solution involving pushing / displacing any items in the way
2304 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002305 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2306
2307 // We attempt the approach which doesn't shuffle views at all
2308 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2309 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2310
2311 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002312
2313 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2314 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002315 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2316 finalSolution = swapSolution;
2317 } else if (noShuffleSolution.isSolution) {
2318 finalSolution = noShuffleSolution;
2319 }
2320
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002321 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002322 if (finalSolution != null) {
Adam Cohen65086992020-02-19 08:40:49 -08002323 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
Adam Cohenfe692872013-12-11 14:47:23 -08002324 ReorderPreviewAnimation.MODE_HINT);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002325 result[0] = finalSolution.cellX;
2326 result[1] = finalSolution.cellY;
2327 resultSpan[0] = finalSolution.spanX;
2328 resultSpan[1] = finalSolution.spanY;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002329 } else {
2330 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2331 }
2332 return result;
2333 }
2334
Adam Cohen482ed822012-03-02 14:15:13 -08002335 boolean foundSolution = true;
2336 if (!DESTRUCTIVE_REORDER) {
2337 setUseTempCoords(true);
2338 }
2339
2340 if (finalSolution != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002341 result[0] = finalSolution.cellX;
2342 result[1] = finalSolution.cellY;
2343 resultSpan[0] = finalSolution.spanX;
2344 resultSpan[1] = finalSolution.spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002345
2346 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2347 // committing anything or animating anything as we just want to determine if a solution
2348 // exists
2349 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2350 if (!DESTRUCTIVE_REORDER) {
2351 copySolutionToTempState(finalSolution, dragView);
2352 }
2353 setItemPlacementDirty(true);
2354 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2355
Adam Cohen19f37922012-03-21 11:59:11 -07002356 if (!DESTRUCTIVE_REORDER &&
2357 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002358 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002359 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002360 setItemPlacementDirty(false);
2361 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002362 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
Adam Cohen65086992020-02-19 08:40:49 -08002363 ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002364 }
2365 }
2366 } else {
2367 foundSolution = false;
2368 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2369 }
2370
2371 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2372 setUseTempCoords(false);
2373 }
Adam Cohen482ed822012-03-02 14:15:13 -08002374
Michael Jurkaa52570f2012-03-20 03:18:20 -07002375 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002376 return result;
2377 }
2378
Adam Cohen19f37922012-03-21 11:59:11 -07002379 void setItemPlacementDirty(boolean dirty) {
2380 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002381 }
Adam Cohen19f37922012-03-21 11:59:11 -07002382 boolean isItemPlacementDirty() {
2383 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002384 }
2385
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002386 private static class ItemConfiguration extends CellAndSpan {
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07002387 final ArrayMap<View, CellAndSpan> map = new ArrayMap<>();
2388 private final ArrayMap<View, CellAndSpan> savedMap = new ArrayMap<>();
2389 final ArrayList<View> sortedViews = new ArrayList<>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002390 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002391 boolean isSolution = false;
Adam Cohen482ed822012-03-02 14:15:13 -08002392
Adam Cohenf3900c22012-11-16 18:28:11 -08002393 void save() {
2394 // Copy current state into savedMap
2395 for (View v: map.keySet()) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002396 savedMap.get(v).copyFrom(map.get(v));
Adam Cohenf3900c22012-11-16 18:28:11 -08002397 }
2398 }
2399
2400 void restore() {
2401 // Restore current state from savedMap
2402 for (View v: savedMap.keySet()) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002403 map.get(v).copyFrom(savedMap.get(v));
Adam Cohenf3900c22012-11-16 18:28:11 -08002404 }
2405 }
2406
2407 void add(View v, CellAndSpan cs) {
2408 map.put(v, cs);
2409 savedMap.put(v, new CellAndSpan());
2410 sortedViews.add(v);
2411 }
2412
Adam Cohen482ed822012-03-02 14:15:13 -08002413 int area() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002414 return spanX * spanY;
Adam Cohenf3900c22012-11-16 18:28:11 -08002415 }
2416
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002417 void getBoundingRectForViews(ArrayList<View> views, Rect outRect) {
2418 boolean first = true;
2419 for (View v: views) {
2420 CellAndSpan c = map.get(v);
2421 if (first) {
2422 outRect.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2423 first = false;
2424 } else {
2425 outRect.union(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2426 }
2427 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002428 }
Adam Cohen482ed822012-03-02 14:15:13 -08002429 }
2430
Adam Cohendf035382011-04-11 17:22:04 -07002431 /**
Adam Cohendf035382011-04-11 17:22:04 -07002432 * Find a starting cell position that will fit the given bounds nearest the requested
2433 * cell location. Uses Euclidean distance to score multiple vacant areas.
2434 *
2435 * @param pixelX The X location at which you want to search for a vacant area.
2436 * @param pixelY The Y location at which you want to search for a vacant area.
2437 * @param spanX Horizontal span of the object.
2438 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07002439 * @param result Previously returned value to possibly recycle.
2440 * @return The X, Y cell of a vacant area that can contain this object,
2441 * nearest the requested location.
2442 */
Adam Cohenf9c184a2016-01-15 16:47:43 -08002443 public int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002444 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002445 }
2446
Michael Jurka0280c3b2010-09-17 15:00:07 -07002447 boolean existsEmptyCell() {
2448 return findCellForSpan(null, 1, 1);
2449 }
2450
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002451 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002452 * Finds the upper-left coordinate of the first rectangle in the grid that can
2453 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2454 * then this method will only return coordinates for rectangles that contain the cell
2455 * (intersectX, intersectY)
2456 *
2457 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2458 * can be found.
2459 * @param spanX The horizontal span of the cell we want to find.
2460 * @param spanY The vertical span of the cell we want to find.
2461 *
2462 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002463 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002464 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002465 if (cellXY == null) {
2466 cellXY = new int[2];
Michael Jurka0280c3b2010-09-17 15:00:07 -07002467 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002468 return mOccupied.findVacantCell(cellXY, spanX, spanY);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002469 }
2470
2471 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002472 * A drag event has begun over this layout.
2473 * It may have begun over this layout (in which case onDragChild is called first),
2474 * or it may have begun on another layout.
2475 */
2476 void onDragEnter() {
Winson Chungc07918d2011-07-01 15:35:26 -07002477 mDragging = true;
2478 }
2479
2480 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002481 * Called when drag has left this CellLayout or has been completed (successfully or not)
2482 */
2483 void onDragExit() {
Joe Onorato4be866d2010-10-10 11:26:02 -07002484 // This can actually be called when we aren't in a drag, e.g. when adding a new
2485 // item to this layout via the customize drawer.
2486 // Guard against that case.
2487 if (mDragging) {
2488 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002489 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002490
2491 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002492 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002493 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2494 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002495 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002496 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002497 }
2498
2499 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002500 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002501 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002502 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002503 *
2504 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002505 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002506 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002507 if (child != null) {
2508 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002509 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002510 child.requestLayout();
Tony Wickham1cdb6d02015-09-17 11:08:27 -07002511 markCellsAsOccupiedForView(child);
Romain Guyd94533d2009-08-17 10:01:15 -07002512 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002513 }
2514
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002515 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002516 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002517 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002518 * @param cellX X coordinate of upper left corner expressed as a cell position
2519 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002520 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002521 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002522 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002523 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002524 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002525 final int cellWidth = mCellWidth;
2526 final int cellHeight = mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -07002527
Winson Chung4b825dcd2011-06-19 12:41:22 -07002528 final int hStartPadding = getPaddingLeft();
2529 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002530
Sunny Goyalaa8a8712016-11-20 15:26:01 +05302531 int width = cellHSpan * cellWidth;
2532 int height = cellVSpan * cellHeight;
2533 int x = hStartPadding + cellX * cellWidth;
2534 int y = vStartPadding + cellY * cellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -07002535
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002536 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002537 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002538
Adam Cohend4844c32011-02-18 19:25:06 -08002539 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002540 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002541 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002542 mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002543 }
2544
Adam Cohend4844c32011-02-18 19:25:06 -08002545 public void markCellsAsUnoccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002546 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002547 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002548 mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002549 }
2550
Adam Cohen2801caf2011-05-13 20:57:39 -07002551 public int getDesiredWidth() {
Sunny Goyalaa8a8712016-11-20 15:26:01 +05302552 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth);
Adam Cohen2801caf2011-05-13 20:57:39 -07002553 }
2554
2555 public int getDesiredHeight() {
Sunny Goyalaa8a8712016-11-20 15:26:01 +05302556 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight);
Adam Cohen2801caf2011-05-13 20:57:39 -07002557 }
2558
Michael Jurka66d72172011-04-12 16:29:25 -07002559 public boolean isOccupied(int x, int y) {
2560 if (x < mCountX && y < mCountY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002561 return mOccupied.cells[x][y];
Michael Jurka66d72172011-04-12 16:29:25 -07002562 } else {
2563 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2564 }
2565 }
2566
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002567 @Override
2568 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2569 return new CellLayout.LayoutParams(getContext(), attrs);
2570 }
2571
2572 @Override
2573 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2574 return p instanceof CellLayout.LayoutParams;
2575 }
2576
2577 @Override
2578 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2579 return new CellLayout.LayoutParams(p);
2580 }
2581
2582 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2583 /**
2584 * Horizontal location of the item in the grid.
2585 */
2586 @ViewDebug.ExportedProperty
2587 public int cellX;
2588
2589 /**
2590 * Vertical location of the item in the grid.
2591 */
2592 @ViewDebug.ExportedProperty
2593 public int cellY;
2594
2595 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002596 * Temporary horizontal location of the item in the grid during reorder
2597 */
2598 public int tmpCellX;
2599
2600 /**
2601 * Temporary vertical location of the item in the grid during reorder
2602 */
2603 public int tmpCellY;
2604
2605 /**
2606 * Indicates that the temporary coordinates should be used to layout the items
2607 */
2608 public boolean useTmpCoords;
2609
2610 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002611 * Number of cells spanned horizontally by the item.
2612 */
2613 @ViewDebug.ExportedProperty
2614 public int cellHSpan;
2615
2616 /**
2617 * Number of cells spanned vertically by the item.
2618 */
2619 @ViewDebug.ExportedProperty
2620 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002621
Adam Cohen1b607ed2011-03-03 17:26:50 -08002622 /**
2623 * Indicates whether the item will set its x, y, width and height parameters freely,
2624 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2625 */
Adam Cohend4844c32011-02-18 19:25:06 -08002626 public boolean isLockedToGrid = true;
2627
Adam Cohen482ed822012-03-02 14:15:13 -08002628 /**
2629 * Indicates whether this item can be reordered. Always true except in the case of the
Sunny Goyalda4fe1a2016-05-26 16:05:17 -07002630 * the AllApps button and QSB place holder.
Adam Cohen482ed822012-03-02 14:15:13 -08002631 */
2632 public boolean canReorder = true;
2633
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002634 // X coordinate of the view in the layout.
2635 @ViewDebug.ExportedProperty
Vadim Tryshevfedca432015-08-19 17:55:02 -07002636 public int x;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002637 // Y coordinate of the view in the layout.
2638 @ViewDebug.ExportedProperty
Vadim Tryshevfedca432015-08-19 17:55:02 -07002639 public int y;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002640
Romain Guy84f296c2009-11-04 15:00:44 -08002641 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002642
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002643 public LayoutParams(Context c, AttributeSet attrs) {
2644 super(c, attrs);
2645 cellHSpan = 1;
2646 cellVSpan = 1;
2647 }
2648
2649 public LayoutParams(ViewGroup.LayoutParams source) {
2650 super(source);
2651 cellHSpan = 1;
2652 cellVSpan = 1;
2653 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002654
2655 public LayoutParams(LayoutParams source) {
2656 super(source);
2657 this.cellX = source.cellX;
2658 this.cellY = source.cellY;
2659 this.cellHSpan = source.cellHSpan;
2660 this.cellVSpan = source.cellVSpan;
2661 }
2662
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002663 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002664 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002665 this.cellX = cellX;
2666 this.cellY = cellY;
2667 this.cellHSpan = cellHSpan;
2668 this.cellVSpan = cellVSpan;
2669 }
2670
Sunny Goyalaa8a8712016-11-20 15:26:01 +05302671 public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount) {
Jon Miranda7ae64ff2016-11-21 16:18:46 -08002672 setup(cellWidth, cellHeight, invertHorizontally, colCount, 1.0f, 1.0f);
2673 }
2674
2675 /**
2676 * Use this method, as opposed to {@link #setup(int, int, boolean, int)}, if the view needs
2677 * to be scaled.
2678 *
2679 * ie. In multi-window mode, we setup widgets so that they are measured and laid out
2680 * using their full/invariant device profile sizes.
2681 */
2682 public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
2683 float cellScaleX, float cellScaleY) {
Adam Cohend4844c32011-02-18 19:25:06 -08002684 if (isLockedToGrid) {
2685 final int myCellHSpan = cellHSpan;
2686 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002687 int myCellX = useTmpCoords ? tmpCellX : cellX;
2688 int myCellY = useTmpCoords ? tmpCellY : cellY;
2689
2690 if (invertHorizontally) {
2691 myCellX = colCount - myCellX - cellHSpan;
2692 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002693
Jon Miranda7ae64ff2016-11-21 16:18:46 -08002694 width = (int) (myCellHSpan * cellWidth / cellScaleX - leftMargin - rightMargin);
2695 height = (int) (myCellVSpan * cellHeight / cellScaleY - topMargin - bottomMargin);
Sunny Goyalaa8a8712016-11-20 15:26:01 +05302696 x = (myCellX * cellWidth + leftMargin);
2697 y = (myCellY * cellHeight + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002698 }
2699 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002700
Sunny Goyal11a50742019-08-07 09:24:26 -07002701 /**
2702 * Sets the position to the provided point
2703 */
2704 public void setXY(Point point) {
2705 cellX = point.x;
2706 cellY = point.y;
2707 }
2708
Winson Chungaafa03c2010-06-11 17:34:16 -07002709 public String toString() {
2710 return "(" + this.cellX + ", " + this.cellY + ")";
2711 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002712 }
2713
Michael Jurka0280c3b2010-09-17 15:00:07 -07002714 // This class stores info for two purposes:
2715 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2716 // its spanX, spanY, and the screen it is on
2717 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2718 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2719 // the CellLayout that was long clicked
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002720 public static final class CellInfo extends CellAndSpan {
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07002721 public final View cell;
Sunny Goyalefb7e842018-10-04 15:11:00 -07002722 final int screenId;
2723 final int container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002724
Sunny Goyal83a8f042015-05-19 12:52:12 -07002725 public CellInfo(View v, ItemInfo info) {
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002726 cellX = info.cellX;
2727 cellY = info.cellY;
2728 spanX = info.spanX;
2729 spanY = info.spanY;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002730 cell = v;
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002731 screenId = info.screenId;
2732 container = info.container;
2733 }
2734
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002735 @Override
2736 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002737 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2738 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002739 }
2740 }
Michael Jurkad771c962011-08-09 15:00:48 -07002741
Tony Wickham86930612015-09-09 13:50:40 -07002742 /**
Samuel Fufa1e2d0042019-11-18 17:12:46 -08002743 * A Delegated cell Drawing for drawing on CellLayout
2744 */
2745 public abstract static class DelegatedCellDrawing {
2746 public int mDelegateCellX;
2747 public int mDelegateCellY;
2748
2749 /**
2750 * Draw under CellLayout
2751 */
2752 public abstract void drawUnderItem(Canvas canvas);
2753
2754 /**
2755 * Draw over CellLayout
2756 */
2757 public abstract void drawOverItem(Canvas canvas);
2758 }
2759
2760 /**
Tony Wickham86930612015-09-09 13:50:40 -07002761 * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing
2762 * if necessary).
2763 */
2764 public boolean hasReorderSolution(ItemInfo itemInfo) {
2765 int[] cellPoint = new int[2];
2766 // Check for a solution starting at every cell.
2767 for (int cellX = 0; cellX < getCountX(); cellX++) {
2768 for (int cellY = 0; cellY < getCountY(); cellY++) {
2769 cellToPoint(cellX, cellY, cellPoint);
2770 if (findReorderSolution(cellPoint[0], cellPoint[1], itemInfo.minSpanX,
2771 itemInfo.minSpanY, itemInfo.spanX, itemInfo.spanY, mDirectionVector, null,
2772 true, new ItemConfiguration()).isSolution) {
2773 return true;
2774 }
2775 }
2776 }
2777 return false;
2778 }
2779
Samuel Fufaa4211432020-02-25 18:47:54 -08002780 /**
2781 * Finds solution to accept hotseat migration to cell layout. commits solution if commitConfig
2782 */
2783 public boolean makeSpaceForHotseatMigration(boolean commitConfig) {
Samuel Fufaa4211432020-02-25 18:47:54 -08002784 int[] cellPoint = new int[2];
2785 int[] directionVector = new int[]{0, -1};
2786 cellToPoint(0, mCountY, cellPoint);
2787 ItemConfiguration configuration = new ItemConfiguration();
2788 if (findReorderSolution(cellPoint[0], cellPoint[1], mCountX, 1, mCountX, 1,
2789 directionVector, null, false, configuration).isSolution) {
2790 if (commitConfig) {
2791 copySolutionToTempState(configuration, null);
2792 commitTempPlacement();
Samuel Fufa82bbdac2020-03-09 18:24:47 -07002793 // undo marking cells occupied since there is actually nothing being placed yet.
2794 mOccupied.markCells(0, mCountY - 1, mCountX, 1, false);
Samuel Fufaa4211432020-02-25 18:47:54 -08002795 }
2796 return true;
2797 }
2798 return false;
2799 }
2800
Samuel Fufa82bbdac2020-03-09 18:24:47 -07002801 /**
2802 * returns a copy of cell layout's grid occupancy
2803 */
2804 public GridOccupancy cloneGridOccupancy() {
2805 GridOccupancy occupancy = new GridOccupancy(mCountX, mCountY);
2806 mOccupied.copyTo(occupancy);
2807 return occupancy;
2808 }
2809
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002810 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002811 return mOccupied.isRegionVacant(x, y, spanX, spanY);
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002812 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002813}