blob: 8179dad29a5f59b1fb632831fed2fa584244a013 [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;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080038import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070039import android.util.Log;
Adam Cohen1462de32012-07-24 22:34:36 -070040import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080041import android.view.MotionEvent;
42import android.view.View;
43import android.view.ViewDebug;
44import android.view.ViewGroup;
Adam Cohenc9735cf2015-01-23 16:11:55 -080045import android.view.accessibility.AccessibilityEvent;
Winson Chung150fbab2010-09-29 17:14:26 -070046import android.view.animation.DecelerateInterpolator;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080047
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 Goyal9e76f682017-02-13 12:13:43 -080053import com.android.launcher3.anim.PropertyListBuilder;
Sunny Goyal6c56c682015-07-16 14:09:05 -070054import com.android.launcher3.config.ProviderConfig;
Sunny Goyal26119432016-02-18 22:09:23 +000055import com.android.launcher3.folder.FolderIcon;
Sunny Goyal06e21a22016-08-11 16:02:02 -070056import com.android.launcher3.graphics.DragPreviewProvider;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -070057import com.android.launcher3.util.CellAndSpan;
58import com.android.launcher3.util.GridOccupancy;
Sunny Goyale2fd14b2015-08-27 17:45:46 -070059import com.android.launcher3.util.ParcelableSparseArray;
Adam Cohen091440a2015-03-18 14:16:05 -070060import com.android.launcher3.util.Thunk;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070061
Sunny Goyalc13403c2016-11-18 23:44:48 -080062import java.lang.annotation.Retention;
63import java.lang.annotation.RetentionPolicy;
Adam Cohen69ce2e52011-07-03 19:25:21 -070064import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070065import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080066import java.util.Collections;
67import java.util.Comparator;
Adam Cohenbfbfd262011-06-13 16:55:12 -070068import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080069import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070070
Sunny Goyal4b6eb262015-05-14 19:24:40 -070071public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
Sunny Goyale9b651e2015-04-24 11:44:51 -070072 public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
73 public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
74
Tony Wickhama0628cc2015-10-14 15:23:04 -070075 private static final String TAG = "CellLayout";
76 private static final boolean LOGD = false;
Winson Chungaafa03c2010-06-11 17:34:16 -070077
Adam Cohen2acce882012-03-28 19:03:19 -070078 private Launcher mLauncher;
Sunny Goyal4ffec482016-02-09 11:28:52 -080079 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070080 @Thunk int mCellWidth;
Sunny Goyal4ffec482016-02-09 11:28:52 -080081 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070082 @Thunk int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070083 private int mFixedCellWidth;
84 private int mFixedCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070085
Sunny Goyal4ffec482016-02-09 11:28:52 -080086 @ViewDebug.ExportedProperty(category = "launcher")
Sunny Goyalff4ba2d2016-04-02 14:12:34 -070087 private int mCountX;
Sunny Goyal4ffec482016-02-09 11:28:52 -080088 @ViewDebug.ExportedProperty(category = "launcher")
Sunny Goyalff4ba2d2016-04-02 14:12:34 -070089 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080090
Adam Cohen917e3882013-10-31 15:03:35 -070091 private boolean mDropPending = false;
Adam Cohenc50438c2014-08-19 17:43:05 -070092 private boolean mIsDragTarget = true;
Sunny Goyale2fd14b2015-08-27 17:45:46 -070093 private boolean mJailContent = true;
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;
Mady Melloref044dd2015-06-02 15:35:07 -0700104 private StylusEventHelper mStylusEventHelper;
Michael Jurkadee05892010-07-27 10:01:56 -0700105
Adam Cohenefca0272016-02-24 19:19:06 -0800106 private ArrayList<FolderIcon.PreviewBackground> mFolderBackgrounds = new ArrayList<FolderIcon.PreviewBackground>();
107 FolderIcon.PreviewBackground mFolderLeaveBehind = new FolderIcon.PreviewBackground();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700108
Michael Jurka5f1c5092010-09-03 14:15:02 -0700109 private float mBackgroundAlpha;
Adam Cohenf34bab52010-09-30 14:11:56 -0700110
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800111 private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active };
112 private static final int[] BACKGROUND_STATE_DEFAULT = new int[0];
113 private final Drawable mBackground;
Sunny Goyal2805e632015-05-20 15:35:32 -0700114
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700115 // These values allow a fixed measurement to be set on the CellLayout.
116 private int mFixedWidth = -1;
117 private int mFixedHeight = -1;
118
Michael Jurka33945b22010-12-21 18:19:38 -0800119 // If we're actively dragging something over this screen, mIsDragOverlapping is true
120 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700121
Winson Chung150fbab2010-09-29 17:14:26 -0700122 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700123 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohen091440a2015-03-18 14:16:05 -0700124 @Thunk Rect[] mDragOutlines = new Rect[4];
125 @Thunk float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700126 private InterruptibleInOutAnimator[] mDragOutlineAnims =
127 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700128
129 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700130 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700131 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700132
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700133 private final ClickShadowView mTouchFeedbackView;
Patrick Dubroy96864c32011-03-10 17:17:23 -0800134
Sunny Goyal316490e2015-06-02 09:38:28 -0700135 @Thunk HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new HashMap<>();
136 @Thunk HashMap<View, ReorderPreviewAnimation> mShakeAnimators = new HashMap<>();
Adam Cohen19f37922012-03-21 11:59:11 -0700137
138 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700139
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700140 // When a drag operation is in progress, holds the nearest cell to the touch point
141 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800142
Joe Onorato4be866d2010-10-10 11:26:02 -0700143 private boolean mDragging = false;
144
Patrick Dubroyce34a972010-10-19 10:34:32 -0700145 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700146 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700147
Sunny Goyalc13403c2016-11-18 23:44:48 -0800148 @Retention(RetentionPolicy.SOURCE)
149 @IntDef({WORKSPACE, HOTSEAT, FOLDER})
150 public @interface ContainerType{}
151 public static final int WORKSPACE = 0;
152 public static final int HOTSEAT = 1;
153 public static final int FOLDER = 2;
154
155 @ContainerType private final int mContainerType;
156
157 private final float mChildScale;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800158
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800159 public static final int MODE_SHOW_REORDER_HINT = 0;
160 public static final int MODE_DRAG_OVER = 1;
161 public static final int MODE_ON_DROP = 2;
162 public static final int MODE_ON_DROP_EXTERNAL = 3;
163 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700164 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800165 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
166
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800167 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700168 private static final int REORDER_ANIMATION_DURATION = 150;
Sunny Goyalc13403c2016-11-18 23:44:48 -0800169 @Thunk final float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700170
Adam Cohen482ed822012-03-02 14:15:13 -0800171 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
172 private Rect mOccupiedRect = new Rect();
173 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700174 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700175 private static final int INVALID_DIRECTION = -100;
Adam Cohen482ed822012-03-02 14:15:13 -0800176
Sunny Goyal2805e632015-05-20 15:35:32 -0700177 private final Rect mTempRect = new Rect();
Winson Chung3a6e7f32013-10-09 15:50:52 -0700178
Michael Jurkaca993832012-06-29 15:17:04 -0700179 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700180
Adam Cohenc9735cf2015-01-23 16:11:55 -0800181 // Related to accessible drag and drop
Sunny Goyale9b651e2015-04-24 11:44:51 -0700182 private DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800183 private boolean mUseTouchHelper = false;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800184
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800185 public CellLayout(Context context) {
186 this(context, null);
187 }
188
189 public CellLayout(Context context, AttributeSet attrs) {
190 this(context, attrs, 0);
191 }
192
193 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
194 super(context, attrs, defStyle);
Sunny Goyalc13403c2016-11-18 23:44:48 -0800195 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
196 mContainerType = a.getInteger(R.styleable.CellLayout_containerType, WORKSPACE);
197 a.recycle();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700198
199 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
200 // the user where a dragged item will land when dropped.
201 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800202 setClipToPadding(false);
Tony2fd02082016-10-07 12:50:01 -0700203 mLauncher = Launcher.getLauncher(context);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700204
Adam Cohen2e6da152015-05-06 11:42:25 -0700205 DeviceProfile grid = mLauncher.getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800206
Winson Chung11a1a532013-09-13 11:14:45 -0700207 mCellWidth = mCellHeight = -1;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800208 mFixedCellWidth = mFixedCellHeight = -1;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700209
210 mCountX = grid.inv.numColumns;
211 mCountY = grid.inv.numRows;
212 mOccupied = new GridOccupancy(mCountX, mCountY);
213 mTmpOccupied = new GridOccupancy(mCountX, mCountY);
214
Adam Cohen5b53f292012-03-29 14:30:35 -0700215 mPreviousReorderDirection[0] = INVALID_DIRECTION;
216 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800217
Adam Cohenefca0272016-02-24 19:19:06 -0800218 mFolderLeaveBehind.delegateCellX = -1;
219 mFolderLeaveBehind.delegateCellY = -1;
220
Sunny Goyalc13403c2016-11-18 23:44:48 -0800221 mChildScale = mContainerType == HOTSEAT ? grid.inv.hotseatScale : 1f;
222
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800223 setAlwaysDrawnWithCacheEnabled(false);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700224 final Resources res = getResources();
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700225
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800226 mBackground = res.getDrawable(R.drawable.bg_celllayout);
Sunny Goyal2805e632015-05-20 15:35:32 -0700227 mBackground.setCallback(this);
Winson Chunge8f1d042015-07-31 12:39:57 -0700228 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurka33945b22010-12-21 18:19:38 -0800229
Sunny Goyalc13403c2016-11-18 23:44:48 -0800230 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700231
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700232 // Initialize the data structures used for the drag visualization.
Patrick Dubroyce34a972010-10-19 10:34:32 -0700233 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700234 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700235 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800236 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700237 }
Sunny Goyalf28e6af2016-08-31 16:02:40 -0700238 mDragOutlinePaint.setColor(getResources().getColor(R.color.outline_color));
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700239
240 // When dragging things around the home screens, we show a green outline of
241 // where the item will land. The outlines gradually fade out, leaving a trail
242 // behind the drag path.
243 // Set up all the animations that are used to implement this fading.
244 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700245 final float fromAlphaValue = 0;
246 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700247
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700248 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700249
250 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700251 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100252 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700253 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700254 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700255 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700256 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700257 final Bitmap outline = (Bitmap)anim.getTag();
258
259 // If an animation is started and then stopped very quickly, we can still
260 // get spurious updates we've cleared the tag. Guard against this.
261 if (outline == null) {
Tony Wickhama0628cc2015-10-14 15:23:04 -0700262 if (LOGD) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700263 Object val = animation.getAnimatedValue();
264 Log.d(TAG, "anim " + thisIndex + " update: " + val +
265 ", isStopped " + anim.isStopped());
266 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700267 // Try to prevent it from continuing to run
268 animation.cancel();
269 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700270 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800271 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700272 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700273 }
274 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700275 // The animation holds a reference to the drag outline bitmap as long is it's
276 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700277 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700278 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700279 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700280 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700281 anim.setTag(null);
282 }
283 }
284 });
285 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700286 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700287
Sunny Goyalc13403c2016-11-18 23:44:48 -0800288 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType);
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530289 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700290
Mady Mellorbb835202015-07-15 16:34:34 -0700291 mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
Mady Melloref044dd2015-06-02 15:35:07 -0700292
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700293 mTouchFeedbackView = new ClickShadowView(context);
294 addView(mTouchFeedbackView);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700295 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700296 }
297
Sunny Goyale9b651e2015-04-24 11:44:51 -0700298 public void enableAccessibleDrag(boolean enable, int dragType) {
Adam Cohenc9735cf2015-01-23 16:11:55 -0800299 mUseTouchHelper = enable;
300 if (!enable) {
301 ViewCompat.setAccessibilityDelegate(this, null);
302 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
303 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
304 setOnClickListener(mLauncher);
305 } else {
Sunny Goyale9b651e2015-04-24 11:44:51 -0700306 if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
307 !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
308 mTouchHelper = new WorkspaceAccessibilityHelper(this);
309 } else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
310 !(mTouchHelper instanceof FolderAccessibilityHelper)) {
311 mTouchHelper = new FolderAccessibilityHelper(this);
312 }
Adam Cohenc9735cf2015-01-23 16:11:55 -0800313 ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
314 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
315 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
316 setOnClickListener(mTouchHelper);
317 }
318
319 // Invalidate the accessibility hierarchy
320 if (getParent() != null) {
321 getParent().notifySubtreeAccessibilityStateChanged(
322 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
323 }
324 }
325
326 @Override
327 public boolean dispatchHoverEvent(MotionEvent event) {
328 // Always attempt to dispatch hover events to accessibility first.
329 if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
330 return true;
331 }
332 return super.dispatchHoverEvent(event);
333 }
334
335 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800336 public boolean onInterceptTouchEvent(MotionEvent ev) {
337 if (mUseTouchHelper ||
338 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
339 return true;
340 }
341 return false;
342 }
343
Mady Melloref044dd2015-06-02 15:35:07 -0700344 @Override
345 public boolean onTouchEvent(MotionEvent ev) {
346 boolean handled = super.onTouchEvent(ev);
347 // Stylus button press on a home screen should not switch between overview mode and
348 // the home screen mode, however, once in overview mode stylus button press should be
349 // enabled to allow rearranging the different home screens. So check what mode
350 // the workspace is in, and only perform stylus button presses while in overview mode.
351 if (mLauncher.mWorkspace.isInOverviewMode()
Mady Mellorbb835202015-07-15 16:34:34 -0700352 && mStylusEventHelper.onMotionEvent(ev)) {
Mady Melloref044dd2015-06-02 15:35:07 -0700353 return true;
354 }
355 return handled;
356 }
357
Chris Craik01f2d7f2013-10-01 14:41:56 -0700358 public void enableHardwareLayer(boolean hasLayer) {
359 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700360 }
361
362 public void buildHardwareLayer() {
363 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700364 }
365
Winson Chung5f8afe62013-08-12 16:19:28 -0700366 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700367 mFixedCellWidth = mCellWidth = width;
368 mFixedCellHeight = mCellHeight = height;
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530369 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
Winson Chung5f8afe62013-08-12 16:19:28 -0700370 }
371
Adam Cohen2801caf2011-05-13 20:57:39 -0700372 public void setGridSize(int x, int y) {
373 mCountX = x;
374 mCountY = y;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700375 mOccupied = new GridOccupancy(mCountX, mCountY);
376 mTmpOccupied = new GridOccupancy(mCountX, mCountY);
Adam Cohen7fbec102012-03-27 12:42:19 -0700377 mTempRectStack.clear();
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530378 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700379 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700380 }
381
Adam Cohen2374abf2013-04-16 14:56:57 -0700382 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
383 public void setInvertIfRtl(boolean invert) {
384 mShortcutsAndWidgets.setInvertIfRtl(invert);
385 }
386
Adam Cohen917e3882013-10-31 15:03:35 -0700387 public void setDropPending(boolean pending) {
388 mDropPending = pending;
389 }
390
391 public boolean isDropPending() {
392 return mDropPending;
393 }
394
Sunny Goyal4b6eb262015-05-14 19:24:40 -0700395 @Override
396 public void setPressedIcon(BubbleTextView icon, Bitmap background) {
Sunny Goyal508da152014-08-14 10:53:27 -0700397 if (icon == null || background == null) {
398 mTouchFeedbackView.setBitmap(null);
399 mTouchFeedbackView.animate().cancel();
400 } else {
Sunny Goyal508da152014-08-14 10:53:27 -0700401 if (mTouchFeedbackView.setBitmap(background)) {
Winsonbe9798b2016-07-20 12:55:49 -0700402 mTouchFeedbackView.alignWithIconView(icon, mShortcutsAndWidgets,
403 null /* clipAgainstView */);
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700404 mTouchFeedbackView.animateShadow();
Sunny Goyal508da152014-08-14 10:53:27 -0700405 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800406 }
407 }
408
Adam Cohenc50438c2014-08-19 17:43:05 -0700409 void disableDragTarget() {
410 mIsDragTarget = false;
411 }
412
Tony Wickham0f97b782015-12-02 17:55:07 -0800413 public boolean isDragTarget() {
414 return mIsDragTarget;
415 }
416
Adam Cohenc50438c2014-08-19 17:43:05 -0700417 void setIsDragOverlapping(boolean isDragOverlapping) {
418 if (mIsDragOverlapping != isDragOverlapping) {
419 mIsDragOverlapping = isDragOverlapping;
Sunny Goyalf5440cb2016-12-14 15:13:00 -0800420 mBackground.setState(mIsDragOverlapping
421 ? BACKGROUND_STATE_ACTIVE : BACKGROUND_STATE_DEFAULT);
Adam Cohenc50438c2014-08-19 17:43:05 -0700422 invalidate();
423 }
424 }
425
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700426 public void disableJailContent() {
427 mJailContent = false;
428 }
429
430 @Override
431 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
432 if (mJailContent) {
433 ParcelableSparseArray jail = getJailedArray(container);
434 super.dispatchSaveInstanceState(jail);
435 container.put(R.id.cell_layout_jail_id, jail);
436 } else {
437 super.dispatchSaveInstanceState(container);
438 }
439 }
440
441 @Override
442 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
443 super.dispatchRestoreInstanceState(mJailContent ? getJailedArray(container) : container);
444 }
445
446 private ParcelableSparseArray getJailedArray(SparseArray<Parcelable> container) {
447 final Parcelable parcelable = container.get(R.id.cell_layout_jail_id);
448 return parcelable instanceof ParcelableSparseArray ?
449 (ParcelableSparseArray) parcelable : new ParcelableSparseArray();
450 }
451
Tony Wickham0f97b782015-12-02 17:55:07 -0800452 public boolean getIsDragOverlapping() {
453 return mIsDragOverlapping;
454 }
455
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700456 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700457 protected void onDraw(Canvas canvas) {
Sunny Goyal05739772015-05-19 19:59:09 -0700458 if (!mIsDragTarget) {
459 return;
460 }
461
Michael Jurka3e7c7632010-10-02 16:01:03 -0700462 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
463 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
464 // When we're small, we are either drawn normally or in the "accepts drops" state (during
465 // a drag). However, we also drag the mini hover background *over* one of those two
466 // backgrounds
Sunny Goyal05739772015-05-19 19:59:09 -0700467 if (mBackgroundAlpha > 0.0f) {
Sunny Goyal2805e632015-05-20 15:35:32 -0700468 mBackground.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700469 }
Romain Guya6abce82009-11-10 02:54:41 -0800470
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700471 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700472 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700473 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700474 if (alpha > 0) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700475 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700476 paint.setAlpha((int)(alpha + .5f));
Sunny Goyal106bf642015-07-16 12:18:06 -0700477 canvas.drawBitmap(b, null, mDragOutlines[i], paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700478 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700479 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800480
Adam Cohen482ed822012-03-02 14:15:13 -0800481 if (DEBUG_VISUALIZE_OCCUPIED) {
482 int[] pt = new int[2];
483 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700484 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800485 for (int i = 0; i < mCountX; i++) {
486 for (int j = 0; j < mCountY; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700487 if (mOccupied.cells[i][j]) {
Adam Cohen482ed822012-03-02 14:15:13 -0800488 cellToPoint(i, j, pt);
489 canvas.save();
490 canvas.translate(pt[0], pt[1]);
491 cd.draw(canvas);
492 canvas.restore();
493 }
494 }
495 }
496 }
497
Adam Cohenefca0272016-02-24 19:19:06 -0800498 for (int i = 0; i < mFolderBackgrounds.size(); i++) {
499 FolderIcon.PreviewBackground bg = mFolderBackgrounds.get(i);
500 cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
501 canvas.save();
502 canvas.translate(mTempLocation[0], mTempLocation[1]);
Sunny Goyal19b93b72017-02-19 20:21:37 -0800503 bg.drawBackground(canvas);
Adam Cohenf172b742016-03-30 19:28:34 -0700504 if (!bg.isClipping) {
Sunny Goyal19b93b72017-02-19 20:21:37 -0800505 bg.drawBackgroundStroke(canvas);
Adam Cohenf172b742016-03-30 19:28:34 -0700506 }
Adam Cohenefca0272016-02-24 19:19:06 -0800507 canvas.restore();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700508 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700509
Adam Cohenefca0272016-02-24 19:19:06 -0800510 if (mFolderLeaveBehind.delegateCellX >= 0 && mFolderLeaveBehind.delegateCellY >= 0) {
511 cellToPoint(mFolderLeaveBehind.delegateCellX,
512 mFolderLeaveBehind.delegateCellY, mTempLocation);
513 canvas.save();
514 canvas.translate(mTempLocation[0], mTempLocation[1]);
Sunny Goyal19b93b72017-02-19 20:21:37 -0800515 mFolderLeaveBehind.drawLeaveBehind(canvas);
Adam Cohenefca0272016-02-24 19:19:06 -0800516 canvas.restore();
Adam Cohenc51934b2011-07-26 21:07:43 -0700517 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700518 }
519
Adam Cohenefca0272016-02-24 19:19:06 -0800520 @Override
521 protected void dispatchDraw(Canvas canvas) {
522 super.dispatchDraw(canvas);
523
524 for (int i = 0; i < mFolderBackgrounds.size(); i++) {
525 FolderIcon.PreviewBackground bg = mFolderBackgrounds.get(i);
Adam Cohenf172b742016-03-30 19:28:34 -0700526 if (bg.isClipping) {
527 cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
528 canvas.save();
529 canvas.translate(mTempLocation[0], mTempLocation[1]);
Sunny Goyal19b93b72017-02-19 20:21:37 -0800530 bg.drawBackgroundStroke(canvas);
Adam Cohenf172b742016-03-30 19:28:34 -0700531 canvas.restore();
532 }
Adam Cohenefca0272016-02-24 19:19:06 -0800533 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700534 }
535
Adam Cohenefca0272016-02-24 19:19:06 -0800536 public void addFolderBackground(FolderIcon.PreviewBackground bg) {
537 mFolderBackgrounds.add(bg);
538 }
539 public void removeFolderBackground(FolderIcon.PreviewBackground bg) {
540 mFolderBackgrounds.remove(bg);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700541 }
542
Adam Cohenc51934b2011-07-26 21:07:43 -0700543 public void setFolderLeaveBehindCell(int x, int y) {
Adam Cohenefca0272016-02-24 19:19:06 -0800544
545 DeviceProfile grid = mLauncher.getDeviceProfile();
546 View child = getChildAt(x, y);
547
548 mFolderLeaveBehind.setup(getResources().getDisplayMetrics(), grid, null,
549 child.getMeasuredWidth(), child.getPaddingTop());
550
551 mFolderLeaveBehind.delegateCellX = x;
552 mFolderLeaveBehind.delegateCellY = y;
Adam Cohenc51934b2011-07-26 21:07:43 -0700553 invalidate();
554 }
555
556 public void clearFolderLeaveBehind() {
Adam Cohenefca0272016-02-24 19:19:06 -0800557 mFolderLeaveBehind.delegateCellX = -1;
558 mFolderLeaveBehind.delegateCellY = -1;
Adam Cohenc51934b2011-07-26 21:07:43 -0700559 invalidate();
560 }
561
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700562 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700563 public boolean shouldDelayChildPressedState() {
564 return false;
565 }
566
Adam Cohen1462de32012-07-24 22:34:36 -0700567 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700568 try {
569 dispatchRestoreInstanceState(states);
570 } catch (IllegalArgumentException ex) {
Sunny Goyal6c56c682015-07-16 14:09:05 -0700571 if (ProviderConfig.IS_DOGFOOD_BUILD) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700572 throw ex;
573 }
574 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
575 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
576 }
Adam Cohen1462de32012-07-24 22:34:36 -0700577 }
578
Michael Jurkae6235dd2011-10-04 15:02:05 -0700579 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700580 public void cancelLongPress() {
581 super.cancelLongPress();
582
583 // Cancel long press for all children
584 final int count = getChildCount();
585 for (int i = 0; i < count; i++) {
586 final View child = getChildAt(i);
587 child.cancelLongPress();
588 }
589 }
590
Michael Jurkadee05892010-07-27 10:01:56 -0700591 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
592 mInterceptTouchListener = listener;
593 }
594
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800595 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700596 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800597 }
598
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800599 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700600 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800601 }
602
Sunny Goyalc13403c2016-11-18 23:44:48 -0800603 public boolean acceptsWidget() {
604 return mContainerType == WORKSPACE;
Sunny Goyale9b651e2015-04-24 11:44:51 -0700605 }
606
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800607 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700608 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700609 final LayoutParams lp = params;
610
Andrew Flynnde38e422012-05-08 11:22:15 -0700611 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800612 if (child instanceof BubbleTextView) {
613 BubbleTextView bubbleChild = (BubbleTextView) child;
Sunny Goyalc13403c2016-11-18 23:44:48 -0800614 bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800615 }
616
Sunny Goyalc13403c2016-11-18 23:44:48 -0800617 child.setScaleX(mChildScale);
618 child.setScaleY(mChildScale);
Adam Cohen307fe232012-08-16 17:55:58 -0700619
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800620 // Generate an id for each view, this assumes we have at most 256x256 cells
621 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700622 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700623 // If the horizontal or vertical span is set to -1, it is taken to
624 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700625 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
626 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800627
Winson Chungaafa03c2010-06-11 17:34:16 -0700628 child.setId(childId);
Tony Wickhama0628cc2015-10-14 15:23:04 -0700629 if (LOGD) {
630 Log.d(TAG, "Adding view to ShortcutsAndWidgetsContainer: " + child);
631 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700632 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700633
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700634 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700635
Winson Chungaafa03c2010-06-11 17:34:16 -0700636 return true;
637 }
638 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800639 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700640
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800641 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700642 public void removeAllViews() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700643 mOccupied.clear();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700644 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700645 }
646
647 @Override
648 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700649 if (mShortcutsAndWidgets.getChildCount() > 0) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700650 mOccupied.clear();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700651 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700652 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700653 }
654
655 @Override
656 public void removeView(View view) {
657 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700658 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700659 }
660
661 @Override
662 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700663 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
664 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700665 }
666
667 @Override
668 public void removeViewInLayout(View view) {
669 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700670 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700671 }
672
673 @Override
674 public void removeViews(int start, int count) {
675 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700676 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700677 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700678 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700679 }
680
681 @Override
682 public void removeViewsInLayout(int start, int count) {
683 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700684 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700685 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700686 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800687 }
688
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700689 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700690 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800691 * @param x X coordinate of the point
692 * @param y Y coordinate of the point
693 * @param result Array of 2 ints to hold the x and y coordinate of the cell
694 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700695 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700696 final int hStartPadding = getPaddingLeft();
697 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800698
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530699 result[0] = (x - hStartPadding) / mCellWidth;
700 result[1] = (y - vStartPadding) / mCellHeight;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800701
Adam Cohend22015c2010-07-26 22:02:18 -0700702 final int xAxis = mCountX;
703 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800704
705 if (result[0] < 0) result[0] = 0;
706 if (result[0] >= xAxis) result[0] = xAxis - 1;
707 if (result[1] < 0) result[1] = 0;
708 if (result[1] >= yAxis) result[1] = yAxis - 1;
709 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700710
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800711 /**
712 * Given a point, return the cell that most closely encloses that point
713 * @param x X coordinate of the point
714 * @param y Y coordinate of the point
715 * @param result Array of 2 ints to hold the x and y coordinate of the cell
716 */
717 void pointToCellRounded(int x, int y, int[] result) {
718 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
719 }
720
721 /**
722 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700723 *
724 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800725 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700726 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800727 * @param result Array of 2 ints to hold the x and y coordinate of the point
728 */
729 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700730 final int hStartPadding = getPaddingLeft();
731 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800732
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530733 result[0] = hStartPadding + cellX * mCellWidth;
734 result[1] = vStartPadding + cellY * mCellHeight;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800735 }
736
Adam Cohene3e27a82011-04-15 12:07:39 -0700737 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800738 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700739 *
740 * @param cellX X coordinate of the cell
741 * @param cellY Y coordinate of the cell
742 *
743 * @param result Array of 2 ints to hold the x and y coordinate of the point
744 */
745 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700746 regionToCenterPoint(cellX, cellY, 1, 1, result);
747 }
748
749 /**
750 * Given a cell coordinate and span return the point that represents the center of the regio
751 *
752 * @param cellX X coordinate of the cell
753 * @param cellY Y coordinate of the cell
754 *
755 * @param result Array of 2 ints to hold the x and y coordinate of the point
756 */
757 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700758 final int hStartPadding = getPaddingLeft();
759 final int vStartPadding = getPaddingTop();
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530760 result[0] = hStartPadding + cellX * mCellWidth + (spanX * mCellWidth) / 2;
761 result[1] = vStartPadding + cellY * mCellHeight + (spanY * mCellHeight) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700762 }
763
Adam Cohen19f37922012-03-21 11:59:11 -0700764 /**
765 * Given a cell coordinate and span fills out a corresponding pixel rect
766 *
767 * @param cellX X coordinate of the cell
768 * @param cellY Y coordinate of the cell
769 * @param result Rect in which to write the result
770 */
771 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
772 final int hStartPadding = getPaddingLeft();
773 final int vStartPadding = getPaddingTop();
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530774 final int left = hStartPadding + cellX * mCellWidth;
775 final int top = vStartPadding + cellY * mCellHeight;
776 result.set(left, top, left + (spanX * mCellWidth), top + (spanY * mCellHeight));
Adam Cohen19f37922012-03-21 11:59:11 -0700777 }
778
Adam Cohen482ed822012-03-02 14:15:13 -0800779 public float getDistanceFromCell(float x, float y, int[] cell) {
780 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700781 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800782 }
783
Adam Cohenf9c184a2016-01-15 16:47:43 -0800784 public int getCellWidth() {
Romain Guy84f296c2009-11-04 15:00:44 -0800785 return mCellWidth;
786 }
787
788 int getCellHeight() {
789 return mCellHeight;
790 }
791
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700792 public void setFixedSize(int width, int height) {
793 mFixedWidth = width;
794 mFixedHeight = height;
795 }
796
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800797 @Override
798 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800799 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800800 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700801 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
802 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700803 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
804 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700805 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Sunny Goyalc6205602015-05-21 20:46:33 -0700806 int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX);
807 int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700808 if (cw != mCellWidth || ch != mCellHeight) {
809 mCellWidth = cw;
810 mCellHeight = ch;
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530811 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700812 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700813 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700814
Winson Chung2d75f122013-09-23 16:53:31 -0700815 int newWidth = childWidthSize;
816 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700817 if (mFixedWidth > 0 && mFixedHeight > 0) {
818 newWidth = mFixedWidth;
819 newHeight = mFixedHeight;
820 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800821 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
822 }
823
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700824 // Make the feedback view large enough to hold the blur bitmap.
825 mTouchFeedbackView.measure(
826 MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
827 MeasureSpec.EXACTLY),
828 MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
829 MeasureSpec.EXACTLY));
830
831 mShortcutsAndWidgets.measure(
832 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
833 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
834
835 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
836 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -0700837 if (mFixedWidth > 0 && mFixedHeight > 0) {
838 setMeasuredDimension(maxWidth, maxHeight);
839 } else {
840 setMeasuredDimension(widthSize, heightSize);
841 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800842 }
843
844 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700845 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Tony Wickham26b01422015-11-10 14:44:32 -0800846 boolean isFullscreen = mShortcutsAndWidgets.getChildCount() > 0 &&
847 ((LayoutParams) mShortcutsAndWidgets.getChildAt(0).getLayoutParams()).isFullscreen;
848 int left = getPaddingLeft();
849 if (!isFullscreen) {
Tony Wickhama501d492015-11-03 18:05:01 -0800850 left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Tony Wickham26b01422015-11-10 14:44:32 -0800851 }
Sunny Goyal7c786f72016-06-01 14:08:21 -0700852 int right = r - l - getPaddingRight();
853 if (!isFullscreen) {
854 right -= (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
855 }
856
Winson Chung38848ca2013-10-08 12:03:44 -0700857 int top = getPaddingTop();
Sunny Goyal7c786f72016-06-01 14:08:21 -0700858 int bottom = b - t - getPaddingBottom();
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700859
860 mTouchFeedbackView.layout(left, top,
861 left + mTouchFeedbackView.getMeasuredWidth(),
862 top + mTouchFeedbackView.getMeasuredHeight());
Sunny Goyal7c786f72016-06-01 14:08:21 -0700863 mShortcutsAndWidgets.layout(left, top, right, bottom);
864
865 // Expand the background drawing bounds by the padding baked into the background drawable
866 mBackground.getPadding(mTempRect);
867 mBackground.setBounds(
868 left - mTempRect.left,
869 top - mTempRect.top,
870 right + mTempRect.right,
871 bottom + mTempRect.bottom);
872 }
873
Tony Wickhama501d492015-11-03 18:05:01 -0800874 /**
875 * Returns the amount of space left over after subtracting padding and cells. This space will be
876 * very small, a few pixels at most, and is a result of rounding down when calculating the cell
877 * width in {@link DeviceProfile#calculateCellWidth(int, int)}.
878 */
879 public int getUnusedHorizontalSpace() {
880 return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
881 }
882
Michael Jurka5f1c5092010-09-03 14:15:02 -0700883 public float getBackgroundAlpha() {
884 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -0700885 }
886
Michael Jurka5f1c5092010-09-03 14:15:02 -0700887 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -0800888 if (mBackgroundAlpha != alpha) {
889 mBackgroundAlpha = alpha;
Sunny Goyal2805e632015-05-20 15:35:32 -0700890 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurkaafaa0502011-12-13 18:22:50 -0800891 }
Michael Jurkadee05892010-07-27 10:01:56 -0700892 }
893
Sunny Goyal2805e632015-05-20 15:35:32 -0700894 @Override
895 protected boolean verifyDrawable(Drawable who) {
896 return super.verifyDrawable(who) || (mIsDragTarget && who == mBackground);
897 }
898
Michael Jurkaa52570f2012-03-20 03:18:20 -0700899 public void setShortcutAndWidgetAlpha(float alpha) {
Sunny Goyal02b50812014-09-10 15:44:42 -0700900 mShortcutsAndWidgets.setAlpha(alpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700901 }
902
Michael Jurkaa52570f2012-03-20 03:18:20 -0700903 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700904 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700905 }
906
Patrick Dubroy440c3602010-07-13 17:50:32 -0700907 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700908 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700909 }
910
Adam Cohen76fc0852011-06-17 13:26:23 -0700911 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800912 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700913 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800914
Adam Cohen19f37922012-03-21 11:59:11 -0700915 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700916 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
917 final ItemInfo info = (ItemInfo) child.getTag();
918
919 // We cancel any existing animations
920 if (mReorderAnimators.containsKey(lp)) {
921 mReorderAnimators.get(lp).cancel();
922 mReorderAnimators.remove(lp);
923 }
924
Adam Cohen482ed822012-03-02 14:15:13 -0800925 final int oldX = lp.x;
926 final int oldY = lp.y;
927 if (adjustOccupied) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700928 GridOccupancy occupied = permanent ? mOccupied : mTmpOccupied;
929 occupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
930 occupied.markCells(cellX, cellY, lp.cellHSpan, lp.cellVSpan, true);
Adam Cohen482ed822012-03-02 14:15:13 -0800931 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700932 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -0800933 if (permanent) {
934 lp.cellX = info.cellX = cellX;
935 lp.cellY = info.cellY = cellY;
936 } else {
937 lp.tmpCellX = cellX;
938 lp.tmpCellY = cellY;
939 }
Jon Mirandae96798e2016-12-07 12:10:44 -0800940 clc.setupLp(child);
Adam Cohenbfbfd262011-06-13 16:55:12 -0700941 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800942 final int newX = lp.x;
943 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700944
Adam Cohen76fc0852011-06-17 13:26:23 -0700945 lp.x = oldX;
946 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -0700947
Adam Cohen482ed822012-03-02 14:15:13 -0800948 // Exit early if we're not actually moving the view
949 if (oldX == newX && oldY == newY) {
950 lp.isLockedToGrid = true;
951 return true;
952 }
953
Jon Mirandacda3bfb2017-02-06 15:54:41 -0800954 ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -0800955 va.setDuration(duration);
956 mReorderAnimators.put(lp, va);
957
958 va.addUpdateListener(new AnimatorUpdateListener() {
959 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -0700960 public void onAnimationUpdate(ValueAnimator animation) {
Jon Mirandae96798e2016-12-07 12:10:44 -0800961 float r = (Float) animation.getAnimatedValue();
Adam Cohen19f37922012-03-21 11:59:11 -0700962 lp.x = (int) ((1 - r) * oldX + r * newX);
963 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -0700964 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -0700965 }
966 });
Adam Cohen482ed822012-03-02 14:15:13 -0800967 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700968 boolean cancelled = false;
969 public void onAnimationEnd(Animator animation) {
970 // If the animation was cancelled, it means that another animation
971 // has interrupted this one, and we don't want to lock the item into
972 // place just yet.
973 if (!cancelled) {
974 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -0800975 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -0700976 }
977 if (mReorderAnimators.containsKey(lp)) {
978 mReorderAnimators.remove(lp);
979 }
980 }
981 public void onAnimationCancel(Animator animation) {
982 cancelled = true;
983 }
984 });
Adam Cohen482ed822012-03-02 14:15:13 -0800985 va.setStartDelay(delay);
986 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -0700987 return true;
988 }
989 return false;
990 }
991
Sunny Goyal06e21a22016-08-11 16:02:02 -0700992 void visualizeDropLocation(View v, DragPreviewProvider outlineProvider, int cellX, int cellY,
993 int spanX, int spanY, boolean resize, DropTarget.DragObject dragObject) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -0700994 final int oldDragCellX = mDragCell[0];
995 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -0800996
Hyunyoung Song0de01172016-10-05 16:27:48 -0700997 if (outlineProvider == null || outlineProvider.generatedDragOutline == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -0700998 return;
999 }
1000
Hyunyoung Song0de01172016-10-05 16:27:48 -07001001 Bitmap dragOutline = outlineProvider.generatedDragOutline;
Adam Cohen482ed822012-03-02 14:15:13 -08001002 if (cellX != oldDragCellX || cellY != oldDragCellY) {
Sunny Goyale78e3d72015-09-24 11:23:31 -07001003 Point dragOffset = dragObject.dragView.getDragVisualizeOffset();
1004 Rect dragRegion = dragObject.dragView.getDragRegion();
1005
Adam Cohen482ed822012-03-02 14:15:13 -08001006 mDragCell[0] = cellX;
1007 mDragCell[1] = cellY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001008
Joe Onorato4be866d2010-10-10 11:26:02 -07001009 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001010 mDragOutlineAnims[oldIndex].animateOut();
1011 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001012 Rect r = mDragOutlines[mDragOutlineCurrent];
Sunny Goyal106bf642015-07-16 12:18:06 -07001013
Adam Cohend41fbf52012-02-16 23:53:59 -08001014 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001015 cellToRect(cellX, cellY, spanX, spanY, r);
Jon Mirandae96798e2016-12-07 12:10:44 -08001016 if (v instanceof LauncherAppWidgetHostView) {
1017 DeviceProfile profile = mLauncher.getDeviceProfile();
Jon Miranda6f6a06a2016-12-15 11:24:18 -08001018 Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
Jon Mirandae96798e2016-12-07 12:10:44 -08001019 }
Sunny Goyal106bf642015-07-16 12:18:06 -07001020 } else {
1021 // Find the top left corner of the rect the object will occupy
1022 final int[] topLeft = mTmpPoint;
1023 cellToPoint(cellX, cellY, topLeft);
1024
1025 int left = topLeft[0];
1026 int top = topLeft[1];
1027
1028 if (v != null && dragOffset == null) {
1029 // When drawing the drag outline, it did not account for margin offsets
1030 // added by the view's parent.
1031 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1032 left += lp.leftMargin;
1033 top += lp.topMargin;
1034
1035 // Offsets due to the size difference between the View and the dragOutline.
1036 // There is a size difference to account for the outer blur, which may lie
1037 // outside the bounds of the view.
Jon Mirandaf7ff3fe2016-12-05 12:04:44 -08001038 top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
Sunny Goyal106bf642015-07-16 12:18:06 -07001039 // We center about the x axis
Sunny Goyalaa8a8712016-11-20 15:26:01 +05301040 left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
Sunny Goyal106bf642015-07-16 12:18:06 -07001041 } else {
1042 if (dragOffset != null && dragRegion != null) {
1043 // Center the drag region *horizontally* in the cell and apply a drag
1044 // outline offset
Sunny Goyalaa8a8712016-11-20 15:26:01 +05301045 left += dragOffset.x + ((mCellWidth * spanX) - dragRegion.width()) / 2;
Sunny Goyal106bf642015-07-16 12:18:06 -07001046 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1047 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1048 top += dragOffset.y + cellPaddingY;
1049 } else {
1050 // Center the drag outline in the cell
Sunny Goyalaa8a8712016-11-20 15:26:01 +05301051 left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
1052 top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
Sunny Goyal106bf642015-07-16 12:18:06 -07001053 }
1054 }
1055 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
Adam Cohend41fbf52012-02-16 23:53:59 -08001056 }
Winson Chung150fbab2010-09-29 17:14:26 -07001057
Jon Miranda6f6a06a2016-12-15 11:24:18 -08001058 Utilities.scaleRectAboutCenter(r, mChildScale);
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001059 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1060 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Sunny Goyale78e3d72015-09-24 11:23:31 -07001061
1062 if (dragObject.stateAnnouncer != null) {
Sunny Goyalc13403c2016-11-18 23:44:48 -08001063 dragObject.stateAnnouncer.announce(getItemMoveDescription(cellX, cellY));
Sunny Goyale78e3d72015-09-24 11:23:31 -07001064 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001065 }
1066 }
1067
Sunny Goyalc13403c2016-11-18 23:44:48 -08001068 public String getItemMoveDescription(int cellX, int cellY) {
1069 if (mContainerType == HOTSEAT) {
1070 return getContext().getString(R.string.move_to_hotseat_position,
1071 Math.max(cellX, cellY) + 1);
1072 } else {
1073 return getContext().getString(R.string.move_to_empty_cell,
1074 cellY + 1, cellX + 1);
1075 }
1076 }
1077
Adam Cohene0310962011-04-18 16:15:31 -07001078 public void clearDragOutlines() {
1079 final int oldIndex = mDragOutlineCurrent;
1080 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001081 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001082 }
1083
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001084 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001085 * Find a vacant area that will fit the given bounds nearest the requested
1086 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001087 *
Romain Guy51afc022009-05-04 18:03:43 -07001088 * @param pixelX The X location at which you want to search for a vacant area.
1089 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001090 * @param minSpanX The minimum horizontal span required
1091 * @param minSpanY The minimum vertical span required
1092 * @param spanX Horizontal span of the object.
1093 * @param spanY Vertical span of the object.
1094 * @param result Array in which to place the result, or null (in which case a new array will
1095 * be allocated)
1096 * @return The X, Y cell of a vacant area that can contain this object,
1097 * nearest the requested location.
1098 */
1099 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1100 int spanY, int[] result, int[] resultSpan) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001101 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
Adam Cohend41fbf52012-02-16 23:53:59 -08001102 result, resultSpan);
1103 }
1104
Adam Cohend41fbf52012-02-16 23:53:59 -08001105 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1106 private void lazyInitTempRectStack() {
1107 if (mTempRectStack.isEmpty()) {
1108 for (int i = 0; i < mCountX * mCountY; i++) {
1109 mTempRectStack.push(new Rect());
1110 }
1111 }
1112 }
Adam Cohen482ed822012-03-02 14:15:13 -08001113
Adam Cohend41fbf52012-02-16 23:53:59 -08001114 private void recycleTempRects(Stack<Rect> used) {
1115 while (!used.isEmpty()) {
1116 mTempRectStack.push(used.pop());
1117 }
1118 }
1119
1120 /**
1121 * Find a vacant area that will fit the given bounds nearest the requested
1122 * cell location. Uses Euclidean distance to score multiple vacant areas.
1123 *
1124 * @param pixelX The X location at which you want to search for a vacant area.
1125 * @param pixelY The Y location at which you want to search for a vacant area.
1126 * @param minSpanX The minimum horizontal span required
1127 * @param minSpanY The minimum vertical span required
1128 * @param spanX Horizontal span of the object.
1129 * @param spanY Vertical span of the object.
1130 * @param ignoreOccupied If true, the result can be an occupied cell
1131 * @param result Array in which to place the result, or null (in which case a new array will
1132 * be allocated)
1133 * @return The X, Y cell of a vacant area that can contain this object,
1134 * nearest the requested location.
1135 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001136 private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1137 int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001138 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001139
Adam Cohene3e27a82011-04-15 12:07:39 -07001140 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1141 // to the center of the item, but we are searching based on the top-left cell, so
1142 // we translate the point over to correspond to the top-left.
Sunny Goyalaa8a8712016-11-20 15:26:01 +05301143 pixelX -= mCellWidth * (spanX - 1) / 2f;
1144 pixelY -= mCellHeight * (spanY - 1) / 2f;
Adam Cohene3e27a82011-04-15 12:07:39 -07001145
Jeff Sharkey70864282009-04-07 21:08:40 -07001146 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001147 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001148 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001149 final Rect bestRect = new Rect(-1, -1, -1, -1);
1150 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001151
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001152 final int countX = mCountX;
1153 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001154
Adam Cohend41fbf52012-02-16 23:53:59 -08001155 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1156 spanX < minSpanX || spanY < minSpanY) {
1157 return bestXY;
1158 }
1159
1160 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001161 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001162 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1163 int ySize = -1;
1164 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001165 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001166 // First, let's see if this thing fits anywhere
1167 for (int i = 0; i < minSpanX; i++) {
1168 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001169 if (mOccupied.cells[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001170 continue inner;
1171 }
Michael Jurkac28de512010-08-13 11:27:44 -07001172 }
1173 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001174 xSize = minSpanX;
1175 ySize = minSpanY;
1176
1177 // We know that the item will fit at _some_ acceptable size, now let's see
1178 // how big we can make it. We'll alternate between incrementing x and y spans
1179 // until we hit a limit.
1180 boolean incX = true;
1181 boolean hitMaxX = xSize >= spanX;
1182 boolean hitMaxY = ySize >= spanY;
1183 while (!(hitMaxX && hitMaxY)) {
1184 if (incX && !hitMaxX) {
1185 for (int j = 0; j < ySize; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001186 if (x + xSize > countX -1 || mOccupied.cells[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001187 // We can't move out horizontally
1188 hitMaxX = true;
1189 }
1190 }
1191 if (!hitMaxX) {
1192 xSize++;
1193 }
1194 } else if (!hitMaxY) {
1195 for (int i = 0; i < xSize; i++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001196 if (y + ySize > countY - 1 || mOccupied.cells[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001197 // We can't move out vertically
1198 hitMaxY = true;
1199 }
1200 }
1201 if (!hitMaxY) {
1202 ySize++;
1203 }
1204 }
1205 hitMaxX |= xSize >= spanX;
1206 hitMaxY |= ySize >= spanY;
1207 incX = !incX;
1208 }
1209 incX = true;
1210 hitMaxX = xSize >= spanX;
1211 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001212 }
Sunny Goyal2805e632015-05-20 15:35:32 -07001213 final int[] cellXY = mTmpPoint;
Adam Cohene3e27a82011-04-15 12:07:39 -07001214 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001215
Adam Cohend41fbf52012-02-16 23:53:59 -08001216 // We verify that the current rect is not a sub-rect of any of our previous
1217 // candidates. In this case, the current rect is disqualified in favour of the
1218 // containing rect.
1219 Rect currentRect = mTempRectStack.pop();
1220 currentRect.set(x, y, x + xSize, y + ySize);
1221 boolean contained = false;
1222 for (Rect r : validRegions) {
1223 if (r.contains(currentRect)) {
1224 contained = true;
1225 break;
1226 }
1227 }
1228 validRegions.push(currentRect);
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001229 double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY);
Adam Cohen482ed822012-03-02 14:15:13 -08001230
Adam Cohend41fbf52012-02-16 23:53:59 -08001231 if ((distance <= bestDistance && !contained) ||
1232 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001233 bestDistance = distance;
1234 bestXY[0] = x;
1235 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001236 if (resultSpan != null) {
1237 resultSpan[0] = xSize;
1238 resultSpan[1] = ySize;
1239 }
1240 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001241 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001242 }
1243 }
1244
Adam Cohenc0dcf592011-06-01 15:30:43 -07001245 // Return -1, -1 if no suitable location found
1246 if (bestDistance == Double.MAX_VALUE) {
1247 bestXY[0] = -1;
1248 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001249 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001250 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001251 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001252 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001253
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001254 /**
Adam Cohen482ed822012-03-02 14:15:13 -08001255 * Find a vacant area that will fit the given bounds nearest the requested
1256 * cell location, and will also weigh in a suggested direction vector of the
1257 * desired location. This method computers distance based on unit grid distances,
1258 * not pixel distances.
1259 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001260 * @param cellX The X cell nearest to which you want to search for a vacant area.
1261 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001262 * @param spanX Horizontal span of the object.
1263 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001264 * @param direction The favored direction in which the views should move from x, y
Sunny Goyal9eba1fd2015-10-16 08:58:57 -07001265 * @param occupied The array which represents which cells in the CellLayout are occupied
Adam Cohen47a876d2012-03-19 13:21:41 -07001266 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001267 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001268 * @param result Array in which to place the result, or null (in which case a new array will
1269 * be allocated)
1270 * @return The X, Y cell of a vacant area that can contain this object,
1271 * nearest the requested location.
1272 */
1273 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001274 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001275 // Keep track of best-scoring drop area
1276 final int[] bestXY = result != null ? result : new int[2];
1277 float bestDistance = Float.MAX_VALUE;
1278 int bestDirectionScore = Integer.MIN_VALUE;
1279
1280 final int countX = mCountX;
1281 final int countY = mCountY;
1282
1283 for (int y = 0; y < countY - (spanY - 1); y++) {
1284 inner:
1285 for (int x = 0; x < countX - (spanX - 1); x++) {
1286 // First, let's see if this thing fits anywhere
1287 for (int i = 0; i < spanX; i++) {
1288 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001289 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001290 continue inner;
1291 }
1292 }
1293 }
1294
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001295 float distance = (float) Math.hypot(x - cellX, y - cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001296 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001297 computeDirectionVector(x - cellX, y - cellY, curDirection);
1298 // The direction score is just the dot product of the two candidate direction
1299 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001300 int curDirectionScore = direction[0] * curDirection[0] +
1301 direction[1] * curDirection[1];
Sunny Goyal8f90dcf2016-08-18 15:01:11 -07001302 if (Float.compare(distance, bestDistance) < 0 ||
1303 (Float.compare(distance, bestDistance) == 0
1304 && curDirectionScore > bestDirectionScore)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001305 bestDistance = distance;
1306 bestDirectionScore = curDirectionScore;
1307 bestXY[0] = x;
1308 bestXY[1] = y;
1309 }
1310 }
1311 }
1312
1313 // Return -1, -1 if no suitable location found
1314 if (bestDistance == Float.MAX_VALUE) {
1315 bestXY[0] = -1;
1316 bestXY[1] = -1;
1317 }
1318 return bestXY;
1319 }
1320
1321 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001322 int[] direction, ItemConfiguration currentState) {
1323 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001324 boolean success = false;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001325 mTmpOccupied.markCells(c, false);
1326 mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001327
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001328 findNearestArea(c.cellX, c.cellY, c.spanX, c.spanY, direction,
1329 mTmpOccupied.cells, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001330
1331 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001332 c.cellX = mTempLocation[0];
1333 c.cellY = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001334 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001335 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001336 mTmpOccupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001337 return success;
1338 }
1339
Adam Cohenf3900c22012-11-16 18:28:11 -08001340 /**
1341 * This helper class defines a cluster of views. It helps with defining complex edges
1342 * of the cluster and determining how those edges interact with other views. The edges
1343 * essentially define a fine-grained boundary around the cluster of views -- like a more
1344 * precise version of a bounding box.
1345 */
1346 private class ViewCluster {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001347 final static int LEFT = 1 << 0;
1348 final static int TOP = 1 << 1;
1349 final static int RIGHT = 1 << 2;
1350 final static int BOTTOM = 1 << 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001351
Adam Cohenf3900c22012-11-16 18:28:11 -08001352 ArrayList<View> views;
1353 ItemConfiguration config;
1354 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001355
Adam Cohenf3900c22012-11-16 18:28:11 -08001356 int[] leftEdge = new int[mCountY];
1357 int[] rightEdge = new int[mCountY];
1358 int[] topEdge = new int[mCountX];
1359 int[] bottomEdge = new int[mCountX];
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001360 int dirtyEdges;
1361 boolean boundingRectDirty;
Adam Cohenf3900c22012-11-16 18:28:11 -08001362
1363 @SuppressWarnings("unchecked")
1364 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1365 this.views = (ArrayList<View>) views.clone();
1366 this.config = config;
1367 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001368 }
1369
Adam Cohenf3900c22012-11-16 18:28:11 -08001370 void resetEdges() {
1371 for (int i = 0; i < mCountX; i++) {
1372 topEdge[i] = -1;
1373 bottomEdge[i] = -1;
1374 }
1375 for (int i = 0; i < mCountY; i++) {
1376 leftEdge[i] = -1;
1377 rightEdge[i] = -1;
1378 }
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001379 dirtyEdges = LEFT | TOP | RIGHT | BOTTOM;
Adam Cohenf3900c22012-11-16 18:28:11 -08001380 boundingRectDirty = true;
1381 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001382
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001383 void computeEdge(int which) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001384 int count = views.size();
1385 for (int i = 0; i < count; i++) {
1386 CellAndSpan cs = config.map.get(views.get(i));
1387 switch (which) {
1388 case LEFT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001389 int left = cs.cellX;
1390 for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001391 if (left < leftEdge[j] || leftEdge[j] < 0) {
1392 leftEdge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001393 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001394 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001395 break;
1396 case RIGHT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001397 int right = cs.cellX + cs.spanX;
1398 for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001399 if (right > rightEdge[j]) {
1400 rightEdge[j] = right;
Adam Cohenf3900c22012-11-16 18:28:11 -08001401 }
1402 }
1403 break;
1404 case TOP:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001405 int top = cs.cellY;
1406 for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001407 if (top < topEdge[j] || topEdge[j] < 0) {
1408 topEdge[j] = top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001409 }
1410 }
1411 break;
1412 case BOTTOM:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001413 int bottom = cs.cellY + cs.spanY;
1414 for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001415 if (bottom > bottomEdge[j]) {
1416 bottomEdge[j] = bottom;
Adam Cohenf3900c22012-11-16 18:28:11 -08001417 }
1418 }
1419 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001420 }
1421 }
1422 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001423
1424 boolean isViewTouchingEdge(View v, int whichEdge) {
1425 CellAndSpan cs = config.map.get(v);
1426
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001427 if ((dirtyEdges & whichEdge) == whichEdge) {
1428 computeEdge(whichEdge);
1429 dirtyEdges &= ~whichEdge;
1430 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001431
1432 switch (whichEdge) {
1433 case LEFT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001434 for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) {
1435 if (leftEdge[i] == cs.cellX + cs.spanX) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001436 return true;
1437 }
1438 }
1439 break;
1440 case RIGHT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001441 for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) {
1442 if (rightEdge[i] == cs.cellX) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001443 return true;
1444 }
1445 }
1446 break;
1447 case TOP:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001448 for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) {
1449 if (topEdge[i] == cs.cellY + cs.spanY) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001450 return true;
1451 }
1452 }
1453 break;
1454 case BOTTOM:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001455 for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) {
1456 if (bottomEdge[i] == cs.cellY) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001457 return true;
1458 }
1459 }
1460 break;
1461 }
1462 return false;
1463 }
1464
1465 void shift(int whichEdge, int delta) {
1466 for (View v: views) {
1467 CellAndSpan c = config.map.get(v);
1468 switch (whichEdge) {
1469 case LEFT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001470 c.cellX -= delta;
Adam Cohenf3900c22012-11-16 18:28:11 -08001471 break;
1472 case RIGHT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001473 c.cellX += delta;
Adam Cohenf3900c22012-11-16 18:28:11 -08001474 break;
1475 case TOP:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001476 c.cellY -= delta;
Adam Cohenf3900c22012-11-16 18:28:11 -08001477 break;
1478 case BOTTOM:
1479 default:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001480 c.cellY += delta;
Adam Cohenf3900c22012-11-16 18:28:11 -08001481 break;
1482 }
1483 }
1484 resetEdges();
1485 }
1486
1487 public void addView(View v) {
1488 views.add(v);
1489 resetEdges();
1490 }
1491
1492 public Rect getBoundingRect() {
1493 if (boundingRectDirty) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001494 config.getBoundingRectForViews(views, boundingRect);
Adam Cohenf3900c22012-11-16 18:28:11 -08001495 }
1496 return boundingRect;
1497 }
1498
Adam Cohenf3900c22012-11-16 18:28:11 -08001499 PositionComparator comparator = new PositionComparator();
1500 class PositionComparator implements Comparator<View> {
1501 int whichEdge = 0;
1502 public int compare(View left, View right) {
1503 CellAndSpan l = config.map.get(left);
1504 CellAndSpan r = config.map.get(right);
1505 switch (whichEdge) {
1506 case LEFT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001507 return (r.cellX + r.spanX) - (l.cellX + l.spanX);
Adam Cohenf3900c22012-11-16 18:28:11 -08001508 case RIGHT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001509 return l.cellX - r.cellX;
Adam Cohenf3900c22012-11-16 18:28:11 -08001510 case TOP:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001511 return (r.cellY + r.spanY) - (l.cellY + l.spanY);
Adam Cohenf3900c22012-11-16 18:28:11 -08001512 case BOTTOM:
1513 default:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001514 return l.cellY - r.cellY;
Adam Cohenf3900c22012-11-16 18:28:11 -08001515 }
1516 }
1517 }
1518
1519 public void sortConfigurationForEdgePush(int edge) {
1520 comparator.whichEdge = edge;
1521 Collections.sort(config.sortedViews, comparator);
1522 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001523 }
1524
Adam Cohenf3900c22012-11-16 18:28:11 -08001525 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1526 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001527
Adam Cohenf3900c22012-11-16 18:28:11 -08001528 ViewCluster cluster = new ViewCluster(views, currentState);
1529 Rect clusterRect = cluster.getBoundingRect();
1530 int whichEdge;
1531 int pushDistance;
1532 boolean fail = false;
1533
1534 // Determine the edge of the cluster that will be leading the push and how far
1535 // the cluster must be shifted.
1536 if (direction[0] < 0) {
1537 whichEdge = ViewCluster.LEFT;
1538 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001539 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001540 whichEdge = ViewCluster.RIGHT;
1541 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1542 } else if (direction[1] < 0) {
1543 whichEdge = ViewCluster.TOP;
1544 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1545 } else {
1546 whichEdge = ViewCluster.BOTTOM;
1547 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001548 }
1549
Adam Cohenf3900c22012-11-16 18:28:11 -08001550 // Break early for invalid push distance.
1551 if (pushDistance <= 0) {
1552 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001553 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001554
1555 // Mark the occupied state as false for the group of views we want to move.
1556 for (View v: views) {
1557 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001558 mTmpOccupied.markCells(c, false);
Adam Cohenf3900c22012-11-16 18:28:11 -08001559 }
1560
1561 // We save the current configuration -- if we fail to find a solution we will revert
1562 // to the initial state. The process of finding a solution modifies the configuration
1563 // in place, hence the need for revert in the failure case.
1564 currentState.save();
1565
1566 // The pushing algorithm is simplified by considering the views in the order in which
1567 // they would be pushed by the cluster. For example, if the cluster is leading with its
1568 // left edge, we consider sort the views by their right edge, from right to left.
1569 cluster.sortConfigurationForEdgePush(whichEdge);
1570
1571 while (pushDistance > 0 && !fail) {
1572 for (View v: currentState.sortedViews) {
1573 // For each view that isn't in the cluster, we see if the leading edge of the
1574 // cluster is contacting the edge of that view. If so, we add that view to the
1575 // cluster.
1576 if (!cluster.views.contains(v) && v != dragView) {
1577 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1578 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1579 if (!lp.canReorder) {
1580 // The push solution includes the all apps button, this is not viable.
1581 fail = true;
1582 break;
1583 }
1584 cluster.addView(v);
1585 CellAndSpan c = currentState.map.get(v);
1586
1587 // Adding view to cluster, mark it as not occupied.
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001588 mTmpOccupied.markCells(c, false);
Adam Cohenf3900c22012-11-16 18:28:11 -08001589 }
1590 }
1591 }
1592 pushDistance--;
1593
1594 // The cluster has been completed, now we move the whole thing over in the appropriate
1595 // direction.
1596 cluster.shift(whichEdge, 1);
1597 }
1598
1599 boolean foundSolution = false;
1600 clusterRect = cluster.getBoundingRect();
1601
1602 // Due to the nature of the algorithm, the only check required to verify a valid solution
1603 // is to ensure that completed shifted cluster lies completely within the cell layout.
1604 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1605 clusterRect.bottom <= mCountY) {
1606 foundSolution = true;
1607 } else {
1608 currentState.restore();
1609 }
1610
1611 // In either case, we set the occupied array as marked for the location of the views
1612 for (View v: cluster.views) {
1613 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001614 mTmpOccupied.markCells(c, true);
Adam Cohenf3900c22012-11-16 18:28:11 -08001615 }
1616
1617 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001618 }
1619
Adam Cohen482ed822012-03-02 14:15:13 -08001620 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001621 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001622 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001623
Adam Cohen8baab352012-03-20 17:39:21 -07001624 boolean success = false;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001625 Rect boundingRect = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001626 // We construct a rect which represents the entire group of views passed in
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001627 currentState.getBoundingRectForViews(views, boundingRect);
Adam Cohen8baab352012-03-20 17:39:21 -07001628
Adam Cohen8baab352012-03-20 17:39:21 -07001629 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001630 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001631 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001632 mTmpOccupied.markCells(c, false);
Adam Cohen8baab352012-03-20 17:39:21 -07001633 }
1634
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001635 GridOccupancy blockOccupied = new GridOccupancy(boundingRect.width(), boundingRect.height());
Adam Cohen47a876d2012-03-19 13:21:41 -07001636 int top = boundingRect.top;
1637 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001638 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001639 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001640 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001641 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001642 blockOccupied.markCells(c.cellX - left, c.cellY - top, c.spanX, c.spanY, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001643 }
1644
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001645 mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001646
Adam Cohenf3900c22012-11-16 18:28:11 -08001647 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001648 boundingRect.height(), direction,
1649 mTmpOccupied.cells, blockOccupied.cells, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001650
Adam Cohen8baab352012-03-20 17:39:21 -07001651 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001652 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001653 int deltaX = mTempLocation[0] - boundingRect.left;
1654 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001655 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001656 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001657 c.cellX += deltaX;
1658 c.cellY += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001659 }
1660 success = true;
1661 }
Adam Cohen8baab352012-03-20 17:39:21 -07001662
1663 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001664 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001665 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001666 mTmpOccupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001667 }
1668 return success;
1669 }
1670
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001671 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1672 // to push items in each of the cardinal directions, in an order based on the direction vector
1673 // passed.
1674 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1675 int[] direction, View ignoreView, ItemConfiguration solution) {
1676 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001677 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001678 // separately in each of the components.
1679 int temp = direction[1];
1680 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001681
1682 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001683 ignoreView, solution)) {
1684 return true;
1685 }
1686 direction[1] = temp;
1687 temp = direction[0];
1688 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001689
1690 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001691 ignoreView, solution)) {
1692 return true;
1693 }
1694 // Revert the direction
1695 direction[0] = temp;
1696
1697 // Now we try pushing in each component of the opposite direction
1698 direction[0] *= -1;
1699 direction[1] *= -1;
1700 temp = direction[1];
1701 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001702 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001703 ignoreView, solution)) {
1704 return true;
1705 }
1706
1707 direction[1] = temp;
1708 temp = direction[0];
1709 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001710 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001711 ignoreView, solution)) {
1712 return true;
1713 }
1714 // revert the direction
1715 direction[0] = temp;
1716 direction[0] *= -1;
1717 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001718
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001719 } else {
1720 // If the direction vector has a single non-zero component, we push first in the
1721 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001722 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001723 ignoreView, solution)) {
1724 return true;
1725 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001726 // Then we try the opposite direction
1727 direction[0] *= -1;
1728 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001729 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001730 ignoreView, solution)) {
1731 return true;
1732 }
1733 // Switch the direction back
1734 direction[0] *= -1;
1735 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001736
1737 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001738 // to find a solution by pushing along the perpendicular axis.
1739
1740 // Swap the components
1741 int temp = direction[1];
1742 direction[1] = direction[0];
1743 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001744 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001745 ignoreView, solution)) {
1746 return true;
1747 }
1748
1749 // Then we try the opposite direction
1750 direction[0] *= -1;
1751 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001752 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001753 ignoreView, solution)) {
1754 return true;
1755 }
1756 // Switch the direction back
1757 direction[0] *= -1;
1758 direction[1] *= -1;
1759
1760 // Swap the components back
1761 temp = direction[1];
1762 direction[1] = direction[0];
1763 direction[0] = temp;
1764 }
1765 return false;
1766 }
1767
Adam Cohen482ed822012-03-02 14:15:13 -08001768 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001769 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001770 // Return early if get invalid cell positions
1771 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001772
Adam Cohen8baab352012-03-20 17:39:21 -07001773 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001774 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001775
Adam Cohen8baab352012-03-20 17:39:21 -07001776 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001777 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001778 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001779 if (c != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001780 c.cellX = cellX;
1781 c.cellY = cellY;
Adam Cohen19f37922012-03-21 11:59:11 -07001782 }
Adam Cohen482ed822012-03-02 14:15:13 -08001783 }
Adam Cohen482ed822012-03-02 14:15:13 -08001784 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1785 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001786 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001787 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001788 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001789 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001790 r1.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001791 if (Rect.intersects(r0, r1)) {
1792 if (!lp.canReorder) {
1793 return false;
1794 }
1795 mIntersectingViews.add(child);
1796 }
1797 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001798
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001799 solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
1800
Winson Chung5f8afe62013-08-12 16:19:28 -07001801 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001802 // we try to find a solution such that no displaced item travels through another item
1803 // without also displacing that item.
1804 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001805 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001806 return true;
1807 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001808
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001809 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08001810 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001811 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001812 return true;
1813 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001814
Adam Cohen482ed822012-03-02 14:15:13 -08001815 // Ok, they couldn't move as a block, let's move them individually
1816 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001817 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001818 return false;
1819 }
1820 }
1821 return true;
1822 }
1823
1824 /*
1825 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1826 * the provided point and the provided cell
1827 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001828 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Jon Mirandae96798e2016-12-07 12:10:44 -08001829 double angle = Math.atan(deltaY / deltaX);
Adam Cohen482ed822012-03-02 14:15:13 -08001830
1831 result[0] = 0;
1832 result[1] = 0;
1833 if (Math.abs(Math.cos(angle)) > 0.5f) {
1834 result[0] = (int) Math.signum(deltaX);
1835 }
1836 if (Math.abs(Math.sin(angle)) > 0.5f) {
1837 result[1] = (int) Math.signum(deltaY);
1838 }
1839 }
1840
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001841 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001842 int spanX, int spanY, int[] direction, View dragView, boolean decX,
1843 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001844 // Copy the current state into the solution. This solution will be manipulated as necessary.
1845 copyCurrentStateToSolution(solution, false);
1846 // Copy the current occupied array into the temporary occupied array. This array will be
1847 // manipulated as necessary to find a solution.
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001848 mOccupied.copyTo(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001849
1850 // We find the nearest cell into which we would place the dragged item, assuming there's
1851 // nothing in its way.
1852 int result[] = new int[2];
1853 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1854
1855 boolean success = false;
1856 // First we try the exact nearest position of the item being dragged,
1857 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001858 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1859 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001860
1861 if (!success) {
1862 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1863 // x, then 1 in y etc.
1864 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001865 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
1866 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001867 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001868 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
1869 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001870 }
1871 solution.isSolution = false;
1872 } else {
1873 solution.isSolution = true;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001874 solution.cellX = result[0];
1875 solution.cellY = result[1];
1876 solution.spanX = spanX;
1877 solution.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08001878 }
1879 return solution;
1880 }
1881
1882 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001883 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001884 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001885 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001886 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001887 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08001888 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07001889 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001890 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001891 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001892 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001893 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08001894 }
1895 }
1896
1897 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001898 mTmpOccupied.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001899
Michael Jurkaa52570f2012-03-20 03:18:20 -07001900 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001901 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001902 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001903 if (child == dragView) continue;
1904 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001905 CellAndSpan c = solution.map.get(child);
1906 if (c != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001907 lp.tmpCellX = c.cellX;
1908 lp.tmpCellY = c.cellY;
Adam Cohen8baab352012-03-20 17:39:21 -07001909 lp.cellHSpan = c.spanX;
1910 lp.cellVSpan = c.spanY;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001911 mTmpOccupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001912 }
1913 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001914 mTmpOccupied.markCells(solution, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001915 }
1916
1917 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
1918 commitDragView) {
1919
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001920 GridOccupancy occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
1921 occupied.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001922
Michael Jurkaa52570f2012-03-20 03:18:20 -07001923 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001924 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001925 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001926 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001927 CellAndSpan c = solution.map.get(child);
1928 if (c != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001929 animateChildToPosition(child, c.cellX, c.cellY, REORDER_ANIMATION_DURATION, 0,
Adam Cohen19f37922012-03-21 11:59:11 -07001930 DESTRUCTIVE_REORDER, false);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001931 occupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001932 }
1933 }
1934 if (commitDragView) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001935 occupied.markCells(solution, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001936 }
1937 }
1938
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001939
1940 // This method starts or changes the reorder preview animations
1941 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
1942 View dragView, int delay, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07001943 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07001944 for (int i = 0; i < childCount; i++) {
1945 View child = mShortcutsAndWidgets.getChildAt(i);
1946 if (child == dragView) continue;
1947 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001948 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
1949 != null && !solution.intersectingViews.contains(child);
1950
Adam Cohen19f37922012-03-21 11:59:11 -07001951 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001952 if (c != null && !skip) {
1953 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001954 lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07001955 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07001956 }
1957 }
1958 }
1959
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001960 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07001961 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001962 class ReorderPreviewAnimation {
Adam Cohen19f37922012-03-21 11:59:11 -07001963 View child;
Adam Cohend024f982012-05-23 18:26:45 -07001964 float finalDeltaX;
1965 float finalDeltaY;
1966 float initDeltaX;
1967 float initDeltaY;
1968 float finalScale;
1969 float initScale;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001970 int mode;
1971 boolean repeating = false;
1972 private static final int PREVIEW_DURATION = 300;
1973 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
1974
Jon Miranda21266912016-12-19 14:12:05 -08001975 private static final float CHILD_DIVIDEND = 4.0f;
1976
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001977 public static final int MODE_HINT = 0;
1978 public static final int MODE_PREVIEW = 1;
1979
Adam Cohene7587d22012-05-24 18:50:02 -07001980 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07001981
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001982 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
1983 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07001984 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
1985 final int x0 = mTmpPoint[0];
1986 final int y0 = mTmpPoint[1];
1987 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
1988 final int x1 = mTmpPoint[0];
1989 final int y1 = mTmpPoint[1];
1990 final int dX = x1 - x0;
1991 final int dY = y1 - y0;
Jon Miranda21266912016-12-19 14:12:05 -08001992
1993 this.child = child;
1994 this.mode = mode;
1995 setInitialAnimationValues(false);
1996 finalScale = (mChildScale - (CHILD_DIVIDEND / child.getWidth())) * initScale;
1997 finalDeltaX = initDeltaX;
1998 finalDeltaY = initDeltaY;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001999 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07002000 if (dX == dY && dX == 0) {
2001 } else {
2002 if (dY == 0) {
Jon Miranda21266912016-12-19 14:12:05 -08002003 finalDeltaX += - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002004 } else if (dX == 0) {
Jon Miranda21266912016-12-19 14:12:05 -08002005 finalDeltaY += - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002006 } else {
2007 double angle = Math.atan( (float) (dY) / dX);
Jon Miranda21266912016-12-19 14:12:05 -08002008 finalDeltaX += (int) (- dir * Math.signum(dX) *
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002009 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
Jon Miranda21266912016-12-19 14:12:05 -08002010 finalDeltaY += (int) (- dir * Math.signum(dY) *
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002011 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002012 }
2013 }
Jon Miranda21266912016-12-19 14:12:05 -08002014 }
2015
2016 void setInitialAnimationValues(boolean restoreOriginalValues) {
2017 if (restoreOriginalValues) {
2018 if (child instanceof LauncherAppWidgetHostView) {
2019 LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
2020 initScale = lahv.getScaleToFit();
2021 initDeltaX = lahv.getTranslationForCentering().x;
2022 initDeltaY = lahv.getTranslationForCentering().y;
2023 } else {
2024 initScale = mChildScale;
2025 initDeltaX = 0;
2026 initDeltaY = 0;
2027 }
2028 } else {
2029 initScale = child.getScaleX();
2030 initDeltaX = child.getTranslationX();
2031 initDeltaY = child.getTranslationY();
2032 }
Adam Cohen19f37922012-03-21 11:59:11 -07002033 }
2034
Adam Cohend024f982012-05-23 18:26:45 -07002035 void animate() {
Jon Miranda21266912016-12-19 14:12:05 -08002036 boolean noMovement = (finalDeltaX == initDeltaX) && (finalDeltaY == initDeltaY);
2037
Adam Cohen19f37922012-03-21 11:59:11 -07002038 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002039 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002040 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002041 mShakeAnimators.remove(child);
Jon Miranda21266912016-12-19 14:12:05 -08002042 if (noMovement) {
Adam Cohene7587d22012-05-24 18:50:02 -07002043 completeAnimationImmediately();
2044 return;
2045 }
Adam Cohen19f37922012-03-21 11:59:11 -07002046 }
Jon Miranda21266912016-12-19 14:12:05 -08002047 if (noMovement) {
Adam Cohen19f37922012-03-21 11:59:11 -07002048 return;
2049 }
Jon Mirandacda3bfb2017-02-06 15:54:41 -08002050 ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002051 a = va;
Tony Wickham9e0702f2015-09-02 14:45:39 -07002052
2053 // Animations are disabled in power save mode, causing the repeated animation to jump
2054 // spastically between beginning and end states. Since this looks bad, we don't repeat
2055 // the animation in power save mode.
Tony Wickham112ac952015-11-12 12:31:50 -08002056 if (!Utilities.isPowerSaverOn(getContext())) {
Tony Wickham9e0702f2015-09-02 14:45:39 -07002057 va.setRepeatMode(ValueAnimator.REVERSE);
2058 va.setRepeatCount(ValueAnimator.INFINITE);
2059 }
2060
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002061 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002062 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002063 va.addUpdateListener(new AnimatorUpdateListener() {
2064 @Override
2065 public void onAnimationUpdate(ValueAnimator animation) {
Jon Mirandae96798e2016-12-07 12:10:44 -08002066 float r = (Float) animation.getAnimatedValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002067 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2068 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2069 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002070 child.setTranslationX(x);
2071 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002072 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002073 child.setScaleX(s);
2074 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002075 }
2076 });
2077 va.addListener(new AnimatorListenerAdapter() {
2078 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002079 // We make sure to end only after a full period
Jon Miranda21266912016-12-19 14:12:05 -08002080 setInitialAnimationValues(true);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002081 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002082 }
2083 });
Adam Cohen19f37922012-03-21 11:59:11 -07002084 mShakeAnimators.put(child, this);
2085 va.start();
2086 }
2087
Adam Cohend024f982012-05-23 18:26:45 -07002088 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002089 if (a != null) {
2090 a.cancel();
2091 }
Adam Cohen19f37922012-03-21 11:59:11 -07002092 }
Adam Cohene7587d22012-05-24 18:50:02 -07002093
Adam Cohen091440a2015-03-18 14:16:05 -07002094 @Thunk void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002095 if (a != null) {
2096 a.cancel();
2097 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002098
Jon Miranda72dbd912017-01-04 14:03:07 -08002099 setInitialAnimationValues(true);
Sunny Goyal9e76f682017-02-13 12:13:43 -08002100 a = LauncherAnimUtils.ofPropertyValuesHolder(child,
2101 new PropertyListBuilder()
2102 .scale(initScale)
2103 .translationX(initDeltaX)
2104 .translationY(initDeltaY)
2105 .build())
2106 .setDuration(REORDER_ANIMATION_DURATION);
Sunny Goyal5d2fc322015-07-06 22:52:49 -07002107 a.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2108 a.start();
Brandon Keely50e6e562012-05-08 16:28:49 -07002109 }
Adam Cohen19f37922012-03-21 11:59:11 -07002110 }
2111
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002112 private void completeAndClearReorderPreviewAnimations() {
2113 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002114 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002115 }
2116 mShakeAnimators.clear();
2117 }
2118
Adam Cohen482ed822012-03-02 14:15:13 -08002119 private void commitTempPlacement() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002120 mTmpOccupied.copyTo(mOccupied);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002121
2122 long screenId = mLauncher.getWorkspace().getIdForScreen(this);
2123 int container = Favorites.CONTAINER_DESKTOP;
2124
Sunny Goyalc13403c2016-11-18 23:44:48 -08002125 if (mContainerType == HOTSEAT) {
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002126 screenId = -1;
2127 container = Favorites.CONTAINER_HOTSEAT;
2128 }
2129
Michael Jurkaa52570f2012-03-20 03:18:20 -07002130 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002131 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002132 View child = mShortcutsAndWidgets.getChildAt(i);
2133 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2134 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002135 // We do a null check here because the item info can be null in the case of the
2136 // AllApps button in the hotseat.
2137 if (info != null) {
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002138 final boolean requiresDbUpdate = (info.cellX != lp.tmpCellX
2139 || info.cellY != lp.tmpCellY || info.spanX != lp.cellHSpan
2140 || info.spanY != lp.cellVSpan);
2141
Adam Cohen2acce882012-03-28 19:03:19 -07002142 info.cellX = lp.cellX = lp.tmpCellX;
2143 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002144 info.spanX = lp.cellHSpan;
2145 info.spanY = lp.cellVSpan;
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002146
2147 if (requiresDbUpdate) {
Sunny Goyal43bf11d2017-02-02 13:52:53 -08002148 mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId,
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002149 info.cellX, info.cellY, info.spanX, info.spanY);
2150 }
Adam Cohen2acce882012-03-28 19:03:19 -07002151 }
Adam Cohen482ed822012-03-02 14:15:13 -08002152 }
2153 }
2154
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002155 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002156 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002157 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002158 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002159 lp.useTmpCoords = useTempCoords;
2160 }
2161 }
2162
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002163 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002164 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2165 int[] result = new int[2];
2166 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002167 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002168 resultSpan);
2169 if (result[0] >= 0 && result[1] >= 0) {
2170 copyCurrentStateToSolution(solution, false);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002171 solution.cellX = result[0];
2172 solution.cellY = result[1];
2173 solution.spanX = resultSpan[0];
2174 solution.spanY = resultSpan[1];
Adam Cohen482ed822012-03-02 14:15:13 -08002175 solution.isSolution = true;
2176 } else {
2177 solution.isSolution = false;
2178 }
2179 return solution;
2180 }
2181
Adam Cohen19f37922012-03-21 11:59:11 -07002182 /* This seems like it should be obvious and straight-forward, but when the direction vector
2183 needs to match with the notion of the dragView pushing other views, we have to employ
2184 a slightly more subtle notion of the direction vector. The question is what two points is
2185 the vector between? The center of the dragView and its desired destination? Not quite, as
2186 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2187 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2188 or right, which helps make pushing feel right.
2189 */
2190 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2191 int spanY, View dragView, int[] resultDirection) {
2192 int[] targetDestination = new int[2];
2193
2194 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2195 Rect dragRect = new Rect();
2196 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2197 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2198
2199 Rect dropRegionRect = new Rect();
2200 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2201 dragView, dropRegionRect, mIntersectingViews);
2202
2203 int dropRegionSpanX = dropRegionRect.width();
2204 int dropRegionSpanY = dropRegionRect.height();
2205
2206 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2207 dropRegionRect.height(), dropRegionRect);
2208
2209 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2210 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2211
2212 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2213 deltaX = 0;
2214 }
2215 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2216 deltaY = 0;
2217 }
2218
2219 if (deltaX == 0 && deltaY == 0) {
2220 // No idea what to do, give a random direction.
2221 resultDirection[0] = 1;
2222 resultDirection[1] = 0;
2223 } else {
2224 computeDirectionVector(deltaX, deltaY, resultDirection);
2225 }
2226 }
2227
2228 // For a given cell and span, fetch the set of views intersecting the region.
2229 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2230 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2231 if (boundingRect != null) {
2232 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2233 }
2234 intersectingViews.clear();
2235 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2236 Rect r1 = new Rect();
2237 final int count = mShortcutsAndWidgets.getChildCount();
2238 for (int i = 0; i < count; i++) {
2239 View child = mShortcutsAndWidgets.getChildAt(i);
2240 if (child == dragView) continue;
2241 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2242 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2243 if (Rect.intersects(r0, r1)) {
2244 mIntersectingViews.add(child);
2245 if (boundingRect != null) {
2246 boundingRect.union(r1);
2247 }
2248 }
2249 }
2250 }
2251
2252 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2253 View dragView, int[] result) {
2254 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2255 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2256 mIntersectingViews);
2257 return !mIntersectingViews.isEmpty();
2258 }
2259
2260 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002261 completeAndClearReorderPreviewAnimations();
2262 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2263 final int count = mShortcutsAndWidgets.getChildCount();
2264 for (int i = 0; i < count; i++) {
2265 View child = mShortcutsAndWidgets.getChildAt(i);
2266 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2267 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2268 lp.tmpCellX = lp.cellX;
2269 lp.tmpCellY = lp.cellY;
2270 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2271 0, false, false);
2272 }
Adam Cohen19f37922012-03-21 11:59:11 -07002273 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002274 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002275 }
Adam Cohen19f37922012-03-21 11:59:11 -07002276 }
2277
Adam Cohenbebf0422012-04-11 18:06:28 -07002278 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2279 View dragView, int[] direction, boolean commit) {
2280 int[] pixelXY = new int[2];
2281 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2282
2283 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002284 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002285 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2286
2287 setUseTempCoords(true);
2288 if (swapSolution != null && swapSolution.isSolution) {
2289 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2290 // committing anything or animating anything as we just want to determine if a solution
2291 // exists
2292 copySolutionToTempState(swapSolution, dragView);
2293 setItemPlacementDirty(true);
2294 animateItemsToSolution(swapSolution, dragView, commit);
2295
2296 if (commit) {
2297 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002298 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002299 setItemPlacementDirty(false);
2300 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002301 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2302 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002303 }
2304 mShortcutsAndWidgets.requestLayout();
2305 }
2306 return swapSolution.isSolution;
2307 }
2308
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002309 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002310 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002311 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002312 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002313
2314 if (resultSpan == null) {
2315 resultSpan = new int[2];
2316 }
2317
Adam Cohen19f37922012-03-21 11:59:11 -07002318 // When we are checking drop validity or actually dropping, we don't recompute the
2319 // direction vector, since we want the solution to match the preview, and it's possible
2320 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002321 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2322 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002323 mDirectionVector[0] = mPreviousReorderDirection[0];
2324 mDirectionVector[1] = mPreviousReorderDirection[1];
2325 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002326 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2327 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2328 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002329 }
2330 } else {
2331 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2332 mPreviousReorderDirection[0] = mDirectionVector[0];
2333 mPreviousReorderDirection[1] = mDirectionVector[1];
2334 }
2335
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002336 // Find a solution involving pushing / displacing any items in the way
2337 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002338 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2339
2340 // We attempt the approach which doesn't shuffle views at all
2341 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2342 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2343
2344 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002345
2346 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2347 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002348 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2349 finalSolution = swapSolution;
2350 } else if (noShuffleSolution.isSolution) {
2351 finalSolution = noShuffleSolution;
2352 }
2353
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002354 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002355 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002356 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2357 ReorderPreviewAnimation.MODE_HINT);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002358 result[0] = finalSolution.cellX;
2359 result[1] = finalSolution.cellY;
2360 resultSpan[0] = finalSolution.spanX;
2361 resultSpan[1] = finalSolution.spanY;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002362 } else {
2363 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2364 }
2365 return result;
2366 }
2367
Adam Cohen482ed822012-03-02 14:15:13 -08002368 boolean foundSolution = true;
2369 if (!DESTRUCTIVE_REORDER) {
2370 setUseTempCoords(true);
2371 }
2372
2373 if (finalSolution != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002374 result[0] = finalSolution.cellX;
2375 result[1] = finalSolution.cellY;
2376 resultSpan[0] = finalSolution.spanX;
2377 resultSpan[1] = finalSolution.spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002378
2379 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2380 // committing anything or animating anything as we just want to determine if a solution
2381 // exists
2382 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2383 if (!DESTRUCTIVE_REORDER) {
2384 copySolutionToTempState(finalSolution, dragView);
2385 }
2386 setItemPlacementDirty(true);
2387 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2388
Adam Cohen19f37922012-03-21 11:59:11 -07002389 if (!DESTRUCTIVE_REORDER &&
2390 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002391 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002392 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002393 setItemPlacementDirty(false);
2394 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002395 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2396 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002397 }
2398 }
2399 } else {
2400 foundSolution = false;
2401 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2402 }
2403
2404 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2405 setUseTempCoords(false);
2406 }
Adam Cohen482ed822012-03-02 14:15:13 -08002407
Michael Jurkaa52570f2012-03-20 03:18:20 -07002408 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002409 return result;
2410 }
2411
Adam Cohen19f37922012-03-21 11:59:11 -07002412 void setItemPlacementDirty(boolean dirty) {
2413 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002414 }
Adam Cohen19f37922012-03-21 11:59:11 -07002415 boolean isItemPlacementDirty() {
2416 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002417 }
2418
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002419 private static class ItemConfiguration extends CellAndSpan {
Adam Cohen8baab352012-03-20 17:39:21 -07002420 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002421 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2422 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002423 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002424 boolean isSolution = false;
Adam Cohen482ed822012-03-02 14:15:13 -08002425
Adam Cohenf3900c22012-11-16 18:28:11 -08002426 void save() {
2427 // Copy current state into savedMap
2428 for (View v: map.keySet()) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002429 savedMap.get(v).copyFrom(map.get(v));
Adam Cohenf3900c22012-11-16 18:28:11 -08002430 }
2431 }
2432
2433 void restore() {
2434 // Restore current state from savedMap
2435 for (View v: savedMap.keySet()) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002436 map.get(v).copyFrom(savedMap.get(v));
Adam Cohenf3900c22012-11-16 18:28:11 -08002437 }
2438 }
2439
2440 void add(View v, CellAndSpan cs) {
2441 map.put(v, cs);
2442 savedMap.put(v, new CellAndSpan());
2443 sortedViews.add(v);
2444 }
2445
Adam Cohen482ed822012-03-02 14:15:13 -08002446 int area() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002447 return spanX * spanY;
Adam Cohenf3900c22012-11-16 18:28:11 -08002448 }
2449
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002450 void getBoundingRectForViews(ArrayList<View> views, Rect outRect) {
2451 boolean first = true;
2452 for (View v: views) {
2453 CellAndSpan c = map.get(v);
2454 if (first) {
2455 outRect.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2456 first = false;
2457 } else {
2458 outRect.union(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2459 }
2460 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002461 }
Adam Cohen482ed822012-03-02 14:15:13 -08002462 }
2463
Adam Cohendf035382011-04-11 17:22:04 -07002464 /**
Adam Cohendf035382011-04-11 17:22:04 -07002465 * Find a starting cell position that will fit the given bounds nearest the requested
2466 * cell location. Uses Euclidean distance to score multiple vacant areas.
2467 *
2468 * @param pixelX The X location at which you want to search for a vacant area.
2469 * @param pixelY The Y location at which you want to search for a vacant area.
2470 * @param spanX Horizontal span of the object.
2471 * @param spanY Vertical span of the object.
2472 * @param ignoreView Considers space occupied by this view as unoccupied
2473 * @param result Previously returned value to possibly recycle.
2474 * @return The X, Y cell of a vacant area that can contain this object,
2475 * nearest the requested location.
2476 */
Adam Cohenf9c184a2016-01-15 16:47:43 -08002477 public int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002478 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002479 }
2480
Michael Jurka0280c3b2010-09-17 15:00:07 -07002481 boolean existsEmptyCell() {
2482 return findCellForSpan(null, 1, 1);
2483 }
2484
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002485 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002486 * Finds the upper-left coordinate of the first rectangle in the grid that can
2487 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2488 * then this method will only return coordinates for rectangles that contain the cell
2489 * (intersectX, intersectY)
2490 *
2491 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2492 * can be found.
2493 * @param spanX The horizontal span of the cell we want to find.
2494 * @param spanY The vertical span of the cell we want to find.
2495 *
2496 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002497 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002498 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002499 if (cellXY == null) {
2500 cellXY = new int[2];
Michael Jurka0280c3b2010-09-17 15:00:07 -07002501 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002502 return mOccupied.findVacantCell(cellXY, spanX, spanY);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002503 }
2504
2505 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002506 * A drag event has begun over this layout.
2507 * It may have begun over this layout (in which case onDragChild is called first),
2508 * or it may have begun on another layout.
2509 */
2510 void onDragEnter() {
Winson Chungc07918d2011-07-01 15:35:26 -07002511 mDragging = true;
2512 }
2513
2514 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002515 * Called when drag has left this CellLayout or has been completed (successfully or not)
2516 */
2517 void onDragExit() {
Joe Onorato4be866d2010-10-10 11:26:02 -07002518 // This can actually be called when we aren't in a drag, e.g. when adding a new
2519 // item to this layout via the customize drawer.
2520 // Guard against that case.
2521 if (mDragging) {
2522 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002523 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002524
2525 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002526 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002527 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2528 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002529 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002530 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002531 }
2532
2533 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002534 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002535 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002536 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002537 *
2538 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002539 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002540 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002541 if (child != null) {
2542 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002543 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002544 child.requestLayout();
Tony Wickham1cdb6d02015-09-17 11:08:27 -07002545 markCellsAsOccupiedForView(child);
Romain Guyd94533d2009-08-17 10:01:15 -07002546 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002547 }
2548
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002549 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002550 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002551 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002552 * @param cellX X coordinate of upper left corner expressed as a cell position
2553 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002554 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002555 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002556 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002557 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002558 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002559 final int cellWidth = mCellWidth;
2560 final int cellHeight = mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -07002561
Winson Chung4b825dcd2011-06-19 12:41:22 -07002562 final int hStartPadding = getPaddingLeft();
2563 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002564
Sunny Goyalaa8a8712016-11-20 15:26:01 +05302565 int width = cellHSpan * cellWidth;
2566 int height = cellVSpan * cellHeight;
2567 int x = hStartPadding + cellX * cellWidth;
2568 int y = vStartPadding + cellY * cellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -07002569
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002570 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002571 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002572
Adam Cohend4844c32011-02-18 19:25:06 -08002573 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002574 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002575 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002576 mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002577 }
2578
Adam Cohend4844c32011-02-18 19:25:06 -08002579 public void markCellsAsUnoccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002580 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002581 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002582 mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002583 }
2584
Adam Cohen2801caf2011-05-13 20:57:39 -07002585 public int getDesiredWidth() {
Sunny Goyalaa8a8712016-11-20 15:26:01 +05302586 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth);
Adam Cohen2801caf2011-05-13 20:57:39 -07002587 }
2588
2589 public int getDesiredHeight() {
Sunny Goyalaa8a8712016-11-20 15:26:01 +05302590 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight);
Adam Cohen2801caf2011-05-13 20:57:39 -07002591 }
2592
Michael Jurka66d72172011-04-12 16:29:25 -07002593 public boolean isOccupied(int x, int y) {
2594 if (x < mCountX && y < mCountY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002595 return mOccupied.cells[x][y];
Michael Jurka66d72172011-04-12 16:29:25 -07002596 } else {
2597 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2598 }
2599 }
2600
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002601 @Override
2602 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2603 return new CellLayout.LayoutParams(getContext(), attrs);
2604 }
2605
2606 @Override
2607 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2608 return p instanceof CellLayout.LayoutParams;
2609 }
2610
2611 @Override
2612 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2613 return new CellLayout.LayoutParams(p);
2614 }
2615
2616 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2617 /**
2618 * Horizontal location of the item in the grid.
2619 */
2620 @ViewDebug.ExportedProperty
2621 public int cellX;
2622
2623 /**
2624 * Vertical location of the item in the grid.
2625 */
2626 @ViewDebug.ExportedProperty
2627 public int cellY;
2628
2629 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002630 * Temporary horizontal location of the item in the grid during reorder
2631 */
2632 public int tmpCellX;
2633
2634 /**
2635 * Temporary vertical location of the item in the grid during reorder
2636 */
2637 public int tmpCellY;
2638
2639 /**
2640 * Indicates that the temporary coordinates should be used to layout the items
2641 */
2642 public boolean useTmpCoords;
2643
2644 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002645 * Number of cells spanned horizontally by the item.
2646 */
2647 @ViewDebug.ExportedProperty
2648 public int cellHSpan;
2649
2650 /**
2651 * Number of cells spanned vertically by the item.
2652 */
2653 @ViewDebug.ExportedProperty
2654 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002655
Adam Cohen1b607ed2011-03-03 17:26:50 -08002656 /**
2657 * Indicates whether the item will set its x, y, width and height parameters freely,
2658 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2659 */
Adam Cohend4844c32011-02-18 19:25:06 -08002660 public boolean isLockedToGrid = true;
2661
Adam Cohen482ed822012-03-02 14:15:13 -08002662 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07002663 * Indicates that this item should use the full extents of its parent.
2664 */
2665 public boolean isFullscreen = false;
2666
2667 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002668 * Indicates whether this item can be reordered. Always true except in the case of the
Sunny Goyalda4fe1a2016-05-26 16:05:17 -07002669 * the AllApps button and QSB place holder.
Adam Cohen482ed822012-03-02 14:15:13 -08002670 */
2671 public boolean canReorder = true;
2672
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002673 // X coordinate of the view in the layout.
2674 @ViewDebug.ExportedProperty
Vadim Tryshevfedca432015-08-19 17:55:02 -07002675 public int x;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002676 // Y coordinate of the view in the layout.
2677 @ViewDebug.ExportedProperty
Vadim Tryshevfedca432015-08-19 17:55:02 -07002678 public int y;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002679
Romain Guy84f296c2009-11-04 15:00:44 -08002680 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002681
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002682 public LayoutParams(Context c, AttributeSet attrs) {
2683 super(c, attrs);
2684 cellHSpan = 1;
2685 cellVSpan = 1;
2686 }
2687
2688 public LayoutParams(ViewGroup.LayoutParams source) {
2689 super(source);
2690 cellHSpan = 1;
2691 cellVSpan = 1;
2692 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002693
2694 public LayoutParams(LayoutParams source) {
2695 super(source);
2696 this.cellX = source.cellX;
2697 this.cellY = source.cellY;
2698 this.cellHSpan = source.cellHSpan;
2699 this.cellVSpan = source.cellVSpan;
2700 }
2701
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002702 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002703 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002704 this.cellX = cellX;
2705 this.cellY = cellY;
2706 this.cellHSpan = cellHSpan;
2707 this.cellVSpan = cellVSpan;
2708 }
2709
Sunny Goyalaa8a8712016-11-20 15:26:01 +05302710 public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount) {
Jon Miranda7ae64ff2016-11-21 16:18:46 -08002711 setup(cellWidth, cellHeight, invertHorizontally, colCount, 1.0f, 1.0f);
2712 }
2713
2714 /**
2715 * Use this method, as opposed to {@link #setup(int, int, boolean, int)}, if the view needs
2716 * to be scaled.
2717 *
2718 * ie. In multi-window mode, we setup widgets so that they are measured and laid out
2719 * using their full/invariant device profile sizes.
2720 */
2721 public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
2722 float cellScaleX, float cellScaleY) {
Adam Cohend4844c32011-02-18 19:25:06 -08002723 if (isLockedToGrid) {
2724 final int myCellHSpan = cellHSpan;
2725 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002726 int myCellX = useTmpCoords ? tmpCellX : cellX;
2727 int myCellY = useTmpCoords ? tmpCellY : cellY;
2728
2729 if (invertHorizontally) {
2730 myCellX = colCount - myCellX - cellHSpan;
2731 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002732
Jon Miranda7ae64ff2016-11-21 16:18:46 -08002733 width = (int) (myCellHSpan * cellWidth / cellScaleX - leftMargin - rightMargin);
2734 height = (int) (myCellVSpan * cellHeight / cellScaleY - topMargin - bottomMargin);
Sunny Goyalaa8a8712016-11-20 15:26:01 +05302735 x = (myCellX * cellWidth + leftMargin);
2736 y = (myCellY * cellHeight + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002737 }
2738 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002739
Winson Chungaafa03c2010-06-11 17:34:16 -07002740 public String toString() {
2741 return "(" + this.cellX + ", " + this.cellY + ")";
2742 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002743
2744 public void setWidth(int width) {
2745 this.width = width;
2746 }
2747
2748 public int getWidth() {
2749 return width;
2750 }
2751
2752 public void setHeight(int height) {
2753 this.height = height;
2754 }
2755
2756 public int getHeight() {
2757 return height;
2758 }
2759
2760 public void setX(int x) {
2761 this.x = x;
2762 }
2763
2764 public int getX() {
2765 return x;
2766 }
2767
2768 public void setY(int y) {
2769 this.y = y;
2770 }
2771
2772 public int getY() {
2773 return y;
2774 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002775 }
2776
Michael Jurka0280c3b2010-09-17 15:00:07 -07002777 // This class stores info for two purposes:
2778 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2779 // its spanX, spanY, and the screen it is on
2780 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2781 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2782 // the CellLayout that was long clicked
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002783 public static final class CellInfo extends CellAndSpan {
Adam Cohenf9c184a2016-01-15 16:47:43 -08002784 public View cell;
Adam Cohendcd297f2013-06-18 13:13:40 -07002785 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07002786 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002787
Sunny Goyal83a8f042015-05-19 12:52:12 -07002788 public CellInfo(View v, ItemInfo info) {
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002789 cellX = info.cellX;
2790 cellY = info.cellY;
2791 spanX = info.spanX;
2792 spanY = info.spanY;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002793 cell = v;
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002794 screenId = info.screenId;
2795 container = info.container;
2796 }
2797
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002798 @Override
2799 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002800 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2801 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002802 }
2803 }
Michael Jurkad771c962011-08-09 15:00:48 -07002804
Tony Wickham86930612015-09-09 13:50:40 -07002805 /**
2806 * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing
2807 * if necessary).
2808 */
2809 public boolean hasReorderSolution(ItemInfo itemInfo) {
2810 int[] cellPoint = new int[2];
2811 // Check for a solution starting at every cell.
2812 for (int cellX = 0; cellX < getCountX(); cellX++) {
2813 for (int cellY = 0; cellY < getCountY(); cellY++) {
2814 cellToPoint(cellX, cellY, cellPoint);
2815 if (findReorderSolution(cellPoint[0], cellPoint[1], itemInfo.minSpanX,
2816 itemInfo.minSpanY, itemInfo.spanX, itemInfo.spanY, mDirectionVector, null,
2817 true, new ItemConfiguration()).isSolution) {
2818 return true;
2819 }
2820 }
2821 }
2822 return false;
2823 }
2824
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002825 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002826 return mOccupied.isRegionVacant(x, y, spanX, spanY);
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002827 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002828}