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