blob: 4087df1b105c2bbfc4ebec8a0dab391f9a71c92e [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
Joe Onorato4be866d2010-10-10 11:26:02 -070019import android.animation.Animator;
Michael Jurka629758f2012-06-14 16:18:21 -070020import android.animation.AnimatorListenerAdapter;
Chet Haase00397b12010-10-07 11:13:10 -070021import android.animation.TimeInterpolator;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070022import android.animation.ValueAnimator;
23import android.animation.ValueAnimator.AnimatorUpdateListener;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080024import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040025import android.content.res.Resources;
Sunny Goyalc13403c2016-11-18 23:44:48 -080026import android.content.res.TypedArray;
Joe Onorato4be866d2010-10-10 11:26:02 -070027import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070028import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080029import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070030import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070031import android.graphics.Point;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080032import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080033import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070034import android.graphics.drawable.Drawable;
Adam Cohen1462de32012-07-24 22:34:36 -070035import android.os.Parcelable;
Sunny Goyalc13403c2016-11-18 23:44:48 -080036import android.support.annotation.IntDef;
Adam Cohenc9735cf2015-01-23 16:11:55 -080037import android.support.v4.view.ViewCompat;
Rajeev Kumar9962dbe2017-06-12 12:16:20 -070038import android.util.ArrayMap;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080039import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070040import android.util.Log;
Adam Cohen1462de32012-07-24 22:34:36 -070041import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080042import android.view.MotionEvent;
43import android.view.View;
44import android.view.ViewDebug;
45import android.view.ViewGroup;
Adam Cohenc9735cf2015-01-23 16:11:55 -080046import android.view.accessibility.AccessibilityEvent;
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -070047
Sunny Goyal4b6eb262015-05-14 19:24:40 -070048import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
Sunny Goyalaa8ef112015-06-12 20:04:41 -070049import com.android.launcher3.LauncherSettings.Favorites;
Sunny Goyale9b651e2015-04-24 11:44:51 -070050import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
51import com.android.launcher3.accessibility.FolderAccessibilityHelper;
52import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -070053import com.android.launcher3.anim.Interpolators;
Sunny Goyal9e76f682017-02-13 12:13:43 -080054import com.android.launcher3.anim.PropertyListBuilder;
Sunny Goyal3d706ad2017-03-06 16:56:39 -080055import com.android.launcher3.config.FeatureFlags;
Jon Mirandaa0233f72017-06-22 18:34:45 -070056import com.android.launcher3.folder.PreviewBackground;
Sunny Goyal06e21a22016-08-11 16:02:02 -070057import com.android.launcher3.graphics.DragPreviewProvider;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -070058import com.android.launcher3.util.CellAndSpan;
59import com.android.launcher3.util.GridOccupancy;
Sunny Goyale2fd14b2015-08-27 17:45:46 -070060import com.android.launcher3.util.ParcelableSparseArray;
Sunny Goyal9b29ca52017-02-17 10:39:44 -080061import com.android.launcher3.util.Themes;
Adam Cohen091440a2015-03-18 14:16:05 -070062import com.android.launcher3.util.Thunk;
Sunny Goyal29947f02017-12-18 13:49:44 -080063import com.android.launcher3.widget.LauncherAppWidgetHostView;
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -070064
Sunny Goyalc13403c2016-11-18 23:44:48 -080065import java.lang.annotation.Retention;
66import java.lang.annotation.RetentionPolicy;
Adam Cohen69ce2e52011-07-03 19:25:21 -070067import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070068import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080069import java.util.Collections;
70import java.util.Comparator;
Adam Cohend41fbf52012-02-16 23:53:59 -080071import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070072
Sunny Goyal4b6eb262015-05-14 19:24:40 -070073public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
Sunny Goyale9b651e2015-04-24 11:44:51 -070074 public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
75 public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
76
Tony Wickhama0628cc2015-10-14 15:23:04 -070077 private static final String TAG = "CellLayout";
78 private static final boolean LOGD = false;
Winson Chungaafa03c2010-06-11 17:34:16 -070079
Rajeev Kumar9962dbe2017-06-12 12:16:20 -070080 private final Launcher mLauncher;
Sunny Goyal4ffec482016-02-09 11:28:52 -080081 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070082 @Thunk int mCellWidth;
Sunny Goyal4ffec482016-02-09 11:28:52 -080083 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070084 @Thunk int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070085 private int mFixedCellWidth;
86 private int mFixedCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070087
Sunny Goyal4ffec482016-02-09 11:28:52 -080088 @ViewDebug.ExportedProperty(category = "launcher")
Sunny Goyalff4ba2d2016-04-02 14:12:34 -070089 private int mCountX;
Sunny Goyal4ffec482016-02-09 11:28:52 -080090 @ViewDebug.ExportedProperty(category = "launcher")
Sunny Goyalff4ba2d2016-04-02 14:12:34 -070091 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080092
Adam Cohen917e3882013-10-31 15:03:35 -070093 private boolean mDropPending = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080094
Patrick Dubroyde7658b2010-09-27 11:15:43 -070095 // These are temporary variables to prevent having to allocate a new object just to
96 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Adam Cohen091440a2015-03-18 14:16:05 -070097 @Thunk final int[] mTmpPoint = new int[2];
Sunny Goyal2805e632015-05-20 15:35:32 -070098 @Thunk final int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070099
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700100 private GridOccupancy mOccupied;
101 private GridOccupancy mTmpOccupied;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800102
Michael Jurkadee05892010-07-27 10:01:56 -0700103 private OnTouchListener mInterceptTouchListener;
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700104 private final StylusEventHelper mStylusEventHelper;
Michael Jurkadee05892010-07-27 10:01:56 -0700105
Jon Mirandaa0233f72017-06-22 18:34:45 -0700106 private final ArrayList<PreviewBackground> mFolderBackgrounds = new ArrayList<>();
107 final PreviewBackground mFolderLeaveBehind = new PreviewBackground();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700108
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800109 private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active };
Sunny Goyale15e2a82017-12-15 13:05:42 -0800110 private static final int[] BACKGROUND_STATE_DEFAULT = EMPTY_STATE_SET;
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800111 private final Drawable mBackground;
Sunny Goyal2805e632015-05-20 15:35:32 -0700112
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700113 // These values allow a fixed measurement to be set on the CellLayout.
114 private int mFixedWidth = -1;
115 private int mFixedHeight = -1;
116
Michael Jurka33945b22010-12-21 18:19:38 -0800117 // If we're actively dragging something over this screen, mIsDragOverlapping is true
118 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700119
Winson Chung150fbab2010-09-29 17:14:26 -0700120 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700121 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700122 @Thunk final Rect[] mDragOutlines = new Rect[4];
123 @Thunk final float[] mDragOutlineAlphas = new float[mDragOutlines.length];
124 private final InterruptibleInOutAnimator[] mDragOutlineAnims =
Joe Onorato4be866d2010-10-10 11:26:02 -0700125 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700126
127 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700128 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700129 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700130
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700131 private final ClickShadowView mTouchFeedbackView;
Patrick Dubroy96864c32011-03-10 17:17:23 -0800132
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700133 @Thunk final ArrayMap<LayoutParams, Animator> mReorderAnimators = new ArrayMap<>();
134 @Thunk final ArrayMap<View, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
Adam Cohen19f37922012-03-21 11:59:11 -0700135
136 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700137
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700138 // When a drag operation is in progress, holds the nearest cell to the touch point
139 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800140
Joe Onorato4be866d2010-10-10 11:26:02 -0700141 private boolean mDragging = false;
142
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700143 private final TimeInterpolator mEaseOutInterpolator;
144 private final ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700145
Sunny Goyalc13403c2016-11-18 23:44:48 -0800146 @Retention(RetentionPolicy.SOURCE)
147 @IntDef({WORKSPACE, HOTSEAT, FOLDER})
148 public @interface ContainerType{}
149 public static final int WORKSPACE = 0;
150 public static final int HOTSEAT = 1;
151 public static final int FOLDER = 2;
152
153 @ContainerType private final int mContainerType;
154
Jon Mirandab28c4fc2017-06-20 10:58:36 -0700155 private final float mChildScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800156
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800157 public static final int MODE_SHOW_REORDER_HINT = 0;
158 public static final int MODE_DRAG_OVER = 1;
159 public static final int MODE_ON_DROP = 2;
160 public static final int MODE_ON_DROP_EXTERNAL = 3;
161 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700162 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800163 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
164
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800165 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700166 private static final int REORDER_ANIMATION_DURATION = 150;
Sunny Goyalc13403c2016-11-18 23:44:48 -0800167 @Thunk final float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700168
Rajeev Kumar9962dbe2017-06-12 12:16:20 -0700169 private final ArrayList<View> mIntersectingViews = new ArrayList<>();
170 private final Rect mOccupiedRect = new Rect();
171 private final int[] mDirectionVector = new int[2];
172 final int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700173 private static final int INVALID_DIRECTION = -100;
Adam Cohen482ed822012-03-02 14:15:13 -0800174
Sunny Goyal2805e632015-05-20 15:35:32 -0700175 private final Rect mTempRect = new Rect();
Winson Chung3a6e7f32013-10-09 15:50:52 -0700176
Michael Jurkaca993832012-06-29 15:17:04 -0700177 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700178
Adam Cohenc9735cf2015-01-23 16:11:55 -0800179 // Related to accessible drag and drop
Sunny Goyale9b651e2015-04-24 11:44:51 -0700180 private DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800181 private boolean mUseTouchHelper = false;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800182
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800183 public CellLayout(Context context) {
184 this(context, null);
185 }
186
187 public CellLayout(Context context, AttributeSet attrs) {
188 this(context, attrs, 0);
189 }
190
191 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
192 super(context, attrs, defStyle);
Sunny Goyalc13403c2016-11-18 23:44:48 -0800193 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
194 mContainerType = a.getInteger(R.styleable.CellLayout_containerType, WORKSPACE);
195 a.recycle();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700196
197 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
198 // the user where a dragged item will land when dropped.
199 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800200 setClipToPadding(false);
Tony2fd02082016-10-07 12:50:01 -0700201 mLauncher = Launcher.getLauncher(context);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700202
Adam Cohen2e6da152015-05-06 11:42:25 -0700203 DeviceProfile grid = mLauncher.getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800204
Winson Chung11a1a532013-09-13 11:14:45 -0700205 mCellWidth = mCellHeight = -1;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800206 mFixedCellWidth = mFixedCellHeight = -1;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700207
208 mCountX = grid.inv.numColumns;
209 mCountY = grid.inv.numRows;
210 mOccupied = new GridOccupancy(mCountX, mCountY);
211 mTmpOccupied = new GridOccupancy(mCountX, mCountY);
212
Adam Cohen5b53f292012-03-29 14:30:35 -0700213 mPreviousReorderDirection[0] = INVALID_DIRECTION;
214 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800215
Adam Cohenefca0272016-02-24 19:19:06 -0800216 mFolderLeaveBehind.delegateCellX = -1;
217 mFolderLeaveBehind.delegateCellY = -1;
218
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800219 setAlwaysDrawnWithCacheEnabled(false);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700220 final Resources res = getResources();
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700221
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800222 mBackground = res.getDrawable(R.drawable.bg_celllayout);
Sunny Goyal2805e632015-05-20 15:35:32 -0700223 mBackground.setCallback(this);
Sunny Goyalaeb16432017-10-16 11:46:41 -0700224 mBackground.setAlpha(0);
Michael Jurka33945b22010-12-21 18:19:38 -0800225
Sunny Goyalc13403c2016-11-18 23:44:48 -0800226 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700227
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700228 // Initialize the data structures used for the drag visualization.
Sunny Goyal5bc6b6f2017-10-26 15:36:10 -0700229 mEaseOutInterpolator = Interpolators.DEACCEL_2_5; // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700230 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700231 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800232 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700233 }
Mario Bertschler54ba6012017-06-08 10:53:53 -0700234 mDragOutlinePaint.setColor(Themes.getAttrColor(context, R.attr.workspaceTextColor));
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700235
236 // When dragging things around the home screens, we show a green outline of
237 // where the item will land. The outlines gradually fade out, leaving a trail
238 // behind the drag path.
239 // Set up all the animations that are used to implement this fading.
240 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700241 final float fromAlphaValue = 0;
242 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700243
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700244 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700245
246 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700247 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100248 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700249 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700250 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700251 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700252 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700253 final Bitmap outline = (Bitmap)anim.getTag();
254
255 // If an animation is started and then stopped very quickly, we can still
256 // get spurious updates we've cleared the tag. Guard against this.
257 if (outline == null) {
Tony Wickhama0628cc2015-10-14 15:23:04 -0700258 if (LOGD) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700259 Object val = animation.getAnimatedValue();
260 Log.d(TAG, "anim " + thisIndex + " update: " + val +
261 ", isStopped " + anim.isStopped());
262 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700263 // Try to prevent it from continuing to run
264 animation.cancel();
265 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700266 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800267 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700268 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700269 }
270 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700271 // The animation holds a reference to the drag outline bitmap as long is it's
272 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700273 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700274 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700275 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700276 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700277 anim.setTag(null);
278 }
279 }
280 });
281 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700282 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700283
Sunny Goyalc13403c2016-11-18 23:44:48 -0800284 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType);
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530285 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700286
Mady Mellorbb835202015-07-15 16:34:34 -0700287 mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
Mady Melloref044dd2015-06-02 15:35:07 -0700288
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700289 mTouchFeedbackView = new ClickShadowView(context);
290 addView(mTouchFeedbackView);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700291 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700292 }
293
Sunny Goyale9b651e2015-04-24 11:44:51 -0700294 public void enableAccessibleDrag(boolean enable, int dragType) {
Adam Cohenc9735cf2015-01-23 16:11:55 -0800295 mUseTouchHelper = enable;
296 if (!enable) {
297 ViewCompat.setAccessibilityDelegate(this, null);
298 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
299 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
300 setOnClickListener(mLauncher);
301 } else {
Sunny Goyale9b651e2015-04-24 11:44:51 -0700302 if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
303 !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
304 mTouchHelper = new WorkspaceAccessibilityHelper(this);
305 } else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
306 !(mTouchHelper instanceof FolderAccessibilityHelper)) {
307 mTouchHelper = new FolderAccessibilityHelper(this);
308 }
Adam Cohenc9735cf2015-01-23 16:11:55 -0800309 ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
310 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
311 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
312 setOnClickListener(mTouchHelper);
313 }
314
315 // Invalidate the accessibility hierarchy
316 if (getParent() != null) {
317 getParent().notifySubtreeAccessibilityStateChanged(
318 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
319 }
320 }
321
322 @Override
323 public boolean dispatchHoverEvent(MotionEvent event) {
324 // Always attempt to dispatch hover events to accessibility first.
325 if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
326 return true;
327 }
328 return super.dispatchHoverEvent(event);
329 }
330
331 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800332 public boolean onInterceptTouchEvent(MotionEvent ev) {
333 if (mUseTouchHelper ||
334 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
335 return true;
336 }
337 return false;
338 }
339
Mady Melloref044dd2015-06-02 15:35:07 -0700340 @Override
341 public boolean onTouchEvent(MotionEvent ev) {
342 boolean handled = super.onTouchEvent(ev);
343 // Stylus button press on a home screen should not switch between overview mode and
344 // the home screen mode, however, once in overview mode stylus button press should be
345 // enabled to allow rearranging the different home screens. So check what mode
346 // the workspace is in, and only perform stylus button presses while in overview mode.
Sunny Goyalf9403d92017-10-18 10:55:56 -0700347 if (mLauncher.isInState(LauncherState.OVERVIEW)
Mady Mellorbb835202015-07-15 16:34:34 -0700348 && mStylusEventHelper.onMotionEvent(ev)) {
Mady Melloref044dd2015-06-02 15:35:07 -0700349 return true;
350 }
351 return handled;
352 }
353
Chris Craik01f2d7f2013-10-01 14:41:56 -0700354 public void enableHardwareLayer(boolean hasLayer) {
355 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700356 }
357
Winson Chung5f8afe62013-08-12 16:19:28 -0700358 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700359 mFixedCellWidth = mCellWidth = width;
360 mFixedCellHeight = mCellHeight = height;
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530361 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
Winson Chung5f8afe62013-08-12 16:19:28 -0700362 }
363
Adam Cohen2801caf2011-05-13 20:57:39 -0700364 public void setGridSize(int x, int y) {
365 mCountX = x;
366 mCountY = y;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700367 mOccupied = new GridOccupancy(mCountX, mCountY);
368 mTmpOccupied = new GridOccupancy(mCountX, mCountY);
Adam Cohen7fbec102012-03-27 12:42:19 -0700369 mTempRectStack.clear();
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530370 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700371 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700372 }
373
Adam Cohen2374abf2013-04-16 14:56:57 -0700374 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
375 public void setInvertIfRtl(boolean invert) {
376 mShortcutsAndWidgets.setInvertIfRtl(invert);
377 }
378
Adam Cohen917e3882013-10-31 15:03:35 -0700379 public void setDropPending(boolean pending) {
380 mDropPending = pending;
381 }
382
383 public boolean isDropPending() {
384 return mDropPending;
385 }
386
Sunny Goyal4b6eb262015-05-14 19:24:40 -0700387 @Override
388 public void setPressedIcon(BubbleTextView icon, Bitmap background) {
Sunny Goyalea529082017-10-31 13:33:03 -0700389 mTouchFeedbackView.setPressedIcon(icon, background);
Patrick Dubroy96864c32011-03-10 17:17:23 -0800390 }
391
Adam Cohenc50438c2014-08-19 17:43:05 -0700392 void setIsDragOverlapping(boolean isDragOverlapping) {
393 if (mIsDragOverlapping != isDragOverlapping) {
394 mIsDragOverlapping = isDragOverlapping;
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800395 mBackground.setState(mIsDragOverlapping
396 ? BACKGROUND_STATE_ACTIVE : BACKGROUND_STATE_DEFAULT);
Adam Cohenc50438c2014-08-19 17:43:05 -0700397 invalidate();
398 }
399 }
400
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700401 @Override
402 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700403 ParcelableSparseArray jail = getJailedArray(container);
404 super.dispatchSaveInstanceState(jail);
405 container.put(R.id.cell_layout_jail_id, jail);
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700406 }
407
408 @Override
409 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700410 super.dispatchRestoreInstanceState(getJailedArray(container));
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700411 }
412
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700413 /**
414 * Wrap the SparseArray in another Parcelable so that the item ids do not conflict with our
415 * our internal resource ids
416 */
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700417 private ParcelableSparseArray getJailedArray(SparseArray<Parcelable> container) {
418 final Parcelable parcelable = container.get(R.id.cell_layout_jail_id);
419 return parcelable instanceof ParcelableSparseArray ?
420 (ParcelableSparseArray) parcelable : new ParcelableSparseArray();
421 }
422
Tony Wickham0f97b782015-12-02 17:55:07 -0800423 public boolean getIsDragOverlapping() {
424 return mIsDragOverlapping;
425 }
426
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700427 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700428 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700429 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
430 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
431 // When we're small, we are either drawn normally or in the "accepts drops" state (during
432 // a drag). However, we also drag the mini hover background *over* one of those two
433 // backgrounds
Sunny Goyalaeb16432017-10-16 11:46:41 -0700434 if (mBackground.getAlpha() > 0) {
Sunny Goyal2805e632015-05-20 15:35:32 -0700435 mBackground.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700436 }
Romain Guya6abce82009-11-10 02:54:41 -0800437
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700438 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700439 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700440 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700441 if (alpha > 0) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700442 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700443 paint.setAlpha((int)(alpha + .5f));
Sunny Goyal106bf642015-07-16 12:18:06 -0700444 canvas.drawBitmap(b, null, mDragOutlines[i], paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700445 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700446 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800447
Adam Cohen482ed822012-03-02 14:15:13 -0800448 if (DEBUG_VISUALIZE_OCCUPIED) {
449 int[] pt = new int[2];
450 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700451 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800452 for (int i = 0; i < mCountX; i++) {
453 for (int j = 0; j < mCountY; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700454 if (mOccupied.cells[i][j]) {
Adam Cohen482ed822012-03-02 14:15:13 -0800455 cellToPoint(i, j, pt);
456 canvas.save();
457 canvas.translate(pt[0], pt[1]);
458 cd.draw(canvas);
459 canvas.restore();
460 }
461 }
462 }
463 }
464
Adam Cohenefca0272016-02-24 19:19:06 -0800465 for (int i = 0; i < mFolderBackgrounds.size(); i++) {
Jon Mirandaa0233f72017-06-22 18:34:45 -0700466 PreviewBackground bg = mFolderBackgrounds.get(i);
Adam Cohenefca0272016-02-24 19:19:06 -0800467 cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
468 canvas.save();
469 canvas.translate(mTempLocation[0], mTempLocation[1]);
Sunny Goyal19b93b72017-02-19 20:21:37 -0800470 bg.drawBackground(canvas);
Adam Cohenf172b742016-03-30 19:28:34 -0700471 if (!bg.isClipping) {
Sunny Goyal19b93b72017-02-19 20:21:37 -0800472 bg.drawBackgroundStroke(canvas);
Adam Cohenf172b742016-03-30 19:28:34 -0700473 }
Adam Cohenefca0272016-02-24 19:19:06 -0800474 canvas.restore();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700475 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700476
Adam Cohenefca0272016-02-24 19:19:06 -0800477 if (mFolderLeaveBehind.delegateCellX >= 0 && mFolderLeaveBehind.delegateCellY >= 0) {
478 cellToPoint(mFolderLeaveBehind.delegateCellX,
479 mFolderLeaveBehind.delegateCellY, mTempLocation);
480 canvas.save();
481 canvas.translate(mTempLocation[0], mTempLocation[1]);
Sunny Goyal19b93b72017-02-19 20:21:37 -0800482 mFolderLeaveBehind.drawLeaveBehind(canvas);
Adam Cohenefca0272016-02-24 19:19:06 -0800483 canvas.restore();
Adam Cohenc51934b2011-07-26 21:07:43 -0700484 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700485 }
486
Adam Cohenefca0272016-02-24 19:19:06 -0800487 @Override
488 protected void dispatchDraw(Canvas canvas) {
489 super.dispatchDraw(canvas);
490
491 for (int i = 0; i < mFolderBackgrounds.size(); i++) {
Jon Mirandaa0233f72017-06-22 18:34:45 -0700492 PreviewBackground bg = mFolderBackgrounds.get(i);
Adam Cohenf172b742016-03-30 19:28:34 -0700493 if (bg.isClipping) {
494 cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
495 canvas.save();
496 canvas.translate(mTempLocation[0], mTempLocation[1]);
Sunny Goyal19b93b72017-02-19 20:21:37 -0800497 bg.drawBackgroundStroke(canvas);
Adam Cohenf172b742016-03-30 19:28:34 -0700498 canvas.restore();
499 }
Adam Cohenefca0272016-02-24 19:19:06 -0800500 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700501 }
502
Jon Mirandaa0233f72017-06-22 18:34:45 -0700503 public void addFolderBackground(PreviewBackground bg) {
Adam Cohenefca0272016-02-24 19:19:06 -0800504 mFolderBackgrounds.add(bg);
505 }
Jon Mirandaa0233f72017-06-22 18:34:45 -0700506 public void removeFolderBackground(PreviewBackground bg) {
Adam Cohenefca0272016-02-24 19:19:06 -0800507 mFolderBackgrounds.remove(bg);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700508 }
509
Adam Cohenc51934b2011-07-26 21:07:43 -0700510 public void setFolderLeaveBehindCell(int x, int y) {
Adam Cohenefca0272016-02-24 19:19:06 -0800511 View child = getChildAt(x, y);
Sunny Goyal368ae772017-05-24 13:19:15 -0700512 mFolderLeaveBehind.setup(mLauncher, null,
Adam Cohenefca0272016-02-24 19:19:06 -0800513 child.getMeasuredWidth(), child.getPaddingTop());
514
515 mFolderLeaveBehind.delegateCellX = x;
516 mFolderLeaveBehind.delegateCellY = y;
Adam Cohenc51934b2011-07-26 21:07:43 -0700517 invalidate();
518 }
519
520 public void clearFolderLeaveBehind() {
Adam Cohenefca0272016-02-24 19:19:06 -0800521 mFolderLeaveBehind.delegateCellX = -1;
522 mFolderLeaveBehind.delegateCellY = -1;
Adam Cohenc51934b2011-07-26 21:07:43 -0700523 invalidate();
524 }
525
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700526 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700527 public boolean shouldDelayChildPressedState() {
528 return false;
529 }
530
Adam Cohen1462de32012-07-24 22:34:36 -0700531 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700532 try {
533 dispatchRestoreInstanceState(states);
534 } catch (IllegalArgumentException ex) {
Sunny Goyal3d706ad2017-03-06 16:56:39 -0800535 if (FeatureFlags.IS_DOGFOOD_BUILD) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700536 throw ex;
537 }
538 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
539 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
540 }
Adam Cohen1462de32012-07-24 22:34:36 -0700541 }
542
Michael Jurkae6235dd2011-10-04 15:02:05 -0700543 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700544 public void cancelLongPress() {
545 super.cancelLongPress();
546
547 // Cancel long press for all children
548 final int count = getChildCount();
549 for (int i = 0; i < count; i++) {
550 final View child = getChildAt(i);
551 child.cancelLongPress();
552 }
553 }
554
Michael Jurkadee05892010-07-27 10:01:56 -0700555 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
556 mInterceptTouchListener = listener;
557 }
558
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800559 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700560 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800561 }
562
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800563 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700564 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800565 }
566
Sunny Goyalc13403c2016-11-18 23:44:48 -0800567 public boolean acceptsWidget() {
568 return mContainerType == WORKSPACE;
Sunny Goyale9b651e2015-04-24 11:44:51 -0700569 }
570
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800571 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700572 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700573 final LayoutParams lp = params;
574
Andrew Flynnde38e422012-05-08 11:22:15 -0700575 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800576 if (child instanceof BubbleTextView) {
577 BubbleTextView bubbleChild = (BubbleTextView) child;
Jon Mirandaf1eae802017-10-04 11:23:33 -0700578 bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800579 }
580
Sunny Goyalc13403c2016-11-18 23:44:48 -0800581 child.setScaleX(mChildScale);
582 child.setScaleY(mChildScale);
Adam Cohen307fe232012-08-16 17:55:58 -0700583
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800584 // Generate an id for each view, this assumes we have at most 256x256 cells
585 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700586 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700587 // If the horizontal or vertical span is set to -1, it is taken to
588 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700589 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
590 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800591
Winson Chungaafa03c2010-06-11 17:34:16 -0700592 child.setId(childId);
Tony Wickhama0628cc2015-10-14 15:23:04 -0700593 if (LOGD) {
594 Log.d(TAG, "Adding view to ShortcutsAndWidgetsContainer: " + child);
595 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700596 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700597
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700598 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700599
Winson Chungaafa03c2010-06-11 17:34:16 -0700600 return true;
601 }
602 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800603 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700604
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800605 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700606 public void removeAllViews() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700607 mOccupied.clear();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700608 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700609 }
610
611 @Override
612 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700613 if (mShortcutsAndWidgets.getChildCount() > 0) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700614 mOccupied.clear();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700615 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700616 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700617 }
618
619 @Override
620 public void removeView(View view) {
621 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700622 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700623 }
624
625 @Override
626 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700627 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
628 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700629 }
630
631 @Override
632 public void removeViewInLayout(View view) {
633 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700634 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700635 }
636
637 @Override
638 public void removeViews(int start, int count) {
639 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700640 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700641 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700642 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700643 }
644
645 @Override
646 public void removeViewsInLayout(int start, int count) {
647 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700648 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700649 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700650 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800651 }
652
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700653 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700654 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800655 * @param x X coordinate of the point
656 * @param y Y coordinate of the point
657 * @param result Array of 2 ints to hold the x and y coordinate of the cell
658 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700659 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700660 final int hStartPadding = getPaddingLeft();
661 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800662
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530663 result[0] = (x - hStartPadding) / mCellWidth;
664 result[1] = (y - vStartPadding) / mCellHeight;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800665
Adam Cohend22015c2010-07-26 22:02:18 -0700666 final int xAxis = mCountX;
667 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800668
669 if (result[0] < 0) result[0] = 0;
670 if (result[0] >= xAxis) result[0] = xAxis - 1;
671 if (result[1] < 0) result[1] = 0;
672 if (result[1] >= yAxis) result[1] = yAxis - 1;
673 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700674
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800675 /**
676 * Given a point, return the cell that most closely encloses that point
677 * @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 */
681 void pointToCellRounded(int x, int y, int[] result) {
682 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
683 }
684
685 /**
686 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700687 *
688 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800689 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700690 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800691 * @param result Array of 2 ints to hold the x and y coordinate of the point
692 */
693 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700694 final int hStartPadding = getPaddingLeft();
695 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800696
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530697 result[0] = hStartPadding + cellX * mCellWidth;
698 result[1] = vStartPadding + cellY * mCellHeight;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800699 }
700
Adam Cohene3e27a82011-04-15 12:07:39 -0700701 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800702 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700703 *
704 * @param cellX X coordinate of the cell
705 * @param cellY Y coordinate of the cell
706 *
707 * @param result Array of 2 ints to hold the x and y coordinate of the point
708 */
709 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700710 regionToCenterPoint(cellX, cellY, 1, 1, result);
711 }
712
713 /**
714 * Given a cell coordinate and span return the point that represents the center of the regio
715 *
716 * @param cellX X coordinate of the cell
717 * @param cellY Y coordinate of the cell
718 *
719 * @param result Array of 2 ints to hold the x and y coordinate of the point
720 */
721 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700722 final int hStartPadding = getPaddingLeft();
723 final int vStartPadding = getPaddingTop();
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530724 result[0] = hStartPadding + cellX * mCellWidth + (spanX * mCellWidth) / 2;
725 result[1] = vStartPadding + cellY * mCellHeight + (spanY * mCellHeight) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700726 }
727
Adam Cohen19f37922012-03-21 11:59:11 -0700728 /**
729 * Given a cell coordinate and span fills out a corresponding pixel rect
730 *
731 * @param cellX X coordinate of the cell
732 * @param cellY Y coordinate of the cell
733 * @param result Rect in which to write the result
734 */
735 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
736 final int hStartPadding = getPaddingLeft();
737 final int vStartPadding = getPaddingTop();
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530738 final int left = hStartPadding + cellX * mCellWidth;
739 final int top = vStartPadding + cellY * mCellHeight;
740 result.set(left, top, left + (spanX * mCellWidth), top + (spanY * mCellHeight));
Adam Cohen19f37922012-03-21 11:59:11 -0700741 }
742
Adam Cohen482ed822012-03-02 14:15:13 -0800743 public float getDistanceFromCell(float x, float y, int[] cell) {
744 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700745 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800746 }
747
Adam Cohenf9c184a2016-01-15 16:47:43 -0800748 public int getCellWidth() {
Romain Guy84f296c2009-11-04 15:00:44 -0800749 return mCellWidth;
750 }
751
Sunny Goyal0b754e52017-08-07 07:42:45 -0700752 public int getCellHeight() {
Romain Guy84f296c2009-11-04 15:00:44 -0800753 return mCellHeight;
754 }
755
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700756 public void setFixedSize(int width, int height) {
757 mFixedWidth = width;
758 mFixedHeight = height;
759 }
760
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800761 @Override
762 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800763 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800764 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700765 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
766 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700767 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
768 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700769 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Sunny Goyalc6205602015-05-21 20:46:33 -0700770 int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX);
771 int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700772 if (cw != mCellWidth || ch != mCellHeight) {
773 mCellWidth = cw;
774 mCellHeight = ch;
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530775 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700776 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700777 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700778
Winson Chung2d75f122013-09-23 16:53:31 -0700779 int newWidth = childWidthSize;
780 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700781 if (mFixedWidth > 0 && mFixedHeight > 0) {
782 newWidth = mFixedWidth;
783 newHeight = mFixedHeight;
784 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800785 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
786 }
787
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700788 // Make the feedback view large enough to hold the blur bitmap.
789 mTouchFeedbackView.measure(
790 MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
791 MeasureSpec.EXACTLY),
792 MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
793 MeasureSpec.EXACTLY));
794
795 mShortcutsAndWidgets.measure(
796 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
797 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
798
799 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
800 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -0700801 if (mFixedWidth > 0 && mFixedHeight > 0) {
802 setMeasuredDimension(maxWidth, maxHeight);
803 } else {
804 setMeasuredDimension(widthSize, heightSize);
805 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800806 }
807
808 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700809 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Tony Wickham26b01422015-11-10 14:44:32 -0800810 int left = getPaddingLeft();
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700811 left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Sunny Goyal7c786f72016-06-01 14:08:21 -0700812 int right = r - l - getPaddingRight();
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700813 right -= (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Sunny Goyal7c786f72016-06-01 14:08:21 -0700814
Winson Chung38848ca2013-10-08 12:03:44 -0700815 int top = getPaddingTop();
Sunny Goyal7c786f72016-06-01 14:08:21 -0700816 int bottom = b - t - getPaddingBottom();
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700817
818 mTouchFeedbackView.layout(left, top,
819 left + mTouchFeedbackView.getMeasuredWidth(),
820 top + mTouchFeedbackView.getMeasuredHeight());
Sunny Goyal7c786f72016-06-01 14:08:21 -0700821 mShortcutsAndWidgets.layout(left, top, right, bottom);
822
823 // Expand the background drawing bounds by the padding baked into the background drawable
824 mBackground.getPadding(mTempRect);
825 mBackground.setBounds(
Jon Miranda28032002017-07-13 16:18:56 -0700826 left - mTempRect.left - getPaddingLeft(),
827 top - mTempRect.top - getPaddingTop(),
828 right + mTempRect.right + getPaddingRight(),
829 bottom + mTempRect.bottom + getPaddingBottom());
Sunny Goyal7c786f72016-06-01 14:08:21 -0700830 }
831
Tony Wickhama501d492015-11-03 18:05:01 -0800832 /**
833 * Returns the amount of space left over after subtracting padding and cells. This space will be
834 * very small, a few pixels at most, and is a result of rounding down when calculating the cell
835 * width in {@link DeviceProfile#calculateCellWidth(int, int)}.
836 */
837 public int getUnusedHorizontalSpace() {
838 return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
839 }
840
Sunny Goyalaeb16432017-10-16 11:46:41 -0700841 public Drawable getScrimBackground() {
842 return mBackground;
Michael Jurkadee05892010-07-27 10:01:56 -0700843 }
844
Sunny Goyal2805e632015-05-20 15:35:32 -0700845 @Override
846 protected boolean verifyDrawable(Drawable who) {
Sunny Goyal7ce471b2017-08-02 03:37:39 -0700847 return super.verifyDrawable(who) || (who == mBackground);
Sunny Goyal2805e632015-05-20 15:35:32 -0700848 }
849
Michael Jurkaa52570f2012-03-20 03:18:20 -0700850 public void setShortcutAndWidgetAlpha(float alpha) {
Sunny Goyal02b50812014-09-10 15:44:42 -0700851 mShortcutsAndWidgets.setAlpha(alpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700852 }
853
Michael Jurkaa52570f2012-03-20 03:18:20 -0700854 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700855 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700856 }
857
Patrick Dubroy440c3602010-07-13 17:50:32 -0700858 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700859 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700860 }
861
Adam Cohen76fc0852011-06-17 13:26:23 -0700862 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800863 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700864 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800865
Adam Cohen19f37922012-03-21 11:59:11 -0700866 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700867 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
868 final ItemInfo info = (ItemInfo) child.getTag();
869
870 // We cancel any existing animations
871 if (mReorderAnimators.containsKey(lp)) {
872 mReorderAnimators.get(lp).cancel();
873 mReorderAnimators.remove(lp);
874 }
875
Adam Cohen482ed822012-03-02 14:15:13 -0800876 final int oldX = lp.x;
877 final int oldY = lp.y;
878 if (adjustOccupied) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700879 GridOccupancy occupied = permanent ? mOccupied : mTmpOccupied;
880 occupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
881 occupied.markCells(cellX, cellY, lp.cellHSpan, lp.cellVSpan, true);
Adam Cohen482ed822012-03-02 14:15:13 -0800882 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700883 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -0800884 if (permanent) {
885 lp.cellX = info.cellX = cellX;
886 lp.cellY = info.cellY = cellY;
887 } else {
888 lp.tmpCellX = cellX;
889 lp.tmpCellY = cellY;
890 }
Jon Mirandae96798e2016-12-07 12:10:44 -0800891 clc.setupLp(child);
Adam Cohenbfbfd262011-06-13 16:55:12 -0700892 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800893 final int newX = lp.x;
894 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700895
Adam Cohen76fc0852011-06-17 13:26:23 -0700896 lp.x = oldX;
897 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -0700898
Adam Cohen482ed822012-03-02 14:15:13 -0800899 // Exit early if we're not actually moving the view
900 if (oldX == newX && oldY == newY) {
901 lp.isLockedToGrid = true;
902 return true;
903 }
904
Jon Mirandacda3bfb2017-02-06 15:54:41 -0800905 ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -0800906 va.setDuration(duration);
907 mReorderAnimators.put(lp, va);
908
909 va.addUpdateListener(new AnimatorUpdateListener() {
910 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -0700911 public void onAnimationUpdate(ValueAnimator animation) {
Jon Mirandae96798e2016-12-07 12:10:44 -0800912 float r = (Float) animation.getAnimatedValue();
Adam Cohen19f37922012-03-21 11:59:11 -0700913 lp.x = (int) ((1 - r) * oldX + r * newX);
914 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -0700915 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -0700916 }
917 });
Adam Cohen482ed822012-03-02 14:15:13 -0800918 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700919 boolean cancelled = false;
920 public void onAnimationEnd(Animator animation) {
921 // If the animation was cancelled, it means that another animation
922 // has interrupted this one, and we don't want to lock the item into
923 // place just yet.
924 if (!cancelled) {
925 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -0800926 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -0700927 }
928 if (mReorderAnimators.containsKey(lp)) {
929 mReorderAnimators.remove(lp);
930 }
931 }
932 public void onAnimationCancel(Animator animation) {
933 cancelled = true;
934 }
935 });
Adam Cohen482ed822012-03-02 14:15:13 -0800936 va.setStartDelay(delay);
937 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -0700938 return true;
939 }
940 return false;
941 }
942
Sunny Goyal06e21a22016-08-11 16:02:02 -0700943 void visualizeDropLocation(View v, DragPreviewProvider outlineProvider, int cellX, int cellY,
944 int spanX, int spanY, boolean resize, DropTarget.DragObject dragObject) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -0700945 final int oldDragCellX = mDragCell[0];
946 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -0800947
Hyunyoung Song0de01172016-10-05 16:27:48 -0700948 if (outlineProvider == null || outlineProvider.generatedDragOutline == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -0700949 return;
950 }
951
Hyunyoung Song0de01172016-10-05 16:27:48 -0700952 Bitmap dragOutline = outlineProvider.generatedDragOutline;
Adam Cohen482ed822012-03-02 14:15:13 -0800953 if (cellX != oldDragCellX || cellY != oldDragCellY) {
Sunny Goyale78e3d72015-09-24 11:23:31 -0700954 Point dragOffset = dragObject.dragView.getDragVisualizeOffset();
955 Rect dragRegion = dragObject.dragView.getDragRegion();
956
Adam Cohen482ed822012-03-02 14:15:13 -0800957 mDragCell[0] = cellX;
958 mDragCell[1] = cellY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700959
Joe Onorato4be866d2010-10-10 11:26:02 -0700960 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -0700961 mDragOutlineAnims[oldIndex].animateOut();
962 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -0800963 Rect r = mDragOutlines[mDragOutlineCurrent];
Sunny Goyal106bf642015-07-16 12:18:06 -0700964
Adam Cohend41fbf52012-02-16 23:53:59 -0800965 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -0800966 cellToRect(cellX, cellY, spanX, spanY, r);
Jon Mirandae96798e2016-12-07 12:10:44 -0800967 if (v instanceof LauncherAppWidgetHostView) {
968 DeviceProfile profile = mLauncher.getDeviceProfile();
Jon Miranda6f6a06a2016-12-15 11:24:18 -0800969 Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
Jon Mirandae96798e2016-12-07 12:10:44 -0800970 }
Sunny Goyal106bf642015-07-16 12:18:06 -0700971 } else {
972 // Find the top left corner of the rect the object will occupy
973 final int[] topLeft = mTmpPoint;
974 cellToPoint(cellX, cellY, topLeft);
975
976 int left = topLeft[0];
977 int top = topLeft[1];
978
979 if (v != null && dragOffset == null) {
980 // When drawing the drag outline, it did not account for margin offsets
981 // added by the view's parent.
982 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
983 left += lp.leftMargin;
984 top += lp.topMargin;
985
986 // Offsets due to the size difference between the View and the dragOutline.
987 // There is a size difference to account for the outer blur, which may lie
988 // outside the bounds of the view.
Jon Mirandaf7ff3fe2016-12-05 12:04:44 -0800989 top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
Sunny Goyal106bf642015-07-16 12:18:06 -0700990 // We center about the x axis
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530991 left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
Sunny Goyal106bf642015-07-16 12:18:06 -0700992 } else {
993 if (dragOffset != null && dragRegion != null) {
994 // Center the drag region *horizontally* in the cell and apply a drag
995 // outline offset
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530996 left += dragOffset.x + ((mCellWidth * spanX) - dragRegion.width()) / 2;
Sunny Goyal106bf642015-07-16 12:18:06 -0700997 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
998 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
999 top += dragOffset.y + cellPaddingY;
1000 } else {
1001 // Center the drag outline in the cell
Sunny Goyalaa8a8712016-11-20 15:26:01 +05301002 left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
1003 top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
Sunny Goyal106bf642015-07-16 12:18:06 -07001004 }
1005 }
1006 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
Adam Cohend41fbf52012-02-16 23:53:59 -08001007 }
Winson Chung150fbab2010-09-29 17:14:26 -07001008
Jon Miranda6f6a06a2016-12-15 11:24:18 -08001009 Utilities.scaleRectAboutCenter(r, mChildScale);
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001010 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1011 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Sunny Goyale78e3d72015-09-24 11:23:31 -07001012
1013 if (dragObject.stateAnnouncer != null) {
Sunny Goyalc13403c2016-11-18 23:44:48 -08001014 dragObject.stateAnnouncer.announce(getItemMoveDescription(cellX, cellY));
Sunny Goyale78e3d72015-09-24 11:23:31 -07001015 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001016 }
1017 }
1018
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,
1893 View dragView, int delay, 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
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001911 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07001912 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001913 class ReorderPreviewAnimation {
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001914 final View child;
Adam Cohend024f982012-05-23 18:26:45 -07001915 float finalDeltaX;
1916 float finalDeltaY;
1917 float initDeltaX;
1918 float initDeltaY;
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001919 final float finalScale;
Adam Cohend024f982012-05-23 18:26:45 -07001920 float initScale;
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07001921 final int mode;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001922 boolean repeating = false;
1923 private static final int PREVIEW_DURATION = 300;
1924 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
1925
Jon Miranda21266912016-12-19 14:12:05 -08001926 private static final float CHILD_DIVIDEND = 4.0f;
1927
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001928 public static final int MODE_HINT = 0;
1929 public static final int MODE_PREVIEW = 1;
1930
Adam Cohene7587d22012-05-24 18:50:02 -07001931 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07001932
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001933 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
1934 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07001935 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
1936 final int x0 = mTmpPoint[0];
1937 final int y0 = mTmpPoint[1];
1938 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
1939 final int x1 = mTmpPoint[0];
1940 final int y1 = mTmpPoint[1];
1941 final int dX = x1 - x0;
1942 final int dY = y1 - y0;
Jon Miranda21266912016-12-19 14:12:05 -08001943
1944 this.child = child;
1945 this.mode = mode;
1946 setInitialAnimationValues(false);
1947 finalScale = (mChildScale - (CHILD_DIVIDEND / child.getWidth())) * initScale;
1948 finalDeltaX = initDeltaX;
1949 finalDeltaY = initDeltaY;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001950 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07001951 if (dX == dY && dX == 0) {
1952 } else {
1953 if (dY == 0) {
Jon Miranda21266912016-12-19 14:12:05 -08001954 finalDeltaX += - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07001955 } else if (dX == 0) {
Jon Miranda21266912016-12-19 14:12:05 -08001956 finalDeltaY += - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07001957 } else {
1958 double angle = Math.atan( (float) (dY) / dX);
Jon Miranda21266912016-12-19 14:12:05 -08001959 finalDeltaX += (int) (- dir * Math.signum(dX) *
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001960 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
Jon Miranda21266912016-12-19 14:12:05 -08001961 finalDeltaY += (int) (- dir * Math.signum(dY) *
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001962 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07001963 }
1964 }
Jon Miranda21266912016-12-19 14:12:05 -08001965 }
1966
1967 void setInitialAnimationValues(boolean restoreOriginalValues) {
1968 if (restoreOriginalValues) {
1969 if (child instanceof LauncherAppWidgetHostView) {
1970 LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
1971 initScale = lahv.getScaleToFit();
1972 initDeltaX = lahv.getTranslationForCentering().x;
1973 initDeltaY = lahv.getTranslationForCentering().y;
1974 } else {
1975 initScale = mChildScale;
1976 initDeltaX = 0;
1977 initDeltaY = 0;
1978 }
1979 } else {
1980 initScale = child.getScaleX();
1981 initDeltaX = child.getTranslationX();
1982 initDeltaY = child.getTranslationY();
1983 }
Adam Cohen19f37922012-03-21 11:59:11 -07001984 }
1985
Adam Cohend024f982012-05-23 18:26:45 -07001986 void animate() {
Jon Miranda21266912016-12-19 14:12:05 -08001987 boolean noMovement = (finalDeltaX == initDeltaX) && (finalDeltaY == initDeltaY);
1988
Adam Cohen19f37922012-03-21 11:59:11 -07001989 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001990 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07001991 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07001992 mShakeAnimators.remove(child);
Jon Miranda21266912016-12-19 14:12:05 -08001993 if (noMovement) {
Adam Cohene7587d22012-05-24 18:50:02 -07001994 completeAnimationImmediately();
1995 return;
1996 }
Adam Cohen19f37922012-03-21 11:59:11 -07001997 }
Jon Miranda21266912016-12-19 14:12:05 -08001998 if (noMovement) {
Adam Cohen19f37922012-03-21 11:59:11 -07001999 return;
2000 }
Jon Mirandacda3bfb2017-02-06 15:54:41 -08002001 ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002002 a = va;
Tony Wickham9e0702f2015-09-02 14:45:39 -07002003
2004 // Animations are disabled in power save mode, causing the repeated animation to jump
2005 // spastically between beginning and end states. Since this looks bad, we don't repeat
2006 // the animation in power save mode.
Tony Wickham112ac952015-11-12 12:31:50 -08002007 if (!Utilities.isPowerSaverOn(getContext())) {
Tony Wickham9e0702f2015-09-02 14:45:39 -07002008 va.setRepeatMode(ValueAnimator.REVERSE);
2009 va.setRepeatCount(ValueAnimator.INFINITE);
2010 }
2011
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002012 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002013 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002014 va.addUpdateListener(new AnimatorUpdateListener() {
2015 @Override
2016 public void onAnimationUpdate(ValueAnimator animation) {
Jon Mirandae96798e2016-12-07 12:10:44 -08002017 float r = (Float) animation.getAnimatedValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002018 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2019 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2020 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002021 child.setTranslationX(x);
2022 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002023 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002024 child.setScaleX(s);
2025 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002026 }
2027 });
2028 va.addListener(new AnimatorListenerAdapter() {
2029 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002030 // We make sure to end only after a full period
Jon Miranda21266912016-12-19 14:12:05 -08002031 setInitialAnimationValues(true);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002032 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002033 }
2034 });
Adam Cohen19f37922012-03-21 11:59:11 -07002035 mShakeAnimators.put(child, this);
2036 va.start();
2037 }
2038
Adam Cohend024f982012-05-23 18:26:45 -07002039 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002040 if (a != null) {
2041 a.cancel();
2042 }
Adam Cohen19f37922012-03-21 11:59:11 -07002043 }
Adam Cohene7587d22012-05-24 18:50:02 -07002044
Adam Cohen091440a2015-03-18 14:16:05 -07002045 @Thunk void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002046 if (a != null) {
2047 a.cancel();
2048 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002049
Jon Miranda72dbd912017-01-04 14:03:07 -08002050 setInitialAnimationValues(true);
Sunny Goyal9e76f682017-02-13 12:13:43 -08002051 a = LauncherAnimUtils.ofPropertyValuesHolder(child,
2052 new PropertyListBuilder()
2053 .scale(initScale)
2054 .translationX(initDeltaX)
2055 .translationY(initDeltaY)
2056 .build())
2057 .setDuration(REORDER_ANIMATION_DURATION);
Sunny Goyal5d2fc322015-07-06 22:52:49 -07002058 a.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2059 a.start();
Brandon Keely50e6e562012-05-08 16:28:49 -07002060 }
Adam Cohen19f37922012-03-21 11:59:11 -07002061 }
2062
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002063 private void completeAndClearReorderPreviewAnimations() {
2064 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002065 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002066 }
2067 mShakeAnimators.clear();
2068 }
2069
Adam Cohen482ed822012-03-02 14:15:13 -08002070 private void commitTempPlacement() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002071 mTmpOccupied.copyTo(mOccupied);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002072
2073 long screenId = mLauncher.getWorkspace().getIdForScreen(this);
2074 int container = Favorites.CONTAINER_DESKTOP;
2075
Sunny Goyalc13403c2016-11-18 23:44:48 -08002076 if (mContainerType == HOTSEAT) {
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002077 screenId = -1;
2078 container = Favorites.CONTAINER_HOTSEAT;
2079 }
2080
Michael Jurkaa52570f2012-03-20 03:18:20 -07002081 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002082 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002083 View child = mShortcutsAndWidgets.getChildAt(i);
2084 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2085 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002086 // We do a null check here because the item info can be null in the case of the
2087 // AllApps button in the hotseat.
2088 if (info != null) {
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002089 final boolean requiresDbUpdate = (info.cellX != lp.tmpCellX
2090 || info.cellY != lp.tmpCellY || info.spanX != lp.cellHSpan
2091 || info.spanY != lp.cellVSpan);
2092
Adam Cohen2acce882012-03-28 19:03:19 -07002093 info.cellX = lp.cellX = lp.tmpCellX;
2094 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002095 info.spanX = lp.cellHSpan;
2096 info.spanY = lp.cellVSpan;
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002097
2098 if (requiresDbUpdate) {
Sunny Goyal43bf11d2017-02-02 13:52:53 -08002099 mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId,
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002100 info.cellX, info.cellY, info.spanX, info.spanY);
2101 }
Adam Cohen2acce882012-03-28 19:03:19 -07002102 }
Adam Cohen482ed822012-03-02 14:15:13 -08002103 }
2104 }
2105
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002106 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002107 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002108 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002109 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002110 lp.useTmpCoords = useTempCoords;
2111 }
2112 }
2113
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002114 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002115 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2116 int[] result = new int[2];
2117 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002118 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002119 resultSpan);
2120 if (result[0] >= 0 && result[1] >= 0) {
2121 copyCurrentStateToSolution(solution, false);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002122 solution.cellX = result[0];
2123 solution.cellY = result[1];
2124 solution.spanX = resultSpan[0];
2125 solution.spanY = resultSpan[1];
Adam Cohen482ed822012-03-02 14:15:13 -08002126 solution.isSolution = true;
2127 } else {
2128 solution.isSolution = false;
2129 }
2130 return solution;
2131 }
2132
Adam Cohen19f37922012-03-21 11:59:11 -07002133 /* This seems like it should be obvious and straight-forward, but when the direction vector
2134 needs to match with the notion of the dragView pushing other views, we have to employ
2135 a slightly more subtle notion of the direction vector. The question is what two points is
2136 the vector between? The center of the dragView and its desired destination? Not quite, as
2137 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2138 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2139 or right, which helps make pushing feel right.
2140 */
2141 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2142 int spanY, View dragView, int[] resultDirection) {
2143 int[] targetDestination = new int[2];
2144
2145 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2146 Rect dragRect = new Rect();
2147 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2148 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2149
2150 Rect dropRegionRect = new Rect();
2151 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2152 dragView, dropRegionRect, mIntersectingViews);
2153
2154 int dropRegionSpanX = dropRegionRect.width();
2155 int dropRegionSpanY = dropRegionRect.height();
2156
2157 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2158 dropRegionRect.height(), dropRegionRect);
2159
2160 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2161 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2162
2163 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2164 deltaX = 0;
2165 }
2166 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2167 deltaY = 0;
2168 }
2169
2170 if (deltaX == 0 && deltaY == 0) {
2171 // No idea what to do, give a random direction.
2172 resultDirection[0] = 1;
2173 resultDirection[1] = 0;
2174 } else {
2175 computeDirectionVector(deltaX, deltaY, resultDirection);
2176 }
2177 }
2178
2179 // For a given cell and span, fetch the set of views intersecting the region.
2180 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2181 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2182 if (boundingRect != null) {
2183 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2184 }
2185 intersectingViews.clear();
2186 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2187 Rect r1 = new Rect();
2188 final int count = mShortcutsAndWidgets.getChildCount();
2189 for (int i = 0; i < count; i++) {
2190 View child = mShortcutsAndWidgets.getChildAt(i);
2191 if (child == dragView) continue;
2192 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2193 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2194 if (Rect.intersects(r0, r1)) {
2195 mIntersectingViews.add(child);
2196 if (boundingRect != null) {
2197 boundingRect.union(r1);
2198 }
2199 }
2200 }
2201 }
2202
2203 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2204 View dragView, int[] result) {
2205 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2206 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2207 mIntersectingViews);
2208 return !mIntersectingViews.isEmpty();
2209 }
2210
2211 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002212 completeAndClearReorderPreviewAnimations();
2213 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2214 final int count = mShortcutsAndWidgets.getChildCount();
2215 for (int i = 0; i < count; i++) {
2216 View child = mShortcutsAndWidgets.getChildAt(i);
2217 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2218 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2219 lp.tmpCellX = lp.cellX;
2220 lp.tmpCellY = lp.cellY;
2221 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2222 0, false, false);
2223 }
Adam Cohen19f37922012-03-21 11:59:11 -07002224 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002225 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002226 }
Adam Cohen19f37922012-03-21 11:59:11 -07002227 }
2228
Adam Cohenbebf0422012-04-11 18:06:28 -07002229 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2230 View dragView, int[] direction, boolean commit) {
2231 int[] pixelXY = new int[2];
2232 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2233
2234 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002235 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002236 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2237
2238 setUseTempCoords(true);
2239 if (swapSolution != null && swapSolution.isSolution) {
2240 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2241 // committing anything or animating anything as we just want to determine if a solution
2242 // exists
2243 copySolutionToTempState(swapSolution, dragView);
2244 setItemPlacementDirty(true);
2245 animateItemsToSolution(swapSolution, dragView, commit);
2246
2247 if (commit) {
2248 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002249 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002250 setItemPlacementDirty(false);
2251 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002252 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2253 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002254 }
2255 mShortcutsAndWidgets.requestLayout();
2256 }
2257 return swapSolution.isSolution;
2258 }
2259
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002260 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002261 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002262 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002263 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002264
2265 if (resultSpan == null) {
2266 resultSpan = new int[2];
2267 }
2268
Adam Cohen19f37922012-03-21 11:59:11 -07002269 // When we are checking drop validity or actually dropping, we don't recompute the
2270 // direction vector, since we want the solution to match the preview, and it's possible
2271 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002272 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2273 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002274 mDirectionVector[0] = mPreviousReorderDirection[0];
2275 mDirectionVector[1] = mPreviousReorderDirection[1];
2276 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002277 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2278 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2279 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002280 }
2281 } else {
2282 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2283 mPreviousReorderDirection[0] = mDirectionVector[0];
2284 mPreviousReorderDirection[1] = mDirectionVector[1];
2285 }
2286
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002287 // Find a solution involving pushing / displacing any items in the way
2288 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002289 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2290
2291 // We attempt the approach which doesn't shuffle views at all
2292 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2293 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2294
2295 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002296
2297 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2298 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002299 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2300 finalSolution = swapSolution;
2301 } else if (noShuffleSolution.isSolution) {
2302 finalSolution = noShuffleSolution;
2303 }
2304
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002305 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002306 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002307 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2308 ReorderPreviewAnimation.MODE_HINT);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002309 result[0] = finalSolution.cellX;
2310 result[1] = finalSolution.cellY;
2311 resultSpan[0] = finalSolution.spanX;
2312 resultSpan[1] = finalSolution.spanY;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002313 } else {
2314 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2315 }
2316 return result;
2317 }
2318
Adam Cohen482ed822012-03-02 14:15:13 -08002319 boolean foundSolution = true;
2320 if (!DESTRUCTIVE_REORDER) {
2321 setUseTempCoords(true);
2322 }
2323
2324 if (finalSolution != null) {
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 Cohen482ed822012-03-02 14:15:13 -08002329
2330 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2331 // committing anything or animating anything as we just want to determine if a solution
2332 // exists
2333 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2334 if (!DESTRUCTIVE_REORDER) {
2335 copySolutionToTempState(finalSolution, dragView);
2336 }
2337 setItemPlacementDirty(true);
2338 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2339
Adam Cohen19f37922012-03-21 11:59:11 -07002340 if (!DESTRUCTIVE_REORDER &&
2341 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002342 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002343 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002344 setItemPlacementDirty(false);
2345 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002346 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2347 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002348 }
2349 }
2350 } else {
2351 foundSolution = false;
2352 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2353 }
2354
2355 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2356 setUseTempCoords(false);
2357 }
Adam Cohen482ed822012-03-02 14:15:13 -08002358
Michael Jurkaa52570f2012-03-20 03:18:20 -07002359 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002360 return result;
2361 }
2362
Adam Cohen19f37922012-03-21 11:59:11 -07002363 void setItemPlacementDirty(boolean dirty) {
2364 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002365 }
Adam Cohen19f37922012-03-21 11:59:11 -07002366 boolean isItemPlacementDirty() {
2367 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002368 }
2369
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002370 private static class ItemConfiguration extends CellAndSpan {
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07002371 final ArrayMap<View, CellAndSpan> map = new ArrayMap<>();
2372 private final ArrayMap<View, CellAndSpan> savedMap = new ArrayMap<>();
2373 final ArrayList<View> sortedViews = new ArrayList<>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002374 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002375 boolean isSolution = false;
Adam Cohen482ed822012-03-02 14:15:13 -08002376
Adam Cohenf3900c22012-11-16 18:28:11 -08002377 void save() {
2378 // Copy current state into savedMap
2379 for (View v: map.keySet()) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002380 savedMap.get(v).copyFrom(map.get(v));
Adam Cohenf3900c22012-11-16 18:28:11 -08002381 }
2382 }
2383
2384 void restore() {
2385 // Restore current state from savedMap
2386 for (View v: savedMap.keySet()) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002387 map.get(v).copyFrom(savedMap.get(v));
Adam Cohenf3900c22012-11-16 18:28:11 -08002388 }
2389 }
2390
2391 void add(View v, CellAndSpan cs) {
2392 map.put(v, cs);
2393 savedMap.put(v, new CellAndSpan());
2394 sortedViews.add(v);
2395 }
2396
Adam Cohen482ed822012-03-02 14:15:13 -08002397 int area() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002398 return spanX * spanY;
Adam Cohenf3900c22012-11-16 18:28:11 -08002399 }
2400
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002401 void getBoundingRectForViews(ArrayList<View> views, Rect outRect) {
2402 boolean first = true;
2403 for (View v: views) {
2404 CellAndSpan c = map.get(v);
2405 if (first) {
2406 outRect.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2407 first = false;
2408 } else {
2409 outRect.union(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2410 }
2411 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002412 }
Adam Cohen482ed822012-03-02 14:15:13 -08002413 }
2414
Adam Cohendf035382011-04-11 17:22:04 -07002415 /**
Adam Cohendf035382011-04-11 17:22:04 -07002416 * Find a starting cell position that will fit the given bounds nearest the requested
2417 * cell location. Uses Euclidean distance to score multiple vacant areas.
2418 *
2419 * @param pixelX The X location at which you want to search for a vacant area.
2420 * @param pixelY The Y location at which you want to search for a vacant area.
2421 * @param spanX Horizontal span of the object.
2422 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07002423 * @param result Previously returned value to possibly recycle.
2424 * @return The X, Y cell of a vacant area that can contain this object,
2425 * nearest the requested location.
2426 */
Adam Cohenf9c184a2016-01-15 16:47:43 -08002427 public int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002428 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002429 }
2430
Michael Jurka0280c3b2010-09-17 15:00:07 -07002431 boolean existsEmptyCell() {
2432 return findCellForSpan(null, 1, 1);
2433 }
2434
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002435 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002436 * Finds the upper-left coordinate of the first rectangle in the grid that can
2437 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2438 * then this method will only return coordinates for rectangles that contain the cell
2439 * (intersectX, intersectY)
2440 *
2441 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2442 * can be found.
2443 * @param spanX The horizontal span of the cell we want to find.
2444 * @param spanY The vertical span of the cell we want to find.
2445 *
2446 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002447 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002448 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002449 if (cellXY == null) {
2450 cellXY = new int[2];
Michael Jurka0280c3b2010-09-17 15:00:07 -07002451 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002452 return mOccupied.findVacantCell(cellXY, spanX, spanY);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002453 }
2454
2455 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002456 * A drag event has begun over this layout.
2457 * It may have begun over this layout (in which case onDragChild is called first),
2458 * or it may have begun on another layout.
2459 */
2460 void onDragEnter() {
Winson Chungc07918d2011-07-01 15:35:26 -07002461 mDragging = true;
2462 }
2463
2464 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002465 * Called when drag has left this CellLayout or has been completed (successfully or not)
2466 */
2467 void onDragExit() {
Joe Onorato4be866d2010-10-10 11:26:02 -07002468 // This can actually be called when we aren't in a drag, e.g. when adding a new
2469 // item to this layout via the customize drawer.
2470 // Guard against that case.
2471 if (mDragging) {
2472 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002473 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002474
2475 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002476 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002477 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2478 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002479 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002480 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002481 }
2482
2483 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002484 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002485 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002486 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002487 *
2488 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002489 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002490 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002491 if (child != null) {
2492 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002493 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002494 child.requestLayout();
Tony Wickham1cdb6d02015-09-17 11:08:27 -07002495 markCellsAsOccupiedForView(child);
Romain Guyd94533d2009-08-17 10:01:15 -07002496 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002497 }
2498
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002499 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002500 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002501 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002502 * @param cellX X coordinate of upper left corner expressed as a cell position
2503 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002504 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002505 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002506 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002507 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002508 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002509 final int cellWidth = mCellWidth;
2510 final int cellHeight = mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -07002511
Winson Chung4b825dcd2011-06-19 12:41:22 -07002512 final int hStartPadding = getPaddingLeft();
2513 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002514
Sunny Goyalaa8a8712016-11-20 15:26:01 +05302515 int width = cellHSpan * cellWidth;
2516 int height = cellVSpan * cellHeight;
2517 int x = hStartPadding + cellX * cellWidth;
2518 int y = vStartPadding + cellY * cellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -07002519
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002520 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002521 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002522
Adam Cohend4844c32011-02-18 19:25:06 -08002523 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002524 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002525 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002526 mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002527 }
2528
Adam Cohend4844c32011-02-18 19:25:06 -08002529 public void markCellsAsUnoccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002530 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002531 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002532 mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002533 }
2534
Adam Cohen2801caf2011-05-13 20:57:39 -07002535 public int getDesiredWidth() {
Sunny Goyalaa8a8712016-11-20 15:26:01 +05302536 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth);
Adam Cohen2801caf2011-05-13 20:57:39 -07002537 }
2538
2539 public int getDesiredHeight() {
Sunny Goyalaa8a8712016-11-20 15:26:01 +05302540 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight);
Adam Cohen2801caf2011-05-13 20:57:39 -07002541 }
2542
Michael Jurka66d72172011-04-12 16:29:25 -07002543 public boolean isOccupied(int x, int y) {
2544 if (x < mCountX && y < mCountY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002545 return mOccupied.cells[x][y];
Michael Jurka66d72172011-04-12 16:29:25 -07002546 } else {
2547 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2548 }
2549 }
2550
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002551 @Override
2552 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2553 return new CellLayout.LayoutParams(getContext(), attrs);
2554 }
2555
2556 @Override
2557 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2558 return p instanceof CellLayout.LayoutParams;
2559 }
2560
2561 @Override
2562 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2563 return new CellLayout.LayoutParams(p);
2564 }
2565
2566 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2567 /**
2568 * Horizontal location of the item in the grid.
2569 */
2570 @ViewDebug.ExportedProperty
2571 public int cellX;
2572
2573 /**
2574 * Vertical location of the item in the grid.
2575 */
2576 @ViewDebug.ExportedProperty
2577 public int cellY;
2578
2579 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002580 * Temporary horizontal location of the item in the grid during reorder
2581 */
2582 public int tmpCellX;
2583
2584 /**
2585 * Temporary vertical location of the item in the grid during reorder
2586 */
2587 public int tmpCellY;
2588
2589 /**
2590 * Indicates that the temporary coordinates should be used to layout the items
2591 */
2592 public boolean useTmpCoords;
2593
2594 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002595 * Number of cells spanned horizontally by the item.
2596 */
2597 @ViewDebug.ExportedProperty
2598 public int cellHSpan;
2599
2600 /**
2601 * Number of cells spanned vertically by the item.
2602 */
2603 @ViewDebug.ExportedProperty
2604 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002605
Adam Cohen1b607ed2011-03-03 17:26:50 -08002606 /**
2607 * Indicates whether the item will set its x, y, width and height parameters freely,
2608 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2609 */
Adam Cohend4844c32011-02-18 19:25:06 -08002610 public boolean isLockedToGrid = true;
2611
Adam Cohen482ed822012-03-02 14:15:13 -08002612 /**
2613 * Indicates whether this item can be reordered. Always true except in the case of the
Sunny Goyalda4fe1a2016-05-26 16:05:17 -07002614 * the AllApps button and QSB place holder.
Adam Cohen482ed822012-03-02 14:15:13 -08002615 */
2616 public boolean canReorder = true;
2617
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002618 // X coordinate of the view in the layout.
2619 @ViewDebug.ExportedProperty
Vadim Tryshevfedca432015-08-19 17:55:02 -07002620 public int x;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002621 // Y coordinate of the view in the layout.
2622 @ViewDebug.ExportedProperty
Vadim Tryshevfedca432015-08-19 17:55:02 -07002623 public int y;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002624
Romain Guy84f296c2009-11-04 15:00:44 -08002625 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002626
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002627 public LayoutParams(Context c, AttributeSet attrs) {
2628 super(c, attrs);
2629 cellHSpan = 1;
2630 cellVSpan = 1;
2631 }
2632
2633 public LayoutParams(ViewGroup.LayoutParams source) {
2634 super(source);
2635 cellHSpan = 1;
2636 cellVSpan = 1;
2637 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002638
2639 public LayoutParams(LayoutParams source) {
2640 super(source);
2641 this.cellX = source.cellX;
2642 this.cellY = source.cellY;
2643 this.cellHSpan = source.cellHSpan;
2644 this.cellVSpan = source.cellVSpan;
2645 }
2646
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002647 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002648 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002649 this.cellX = cellX;
2650 this.cellY = cellY;
2651 this.cellHSpan = cellHSpan;
2652 this.cellVSpan = cellVSpan;
2653 }
2654
Sunny Goyalaa8a8712016-11-20 15:26:01 +05302655 public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount) {
Jon Miranda7ae64ff2016-11-21 16:18:46 -08002656 setup(cellWidth, cellHeight, invertHorizontally, colCount, 1.0f, 1.0f);
2657 }
2658
2659 /**
2660 * Use this method, as opposed to {@link #setup(int, int, boolean, int)}, if the view needs
2661 * to be scaled.
2662 *
2663 * ie. In multi-window mode, we setup widgets so that they are measured and laid out
2664 * using their full/invariant device profile sizes.
2665 */
2666 public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
2667 float cellScaleX, float cellScaleY) {
Adam Cohend4844c32011-02-18 19:25:06 -08002668 if (isLockedToGrid) {
2669 final int myCellHSpan = cellHSpan;
2670 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002671 int myCellX = useTmpCoords ? tmpCellX : cellX;
2672 int myCellY = useTmpCoords ? tmpCellY : cellY;
2673
2674 if (invertHorizontally) {
2675 myCellX = colCount - myCellX - cellHSpan;
2676 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002677
Jon Miranda7ae64ff2016-11-21 16:18:46 -08002678 width = (int) (myCellHSpan * cellWidth / cellScaleX - leftMargin - rightMargin);
2679 height = (int) (myCellVSpan * cellHeight / cellScaleY - topMargin - bottomMargin);
Sunny Goyalaa8a8712016-11-20 15:26:01 +05302680 x = (myCellX * cellWidth + leftMargin);
2681 y = (myCellY * cellHeight + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002682 }
2683 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002684
Winson Chungaafa03c2010-06-11 17:34:16 -07002685 public String toString() {
2686 return "(" + this.cellX + ", " + this.cellY + ")";
2687 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002688
2689 public void setWidth(int width) {
2690 this.width = width;
2691 }
2692
2693 public int getWidth() {
2694 return width;
2695 }
2696
2697 public void setHeight(int height) {
2698 this.height = height;
2699 }
2700
2701 public int getHeight() {
2702 return height;
2703 }
2704
2705 public void setX(int x) {
2706 this.x = x;
2707 }
2708
2709 public int getX() {
2710 return x;
2711 }
2712
2713 public void setY(int y) {
2714 this.y = y;
2715 }
2716
2717 public int getY() {
2718 return y;
2719 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002720 }
2721
Michael Jurka0280c3b2010-09-17 15:00:07 -07002722 // This class stores info for two purposes:
2723 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2724 // its spanX, spanY, and the screen it is on
2725 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2726 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2727 // the CellLayout that was long clicked
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002728 public static final class CellInfo extends CellAndSpan {
Rajeev Kumar9962dbe2017-06-12 12:16:20 -07002729 public final View cell;
2730 final long screenId;
2731 final long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002732
Sunny Goyal83a8f042015-05-19 12:52:12 -07002733 public CellInfo(View v, ItemInfo info) {
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002734 cellX = info.cellX;
2735 cellY = info.cellY;
2736 spanX = info.spanX;
2737 spanY = info.spanY;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002738 cell = v;
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002739 screenId = info.screenId;
2740 container = info.container;
2741 }
2742
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002743 @Override
2744 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002745 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2746 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002747 }
2748 }
Michael Jurkad771c962011-08-09 15:00:48 -07002749
Tony Wickham86930612015-09-09 13:50:40 -07002750 /**
2751 * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing
2752 * if necessary).
2753 */
2754 public boolean hasReorderSolution(ItemInfo itemInfo) {
2755 int[] cellPoint = new int[2];
2756 // Check for a solution starting at every cell.
2757 for (int cellX = 0; cellX < getCountX(); cellX++) {
2758 for (int cellY = 0; cellY < getCountY(); cellY++) {
2759 cellToPoint(cellX, cellY, cellPoint);
2760 if (findReorderSolution(cellPoint[0], cellPoint[1], itemInfo.minSpanX,
2761 itemInfo.minSpanY, itemInfo.spanX, itemInfo.spanY, mDirectionVector, null,
2762 true, new ItemConfiguration()).isSolution) {
2763 return true;
2764 }
2765 }
2766 }
2767 return false;
2768 }
2769
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002770 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002771 return mOccupied.isRegionVacant(x, y, spanX, spanY);
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002772 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002773}