blob: e3bb5fa72cdd700b8c436a5d39054ed1d36292bf [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;
Adam Cohenc9735cf2015-01-23 16:11:55 -080024import android.annotation.TargetApi;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080025import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040026import android.content.res.Resources;
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;
Sunny Goyal2805e632015-05-20 15:35:32 -070035import android.graphics.drawable.TransitionDrawable;
Adam Cohenc9735cf2015-01-23 16:11:55 -080036import android.os.Build;
Adam Cohen1462de32012-07-24 22:34:36 -070037import android.os.Parcelable;
Adam Cohenc9735cf2015-01-23 16:11:55 -080038import android.support.v4.view.ViewCompat;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080039import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070040import android.util.Log;
Adam Cohen1462de32012-07-24 22:34:36 -070041import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080042import android.view.MotionEvent;
43import android.view.View;
44import android.view.ViewDebug;
45import android.view.ViewGroup;
Adam Cohenc9735cf2015-01-23 16:11:55 -080046import android.view.accessibility.AccessibilityEvent;
Winson Chung150fbab2010-09-29 17:14:26 -070047import android.view.animation.DecelerateInterpolator;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080048
Sunny Goyal4b6eb262015-05-14 19:24:40 -070049import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
Sunny Goyalaa8ef112015-06-12 20:04:41 -070050import com.android.launcher3.LauncherSettings.Favorites;
Sunny Goyale9b651e2015-04-24 11:44:51 -070051import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
52import com.android.launcher3.accessibility.FolderAccessibilityHelper;
53import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
Tony Wickhame0c33232016-02-08 11:37:04 -080054import com.android.launcher3.config.FeatureFlags;
Sunny Goyal6c56c682015-07-16 14:09:05 -070055import com.android.launcher3.config.ProviderConfig;
Sunny Goyal26119432016-02-18 22:09:23 +000056import com.android.launcher3.folder.FolderIcon;
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
Adam Cohen69ce2e52011-07-03 19:25:21 -070062import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070063import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080064import java.util.Collections;
65import java.util.Comparator;
Adam Cohenbfbfd262011-06-13 16:55:12 -070066import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080067import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070068
Sunny Goyal4b6eb262015-05-14 19:24:40 -070069public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
Sunny Goyale9b651e2015-04-24 11:44:51 -070070 public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
71 public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
72
Tony Wickhama0628cc2015-10-14 15:23:04 -070073 private static final String TAG = "CellLayout";
74 private static final boolean LOGD = false;
Winson Chungaafa03c2010-06-11 17:34:16 -070075
Adam Cohen2acce882012-03-28 19:03:19 -070076 private Launcher mLauncher;
Sunny Goyal4ffec482016-02-09 11:28:52 -080077 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070078 @Thunk int mCellWidth;
Sunny Goyal4ffec482016-02-09 11:28:52 -080079 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070080 @Thunk int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070081 private int mFixedCellWidth;
82 private int mFixedCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070083
Sunny Goyal4ffec482016-02-09 11:28:52 -080084 @ViewDebug.ExportedProperty(category = "launcher")
Sunny Goyalff4ba2d2016-04-02 14:12:34 -070085 private int mCountX;
Sunny Goyal4ffec482016-02-09 11:28:52 -080086 @ViewDebug.ExportedProperty(category = "launcher")
Sunny Goyalff4ba2d2016-04-02 14:12:34 -070087 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080088
Adam Cohen234c4cd2011-07-17 21:03:04 -070089 private int mOriginalWidthGap;
90 private int mOriginalHeightGap;
Sunny Goyal4ffec482016-02-09 11:28:52 -080091 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070092 @Thunk int mWidthGap;
Sunny Goyal4ffec482016-02-09 11:28:52 -080093 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070094 @Thunk int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070095 private int mMaxGap;
Adam Cohen917e3882013-10-31 15:03:35 -070096 private boolean mDropPending = false;
Adam Cohenc50438c2014-08-19 17:43:05 -070097 private boolean mIsDragTarget = true;
Sunny Goyale2fd14b2015-08-27 17:45:46 -070098 private boolean mJailContent = true;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080099
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700100 // These are temporary variables to prevent having to allocate a new object just to
101 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Adam Cohen091440a2015-03-18 14:16:05 -0700102 @Thunk final int[] mTmpPoint = new int[2];
Sunny Goyal2805e632015-05-20 15:35:32 -0700103 @Thunk final int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700104
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700105 private GridOccupancy mOccupied;
106 private GridOccupancy mTmpOccupied;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800107
Michael Jurkadee05892010-07-27 10:01:56 -0700108 private OnTouchListener mInterceptTouchListener;
Mady Melloref044dd2015-06-02 15:35:07 -0700109 private StylusEventHelper mStylusEventHelper;
Michael Jurkadee05892010-07-27 10:01:56 -0700110
Adam Cohenefca0272016-02-24 19:19:06 -0800111 private ArrayList<FolderIcon.PreviewBackground> mFolderBackgrounds = new ArrayList<FolderIcon.PreviewBackground>();
112 FolderIcon.PreviewBackground mFolderLeaveBehind = new FolderIcon.PreviewBackground();
113 Paint mFolderBgPaint = new Paint();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700114
Michael Jurka5f1c5092010-09-03 14:15:02 -0700115 private float mBackgroundAlpha;
Adam Cohenf34bab52010-09-30 14:11:56 -0700116
Tony Wickham33326072016-04-04 15:05:49 -0700117 private static final int BACKGROUND_ACTIVATE_DURATION =
118 FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ? 120 : 0;
Sunny Goyal2805e632015-05-20 15:35:32 -0700119 private final TransitionDrawable mBackground;
120
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700121 // These values allow a fixed measurement to be set on the CellLayout.
122 private int mFixedWidth = -1;
123 private int mFixedHeight = -1;
124
Michael Jurka33945b22010-12-21 18:19:38 -0800125 // If we're actively dragging something over this screen, mIsDragOverlapping is true
126 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700127
Winson Chung150fbab2010-09-29 17:14:26 -0700128 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700129 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohen091440a2015-03-18 14:16:05 -0700130 @Thunk Rect[] mDragOutlines = new Rect[4];
131 @Thunk float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700132 private InterruptibleInOutAnimator[] mDragOutlineAnims =
133 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700134
135 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700136 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700137 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700138
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700139 private final ClickShadowView mTouchFeedbackView;
Patrick Dubroy96864c32011-03-10 17:17:23 -0800140
Sunny Goyal316490e2015-06-02 09:38:28 -0700141 @Thunk HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new HashMap<>();
142 @Thunk HashMap<View, ReorderPreviewAnimation> mShakeAnimators = new HashMap<>();
Adam Cohen19f37922012-03-21 11:59:11 -0700143
144 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700145
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700146 // When a drag operation is in progress, holds the nearest cell to the touch point
147 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800148
Joe Onorato4be866d2010-10-10 11:26:02 -0700149 private boolean mDragging = false;
150
Patrick Dubroyce34a972010-10-19 10:34:32 -0700151 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700152 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700153
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800154 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700155 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800156
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800157 public static final int MODE_SHOW_REORDER_HINT = 0;
158 public static final int MODE_DRAG_OVER = 1;
159 public static final int MODE_ON_DROP = 2;
160 public static final int MODE_ON_DROP_EXTERNAL = 3;
161 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700162 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800163 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
164
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800165 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700166 private static final int REORDER_ANIMATION_DURATION = 150;
Adam Cohen091440a2015-03-18 14:16:05 -0700167 @Thunk float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700168
Adam Cohen482ed822012-03-02 14:15:13 -0800169 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
170 private Rect mOccupiedRect = new Rect();
171 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700172 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700173 private static final int INVALID_DIRECTION = -100;
Adam Cohen482ed822012-03-02 14:15:13 -0800174
Sunny Goyal2805e632015-05-20 15:35:32 -0700175 private final Rect mTempRect = new Rect();
Winson Chung3a6e7f32013-10-09 15:50:52 -0700176
Michael Jurkaca993832012-06-29 15:17:04 -0700177 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700178
Adam Cohenc9735cf2015-01-23 16:11:55 -0800179 // Related to accessible drag and drop
Sunny Goyale9b651e2015-04-24 11:44:51 -0700180 private DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800181 private boolean mUseTouchHelper = false;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800182
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800183 public CellLayout(Context context) {
184 this(context, null);
185 }
186
187 public CellLayout(Context context, AttributeSet attrs) {
188 this(context, attrs, 0);
189 }
190
191 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
192 super(context, attrs, defStyle);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700193
194 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
195 // the user where a dragged item will land when dropped.
196 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800197 setClipToPadding(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700198 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700199
Adam Cohen2e6da152015-05-06 11:42:25 -0700200 DeviceProfile grid = mLauncher.getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800201
Winson Chung11a1a532013-09-13 11:14:45 -0700202 mCellWidth = mCellHeight = -1;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800203 mFixedCellWidth = mFixedCellHeight = -1;
Winson Chung5f8afe62013-08-12 16:19:28 -0700204 mWidthGap = mOriginalWidthGap = 0;
205 mHeightGap = mOriginalHeightGap = 0;
206 mMaxGap = Integer.MAX_VALUE;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700207
208 mCountX = grid.inv.numColumns;
209 mCountY = grid.inv.numRows;
210 mOccupied = new GridOccupancy(mCountX, mCountY);
211 mTmpOccupied = new GridOccupancy(mCountX, mCountY);
212
Adam Cohen5b53f292012-03-29 14:30:35 -0700213 mPreviousReorderDirection[0] = INVALID_DIRECTION;
214 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800215
Adam Cohenefca0272016-02-24 19:19:06 -0800216 mFolderLeaveBehind.delegateCellX = -1;
217 mFolderLeaveBehind.delegateCellY = -1;
218
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800219 setAlwaysDrawnWithCacheEnabled(false);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700220 final Resources res = getResources();
Winson Chung6e1c0d32013-10-25 15:24:24 -0700221 mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700222
Tony Wickhame0c33232016-02-08 11:37:04 -0800223 mBackground = (TransitionDrawable) res.getDrawable(
224 FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ? R.drawable.bg_screenpanel
225 : R.drawable.bg_celllayout);
Sunny Goyal2805e632015-05-20 15:35:32 -0700226 mBackground.setCallback(this);
Winson Chunge8f1d042015-07-31 12:39:57 -0700227 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurka33945b22010-12-21 18:19:38 -0800228
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800229 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
Winson Chung5f8afe62013-08-12 16:19:28 -0700230 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 }
238
239 // When dragging things around the home screens, we show a green outline of
240 // where the item will land. The outlines gradually fade out, leaving a trail
241 // behind the drag path.
242 // Set up all the animations that are used to implement this fading.
243 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700244 final float fromAlphaValue = 0;
245 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700246
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700247 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700248
249 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700250 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100251 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700252 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700253 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700254 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700255 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700256 final Bitmap outline = (Bitmap)anim.getTag();
257
258 // If an animation is started and then stopped very quickly, we can still
259 // get spurious updates we've cleared the tag. Guard against this.
260 if (outline == null) {
Tony Wickhama0628cc2015-10-14 15:23:04 -0700261 if (LOGD) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700262 Object val = animation.getAnimatedValue();
263 Log.d(TAG, "anim " + thisIndex + " update: " + val +
264 ", isStopped " + anim.isStopped());
265 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700266 // Try to prevent it from continuing to run
267 animation.cancel();
268 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700269 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800270 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700271 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700272 }
273 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700274 // The animation holds a reference to the drag outline bitmap as long is it's
275 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700276 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700277 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700278 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700279 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700280 anim.setTag(null);
281 }
282 }
283 });
284 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700285 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700286
Michael Jurkaa52570f2012-03-20 03:18:20 -0700287 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700288 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700289 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
Adam Cohenc9735cf2015-01-23 16:11:55 -0800298 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
Sunny Goyale9b651e2015-04-24 11:44:51 -0700299 public void enableAccessibleDrag(boolean enable, int dragType) {
Adam Cohenc9735cf2015-01-23 16:11:55 -0800300 mUseTouchHelper = enable;
301 if (!enable) {
302 ViewCompat.setAccessibilityDelegate(this, null);
303 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
304 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
305 setOnClickListener(mLauncher);
306 } else {
Sunny Goyale9b651e2015-04-24 11:44:51 -0700307 if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
308 !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
309 mTouchHelper = new WorkspaceAccessibilityHelper(this);
310 } else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
311 !(mTouchHelper instanceof FolderAccessibilityHelper)) {
312 mTouchHelper = new FolderAccessibilityHelper(this);
313 }
Adam Cohenc9735cf2015-01-23 16:11:55 -0800314 ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
315 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
316 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
317 setOnClickListener(mTouchHelper);
318 }
319
320 // Invalidate the accessibility hierarchy
321 if (getParent() != null) {
322 getParent().notifySubtreeAccessibilityStateChanged(
323 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
324 }
325 }
326
327 @Override
328 public boolean dispatchHoverEvent(MotionEvent event) {
329 // Always attempt to dispatch hover events to accessibility first.
330 if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
331 return true;
332 }
333 return super.dispatchHoverEvent(event);
334 }
335
336 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800337 public boolean onInterceptTouchEvent(MotionEvent ev) {
338 if (mUseTouchHelper ||
339 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
340 return true;
341 }
342 return false;
343 }
344
Mady Melloref044dd2015-06-02 15:35:07 -0700345 @Override
346 public boolean onTouchEvent(MotionEvent ev) {
347 boolean handled = super.onTouchEvent(ev);
348 // Stylus button press on a home screen should not switch between overview mode and
349 // the home screen mode, however, once in overview mode stylus button press should be
350 // enabled to allow rearranging the different home screens. So check what mode
351 // the workspace is in, and only perform stylus button presses while in overview mode.
352 if (mLauncher.mWorkspace.isInOverviewMode()
Mady Mellorbb835202015-07-15 16:34:34 -0700353 && mStylusEventHelper.onMotionEvent(ev)) {
Mady Melloref044dd2015-06-02 15:35:07 -0700354 return true;
355 }
356 return handled;
357 }
358
Chris Craik01f2d7f2013-10-01 14:41:56 -0700359 public void enableHardwareLayer(boolean hasLayer) {
360 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700361 }
362
363 public void buildHardwareLayer() {
364 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700365 }
366
Adam Cohen307fe232012-08-16 17:55:58 -0700367 public float getChildrenScale() {
368 return mIsHotseat ? mHotseatScale : 1.0f;
369 }
370
Winson Chung5f8afe62013-08-12 16:19:28 -0700371 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700372 mFixedCellWidth = mCellWidth = width;
373 mFixedCellHeight = mCellHeight = height;
Winson Chung5f8afe62013-08-12 16:19:28 -0700374 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
375 mCountX, mCountY);
376 }
377
Adam Cohen2801caf2011-05-13 20:57:39 -0700378 public void setGridSize(int x, int y) {
379 mCountX = x;
380 mCountY = y;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700381 mOccupied = new GridOccupancy(mCountX, mCountY);
382 mTmpOccupied = new GridOccupancy(mCountX, mCountY);
Adam Cohen7fbec102012-03-27 12:42:19 -0700383 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700384 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700385 mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700386 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700387 }
388
Adam Cohen2374abf2013-04-16 14:56:57 -0700389 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
390 public void setInvertIfRtl(boolean invert) {
391 mShortcutsAndWidgets.setInvertIfRtl(invert);
392 }
393
Adam Cohen917e3882013-10-31 15:03:35 -0700394 public void setDropPending(boolean pending) {
395 mDropPending = pending;
396 }
397
398 public boolean isDropPending() {
399 return mDropPending;
400 }
401
Sunny Goyal4b6eb262015-05-14 19:24:40 -0700402 @Override
403 public void setPressedIcon(BubbleTextView icon, Bitmap background) {
Sunny Goyal508da152014-08-14 10:53:27 -0700404 if (icon == null || background == null) {
405 mTouchFeedbackView.setBitmap(null);
406 mTouchFeedbackView.animate().cancel();
407 } else {
Sunny Goyal508da152014-08-14 10:53:27 -0700408 if (mTouchFeedbackView.setBitmap(background)) {
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700409 mTouchFeedbackView.alignWithIconView(icon, mShortcutsAndWidgets);
410 mTouchFeedbackView.animateShadow();
Sunny Goyal508da152014-08-14 10:53:27 -0700411 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800412 }
413 }
414
Adam Cohenc50438c2014-08-19 17:43:05 -0700415 void disableDragTarget() {
416 mIsDragTarget = false;
417 }
418
Tony Wickham0f97b782015-12-02 17:55:07 -0800419 public boolean isDragTarget() {
420 return mIsDragTarget;
421 }
422
Adam Cohenc50438c2014-08-19 17:43:05 -0700423 void setIsDragOverlapping(boolean isDragOverlapping) {
424 if (mIsDragOverlapping != isDragOverlapping) {
425 mIsDragOverlapping = isDragOverlapping;
Sunny Goyal2805e632015-05-20 15:35:32 -0700426 if (mIsDragOverlapping) {
427 mBackground.startTransition(BACKGROUND_ACTIVATE_DURATION);
428 } else {
Winson Chunge8f1d042015-07-31 12:39:57 -0700429 if (mBackgroundAlpha > 0f) {
430 mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
431 } else {
432 mBackground.resetTransition();
433 }
Sunny Goyal2805e632015-05-20 15:35:32 -0700434 }
Adam Cohenc50438c2014-08-19 17:43:05 -0700435 invalidate();
436 }
437 }
438
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700439 public void disableJailContent() {
440 mJailContent = false;
441 }
442
443 @Override
444 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
445 if (mJailContent) {
446 ParcelableSparseArray jail = getJailedArray(container);
447 super.dispatchSaveInstanceState(jail);
448 container.put(R.id.cell_layout_jail_id, jail);
449 } else {
450 super.dispatchSaveInstanceState(container);
451 }
452 }
453
454 @Override
455 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
456 super.dispatchRestoreInstanceState(mJailContent ? getJailedArray(container) : container);
457 }
458
459 private ParcelableSparseArray getJailedArray(SparseArray<Parcelable> container) {
460 final Parcelable parcelable = container.get(R.id.cell_layout_jail_id);
461 return parcelable instanceof ParcelableSparseArray ?
462 (ParcelableSparseArray) parcelable : new ParcelableSparseArray();
463 }
464
Tony Wickham0f97b782015-12-02 17:55:07 -0800465 public boolean getIsDragOverlapping() {
466 return mIsDragOverlapping;
467 }
468
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700469 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700470 protected void onDraw(Canvas canvas) {
Sunny Goyal05739772015-05-19 19:59:09 -0700471 if (!mIsDragTarget) {
472 return;
473 }
474
Michael Jurka3e7c7632010-10-02 16:01:03 -0700475 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
476 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
477 // When we're small, we are either drawn normally or in the "accepts drops" state (during
478 // a drag). However, we also drag the mini hover background *over* one of those two
479 // backgrounds
Sunny Goyal05739772015-05-19 19:59:09 -0700480 if (mBackgroundAlpha > 0.0f) {
Sunny Goyal2805e632015-05-20 15:35:32 -0700481 mBackground.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700482 }
Romain Guya6abce82009-11-10 02:54:41 -0800483
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700484 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700485 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700486 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700487 if (alpha > 0) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700488 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700489 paint.setAlpha((int)(alpha + .5f));
Sunny Goyal106bf642015-07-16 12:18:06 -0700490 canvas.drawBitmap(b, null, mDragOutlines[i], paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700491 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700492 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800493
Adam Cohen482ed822012-03-02 14:15:13 -0800494 if (DEBUG_VISUALIZE_OCCUPIED) {
495 int[] pt = new int[2];
496 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700497 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800498 for (int i = 0; i < mCountX; i++) {
499 for (int j = 0; j < mCountY; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700500 if (mOccupied.cells[i][j]) {
Adam Cohen482ed822012-03-02 14:15:13 -0800501 cellToPoint(i, j, pt);
502 canvas.save();
503 canvas.translate(pt[0], pt[1]);
504 cd.draw(canvas);
505 canvas.restore();
506 }
507 }
508 }
509 }
510
Adam Cohenefca0272016-02-24 19:19:06 -0800511 for (int i = 0; i < mFolderBackgrounds.size(); i++) {
512 FolderIcon.PreviewBackground bg = mFolderBackgrounds.get(i);
513 cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
514 canvas.save();
515 canvas.translate(mTempLocation[0], mTempLocation[1]);
516 bg.drawBackground(canvas, mFolderBgPaint);
Adam Cohenf172b742016-03-30 19:28:34 -0700517 if (!bg.isClipping) {
518 bg.drawBackgroundStroke(canvas, mFolderBgPaint);
519 }
Adam Cohenefca0272016-02-24 19:19:06 -0800520 canvas.restore();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700521 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700522
Adam Cohenefca0272016-02-24 19:19:06 -0800523 if (mFolderLeaveBehind.delegateCellX >= 0 && mFolderLeaveBehind.delegateCellY >= 0) {
524 cellToPoint(mFolderLeaveBehind.delegateCellX,
525 mFolderLeaveBehind.delegateCellY, mTempLocation);
526 canvas.save();
527 canvas.translate(mTempLocation[0], mTempLocation[1]);
528 mFolderLeaveBehind.drawLeaveBehind(canvas, mFolderBgPaint);
529 canvas.restore();
Adam Cohenc51934b2011-07-26 21:07:43 -0700530 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700531 }
532
Adam Cohenefca0272016-02-24 19:19:06 -0800533 @Override
534 protected void dispatchDraw(Canvas canvas) {
535 super.dispatchDraw(canvas);
536
537 for (int i = 0; i < mFolderBackgrounds.size(); i++) {
538 FolderIcon.PreviewBackground bg = mFolderBackgrounds.get(i);
Adam Cohenf172b742016-03-30 19:28:34 -0700539 if (bg.isClipping) {
540 cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
541 canvas.save();
542 canvas.translate(mTempLocation[0], mTempLocation[1]);
543 bg.drawBackgroundStroke(canvas, mFolderBgPaint);
544 canvas.restore();
545 }
Adam Cohenefca0272016-02-24 19:19:06 -0800546 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700547 }
548
Adam Cohenefca0272016-02-24 19:19:06 -0800549 public void addFolderBackground(FolderIcon.PreviewBackground bg) {
550 mFolderBackgrounds.add(bg);
551 }
552 public void removeFolderBackground(FolderIcon.PreviewBackground bg) {
553 mFolderBackgrounds.remove(bg);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700554 }
555
Adam Cohenc51934b2011-07-26 21:07:43 -0700556 public void setFolderLeaveBehindCell(int x, int y) {
Adam Cohenefca0272016-02-24 19:19:06 -0800557
558 DeviceProfile grid = mLauncher.getDeviceProfile();
559 View child = getChildAt(x, y);
560
561 mFolderLeaveBehind.setup(getResources().getDisplayMetrics(), grid, null,
562 child.getMeasuredWidth(), child.getPaddingTop());
563
564 mFolderLeaveBehind.delegateCellX = x;
565 mFolderLeaveBehind.delegateCellY = y;
Adam Cohenc51934b2011-07-26 21:07:43 -0700566 invalidate();
567 }
568
569 public void clearFolderLeaveBehind() {
Adam Cohenefca0272016-02-24 19:19:06 -0800570 mFolderLeaveBehind.delegateCellX = -1;
571 mFolderLeaveBehind.delegateCellY = -1;
Adam Cohenc51934b2011-07-26 21:07:43 -0700572 invalidate();
573 }
574
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700575 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700576 public boolean shouldDelayChildPressedState() {
577 return false;
578 }
579
Adam Cohen1462de32012-07-24 22:34:36 -0700580 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700581 try {
582 dispatchRestoreInstanceState(states);
583 } catch (IllegalArgumentException ex) {
Sunny Goyal6c56c682015-07-16 14:09:05 -0700584 if (ProviderConfig.IS_DOGFOOD_BUILD) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700585 throw ex;
586 }
587 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
588 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
589 }
Adam Cohen1462de32012-07-24 22:34:36 -0700590 }
591
Michael Jurkae6235dd2011-10-04 15:02:05 -0700592 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700593 public void cancelLongPress() {
594 super.cancelLongPress();
595
596 // Cancel long press for all children
597 final int count = getChildCount();
598 for (int i = 0; i < count; i++) {
599 final View child = getChildAt(i);
600 child.cancelLongPress();
601 }
602 }
603
Michael Jurkadee05892010-07-27 10:01:56 -0700604 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
605 mInterceptTouchListener = listener;
606 }
607
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800608 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700609 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800610 }
611
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800612 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700613 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800614 }
615
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800616 public void setIsHotseat(boolean isHotseat) {
617 mIsHotseat = isHotseat;
Winson Chung5f8afe62013-08-12 16:19:28 -0700618 mShortcutsAndWidgets.setIsHotseat(isHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800619 }
620
Sunny Goyale9b651e2015-04-24 11:44:51 -0700621 public boolean isHotseat() {
622 return mIsHotseat;
623 }
624
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800625 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700626 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700627 final LayoutParams lp = params;
628
Andrew Flynnde38e422012-05-08 11:22:15 -0700629 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800630 if (child instanceof BubbleTextView) {
631 BubbleTextView bubbleChild = (BubbleTextView) child;
Winson Chung5f8afe62013-08-12 16:19:28 -0700632 bubbleChild.setTextVisibility(!mIsHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800633 }
634
Adam Cohen307fe232012-08-16 17:55:58 -0700635 child.setScaleX(getChildrenScale());
636 child.setScaleY(getChildrenScale());
637
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800638 // Generate an id for each view, this assumes we have at most 256x256 cells
639 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700640 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700641 // If the horizontal or vertical span is set to -1, it is taken to
642 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700643 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
644 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800645
Winson Chungaafa03c2010-06-11 17:34:16 -0700646 child.setId(childId);
Tony Wickhama0628cc2015-10-14 15:23:04 -0700647 if (LOGD) {
648 Log.d(TAG, "Adding view to ShortcutsAndWidgetsContainer: " + child);
649 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700650 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700651
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700652 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700653
Winson Chungaafa03c2010-06-11 17:34:16 -0700654 return true;
655 }
656 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800657 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700658
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800659 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700660 public void removeAllViews() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700661 mOccupied.clear();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700662 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700663 }
664
665 @Override
666 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700667 if (mShortcutsAndWidgets.getChildCount() > 0) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700668 mOccupied.clear();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700669 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700670 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700671 }
672
673 @Override
674 public void removeView(View view) {
675 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700676 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700677 }
678
679 @Override
680 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700681 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
682 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700683 }
684
685 @Override
686 public void removeViewInLayout(View view) {
687 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700688 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700689 }
690
691 @Override
692 public void removeViews(int start, int count) {
693 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700694 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700695 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700696 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700697 }
698
699 @Override
700 public void removeViewsInLayout(int start, int count) {
701 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700702 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700703 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700704 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800705 }
706
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700707 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700708 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800709 * @param x X coordinate of the point
710 * @param y Y coordinate of the point
711 * @param result Array of 2 ints to hold the x and y coordinate of the cell
712 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700713 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700714 final int hStartPadding = getPaddingLeft();
715 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800716
717 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
718 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
719
Adam Cohend22015c2010-07-26 22:02:18 -0700720 final int xAxis = mCountX;
721 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800722
723 if (result[0] < 0) result[0] = 0;
724 if (result[0] >= xAxis) result[0] = xAxis - 1;
725 if (result[1] < 0) result[1] = 0;
726 if (result[1] >= yAxis) result[1] = yAxis - 1;
727 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700728
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800729 /**
730 * Given a point, return the cell that most closely encloses that point
731 * @param x X coordinate of the point
732 * @param y Y coordinate of the point
733 * @param result Array of 2 ints to hold the x and y coordinate of the cell
734 */
735 void pointToCellRounded(int x, int y, int[] result) {
736 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
737 }
738
739 /**
740 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700741 *
742 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800743 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700744 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800745 * @param result Array of 2 ints to hold the x and y coordinate of the point
746 */
747 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700748 final int hStartPadding = getPaddingLeft();
749 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800750
751 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
752 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
753 }
754
Adam Cohene3e27a82011-04-15 12:07:39 -0700755 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800756 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700757 *
758 * @param cellX X coordinate of the cell
759 * @param cellY Y coordinate of the cell
760 *
761 * @param result Array of 2 ints to hold the x and y coordinate of the point
762 */
763 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700764 regionToCenterPoint(cellX, cellY, 1, 1, result);
765 }
766
767 /**
768 * Given a cell coordinate and span return the point that represents the center of the regio
769 *
770 * @param cellX X coordinate of the cell
771 * @param cellY Y coordinate of the cell
772 *
773 * @param result Array of 2 ints to hold the x and y coordinate of the point
774 */
775 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700776 final int hStartPadding = getPaddingLeft();
777 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700778 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
779 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
780 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
781 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700782 }
783
Adam Cohen19f37922012-03-21 11:59:11 -0700784 /**
785 * Given a cell coordinate and span fills out a corresponding pixel rect
786 *
787 * @param cellX X coordinate of the cell
788 * @param cellY Y coordinate of the cell
789 * @param result Rect in which to write the result
790 */
791 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
792 final int hStartPadding = getPaddingLeft();
793 final int vStartPadding = getPaddingTop();
794 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
795 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
796 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
797 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
798 }
799
Adam Cohen482ed822012-03-02 14:15:13 -0800800 public float getDistanceFromCell(float x, float y, int[] cell) {
801 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700802 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800803 }
804
Adam Cohenf9c184a2016-01-15 16:47:43 -0800805 public int getCellWidth() {
Romain Guy84f296c2009-11-04 15:00:44 -0800806 return mCellWidth;
807 }
808
809 int getCellHeight() {
810 return mCellHeight;
811 }
812
Adam Cohend4844c32011-02-18 19:25:06 -0800813 int getWidthGap() {
814 return mWidthGap;
815 }
816
817 int getHeightGap() {
818 return mHeightGap;
819 }
820
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700821 public void setFixedSize(int width, int height) {
822 mFixedWidth = width;
823 mFixedHeight = height;
824 }
825
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800826 @Override
827 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800828 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800829 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700830 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
831 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700832 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
833 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700834 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Sunny Goyalc6205602015-05-21 20:46:33 -0700835 int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX);
836 int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700837 if (cw != mCellWidth || ch != mCellHeight) {
838 mCellWidth = cw;
839 mCellHeight = ch;
840 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
841 mHeightGap, mCountX, mCountY);
842 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700843 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700844
Winson Chung2d75f122013-09-23 16:53:31 -0700845 int newWidth = childWidthSize;
846 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700847 if (mFixedWidth > 0 && mFixedHeight > 0) {
848 newWidth = mFixedWidth;
849 newHeight = mFixedHeight;
850 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800851 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
852 }
853
Adam Cohend22015c2010-07-26 22:02:18 -0700854 int numWidthGaps = mCountX - 1;
855 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800856
Adam Cohen234c4cd2011-07-17 21:03:04 -0700857 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700858 int hSpace = childWidthSize;
859 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700860 int hFreeSpace = hSpace - (mCountX * mCellWidth);
861 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700862 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
863 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700864 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
865 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700866 } else {
867 mWidthGap = mOriginalWidthGap;
868 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700869 }
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700870
871 // Make the feedback view large enough to hold the blur bitmap.
872 mTouchFeedbackView.measure(
873 MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
874 MeasureSpec.EXACTLY),
875 MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
876 MeasureSpec.EXACTLY));
877
878 mShortcutsAndWidgets.measure(
879 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
880 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
881
882 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
883 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -0700884 if (mFixedWidth > 0 && mFixedHeight > 0) {
885 setMeasuredDimension(maxWidth, maxHeight);
886 } else {
887 setMeasuredDimension(widthSize, heightSize);
888 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800889 }
890
891 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700892 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Tony Wickham26b01422015-11-10 14:44:32 -0800893 boolean isFullscreen = mShortcutsAndWidgets.getChildCount() > 0 &&
894 ((LayoutParams) mShortcutsAndWidgets.getChildAt(0).getLayoutParams()).isFullscreen;
895 int left = getPaddingLeft();
896 if (!isFullscreen) {
Tony Wickhama501d492015-11-03 18:05:01 -0800897 left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Tony Wickham26b01422015-11-10 14:44:32 -0800898 }
Winson Chung38848ca2013-10-08 12:03:44 -0700899 int top = getPaddingTop();
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700900
901 mTouchFeedbackView.layout(left, top,
902 left + mTouchFeedbackView.getMeasuredWidth(),
903 top + mTouchFeedbackView.getMeasuredHeight());
904 mShortcutsAndWidgets.layout(left, top,
905 left + r - l,
906 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800907 }
908
Tony Wickhama501d492015-11-03 18:05:01 -0800909 /**
910 * Returns the amount of space left over after subtracting padding and cells. This space will be
911 * very small, a few pixels at most, and is a result of rounding down when calculating the cell
912 * width in {@link DeviceProfile#calculateCellWidth(int, int)}.
913 */
914 public int getUnusedHorizontalSpace() {
915 return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
916 }
917
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800918 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700919 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
920 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -0700921
922 // Expand the background drawing bounds by the padding baked into the background drawable
Sunny Goyal2805e632015-05-20 15:35:32 -0700923 mBackground.getPadding(mTempRect);
924 mBackground.setBounds(-mTempRect.left, -mTempRect.top,
925 w + mTempRect.right, h + mTempRect.bottom);
Michael Jurkadee05892010-07-27 10:01:56 -0700926 }
927
928 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800929 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700930 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800931 }
932
933 @Override
934 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700935 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800936 }
937
Michael Jurka5f1c5092010-09-03 14:15:02 -0700938 public float getBackgroundAlpha() {
939 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -0700940 }
941
Michael Jurka5f1c5092010-09-03 14:15:02 -0700942 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -0800943 if (mBackgroundAlpha != alpha) {
944 mBackgroundAlpha = alpha;
Sunny Goyal2805e632015-05-20 15:35:32 -0700945 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurkaafaa0502011-12-13 18:22:50 -0800946 }
Michael Jurkadee05892010-07-27 10:01:56 -0700947 }
948
Sunny Goyal2805e632015-05-20 15:35:32 -0700949 @Override
950 protected boolean verifyDrawable(Drawable who) {
951 return super.verifyDrawable(who) || (mIsDragTarget && who == mBackground);
952 }
953
Michael Jurkaa52570f2012-03-20 03:18:20 -0700954 public void setShortcutAndWidgetAlpha(float alpha) {
Sunny Goyal02b50812014-09-10 15:44:42 -0700955 mShortcutsAndWidgets.setAlpha(alpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700956 }
957
Michael Jurkaa52570f2012-03-20 03:18:20 -0700958 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700959 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700960 }
961
Patrick Dubroy440c3602010-07-13 17:50:32 -0700962 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700963 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700964 }
965
Adam Cohen76fc0852011-06-17 13:26:23 -0700966 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800967 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700968 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800969
Adam Cohen19f37922012-03-21 11:59:11 -0700970 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700971 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
972 final ItemInfo info = (ItemInfo) child.getTag();
973
974 // We cancel any existing animations
975 if (mReorderAnimators.containsKey(lp)) {
976 mReorderAnimators.get(lp).cancel();
977 mReorderAnimators.remove(lp);
978 }
979
Adam Cohen482ed822012-03-02 14:15:13 -0800980 final int oldX = lp.x;
981 final int oldY = lp.y;
982 if (adjustOccupied) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700983 GridOccupancy occupied = permanent ? mOccupied : mTmpOccupied;
984 occupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
985 occupied.markCells(cellX, cellY, lp.cellHSpan, lp.cellVSpan, true);
Adam Cohen482ed822012-03-02 14:15:13 -0800986 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700987 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -0800988 if (permanent) {
989 lp.cellX = info.cellX = cellX;
990 lp.cellY = info.cellY = cellY;
991 } else {
992 lp.tmpCellX = cellX;
993 lp.tmpCellY = cellY;
994 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700995 clc.setupLp(lp);
996 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800997 final int newX = lp.x;
998 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700999
Adam Cohen76fc0852011-06-17 13:26:23 -07001000 lp.x = oldX;
1001 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001002
Adam Cohen482ed822012-03-02 14:15:13 -08001003 // Exit early if we're not actually moving the view
1004 if (oldX == newX && oldY == newY) {
1005 lp.isLockedToGrid = true;
1006 return true;
1007 }
1008
Michael Jurkaf1ad6082013-03-13 12:55:46 +01001009 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001010 va.setDuration(duration);
1011 mReorderAnimators.put(lp, va);
1012
1013 va.addUpdateListener(new AnimatorUpdateListener() {
1014 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001015 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001016 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001017 lp.x = (int) ((1 - r) * oldX + r * newX);
1018 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001019 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001020 }
1021 });
Adam Cohen482ed822012-03-02 14:15:13 -08001022 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001023 boolean cancelled = false;
1024 public void onAnimationEnd(Animator animation) {
1025 // If the animation was cancelled, it means that another animation
1026 // has interrupted this one, and we don't want to lock the item into
1027 // place just yet.
1028 if (!cancelled) {
1029 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001030 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001031 }
1032 if (mReorderAnimators.containsKey(lp)) {
1033 mReorderAnimators.remove(lp);
1034 }
1035 }
1036 public void onAnimationCancel(Animator animation) {
1037 cancelled = true;
1038 }
1039 });
Adam Cohen482ed822012-03-02 14:15:13 -08001040 va.setStartDelay(delay);
1041 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001042 return true;
1043 }
1044 return false;
1045 }
1046
Tony Wickhama501d492015-11-03 18:05:01 -08001047 void visualizeDropLocation(View v, Bitmap dragOutline, int cellX, int cellY, int spanX,
1048 int spanY, boolean resize, DropTarget.DragObject dragObject) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001049 final int oldDragCellX = mDragCell[0];
1050 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001051
Adam Cohen2801caf2011-05-13 20:57:39 -07001052 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001053 return;
1054 }
1055
Adam Cohen482ed822012-03-02 14:15:13 -08001056 if (cellX != oldDragCellX || cellY != oldDragCellY) {
Sunny Goyale78e3d72015-09-24 11:23:31 -07001057 Point dragOffset = dragObject.dragView.getDragVisualizeOffset();
1058 Rect dragRegion = dragObject.dragView.getDragRegion();
1059
Adam Cohen482ed822012-03-02 14:15:13 -08001060 mDragCell[0] = cellX;
1061 mDragCell[1] = cellY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001062
Joe Onorato4be866d2010-10-10 11:26:02 -07001063 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001064 mDragOutlineAnims[oldIndex].animateOut();
1065 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001066 Rect r = mDragOutlines[mDragOutlineCurrent];
Sunny Goyal106bf642015-07-16 12:18:06 -07001067
Adam Cohend41fbf52012-02-16 23:53:59 -08001068 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001069 cellToRect(cellX, cellY, spanX, spanY, r);
Sunny Goyal106bf642015-07-16 12:18:06 -07001070 } else {
1071 // Find the top left corner of the rect the object will occupy
1072 final int[] topLeft = mTmpPoint;
1073 cellToPoint(cellX, cellY, topLeft);
1074
1075 int left = topLeft[0];
1076 int top = topLeft[1];
1077
1078 if (v != null && dragOffset == null) {
1079 // When drawing the drag outline, it did not account for margin offsets
1080 // added by the view's parent.
1081 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1082 left += lp.leftMargin;
1083 top += lp.topMargin;
1084
1085 // Offsets due to the size difference between the View and the dragOutline.
1086 // There is a size difference to account for the outer blur, which may lie
1087 // outside the bounds of the view.
1088 top += (v.getHeight() - dragOutline.getHeight()) / 2;
1089 // We center about the x axis
1090 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1091 - dragOutline.getWidth()) / 2;
1092 } else {
1093 if (dragOffset != null && dragRegion != null) {
1094 // Center the drag region *horizontally* in the cell and apply a drag
1095 // outline offset
1096 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1097 - dragRegion.width()) / 2;
1098 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1099 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1100 top += dragOffset.y + cellPaddingY;
1101 } else {
1102 // Center the drag outline in the cell
1103 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1104 - dragOutline.getWidth()) / 2;
1105 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1106 - dragOutline.getHeight()) / 2;
1107 }
1108 }
1109 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
Adam Cohend41fbf52012-02-16 23:53:59 -08001110 }
Winson Chung150fbab2010-09-29 17:14:26 -07001111
Sunny Goyal106bf642015-07-16 12:18:06 -07001112 Utilities.scaleRectAboutCenter(r, getChildrenScale());
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001113 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1114 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Sunny Goyale78e3d72015-09-24 11:23:31 -07001115
1116 if (dragObject.stateAnnouncer != null) {
1117 String msg;
1118 if (isHotseat()) {
1119 msg = getContext().getString(R.string.move_to_hotseat_position,
1120 Math.max(cellX, cellY) + 1);
1121 } else {
1122 msg = getContext().getString(R.string.move_to_empty_cell,
1123 cellY + 1, cellX + 1);
1124 }
1125 dragObject.stateAnnouncer.announce(msg);
1126 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001127 }
1128 }
1129
Adam Cohene0310962011-04-18 16:15:31 -07001130 public void clearDragOutlines() {
1131 final int oldIndex = mDragOutlineCurrent;
1132 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001133 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001134 }
1135
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001136 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001137 * Find a vacant area that will fit the given bounds nearest the requested
1138 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001139 *
Romain Guy51afc022009-05-04 18:03:43 -07001140 * @param pixelX The X location at which you want to search for a vacant area.
1141 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001142 * @param minSpanX The minimum horizontal span required
1143 * @param minSpanY The minimum vertical span required
1144 * @param spanX Horizontal span of the object.
1145 * @param spanY Vertical span of the object.
1146 * @param result Array in which to place the result, or null (in which case a new array will
1147 * be allocated)
1148 * @return The X, Y cell of a vacant area that can contain this object,
1149 * nearest the requested location.
1150 */
1151 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1152 int spanY, int[] result, int[] resultSpan) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001153 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
Adam Cohend41fbf52012-02-16 23:53:59 -08001154 result, resultSpan);
1155 }
1156
Adam Cohend41fbf52012-02-16 23:53:59 -08001157 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1158 private void lazyInitTempRectStack() {
1159 if (mTempRectStack.isEmpty()) {
1160 for (int i = 0; i < mCountX * mCountY; i++) {
1161 mTempRectStack.push(new Rect());
1162 }
1163 }
1164 }
Adam Cohen482ed822012-03-02 14:15:13 -08001165
Adam Cohend41fbf52012-02-16 23:53:59 -08001166 private void recycleTempRects(Stack<Rect> used) {
1167 while (!used.isEmpty()) {
1168 mTempRectStack.push(used.pop());
1169 }
1170 }
1171
1172 /**
1173 * Find a vacant area that will fit the given bounds nearest the requested
1174 * cell location. Uses Euclidean distance to score multiple vacant areas.
1175 *
1176 * @param pixelX The X location at which you want to search for a vacant area.
1177 * @param pixelY The Y location at which you want to search for a vacant area.
1178 * @param minSpanX The minimum horizontal span required
1179 * @param minSpanY The minimum vertical span required
1180 * @param spanX Horizontal span of the object.
1181 * @param spanY Vertical span of the object.
1182 * @param ignoreOccupied If true, the result can be an occupied cell
1183 * @param result Array in which to place the result, or null (in which case a new array will
1184 * be allocated)
1185 * @return The X, Y cell of a vacant area that can contain this object,
1186 * nearest the requested location.
1187 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001188 private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1189 int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001190 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001191
Adam Cohene3e27a82011-04-15 12:07:39 -07001192 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1193 // to the center of the item, but we are searching based on the top-left cell, so
1194 // we translate the point over to correspond to the top-left.
1195 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1196 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1197
Jeff Sharkey70864282009-04-07 21:08:40 -07001198 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001199 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001200 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001201 final Rect bestRect = new Rect(-1, -1, -1, -1);
1202 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001203
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001204 final int countX = mCountX;
1205 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001206
Adam Cohend41fbf52012-02-16 23:53:59 -08001207 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1208 spanX < minSpanX || spanY < minSpanY) {
1209 return bestXY;
1210 }
1211
1212 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001213 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001214 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1215 int ySize = -1;
1216 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001217 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001218 // First, let's see if this thing fits anywhere
1219 for (int i = 0; i < minSpanX; i++) {
1220 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001221 if (mOccupied.cells[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001222 continue inner;
1223 }
Michael Jurkac28de512010-08-13 11:27:44 -07001224 }
1225 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001226 xSize = minSpanX;
1227 ySize = minSpanY;
1228
1229 // We know that the item will fit at _some_ acceptable size, now let's see
1230 // how big we can make it. We'll alternate between incrementing x and y spans
1231 // until we hit a limit.
1232 boolean incX = true;
1233 boolean hitMaxX = xSize >= spanX;
1234 boolean hitMaxY = ySize >= spanY;
1235 while (!(hitMaxX && hitMaxY)) {
1236 if (incX && !hitMaxX) {
1237 for (int j = 0; j < ySize; j++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001238 if (x + xSize > countX -1 || mOccupied.cells[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001239 // We can't move out horizontally
1240 hitMaxX = true;
1241 }
1242 }
1243 if (!hitMaxX) {
1244 xSize++;
1245 }
1246 } else if (!hitMaxY) {
1247 for (int i = 0; i < xSize; i++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001248 if (y + ySize > countY - 1 || mOccupied.cells[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001249 // We can't move out vertically
1250 hitMaxY = true;
1251 }
1252 }
1253 if (!hitMaxY) {
1254 ySize++;
1255 }
1256 }
1257 hitMaxX |= xSize >= spanX;
1258 hitMaxY |= ySize >= spanY;
1259 incX = !incX;
1260 }
1261 incX = true;
1262 hitMaxX = xSize >= spanX;
1263 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001264 }
Sunny Goyal2805e632015-05-20 15:35:32 -07001265 final int[] cellXY = mTmpPoint;
Adam Cohene3e27a82011-04-15 12:07:39 -07001266 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001267
Adam Cohend41fbf52012-02-16 23:53:59 -08001268 // We verify that the current rect is not a sub-rect of any of our previous
1269 // candidates. In this case, the current rect is disqualified in favour of the
1270 // containing rect.
1271 Rect currentRect = mTempRectStack.pop();
1272 currentRect.set(x, y, x + xSize, y + ySize);
1273 boolean contained = false;
1274 for (Rect r : validRegions) {
1275 if (r.contains(currentRect)) {
1276 contained = true;
1277 break;
1278 }
1279 }
1280 validRegions.push(currentRect);
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001281 double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY);
Adam Cohen482ed822012-03-02 14:15:13 -08001282
Adam Cohend41fbf52012-02-16 23:53:59 -08001283 if ((distance <= bestDistance && !contained) ||
1284 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001285 bestDistance = distance;
1286 bestXY[0] = x;
1287 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001288 if (resultSpan != null) {
1289 resultSpan[0] = xSize;
1290 resultSpan[1] = ySize;
1291 }
1292 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001293 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001294 }
1295 }
1296
Adam Cohenc0dcf592011-06-01 15:30:43 -07001297 // Return -1, -1 if no suitable location found
1298 if (bestDistance == Double.MAX_VALUE) {
1299 bestXY[0] = -1;
1300 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001301 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001302 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001303 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001304 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001305
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001306 /**
Adam Cohen482ed822012-03-02 14:15:13 -08001307 * Find a vacant area that will fit the given bounds nearest the requested
1308 * cell location, and will also weigh in a suggested direction vector of the
1309 * desired location. This method computers distance based on unit grid distances,
1310 * not pixel distances.
1311 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001312 * @param cellX The X cell nearest to which you want to search for a vacant area.
1313 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001314 * @param spanX Horizontal span of the object.
1315 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001316 * @param direction The favored direction in which the views should move from x, y
Sunny Goyal9eba1fd2015-10-16 08:58:57 -07001317 * @param occupied The array which represents which cells in the CellLayout are occupied
Adam Cohen47a876d2012-03-19 13:21:41 -07001318 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001319 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001320 * @param result Array in which to place the result, or null (in which case a new array will
1321 * be allocated)
1322 * @return The X, Y cell of a vacant area that can contain this object,
1323 * nearest the requested location.
1324 */
1325 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001326 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001327 // Keep track of best-scoring drop area
1328 final int[] bestXY = result != null ? result : new int[2];
1329 float bestDistance = Float.MAX_VALUE;
1330 int bestDirectionScore = Integer.MIN_VALUE;
1331
1332 final int countX = mCountX;
1333 final int countY = mCountY;
1334
1335 for (int y = 0; y < countY - (spanY - 1); y++) {
1336 inner:
1337 for (int x = 0; x < countX - (spanX - 1); x++) {
1338 // First, let's see if this thing fits anywhere
1339 for (int i = 0; i < spanX; i++) {
1340 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001341 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001342 continue inner;
1343 }
1344 }
1345 }
1346
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001347 float distance = (float) Math.hypot(x - cellX, y - cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001348 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001349 computeDirectionVector(x - cellX, y - cellY, curDirection);
1350 // The direction score is just the dot product of the two candidate direction
1351 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001352 int curDirectionScore = direction[0] * curDirection[0] +
1353 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001354 boolean exactDirectionOnly = false;
1355 boolean directionMatches = direction[0] == curDirection[0] &&
1356 direction[0] == curDirection[0];
1357 if ((directionMatches || !exactDirectionOnly) &&
1358 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001359 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1360 bestDistance = distance;
1361 bestDirectionScore = curDirectionScore;
1362 bestXY[0] = x;
1363 bestXY[1] = y;
1364 }
1365 }
1366 }
1367
1368 // Return -1, -1 if no suitable location found
1369 if (bestDistance == Float.MAX_VALUE) {
1370 bestXY[0] = -1;
1371 bestXY[1] = -1;
1372 }
1373 return bestXY;
1374 }
1375
1376 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001377 int[] direction, ItemConfiguration currentState) {
1378 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001379 boolean success = false;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001380 mTmpOccupied.markCells(c, false);
1381 mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001382
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001383 findNearestArea(c.cellX, c.cellY, c.spanX, c.spanY, direction,
1384 mTmpOccupied.cells, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001385
1386 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001387 c.cellX = mTempLocation[0];
1388 c.cellY = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001389 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001390 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001391 mTmpOccupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001392 return success;
1393 }
1394
Adam Cohenf3900c22012-11-16 18:28:11 -08001395 /**
1396 * This helper class defines a cluster of views. It helps with defining complex edges
1397 * of the cluster and determining how those edges interact with other views. The edges
1398 * essentially define a fine-grained boundary around the cluster of views -- like a more
1399 * precise version of a bounding box.
1400 */
1401 private class ViewCluster {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001402 final static int LEFT = 1 << 0;
1403 final static int TOP = 1 << 1;
1404 final static int RIGHT = 1 << 2;
1405 final static int BOTTOM = 1 << 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001406
Adam Cohenf3900c22012-11-16 18:28:11 -08001407 ArrayList<View> views;
1408 ItemConfiguration config;
1409 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001410
Adam Cohenf3900c22012-11-16 18:28:11 -08001411 int[] leftEdge = new int[mCountY];
1412 int[] rightEdge = new int[mCountY];
1413 int[] topEdge = new int[mCountX];
1414 int[] bottomEdge = new int[mCountX];
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001415 int dirtyEdges;
1416 boolean boundingRectDirty;
Adam Cohenf3900c22012-11-16 18:28:11 -08001417
1418 @SuppressWarnings("unchecked")
1419 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1420 this.views = (ArrayList<View>) views.clone();
1421 this.config = config;
1422 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001423 }
1424
Adam Cohenf3900c22012-11-16 18:28:11 -08001425 void resetEdges() {
1426 for (int i = 0; i < mCountX; i++) {
1427 topEdge[i] = -1;
1428 bottomEdge[i] = -1;
1429 }
1430 for (int i = 0; i < mCountY; i++) {
1431 leftEdge[i] = -1;
1432 rightEdge[i] = -1;
1433 }
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001434 dirtyEdges = LEFT | TOP | RIGHT | BOTTOM;
Adam Cohenf3900c22012-11-16 18:28:11 -08001435 boundingRectDirty = true;
1436 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001437
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001438 void computeEdge(int which) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001439 int count = views.size();
1440 for (int i = 0; i < count; i++) {
1441 CellAndSpan cs = config.map.get(views.get(i));
1442 switch (which) {
1443 case LEFT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001444 int left = cs.cellX;
1445 for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001446 if (left < leftEdge[j] || leftEdge[j] < 0) {
1447 leftEdge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001448 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001449 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001450 break;
1451 case RIGHT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001452 int right = cs.cellX + cs.spanX;
1453 for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001454 if (right > rightEdge[j]) {
1455 rightEdge[j] = right;
Adam Cohenf3900c22012-11-16 18:28:11 -08001456 }
1457 }
1458 break;
1459 case TOP:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001460 int top = cs.cellY;
1461 for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001462 if (top < topEdge[j] || topEdge[j] < 0) {
1463 topEdge[j] = top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001464 }
1465 }
1466 break;
1467 case BOTTOM:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001468 int bottom = cs.cellY + cs.spanY;
1469 for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) {
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001470 if (bottom > bottomEdge[j]) {
1471 bottomEdge[j] = bottom;
Adam Cohenf3900c22012-11-16 18:28:11 -08001472 }
1473 }
1474 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001475 }
1476 }
1477 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001478
1479 boolean isViewTouchingEdge(View v, int whichEdge) {
1480 CellAndSpan cs = config.map.get(v);
1481
Sunny Goyal6dc98b92016-04-03 15:25:29 -07001482 if ((dirtyEdges & whichEdge) == whichEdge) {
1483 computeEdge(whichEdge);
1484 dirtyEdges &= ~whichEdge;
1485 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001486
1487 switch (whichEdge) {
1488 case LEFT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001489 for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) {
1490 if (leftEdge[i] == cs.cellX + cs.spanX) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001491 return true;
1492 }
1493 }
1494 break;
1495 case RIGHT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001496 for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) {
1497 if (rightEdge[i] == cs.cellX) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001498 return true;
1499 }
1500 }
1501 break;
1502 case TOP:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001503 for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) {
1504 if (topEdge[i] == cs.cellY + cs.spanY) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001505 return true;
1506 }
1507 }
1508 break;
1509 case BOTTOM:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001510 for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) {
1511 if (bottomEdge[i] == cs.cellY) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001512 return true;
1513 }
1514 }
1515 break;
1516 }
1517 return false;
1518 }
1519
1520 void shift(int whichEdge, int delta) {
1521 for (View v: views) {
1522 CellAndSpan c = config.map.get(v);
1523 switch (whichEdge) {
1524 case LEFT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001525 c.cellX -= delta;
Adam Cohenf3900c22012-11-16 18:28:11 -08001526 break;
1527 case RIGHT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001528 c.cellX += delta;
Adam Cohenf3900c22012-11-16 18:28:11 -08001529 break;
1530 case TOP:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001531 c.cellY -= delta;
Adam Cohenf3900c22012-11-16 18:28:11 -08001532 break;
1533 case BOTTOM:
1534 default:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001535 c.cellY += delta;
Adam Cohenf3900c22012-11-16 18:28:11 -08001536 break;
1537 }
1538 }
1539 resetEdges();
1540 }
1541
1542 public void addView(View v) {
1543 views.add(v);
1544 resetEdges();
1545 }
1546
1547 public Rect getBoundingRect() {
1548 if (boundingRectDirty) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001549 config.getBoundingRectForViews(views, boundingRect);
Adam Cohenf3900c22012-11-16 18:28:11 -08001550 }
1551 return boundingRect;
1552 }
1553
Adam Cohenf3900c22012-11-16 18:28:11 -08001554 PositionComparator comparator = new PositionComparator();
1555 class PositionComparator implements Comparator<View> {
1556 int whichEdge = 0;
1557 public int compare(View left, View right) {
1558 CellAndSpan l = config.map.get(left);
1559 CellAndSpan r = config.map.get(right);
1560 switch (whichEdge) {
1561 case LEFT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001562 return (r.cellX + r.spanX) - (l.cellX + l.spanX);
Adam Cohenf3900c22012-11-16 18:28:11 -08001563 case RIGHT:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001564 return l.cellX - r.cellX;
Adam Cohenf3900c22012-11-16 18:28:11 -08001565 case TOP:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001566 return (r.cellY + r.spanY) - (l.cellY + l.spanY);
Adam Cohenf3900c22012-11-16 18:28:11 -08001567 case BOTTOM:
1568 default:
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001569 return l.cellY - r.cellY;
Adam Cohenf3900c22012-11-16 18:28:11 -08001570 }
1571 }
1572 }
1573
1574 public void sortConfigurationForEdgePush(int edge) {
1575 comparator.whichEdge = edge;
1576 Collections.sort(config.sortedViews, comparator);
1577 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001578 }
1579
Adam Cohenf3900c22012-11-16 18:28:11 -08001580 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1581 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001582
Adam Cohenf3900c22012-11-16 18:28:11 -08001583 ViewCluster cluster = new ViewCluster(views, currentState);
1584 Rect clusterRect = cluster.getBoundingRect();
1585 int whichEdge;
1586 int pushDistance;
1587 boolean fail = false;
1588
1589 // Determine the edge of the cluster that will be leading the push and how far
1590 // the cluster must be shifted.
1591 if (direction[0] < 0) {
1592 whichEdge = ViewCluster.LEFT;
1593 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001594 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001595 whichEdge = ViewCluster.RIGHT;
1596 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1597 } else if (direction[1] < 0) {
1598 whichEdge = ViewCluster.TOP;
1599 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1600 } else {
1601 whichEdge = ViewCluster.BOTTOM;
1602 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001603 }
1604
Adam Cohenf3900c22012-11-16 18:28:11 -08001605 // Break early for invalid push distance.
1606 if (pushDistance <= 0) {
1607 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001608 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001609
1610 // Mark the occupied state as false for the group of views we want to move.
1611 for (View v: views) {
1612 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001613 mTmpOccupied.markCells(c, false);
Adam Cohenf3900c22012-11-16 18:28:11 -08001614 }
1615
1616 // We save the current configuration -- if we fail to find a solution we will revert
1617 // to the initial state. The process of finding a solution modifies the configuration
1618 // in place, hence the need for revert in the failure case.
1619 currentState.save();
1620
1621 // The pushing algorithm is simplified by considering the views in the order in which
1622 // they would be pushed by the cluster. For example, if the cluster is leading with its
1623 // left edge, we consider sort the views by their right edge, from right to left.
1624 cluster.sortConfigurationForEdgePush(whichEdge);
1625
1626 while (pushDistance > 0 && !fail) {
1627 for (View v: currentState.sortedViews) {
1628 // For each view that isn't in the cluster, we see if the leading edge of the
1629 // cluster is contacting the edge of that view. If so, we add that view to the
1630 // cluster.
1631 if (!cluster.views.contains(v) && v != dragView) {
1632 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1633 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1634 if (!lp.canReorder) {
1635 // The push solution includes the all apps button, this is not viable.
1636 fail = true;
1637 break;
1638 }
1639 cluster.addView(v);
1640 CellAndSpan c = currentState.map.get(v);
1641
1642 // Adding view to cluster, mark it as not occupied.
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001643 mTmpOccupied.markCells(c, false);
Adam Cohenf3900c22012-11-16 18:28:11 -08001644 }
1645 }
1646 }
1647 pushDistance--;
1648
1649 // The cluster has been completed, now we move the whole thing over in the appropriate
1650 // direction.
1651 cluster.shift(whichEdge, 1);
1652 }
1653
1654 boolean foundSolution = false;
1655 clusterRect = cluster.getBoundingRect();
1656
1657 // Due to the nature of the algorithm, the only check required to verify a valid solution
1658 // is to ensure that completed shifted cluster lies completely within the cell layout.
1659 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1660 clusterRect.bottom <= mCountY) {
1661 foundSolution = true;
1662 } else {
1663 currentState.restore();
1664 }
1665
1666 // In either case, we set the occupied array as marked for the location of the views
1667 for (View v: cluster.views) {
1668 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001669 mTmpOccupied.markCells(c, true);
Adam Cohenf3900c22012-11-16 18:28:11 -08001670 }
1671
1672 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001673 }
1674
Adam Cohen482ed822012-03-02 14:15:13 -08001675 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001676 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001677 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001678
Adam Cohen8baab352012-03-20 17:39:21 -07001679 boolean success = false;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001680 Rect boundingRect = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001681 // We construct a rect which represents the entire group of views passed in
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001682 currentState.getBoundingRectForViews(views, boundingRect);
Adam Cohen8baab352012-03-20 17:39:21 -07001683
Adam Cohen8baab352012-03-20 17:39:21 -07001684 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001685 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001686 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001687 mTmpOccupied.markCells(c, false);
Adam Cohen8baab352012-03-20 17:39:21 -07001688 }
1689
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001690 GridOccupancy blockOccupied = new GridOccupancy(boundingRect.width(), boundingRect.height());
Adam Cohen47a876d2012-03-19 13:21:41 -07001691 int top = boundingRect.top;
1692 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001693 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001694 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001695 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001696 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001697 blockOccupied.markCells(c.cellX - left, c.cellY - top, c.spanX, c.spanY, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001698 }
1699
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001700 mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001701
Adam Cohenf3900c22012-11-16 18:28:11 -08001702 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001703 boundingRect.height(), direction,
1704 mTmpOccupied.cells, blockOccupied.cells, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001705
Adam Cohen8baab352012-03-20 17:39:21 -07001706 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001707 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001708 int deltaX = mTempLocation[0] - boundingRect.left;
1709 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001710 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001711 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001712 c.cellX += deltaX;
1713 c.cellY += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001714 }
1715 success = true;
1716 }
Adam Cohen8baab352012-03-20 17:39:21 -07001717
1718 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001719 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001720 CellAndSpan c = currentState.map.get(v);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001721 mTmpOccupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001722 }
1723 return success;
1724 }
1725
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001726 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1727 // to push items in each of the cardinal directions, in an order based on the direction vector
1728 // passed.
1729 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1730 int[] direction, View ignoreView, ItemConfiguration solution) {
1731 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001732 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001733 // separately in each of the components.
1734 int temp = direction[1];
1735 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001736
1737 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001738 ignoreView, solution)) {
1739 return true;
1740 }
1741 direction[1] = temp;
1742 temp = direction[0];
1743 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001744
1745 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001746 ignoreView, solution)) {
1747 return true;
1748 }
1749 // Revert the direction
1750 direction[0] = temp;
1751
1752 // Now we try pushing in each component of the opposite direction
1753 direction[0] *= -1;
1754 direction[1] *= -1;
1755 temp = direction[1];
1756 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001757 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001758 ignoreView, solution)) {
1759 return true;
1760 }
1761
1762 direction[1] = temp;
1763 temp = direction[0];
1764 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001765 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001766 ignoreView, solution)) {
1767 return true;
1768 }
1769 // revert the direction
1770 direction[0] = temp;
1771 direction[0] *= -1;
1772 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001773
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001774 } else {
1775 // If the direction vector has a single non-zero component, we push first in the
1776 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001777 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001778 ignoreView, solution)) {
1779 return true;
1780 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001781 // Then we try the opposite direction
1782 direction[0] *= -1;
1783 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001784 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001785 ignoreView, solution)) {
1786 return true;
1787 }
1788 // Switch the direction back
1789 direction[0] *= -1;
1790 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001791
1792 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001793 // to find a solution by pushing along the perpendicular axis.
1794
1795 // Swap the components
1796 int temp = direction[1];
1797 direction[1] = direction[0];
1798 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001799 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001800 ignoreView, solution)) {
1801 return true;
1802 }
1803
1804 // Then we try the opposite direction
1805 direction[0] *= -1;
1806 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001807 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001808 ignoreView, solution)) {
1809 return true;
1810 }
1811 // Switch the direction back
1812 direction[0] *= -1;
1813 direction[1] *= -1;
1814
1815 // Swap the components back
1816 temp = direction[1];
1817 direction[1] = direction[0];
1818 direction[0] = temp;
1819 }
1820 return false;
1821 }
1822
Adam Cohen482ed822012-03-02 14:15:13 -08001823 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001824 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001825 // Return early if get invalid cell positions
1826 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001827
Adam Cohen8baab352012-03-20 17:39:21 -07001828 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001829 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001830
Adam Cohen8baab352012-03-20 17:39:21 -07001831 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001832 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001833 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001834 if (c != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001835 c.cellX = cellX;
1836 c.cellY = cellY;
Adam Cohen19f37922012-03-21 11:59:11 -07001837 }
Adam Cohen482ed822012-03-02 14:15:13 -08001838 }
Adam Cohen482ed822012-03-02 14:15:13 -08001839 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1840 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001841 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001842 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001843 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001844 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001845 r1.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001846 if (Rect.intersects(r0, r1)) {
1847 if (!lp.canReorder) {
1848 return false;
1849 }
1850 mIntersectingViews.add(child);
1851 }
1852 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001853
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001854 solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
1855
Winson Chung5f8afe62013-08-12 16:19:28 -07001856 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001857 // we try to find a solution such that no displaced item travels through another item
1858 // without also displacing that item.
1859 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001860 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001861 return true;
1862 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001863
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001864 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08001865 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001866 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001867 return true;
1868 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001869
Adam Cohen482ed822012-03-02 14:15:13 -08001870 // Ok, they couldn't move as a block, let's move them individually
1871 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001872 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001873 return false;
1874 }
1875 }
1876 return true;
1877 }
1878
1879 /*
1880 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1881 * the provided point and the provided cell
1882 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001883 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001884 double angle = Math.atan(((float) deltaY) / deltaX);
1885
1886 result[0] = 0;
1887 result[1] = 0;
1888 if (Math.abs(Math.cos(angle)) > 0.5f) {
1889 result[0] = (int) Math.signum(deltaX);
1890 }
1891 if (Math.abs(Math.sin(angle)) > 0.5f) {
1892 result[1] = (int) Math.signum(deltaY);
1893 }
1894 }
1895
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001896 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001897 int spanX, int spanY, int[] direction, View dragView, boolean decX,
1898 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001899 // Copy the current state into the solution. This solution will be manipulated as necessary.
1900 copyCurrentStateToSolution(solution, false);
1901 // Copy the current occupied array into the temporary occupied array. This array will be
1902 // manipulated as necessary to find a solution.
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001903 mOccupied.copyTo(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001904
1905 // We find the nearest cell into which we would place the dragged item, assuming there's
1906 // nothing in its way.
1907 int result[] = new int[2];
1908 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1909
1910 boolean success = false;
1911 // First we try the exact nearest position of the item being dragged,
1912 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001913 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1914 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001915
1916 if (!success) {
1917 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1918 // x, then 1 in y etc.
1919 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001920 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
1921 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001922 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001923 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
1924 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001925 }
1926 solution.isSolution = false;
1927 } else {
1928 solution.isSolution = true;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001929 solution.cellX = result[0];
1930 solution.cellY = result[1];
1931 solution.spanX = spanX;
1932 solution.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08001933 }
1934 return solution;
1935 }
1936
1937 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001938 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001939 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001940 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001941 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001942 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08001943 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07001944 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001945 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001946 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001947 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001948 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08001949 }
1950 }
1951
1952 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001953 mTmpOccupied.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001954
Michael Jurkaa52570f2012-03-20 03:18:20 -07001955 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001956 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001957 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001958 if (child == dragView) continue;
1959 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001960 CellAndSpan c = solution.map.get(child);
1961 if (c != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001962 lp.tmpCellX = c.cellX;
1963 lp.tmpCellY = c.cellY;
Adam Cohen8baab352012-03-20 17:39:21 -07001964 lp.cellHSpan = c.spanX;
1965 lp.cellVSpan = c.spanY;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001966 mTmpOccupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001967 }
1968 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001969 mTmpOccupied.markCells(solution, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001970 }
1971
1972 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
1973 commitDragView) {
1974
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001975 GridOccupancy occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
1976 occupied.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001977
Michael Jurkaa52570f2012-03-20 03:18:20 -07001978 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001979 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001980 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001981 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001982 CellAndSpan c = solution.map.get(child);
1983 if (c != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001984 animateChildToPosition(child, c.cellX, c.cellY, REORDER_ANIMATION_DURATION, 0,
Adam Cohen19f37922012-03-21 11:59:11 -07001985 DESTRUCTIVE_REORDER, false);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001986 occupied.markCells(c, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001987 }
1988 }
1989 if (commitDragView) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07001990 occupied.markCells(solution, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001991 }
1992 }
1993
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001994
1995 // This method starts or changes the reorder preview animations
1996 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
1997 View dragView, int delay, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07001998 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07001999 for (int i = 0; i < childCount; i++) {
2000 View child = mShortcutsAndWidgets.getChildAt(i);
2001 if (child == dragView) continue;
2002 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002003 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
2004 != null && !solution.intersectingViews.contains(child);
2005
Adam Cohen19f37922012-03-21 11:59:11 -07002006 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002007 if (c != null && !skip) {
2008 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002009 lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002010 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002011 }
2012 }
2013 }
2014
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002015 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07002016 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002017 class ReorderPreviewAnimation {
Adam Cohen19f37922012-03-21 11:59:11 -07002018 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002019 float finalDeltaX;
2020 float finalDeltaY;
2021 float initDeltaX;
2022 float initDeltaY;
2023 float finalScale;
2024 float initScale;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002025 int mode;
2026 boolean repeating = false;
2027 private static final int PREVIEW_DURATION = 300;
2028 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
2029
2030 public static final int MODE_HINT = 0;
2031 public static final int MODE_PREVIEW = 1;
2032
Adam Cohene7587d22012-05-24 18:50:02 -07002033 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002034
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002035 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
2036 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07002037 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2038 final int x0 = mTmpPoint[0];
2039 final int y0 = mTmpPoint[1];
2040 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2041 final int x1 = mTmpPoint[0];
2042 final int y1 = mTmpPoint[1];
2043 final int dX = x1 - x0;
2044 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002045 finalDeltaX = 0;
2046 finalDeltaY = 0;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002047 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07002048 if (dX == dY && dX == 0) {
2049 } else {
2050 if (dY == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002051 finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002052 } else if (dX == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002053 finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002054 } else {
2055 double angle = Math.atan( (float) (dY) / dX);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002056 finalDeltaX = (int) (- dir * Math.signum(dX) *
2057 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
2058 finalDeltaY = (int) (- dir * Math.signum(dY) *
2059 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002060 }
2061 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002062 this.mode = mode;
Adam Cohend024f982012-05-23 18:26:45 -07002063 initDeltaX = child.getTranslationX();
2064 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002065 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002066 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002067 this.child = child;
2068 }
2069
Adam Cohend024f982012-05-23 18:26:45 -07002070 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002071 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002072 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002073 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002074 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002075 if (finalDeltaX == 0 && finalDeltaY == 0) {
2076 completeAnimationImmediately();
2077 return;
2078 }
Adam Cohen19f37922012-03-21 11:59:11 -07002079 }
Adam Cohend024f982012-05-23 18:26:45 -07002080 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002081 return;
2082 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002083 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002084 a = va;
Tony Wickham9e0702f2015-09-02 14:45:39 -07002085
2086 // Animations are disabled in power save mode, causing the repeated animation to jump
2087 // spastically between beginning and end states. Since this looks bad, we don't repeat
2088 // the animation in power save mode.
Tony Wickham112ac952015-11-12 12:31:50 -08002089 if (!Utilities.isPowerSaverOn(getContext())) {
Tony Wickham9e0702f2015-09-02 14:45:39 -07002090 va.setRepeatMode(ValueAnimator.REVERSE);
2091 va.setRepeatCount(ValueAnimator.INFINITE);
2092 }
2093
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002094 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002095 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002096 va.addUpdateListener(new AnimatorUpdateListener() {
2097 @Override
2098 public void onAnimationUpdate(ValueAnimator animation) {
2099 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002100 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2101 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2102 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002103 child.setTranslationX(x);
2104 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002105 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002106 child.setScaleX(s);
2107 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002108 }
2109 });
2110 va.addListener(new AnimatorListenerAdapter() {
2111 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002112 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002113 initDeltaX = 0;
2114 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002115 initScale = getChildrenScale();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002116 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002117 }
2118 });
Adam Cohen19f37922012-03-21 11:59:11 -07002119 mShakeAnimators.put(child, this);
2120 va.start();
2121 }
2122
Adam Cohend024f982012-05-23 18:26:45 -07002123 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002124 if (a != null) {
2125 a.cancel();
2126 }
Adam Cohen19f37922012-03-21 11:59:11 -07002127 }
Adam Cohene7587d22012-05-24 18:50:02 -07002128
Adam Cohen091440a2015-03-18 14:16:05 -07002129 @Thunk void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002130 if (a != null) {
2131 a.cancel();
2132 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002133
Sunny Goyal5d2fc322015-07-06 22:52:49 -07002134 a = new LauncherViewPropertyAnimator(child)
2135 .scaleX(getChildrenScale())
2136 .scaleY(getChildrenScale())
2137 .translationX(0)
2138 .translationY(0)
2139 .setDuration(REORDER_ANIMATION_DURATION);
2140 a.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2141 a.start();
Brandon Keely50e6e562012-05-08 16:28:49 -07002142 }
Adam Cohen19f37922012-03-21 11:59:11 -07002143 }
2144
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002145 private void completeAndClearReorderPreviewAnimations() {
2146 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002147 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002148 }
2149 mShakeAnimators.clear();
2150 }
2151
Adam Cohen482ed822012-03-02 14:15:13 -08002152 private void commitTempPlacement() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002153 mTmpOccupied.copyTo(mOccupied);
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002154
2155 long screenId = mLauncher.getWorkspace().getIdForScreen(this);
2156 int container = Favorites.CONTAINER_DESKTOP;
2157
2158 if (mLauncher.isHotseatLayout(this)) {
2159 screenId = -1;
2160 container = Favorites.CONTAINER_HOTSEAT;
2161 }
2162
Michael Jurkaa52570f2012-03-20 03:18:20 -07002163 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002164 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002165 View child = mShortcutsAndWidgets.getChildAt(i);
2166 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2167 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002168 // We do a null check here because the item info can be null in the case of the
2169 // AllApps button in the hotseat.
2170 if (info != null) {
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002171 final boolean requiresDbUpdate = (info.cellX != lp.tmpCellX
2172 || info.cellY != lp.tmpCellY || info.spanX != lp.cellHSpan
2173 || info.spanY != lp.cellVSpan);
2174
Adam Cohen2acce882012-03-28 19:03:19 -07002175 info.cellX = lp.cellX = lp.tmpCellX;
2176 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002177 info.spanX = lp.cellHSpan;
2178 info.spanY = lp.cellVSpan;
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002179
2180 if (requiresDbUpdate) {
2181 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId,
2182 info.cellX, info.cellY, info.spanX, info.spanY);
2183 }
Adam Cohen2acce882012-03-28 19:03:19 -07002184 }
Adam Cohen482ed822012-03-02 14:15:13 -08002185 }
2186 }
2187
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002188 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002189 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002190 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002191 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002192 lp.useTmpCoords = useTempCoords;
2193 }
2194 }
2195
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002196 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002197 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2198 int[] result = new int[2];
2199 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002200 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002201 resultSpan);
2202 if (result[0] >= 0 && result[1] >= 0) {
2203 copyCurrentStateToSolution(solution, false);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002204 solution.cellX = result[0];
2205 solution.cellY = result[1];
2206 solution.spanX = resultSpan[0];
2207 solution.spanY = resultSpan[1];
Adam Cohen482ed822012-03-02 14:15:13 -08002208 solution.isSolution = true;
2209 } else {
2210 solution.isSolution = false;
2211 }
2212 return solution;
2213 }
2214
2215 public void prepareChildForDrag(View child) {
2216 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002217 }
2218
Adam Cohen19f37922012-03-21 11:59:11 -07002219 /* This seems like it should be obvious and straight-forward, but when the direction vector
2220 needs to match with the notion of the dragView pushing other views, we have to employ
2221 a slightly more subtle notion of the direction vector. The question is what two points is
2222 the vector between? The center of the dragView and its desired destination? Not quite, as
2223 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2224 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2225 or right, which helps make pushing feel right.
2226 */
2227 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2228 int spanY, View dragView, int[] resultDirection) {
2229 int[] targetDestination = new int[2];
2230
2231 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2232 Rect dragRect = new Rect();
2233 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2234 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2235
2236 Rect dropRegionRect = new Rect();
2237 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2238 dragView, dropRegionRect, mIntersectingViews);
2239
2240 int dropRegionSpanX = dropRegionRect.width();
2241 int dropRegionSpanY = dropRegionRect.height();
2242
2243 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2244 dropRegionRect.height(), dropRegionRect);
2245
2246 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2247 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2248
2249 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2250 deltaX = 0;
2251 }
2252 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2253 deltaY = 0;
2254 }
2255
2256 if (deltaX == 0 && deltaY == 0) {
2257 // No idea what to do, give a random direction.
2258 resultDirection[0] = 1;
2259 resultDirection[1] = 0;
2260 } else {
2261 computeDirectionVector(deltaX, deltaY, resultDirection);
2262 }
2263 }
2264
2265 // For a given cell and span, fetch the set of views intersecting the region.
2266 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2267 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2268 if (boundingRect != null) {
2269 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2270 }
2271 intersectingViews.clear();
2272 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2273 Rect r1 = new Rect();
2274 final int count = mShortcutsAndWidgets.getChildCount();
2275 for (int i = 0; i < count; i++) {
2276 View child = mShortcutsAndWidgets.getChildAt(i);
2277 if (child == dragView) continue;
2278 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2279 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2280 if (Rect.intersects(r0, r1)) {
2281 mIntersectingViews.add(child);
2282 if (boundingRect != null) {
2283 boundingRect.union(r1);
2284 }
2285 }
2286 }
2287 }
2288
2289 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2290 View dragView, int[] result) {
2291 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2292 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2293 mIntersectingViews);
2294 return !mIntersectingViews.isEmpty();
2295 }
2296
2297 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002298 completeAndClearReorderPreviewAnimations();
2299 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2300 final int count = mShortcutsAndWidgets.getChildCount();
2301 for (int i = 0; i < count; i++) {
2302 View child = mShortcutsAndWidgets.getChildAt(i);
2303 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2304 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2305 lp.tmpCellX = lp.cellX;
2306 lp.tmpCellY = lp.cellY;
2307 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2308 0, false, false);
2309 }
Adam Cohen19f37922012-03-21 11:59:11 -07002310 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002311 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002312 }
Adam Cohen19f37922012-03-21 11:59:11 -07002313 }
2314
Adam Cohenbebf0422012-04-11 18:06:28 -07002315 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2316 View dragView, int[] direction, boolean commit) {
2317 int[] pixelXY = new int[2];
2318 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2319
2320 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002321 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002322 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2323
2324 setUseTempCoords(true);
2325 if (swapSolution != null && swapSolution.isSolution) {
2326 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2327 // committing anything or animating anything as we just want to determine if a solution
2328 // exists
2329 copySolutionToTempState(swapSolution, dragView);
2330 setItemPlacementDirty(true);
2331 animateItemsToSolution(swapSolution, dragView, commit);
2332
2333 if (commit) {
2334 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002335 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002336 setItemPlacementDirty(false);
2337 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002338 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2339 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002340 }
2341 mShortcutsAndWidgets.requestLayout();
2342 }
2343 return swapSolution.isSolution;
2344 }
2345
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002346 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002347 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002348 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002349 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002350
2351 if (resultSpan == null) {
2352 resultSpan = new int[2];
2353 }
2354
Adam Cohen19f37922012-03-21 11:59:11 -07002355 // When we are checking drop validity or actually dropping, we don't recompute the
2356 // direction vector, since we want the solution to match the preview, and it's possible
2357 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002358 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2359 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002360 mDirectionVector[0] = mPreviousReorderDirection[0];
2361 mDirectionVector[1] = mPreviousReorderDirection[1];
2362 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002363 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2364 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2365 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002366 }
2367 } else {
2368 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2369 mPreviousReorderDirection[0] = mDirectionVector[0];
2370 mPreviousReorderDirection[1] = mDirectionVector[1];
2371 }
2372
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002373 // Find a solution involving pushing / displacing any items in the way
2374 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002375 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2376
2377 // We attempt the approach which doesn't shuffle views at all
2378 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2379 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2380
2381 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002382
2383 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2384 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002385 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2386 finalSolution = swapSolution;
2387 } else if (noShuffleSolution.isSolution) {
2388 finalSolution = noShuffleSolution;
2389 }
2390
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002391 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002392 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002393 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2394 ReorderPreviewAnimation.MODE_HINT);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002395 result[0] = finalSolution.cellX;
2396 result[1] = finalSolution.cellY;
2397 resultSpan[0] = finalSolution.spanX;
2398 resultSpan[1] = finalSolution.spanY;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002399 } else {
2400 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2401 }
2402 return result;
2403 }
2404
Adam Cohen482ed822012-03-02 14:15:13 -08002405 boolean foundSolution = true;
2406 if (!DESTRUCTIVE_REORDER) {
2407 setUseTempCoords(true);
2408 }
2409
2410 if (finalSolution != null) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002411 result[0] = finalSolution.cellX;
2412 result[1] = finalSolution.cellY;
2413 resultSpan[0] = finalSolution.spanX;
2414 resultSpan[1] = finalSolution.spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002415
2416 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2417 // committing anything or animating anything as we just want to determine if a solution
2418 // exists
2419 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2420 if (!DESTRUCTIVE_REORDER) {
2421 copySolutionToTempState(finalSolution, dragView);
2422 }
2423 setItemPlacementDirty(true);
2424 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2425
Adam Cohen19f37922012-03-21 11:59:11 -07002426 if (!DESTRUCTIVE_REORDER &&
2427 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002428 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002429 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002430 setItemPlacementDirty(false);
2431 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002432 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2433 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002434 }
2435 }
2436 } else {
2437 foundSolution = false;
2438 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2439 }
2440
2441 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2442 setUseTempCoords(false);
2443 }
Adam Cohen482ed822012-03-02 14:15:13 -08002444
Michael Jurkaa52570f2012-03-20 03:18:20 -07002445 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002446 return result;
2447 }
2448
Adam Cohen19f37922012-03-21 11:59:11 -07002449 void setItemPlacementDirty(boolean dirty) {
2450 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002451 }
Adam Cohen19f37922012-03-21 11:59:11 -07002452 boolean isItemPlacementDirty() {
2453 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002454 }
2455
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002456 private static class ItemConfiguration extends CellAndSpan {
Adam Cohen8baab352012-03-20 17:39:21 -07002457 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002458 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2459 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002460 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002461 boolean isSolution = false;
Adam Cohen482ed822012-03-02 14:15:13 -08002462
Adam Cohenf3900c22012-11-16 18:28:11 -08002463 void save() {
2464 // Copy current state into savedMap
2465 for (View v: map.keySet()) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002466 savedMap.get(v).copyFrom(map.get(v));
Adam Cohenf3900c22012-11-16 18:28:11 -08002467 }
2468 }
2469
2470 void restore() {
2471 // Restore current state from savedMap
2472 for (View v: savedMap.keySet()) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002473 map.get(v).copyFrom(savedMap.get(v));
Adam Cohenf3900c22012-11-16 18:28:11 -08002474 }
2475 }
2476
2477 void add(View v, CellAndSpan cs) {
2478 map.put(v, cs);
2479 savedMap.put(v, new CellAndSpan());
2480 sortedViews.add(v);
2481 }
2482
Adam Cohen482ed822012-03-02 14:15:13 -08002483 int area() {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002484 return spanX * spanY;
Adam Cohenf3900c22012-11-16 18:28:11 -08002485 }
2486
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002487 void getBoundingRectForViews(ArrayList<View> views, Rect outRect) {
2488 boolean first = true;
2489 for (View v: views) {
2490 CellAndSpan c = map.get(v);
2491 if (first) {
2492 outRect.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2493 first = false;
2494 } else {
2495 outRect.union(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
2496 }
2497 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002498 }
Adam Cohen482ed822012-03-02 14:15:13 -08002499 }
2500
Adam Cohendf035382011-04-11 17:22:04 -07002501 /**
Adam Cohendf035382011-04-11 17:22:04 -07002502 * Find a starting cell position that will fit the given bounds nearest the requested
2503 * cell location. Uses Euclidean distance to score multiple vacant areas.
2504 *
2505 * @param pixelX The X location at which you want to search for a vacant area.
2506 * @param pixelY The Y location at which you want to search for a vacant area.
2507 * @param spanX Horizontal span of the object.
2508 * @param spanY Vertical span of the object.
2509 * @param ignoreView Considers space occupied by this view as unoccupied
2510 * @param result Previously returned value to possibly recycle.
2511 * @return The X, Y cell of a vacant area that can contain this object,
2512 * nearest the requested location.
2513 */
Adam Cohenf9c184a2016-01-15 16:47:43 -08002514 public int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002515 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002516 }
2517
Michael Jurka0280c3b2010-09-17 15:00:07 -07002518 boolean existsEmptyCell() {
2519 return findCellForSpan(null, 1, 1);
2520 }
2521
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002522 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002523 * Finds the upper-left coordinate of the first rectangle in the grid that can
2524 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2525 * then this method will only return coordinates for rectangles that contain the cell
2526 * (intersectX, intersectY)
2527 *
2528 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2529 * can be found.
2530 * @param spanX The horizontal span of the cell we want to find.
2531 * @param spanY The vertical span of the cell we want to find.
2532 *
2533 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002534 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002535 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002536 if (cellXY == null) {
2537 cellXY = new int[2];
Michael Jurka0280c3b2010-09-17 15:00:07 -07002538 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002539 return mOccupied.findVacantCell(cellXY, spanX, spanY);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002540 }
2541
2542 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002543 * A drag event has begun over this layout.
2544 * It may have begun over this layout (in which case onDragChild is called first),
2545 * or it may have begun on another layout.
2546 */
2547 void onDragEnter() {
Winson Chungc07918d2011-07-01 15:35:26 -07002548 mDragging = true;
2549 }
2550
2551 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002552 * Called when drag has left this CellLayout or has been completed (successfully or not)
2553 */
2554 void onDragExit() {
Joe Onorato4be866d2010-10-10 11:26:02 -07002555 // This can actually be called when we aren't in a drag, e.g. when adding a new
2556 // item to this layout via the customize drawer.
2557 // Guard against that case.
2558 if (mDragging) {
2559 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002560 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002561
2562 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002563 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002564 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2565 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002566 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002567 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002568 }
2569
2570 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002571 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002572 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002573 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002574 *
2575 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002576 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002577 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002578 if (child != null) {
2579 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002580 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002581 child.requestLayout();
Tony Wickham1cdb6d02015-09-17 11:08:27 -07002582 markCellsAsOccupiedForView(child);
Romain Guyd94533d2009-08-17 10:01:15 -07002583 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002584 }
2585
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002586 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002587 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002588 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002589 * @param cellX X coordinate of upper left corner expressed as a cell position
2590 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002591 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002592 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002593 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002594 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002595 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002596 final int cellWidth = mCellWidth;
2597 final int cellHeight = mCellHeight;
2598 final int widthGap = mWidthGap;
2599 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002600
Winson Chung4b825dcd2011-06-19 12:41:22 -07002601 final int hStartPadding = getPaddingLeft();
2602 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002603
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002604 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2605 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2606
2607 int x = hStartPadding + cellX * (cellWidth + widthGap);
2608 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002609
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002610 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002611 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002612
Adam Cohend4844c32011-02-18 19:25:06 -08002613 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002614 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002615 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002616 mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002617 }
2618
Adam Cohend4844c32011-02-18 19:25:06 -08002619 public void markCellsAsUnoccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002620 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002621 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002622 mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002623 }
2624
Adam Cohen2801caf2011-05-13 20:57:39 -07002625 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002626 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002627 (Math.max((mCountX - 1), 0) * mWidthGap);
2628 }
2629
2630 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002631 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002632 (Math.max((mCountY - 1), 0) * mHeightGap);
2633 }
2634
Michael Jurka66d72172011-04-12 16:29:25 -07002635 public boolean isOccupied(int x, int y) {
2636 if (x < mCountX && y < mCountY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002637 return mOccupied.cells[x][y];
Michael Jurka66d72172011-04-12 16:29:25 -07002638 } else {
2639 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2640 }
2641 }
2642
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002643 @Override
2644 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2645 return new CellLayout.LayoutParams(getContext(), attrs);
2646 }
2647
2648 @Override
2649 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2650 return p instanceof CellLayout.LayoutParams;
2651 }
2652
2653 @Override
2654 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2655 return new CellLayout.LayoutParams(p);
2656 }
2657
2658 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2659 /**
2660 * Horizontal location of the item in the grid.
2661 */
2662 @ViewDebug.ExportedProperty
2663 public int cellX;
2664
2665 /**
2666 * Vertical location of the item in the grid.
2667 */
2668 @ViewDebug.ExportedProperty
2669 public int cellY;
2670
2671 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002672 * Temporary horizontal location of the item in the grid during reorder
2673 */
2674 public int tmpCellX;
2675
2676 /**
2677 * Temporary vertical location of the item in the grid during reorder
2678 */
2679 public int tmpCellY;
2680
2681 /**
2682 * Indicates that the temporary coordinates should be used to layout the items
2683 */
2684 public boolean useTmpCoords;
2685
2686 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002687 * Number of cells spanned horizontally by the item.
2688 */
2689 @ViewDebug.ExportedProperty
2690 public int cellHSpan;
2691
2692 /**
2693 * Number of cells spanned vertically by the item.
2694 */
2695 @ViewDebug.ExportedProperty
2696 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002697
Adam Cohen1b607ed2011-03-03 17:26:50 -08002698 /**
2699 * Indicates whether the item will set its x, y, width and height parameters freely,
2700 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2701 */
Adam Cohend4844c32011-02-18 19:25:06 -08002702 public boolean isLockedToGrid = true;
2703
Adam Cohen482ed822012-03-02 14:15:13 -08002704 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07002705 * Indicates that this item should use the full extents of its parent.
2706 */
2707 public boolean isFullscreen = false;
2708
2709 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002710 * Indicates whether this item can be reordered. Always true except in the case of the
2711 * the AllApps button.
2712 */
2713 public boolean canReorder = true;
2714
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002715 // X coordinate of the view in the layout.
2716 @ViewDebug.ExportedProperty
Vadim Tryshevfedca432015-08-19 17:55:02 -07002717 public int x;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002718 // Y coordinate of the view in the layout.
2719 @ViewDebug.ExportedProperty
Vadim Tryshevfedca432015-08-19 17:55:02 -07002720 public int y;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002721
Romain Guy84f296c2009-11-04 15:00:44 -08002722 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002723
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002724 public LayoutParams(Context c, AttributeSet attrs) {
2725 super(c, attrs);
2726 cellHSpan = 1;
2727 cellVSpan = 1;
2728 }
2729
2730 public LayoutParams(ViewGroup.LayoutParams source) {
2731 super(source);
2732 cellHSpan = 1;
2733 cellVSpan = 1;
2734 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002735
2736 public LayoutParams(LayoutParams source) {
2737 super(source);
2738 this.cellX = source.cellX;
2739 this.cellY = source.cellY;
2740 this.cellHSpan = source.cellHSpan;
2741 this.cellVSpan = source.cellVSpan;
2742 }
2743
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002744 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002745 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002746 this.cellX = cellX;
2747 this.cellY = cellY;
2748 this.cellHSpan = cellHSpan;
2749 this.cellVSpan = cellVSpan;
2750 }
2751
Adam Cohen2374abf2013-04-16 14:56:57 -07002752 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
2753 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08002754 if (isLockedToGrid) {
2755 final int myCellHSpan = cellHSpan;
2756 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002757 int myCellX = useTmpCoords ? tmpCellX : cellX;
2758 int myCellY = useTmpCoords ? tmpCellY : cellY;
2759
2760 if (invertHorizontally) {
2761 myCellX = colCount - myCellX - cellHSpan;
2762 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002763
Adam Cohend4844c32011-02-18 19:25:06 -08002764 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2765 leftMargin - rightMargin;
2766 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2767 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002768 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2769 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002770 }
2771 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002772
Winson Chungaafa03c2010-06-11 17:34:16 -07002773 public String toString() {
2774 return "(" + this.cellX + ", " + this.cellY + ")";
2775 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002776
2777 public void setWidth(int width) {
2778 this.width = width;
2779 }
2780
2781 public int getWidth() {
2782 return width;
2783 }
2784
2785 public void setHeight(int height) {
2786 this.height = height;
2787 }
2788
2789 public int getHeight() {
2790 return height;
2791 }
2792
2793 public void setX(int x) {
2794 this.x = x;
2795 }
2796
2797 public int getX() {
2798 return x;
2799 }
2800
2801 public void setY(int y) {
2802 this.y = y;
2803 }
2804
2805 public int getY() {
2806 return y;
2807 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002808 }
2809
Michael Jurka0280c3b2010-09-17 15:00:07 -07002810 // This class stores info for two purposes:
2811 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2812 // its spanX, spanY, and the screen it is on
2813 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2814 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2815 // the CellLayout that was long clicked
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002816 public static final class CellInfo extends CellAndSpan {
Adam Cohenf9c184a2016-01-15 16:47:43 -08002817 public View cell;
Adam Cohendcd297f2013-06-18 13:13:40 -07002818 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07002819 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002820
Sunny Goyal83a8f042015-05-19 12:52:12 -07002821 public CellInfo(View v, ItemInfo info) {
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002822 cellX = info.cellX;
2823 cellY = info.cellY;
2824 spanX = info.spanX;
2825 spanY = info.spanY;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002826 cell = v;
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002827 screenId = info.screenId;
2828 container = info.container;
2829 }
2830
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002831 @Override
2832 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002833 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2834 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002835 }
2836 }
Michael Jurkad771c962011-08-09 15:00:48 -07002837
Tony Wickham86930612015-09-09 13:50:40 -07002838 /**
2839 * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing
2840 * if necessary).
2841 */
2842 public boolean hasReorderSolution(ItemInfo itemInfo) {
2843 int[] cellPoint = new int[2];
2844 // Check for a solution starting at every cell.
2845 for (int cellX = 0; cellX < getCountX(); cellX++) {
2846 for (int cellY = 0; cellY < getCountY(); cellY++) {
2847 cellToPoint(cellX, cellY, cellPoint);
2848 if (findReorderSolution(cellPoint[0], cellPoint[1], itemInfo.minSpanX,
2849 itemInfo.minSpanY, itemInfo.spanX, itemInfo.spanY, mDirectionVector, null,
2850 true, new ItemConfiguration()).isSolution) {
2851 return true;
2852 }
2853 }
2854 }
2855 return false;
2856 }
2857
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002858 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -07002859 return mOccupied.isRegionVacant(x, y, spanX, spanY);
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002860 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002861}