blob: 94e3e41419342a0f67504d4ef09fd8b5b8289341 [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;
Brandon Keely50e6e562012-05-08 16:28:49 -070021import android.animation.AnimatorSet;
Chet Haase00397b12010-10-07 11:13:10 -070022import android.animation.TimeInterpolator;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070023import android.animation.ValueAnimator;
24import android.animation.ValueAnimator.AnimatorUpdateListener;
Adam Cohenc9735cf2015-01-23 16:11:55 -080025import android.annotation.TargetApi;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080026import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040027import android.content.res.Resources;
Joe Onorato4be866d2010-10-10 11:26:02 -070028import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070029import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080030import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070031import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070032import android.graphics.Point;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080033import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080034import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070035import android.graphics.drawable.Drawable;
Sunny Goyal2805e632015-05-20 15:35:32 -070036import android.graphics.drawable.TransitionDrawable;
Adam Cohenc9735cf2015-01-23 16:11:55 -080037import android.os.Build;
Adam Cohen1462de32012-07-24 22:34:36 -070038import android.os.Parcelable;
Adam Cohenc9735cf2015-01-23 16:11:55 -080039import android.support.v4.view.ViewCompat;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080040import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070041import android.util.Log;
Adam Cohen1462de32012-07-24 22:34:36 -070042import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080043import android.view.MotionEvent;
44import android.view.View;
45import android.view.ViewDebug;
46import android.view.ViewGroup;
Adam Cohenc9735cf2015-01-23 16:11:55 -080047import android.view.accessibility.AccessibilityEvent;
Winson Chung150fbab2010-09-29 17:14:26 -070048import android.view.animation.DecelerateInterpolator;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080049
Sunny Goyal4b6eb262015-05-14 19:24:40 -070050import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
Daniel Sandler325dc232013-06-05 22:57:57 -040051import com.android.launcher3.FolderIcon.FolderRingAnimator;
Sunny Goyale9b651e2015-04-24 11:44:51 -070052import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
53import com.android.launcher3.accessibility.FolderAccessibilityHelper;
54import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
Sunny Goyald1a0e8b2015-08-27 17:45:46 -070055import com.android.launcher3.util.ParcelableSparseArray;
Adam Cohen091440a2015-03-18 14:16:05 -070056import com.android.launcher3.util.Thunk;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070057
Adam Cohen69ce2e52011-07-03 19:25:21 -070058import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070059import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080060import java.util.Collections;
61import java.util.Comparator;
Adam Cohenbfbfd262011-06-13 16:55:12 -070062import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080063import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070064
Sunny Goyal4b6eb262015-05-14 19:24:40 -070065public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
Sunny Goyale9b651e2015-04-24 11:44:51 -070066 public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
67 public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
68
Tony Wickhama0628cc2015-10-14 15:23:04 -070069 private static final String TAG = "CellLayout";
70 private static final boolean LOGD = false;
Winson Chungaafa03c2010-06-11 17:34:16 -070071
Adam Cohen2acce882012-03-28 19:03:19 -070072 private Launcher mLauncher;
Adam Cohen091440a2015-03-18 14:16:05 -070073 @Thunk int mCellWidth;
74 @Thunk int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070075 private int mFixedCellWidth;
76 private int mFixedCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070077
Adam Cohen091440a2015-03-18 14:16:05 -070078 @Thunk int mCountX;
79 @Thunk int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080080
Adam Cohen234c4cd2011-07-17 21:03:04 -070081 private int mOriginalWidthGap;
82 private int mOriginalHeightGap;
Adam Cohen091440a2015-03-18 14:16:05 -070083 @Thunk int mWidthGap;
84 @Thunk int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070085 private int mMaxGap;
Adam Cohen917e3882013-10-31 15:03:35 -070086 private boolean mDropPending = false;
Adam Cohenc50438c2014-08-19 17:43:05 -070087 private boolean mIsDragTarget = true;
Sunny Goyald1a0e8b2015-08-27 17:45:46 -070088 private boolean mJailContent = true;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080089
Patrick Dubroyde7658b2010-09-27 11:15:43 -070090 // These are temporary variables to prevent having to allocate a new object just to
91 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Adam Cohen091440a2015-03-18 14:16:05 -070092 @Thunk final int[] mTmpPoint = new int[2];
Sunny Goyal2805e632015-05-20 15:35:32 -070093 @Thunk final int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070094
The Android Open Source Project31dd5032009-03-03 19:32:27 -080095 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080096 boolean[][] mTmpOccupied;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080097
Michael Jurkadee05892010-07-27 10:01:56 -070098 private OnTouchListener mInterceptTouchListener;
Mady Melloref044dd2015-06-02 15:35:07 -070099 private StylusEventHelper mStylusEventHelper;
Michael Jurkadee05892010-07-27 10:01:56 -0700100
Adam Cohen69ce2e52011-07-03 19:25:21 -0700101 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -0700102 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -0700103
Michael Jurka5f1c5092010-09-03 14:15:02 -0700104 private float mBackgroundAlpha;
Adam Cohenf34bab52010-09-30 14:11:56 -0700105
Sunny Goyal2805e632015-05-20 15:35:32 -0700106 private static final int BACKGROUND_ACTIVATE_DURATION = 120;
107 private final TransitionDrawable mBackground;
108
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700109 // These values allow a fixed measurement to be set on the CellLayout.
110 private int mFixedWidth = -1;
111 private int mFixedHeight = -1;
112
Michael Jurka33945b22010-12-21 18:19:38 -0800113 // If we're actively dragging something over this screen, mIsDragOverlapping is true
114 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700115
Winson Chung150fbab2010-09-29 17:14:26 -0700116 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700117 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohen091440a2015-03-18 14:16:05 -0700118 @Thunk Rect[] mDragOutlines = new Rect[4];
119 @Thunk float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700120 private InterruptibleInOutAnimator[] mDragOutlineAnims =
121 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700122
123 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700124 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700125 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700126
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700127 private final ClickShadowView mTouchFeedbackView;
Patrick Dubroy96864c32011-03-10 17:17:23 -0800128
Sunny Goyal316490e2015-06-02 09:38:28 -0700129 @Thunk HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new HashMap<>();
130 @Thunk HashMap<View, ReorderPreviewAnimation> mShakeAnimators = new HashMap<>();
Adam Cohen19f37922012-03-21 11:59:11 -0700131
132 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700133
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700134 // When a drag operation is in progress, holds the nearest cell to the touch point
135 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800136
Joe Onorato4be866d2010-10-10 11:26:02 -0700137 private boolean mDragging = false;
138
Patrick Dubroyce34a972010-10-19 10:34:32 -0700139 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700140 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700141
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800142 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700143 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800144
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800145 public static final int MODE_SHOW_REORDER_HINT = 0;
146 public static final int MODE_DRAG_OVER = 1;
147 public static final int MODE_ON_DROP = 2;
148 public static final int MODE_ON_DROP_EXTERNAL = 3;
149 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700150 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800151 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
152
Adam Cohena897f392012-04-27 18:12:05 -0700153 static final int LANDSCAPE = 0;
154 static final int PORTRAIT = 1;
155
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800156 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700157 private static final int REORDER_ANIMATION_DURATION = 150;
Adam Cohen091440a2015-03-18 14:16:05 -0700158 @Thunk float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700159
Adam Cohen482ed822012-03-02 14:15:13 -0800160 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
161 private Rect mOccupiedRect = new Rect();
162 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700163 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700164 private static final int INVALID_DIRECTION = -100;
Adam Cohen482ed822012-03-02 14:15:13 -0800165
Sunny Goyal2805e632015-05-20 15:35:32 -0700166 private final Rect mTempRect = new Rect();
Winson Chung3a6e7f32013-10-09 15:50:52 -0700167
Michael Jurkaca993832012-06-29 15:17:04 -0700168 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700169
Adam Cohenc9735cf2015-01-23 16:11:55 -0800170 // Related to accessible drag and drop
Sunny Goyale9b651e2015-04-24 11:44:51 -0700171 private DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800172 private boolean mUseTouchHelper = false;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800173
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800174 public CellLayout(Context context) {
175 this(context, null);
176 }
177
178 public CellLayout(Context context, AttributeSet attrs) {
179 this(context, attrs, 0);
180 }
181
182 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
183 super(context, attrs, defStyle);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700184
185 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
186 // the user where a dragged item will land when dropped.
187 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800188 setClipToPadding(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700189 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700190
Adam Cohen2e6da152015-05-06 11:42:25 -0700191 DeviceProfile grid = mLauncher.getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800192
Winson Chung11a1a532013-09-13 11:14:45 -0700193 mCellWidth = mCellHeight = -1;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800194 mFixedCellWidth = mFixedCellHeight = -1;
Winson Chung5f8afe62013-08-12 16:19:28 -0700195 mWidthGap = mOriginalWidthGap = 0;
196 mHeightGap = mOriginalHeightGap = 0;
197 mMaxGap = Integer.MAX_VALUE;
Adam Cohen2e6da152015-05-06 11:42:25 -0700198 mCountX = (int) grid.inv.numColumns;
199 mCountY = (int) grid.inv.numRows;
Michael Jurka0280c3b2010-09-17 15:00:07 -0700200 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800201 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700202 mPreviousReorderDirection[0] = INVALID_DIRECTION;
203 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800204
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800205 setAlwaysDrawnWithCacheEnabled(false);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700206 final Resources res = getResources();
Winson Chung6e1c0d32013-10-25 15:24:24 -0700207 mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700208
Sunny Goyal2805e632015-05-20 15:35:32 -0700209 mBackground = (TransitionDrawable) res.getDrawable(R.drawable.bg_screenpanel);
210 mBackground.setCallback(this);
Winson Chunge8f1d042015-07-31 12:39:57 -0700211 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurka33945b22010-12-21 18:19:38 -0800212
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800213 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
Winson Chung5f8afe62013-08-12 16:19:28 -0700214 grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700215
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700216 // Initialize the data structures used for the drag visualization.
Patrick Dubroyce34a972010-10-19 10:34:32 -0700217 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700218 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700219 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800220 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700221 }
222
223 // When dragging things around the home screens, we show a green outline of
224 // where the item will land. The outlines gradually fade out, leaving a trail
225 // behind the drag path.
226 // Set up all the animations that are used to implement this fading.
227 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700228 final float fromAlphaValue = 0;
229 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700230
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700231 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700232
233 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700234 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100235 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700236 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700237 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700238 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700239 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700240 final Bitmap outline = (Bitmap)anim.getTag();
241
242 // If an animation is started and then stopped very quickly, we can still
243 // get spurious updates we've cleared the tag. Guard against this.
244 if (outline == null) {
Tony Wickhama0628cc2015-10-14 15:23:04 -0700245 if (LOGD) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700246 Object val = animation.getAnimatedValue();
247 Log.d(TAG, "anim " + thisIndex + " update: " + val +
248 ", isStopped " + anim.isStopped());
249 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700250 // Try to prevent it from continuing to run
251 animation.cancel();
252 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700253 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800254 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700255 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700256 }
257 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700258 // The animation holds a reference to the drag outline bitmap as long is it's
259 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700260 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700261 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700262 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700263 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700264 anim.setTag(null);
265 }
266 }
267 });
268 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700269 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700270
Michael Jurkaa52570f2012-03-20 03:18:20 -0700271 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700272 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700273 mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700274
Mady Melloref044dd2015-06-02 15:35:07 -0700275 mStylusEventHelper = new StylusEventHelper(this);
276
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700277 mTouchFeedbackView = new ClickShadowView(context);
278 addView(mTouchFeedbackView);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700279 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700280 }
281
Adam Cohenc9735cf2015-01-23 16:11:55 -0800282 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
Sunny Goyale9b651e2015-04-24 11:44:51 -0700283 public void enableAccessibleDrag(boolean enable, int dragType) {
Adam Cohenc9735cf2015-01-23 16:11:55 -0800284 mUseTouchHelper = enable;
285 if (!enable) {
286 ViewCompat.setAccessibilityDelegate(this, null);
287 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
288 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
289 setOnClickListener(mLauncher);
290 } else {
Sunny Goyale9b651e2015-04-24 11:44:51 -0700291 if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
292 !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
293 mTouchHelper = new WorkspaceAccessibilityHelper(this);
294 } else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
295 !(mTouchHelper instanceof FolderAccessibilityHelper)) {
296 mTouchHelper = new FolderAccessibilityHelper(this);
297 }
Adam Cohenc9735cf2015-01-23 16:11:55 -0800298 ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
299 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
300 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
301 setOnClickListener(mTouchHelper);
302 }
303
304 // Invalidate the accessibility hierarchy
305 if (getParent() != null) {
306 getParent().notifySubtreeAccessibilityStateChanged(
307 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
308 }
309 }
310
311 @Override
312 public boolean dispatchHoverEvent(MotionEvent event) {
313 // Always attempt to dispatch hover events to accessibility first.
314 if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
315 return true;
316 }
317 return super.dispatchHoverEvent(event);
318 }
319
320 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800321 public boolean onInterceptTouchEvent(MotionEvent ev) {
322 if (mUseTouchHelper ||
323 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
324 return true;
325 }
326 return false;
327 }
328
Mady Melloref044dd2015-06-02 15:35:07 -0700329 @Override
330 public boolean onTouchEvent(MotionEvent ev) {
331 boolean handled = super.onTouchEvent(ev);
332 // Stylus button press on a home screen should not switch between overview mode and
333 // the home screen mode, however, once in overview mode stylus button press should be
334 // enabled to allow rearranging the different home screens. So check what mode
335 // the workspace is in, and only perform stylus button presses while in overview mode.
336 if (mLauncher.mWorkspace.isInOverviewMode()
337 && mStylusEventHelper.checkAndPerformStylusEvent(ev)) {
338 return true;
339 }
340 return handled;
341 }
342
Chris Craik01f2d7f2013-10-01 14:41:56 -0700343 public void enableHardwareLayer(boolean hasLayer) {
344 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700345 }
346
347 public void buildHardwareLayer() {
348 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700349 }
350
Adam Cohen307fe232012-08-16 17:55:58 -0700351 public float getChildrenScale() {
352 return mIsHotseat ? mHotseatScale : 1.0f;
353 }
354
Winson Chung5f8afe62013-08-12 16:19:28 -0700355 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700356 mFixedCellWidth = mCellWidth = width;
357 mFixedCellHeight = mCellHeight = height;
Winson Chung5f8afe62013-08-12 16:19:28 -0700358 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
359 mCountX, mCountY);
360 }
361
Adam Cohen2801caf2011-05-13 20:57:39 -0700362 public void setGridSize(int x, int y) {
363 mCountX = x;
364 mCountY = y;
365 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800366 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700367 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700368 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700369 mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700370 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700371 }
372
Adam Cohen2374abf2013-04-16 14:56:57 -0700373 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
374 public void setInvertIfRtl(boolean invert) {
375 mShortcutsAndWidgets.setInvertIfRtl(invert);
376 }
377
Adam Cohen917e3882013-10-31 15:03:35 -0700378 public void setDropPending(boolean pending) {
379 mDropPending = pending;
380 }
381
382 public boolean isDropPending() {
383 return mDropPending;
384 }
385
Sunny Goyal4b6eb262015-05-14 19:24:40 -0700386 @Override
387 public void setPressedIcon(BubbleTextView icon, Bitmap background) {
Sunny Goyal508da152014-08-14 10:53:27 -0700388 if (icon == null || background == null) {
389 mTouchFeedbackView.setBitmap(null);
390 mTouchFeedbackView.animate().cancel();
391 } else {
Sunny Goyal508da152014-08-14 10:53:27 -0700392 if (mTouchFeedbackView.setBitmap(background)) {
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700393 mTouchFeedbackView.alignWithIconView(icon, mShortcutsAndWidgets);
394 mTouchFeedbackView.animateShadow();
Sunny Goyal508da152014-08-14 10:53:27 -0700395 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800396 }
397 }
398
Adam Cohenc50438c2014-08-19 17:43:05 -0700399 void disableDragTarget() {
400 mIsDragTarget = false;
401 }
402
403 boolean isDragTarget() {
404 return mIsDragTarget;
405 }
406
407 void setIsDragOverlapping(boolean isDragOverlapping) {
408 if (mIsDragOverlapping != isDragOverlapping) {
409 mIsDragOverlapping = isDragOverlapping;
Sunny Goyal2805e632015-05-20 15:35:32 -0700410 if (mIsDragOverlapping) {
411 mBackground.startTransition(BACKGROUND_ACTIVATE_DURATION);
412 } else {
Winson Chunge8f1d042015-07-31 12:39:57 -0700413 if (mBackgroundAlpha > 0f) {
414 mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
415 } else {
416 mBackground.resetTransition();
417 }
Sunny Goyal2805e632015-05-20 15:35:32 -0700418 }
Adam Cohenc50438c2014-08-19 17:43:05 -0700419 invalidate();
420 }
421 }
422
Sunny Goyald1a0e8b2015-08-27 17:45:46 -0700423 public boolean getIsDragOverlapping() {
Michael Jurka33945b22010-12-21 18:19:38 -0800424 return mIsDragOverlapping;
425 }
426
Sunny Goyald1a0e8b2015-08-27 17:45:46 -0700427 public void disableJailContent() {
428 mJailContent = false;
429 }
430
431 @Override
432 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
433 if (mJailContent) {
434 ParcelableSparseArray jail = getJailedArray(container);
435 super.dispatchSaveInstanceState(jail);
436 container.put(R.id.cell_layout_jail_id, jail);
437 } else {
438 super.dispatchSaveInstanceState(container);
439 }
440 }
441
442 @Override
443 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
444 super.dispatchRestoreInstanceState(mJailContent ? getJailedArray(container) : container);
445 }
446
447 private ParcelableSparseArray getJailedArray(SparseArray<Parcelable> container) {
448 final Parcelable parcelable = container.get(R.id.cell_layout_jail_id);
449 return parcelable instanceof ParcelableSparseArray ?
450 (ParcelableSparseArray) parcelable : new ParcelableSparseArray();
451 }
452
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700453 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700454 protected void onDraw(Canvas canvas) {
Sunny Goyal05739772015-05-19 19:59:09 -0700455 if (!mIsDragTarget) {
456 return;
457 }
458
Michael Jurka3e7c7632010-10-02 16:01:03 -0700459 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
460 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
461 // When we're small, we are either drawn normally or in the "accepts drops" state (during
462 // a drag). However, we also drag the mini hover background *over* one of those two
463 // backgrounds
Sunny Goyal05739772015-05-19 19:59:09 -0700464 if (mBackgroundAlpha > 0.0f) {
Sunny Goyal2805e632015-05-20 15:35:32 -0700465 mBackground.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700466 }
Romain Guya6abce82009-11-10 02:54:41 -0800467
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700468 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700469 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700470 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700471 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800472 final Rect r = mDragOutlines[i];
Winson Chung3a6e7f32013-10-09 15:50:52 -0700473 mTempRect.set(r);
474 Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale());
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));
Winson Chung3a6e7f32013-10-09 15:50:52 -0700477 canvas.drawBitmap(b, null, mTempRect, 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++) {
487 if (mOccupied[i][j]) {
488 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
Andrew Flynn850d2e72012-04-26 16:51:20 -0700498 int previewOffset = FolderRingAnimator.sPreviewSize;
499
Adam Cohen69ce2e52011-07-03 19:25:21 -0700500 // The folder outer / inner ring image(s)
Adam Cohen2e6da152015-05-06 11:42:25 -0700501 DeviceProfile grid = mLauncher.getDeviceProfile();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700502 for (int i = 0; i < mFolderOuterRings.size(); i++) {
503 FolderRingAnimator fra = mFolderOuterRings.get(i);
504
Adam Cohen5108bc02013-09-20 17:04:51 -0700505 Drawable d;
506 int width, height;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700507 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700508 View child = getChildAt(fra.mCellX, fra.mCellY);
Adam Cohen558f1c22013-10-09 15:15:24 -0700509
Winson Chung89f97052013-09-20 11:32:26 -0700510 if (child != null) {
Adam Cohen558f1c22013-10-09 15:15:24 -0700511 int centerX = mTempLocation[0] + mCellWidth / 2;
512 int centerY = mTempLocation[1] + previewOffset / 2 +
513 child.getPaddingTop() + grid.folderBackgroundOffset;
514
Adam Cohen5108bc02013-09-20 17:04:51 -0700515 // Draw outer ring, if it exists
516 if (FolderIcon.HAS_OUTER_RING) {
517 d = FolderRingAnimator.sSharedOuterRingDrawable;
518 width = (int) (fra.getOuterRingSize() * getChildrenScale());
519 height = width;
520 canvas.save();
521 canvas.translate(centerX - width / 2, centerY - height / 2);
522 d.setBounds(0, 0, width, height);
523 d.draw(canvas);
524 canvas.restore();
525 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700526
Winson Chung89f97052013-09-20 11:32:26 -0700527 // Draw inner ring
528 d = FolderRingAnimator.sSharedInnerRingDrawable;
529 width = (int) (fra.getInnerRingSize() * getChildrenScale());
530 height = width;
Winson Chung89f97052013-09-20 11:32:26 -0700531 canvas.save();
532 canvas.translate(centerX - width / 2, centerY - width / 2);
533 d.setBounds(0, 0, width, height);
534 d.draw(canvas);
535 canvas.restore();
536 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700537 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700538
539 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
540 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
541 int width = d.getIntrinsicWidth();
542 int height = d.getIntrinsicHeight();
543
544 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700545 View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
Winson Chung89f97052013-09-20 11:32:26 -0700546 if (child != null) {
547 int centerX = mTempLocation[0] + mCellWidth / 2;
548 int centerY = mTempLocation[1] + previewOffset / 2 +
549 child.getPaddingTop() + grid.folderBackgroundOffset;
Adam Cohenc51934b2011-07-26 21:07:43 -0700550
Winson Chung89f97052013-09-20 11:32:26 -0700551 canvas.save();
552 canvas.translate(centerX - width / 2, centerY - width / 2);
553 d.setBounds(0, 0, width, height);
554 d.draw(canvas);
555 canvas.restore();
556 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700557 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700558 }
559
560 public void showFolderAccept(FolderRingAnimator fra) {
561 mFolderOuterRings.add(fra);
562 }
563
564 public void hideFolderAccept(FolderRingAnimator fra) {
565 if (mFolderOuterRings.contains(fra)) {
566 mFolderOuterRings.remove(fra);
567 }
568 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700569 }
570
Adam Cohenc51934b2011-07-26 21:07:43 -0700571 public void setFolderLeaveBehindCell(int x, int y) {
572 mFolderLeaveBehindCell[0] = x;
573 mFolderLeaveBehindCell[1] = y;
574 invalidate();
575 }
576
577 public void clearFolderLeaveBehind() {
578 mFolderLeaveBehindCell[0] = -1;
579 mFolderLeaveBehindCell[1] = -1;
580 invalidate();
581 }
582
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700583 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700584 public boolean shouldDelayChildPressedState() {
585 return false;
586 }
587
Adam Cohen1462de32012-07-24 22:34:36 -0700588 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700589 try {
590 dispatchRestoreInstanceState(states);
591 } catch (IllegalArgumentException ex) {
592 if (LauncherAppState.isDogfoodBuild()) {
593 throw ex;
594 }
595 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
596 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
597 }
Adam Cohen1462de32012-07-24 22:34:36 -0700598 }
599
Michael Jurkae6235dd2011-10-04 15:02:05 -0700600 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700601 public void cancelLongPress() {
602 super.cancelLongPress();
603
604 // Cancel long press for all children
605 final int count = getChildCount();
606 for (int i = 0; i < count; i++) {
607 final View child = getChildAt(i);
608 child.cancelLongPress();
609 }
610 }
611
Michael Jurkadee05892010-07-27 10:01:56 -0700612 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
613 mInterceptTouchListener = listener;
614 }
615
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800616 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700617 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800618 }
619
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800620 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700621 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800622 }
623
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800624 public void setIsHotseat(boolean isHotseat) {
625 mIsHotseat = isHotseat;
Winson Chung5f8afe62013-08-12 16:19:28 -0700626 mShortcutsAndWidgets.setIsHotseat(isHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800627 }
628
Sunny Goyale9b651e2015-04-24 11:44:51 -0700629 public boolean isHotseat() {
630 return mIsHotseat;
631 }
632
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800633 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700634 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700635 final LayoutParams lp = params;
636
Andrew Flynnde38e422012-05-08 11:22:15 -0700637 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800638 if (child instanceof BubbleTextView) {
639 BubbleTextView bubbleChild = (BubbleTextView) child;
Winson Chung5f8afe62013-08-12 16:19:28 -0700640 bubbleChild.setTextVisibility(!mIsHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800641 }
642
Adam Cohen307fe232012-08-16 17:55:58 -0700643 child.setScaleX(getChildrenScale());
644 child.setScaleY(getChildrenScale());
645
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800646 // Generate an id for each view, this assumes we have at most 256x256 cells
647 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700648 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700649 // If the horizontal or vertical span is set to -1, it is taken to
650 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700651 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
652 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800653
Winson Chungaafa03c2010-06-11 17:34:16 -0700654 child.setId(childId);
Tony Wickhama0628cc2015-10-14 15:23:04 -0700655 if (LOGD) {
656 Log.d(TAG, "Adding view to ShortcutsAndWidgetsContainer: " + child);
657 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700658 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700659
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700660 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700661
Winson Chungaafa03c2010-06-11 17:34:16 -0700662 return true;
663 }
664 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800665 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700666
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800667 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700668 public void removeAllViews() {
669 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700670 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700671 }
672
673 @Override
674 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700675 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700676 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700677 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700678 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700679 }
680
681 @Override
682 public void removeView(View view) {
683 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700684 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700685 }
686
687 @Override
688 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700689 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
690 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700691 }
692
693 @Override
694 public void removeViewInLayout(View view) {
695 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700696 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700697 }
698
699 @Override
700 public void removeViews(int start, int count) {
701 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700702 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700703 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700704 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700705 }
706
707 @Override
708 public void removeViewsInLayout(int start, int count) {
709 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700710 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700711 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700712 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800713 }
714
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700715 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700716 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800717 * @param x X coordinate of the point
718 * @param y Y coordinate of the point
719 * @param result Array of 2 ints to hold the x and y coordinate of the cell
720 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700721 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700722 final int hStartPadding = getPaddingLeft();
723 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800724
725 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
726 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
727
Adam Cohend22015c2010-07-26 22:02:18 -0700728 final int xAxis = mCountX;
729 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800730
731 if (result[0] < 0) result[0] = 0;
732 if (result[0] >= xAxis) result[0] = xAxis - 1;
733 if (result[1] < 0) result[1] = 0;
734 if (result[1] >= yAxis) result[1] = yAxis - 1;
735 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700736
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800737 /**
738 * Given a point, return the cell that most closely encloses that point
739 * @param x X coordinate of the point
740 * @param y Y coordinate of the point
741 * @param result Array of 2 ints to hold the x and y coordinate of the cell
742 */
743 void pointToCellRounded(int x, int y, int[] result) {
744 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
745 }
746
747 /**
748 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700749 *
750 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800751 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700752 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800753 * @param result Array of 2 ints to hold the x and y coordinate of the point
754 */
755 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700756 final int hStartPadding = getPaddingLeft();
757 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800758
759 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
760 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
761 }
762
Adam Cohene3e27a82011-04-15 12:07:39 -0700763 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800764 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700765 *
766 * @param cellX X coordinate of the cell
767 * @param cellY Y coordinate of the cell
768 *
769 * @param result Array of 2 ints to hold the x and y coordinate of the point
770 */
771 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700772 regionToCenterPoint(cellX, cellY, 1, 1, result);
773 }
774
775 /**
776 * Given a cell coordinate and span return the point that represents the center of the regio
777 *
778 * @param cellX X coordinate of the cell
779 * @param cellY Y coordinate of the cell
780 *
781 * @param result Array of 2 ints to hold the x and y coordinate of the point
782 */
783 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700784 final int hStartPadding = getPaddingLeft();
785 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700786 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
787 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
788 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
789 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700790 }
791
Adam Cohen19f37922012-03-21 11:59:11 -0700792 /**
793 * Given a cell coordinate and span fills out a corresponding pixel rect
794 *
795 * @param cellX X coordinate of the cell
796 * @param cellY Y coordinate of the cell
797 * @param result Rect in which to write the result
798 */
799 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
800 final int hStartPadding = getPaddingLeft();
801 final int vStartPadding = getPaddingTop();
802 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
803 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
804 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
805 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
806 }
807
Adam Cohen482ed822012-03-02 14:15:13 -0800808 public float getDistanceFromCell(float x, float y, int[] cell) {
809 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700810 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800811 }
812
Romain Guy84f296c2009-11-04 15:00:44 -0800813 int getCellWidth() {
814 return mCellWidth;
815 }
816
817 int getCellHeight() {
818 return mCellHeight;
819 }
820
Adam Cohend4844c32011-02-18 19:25:06 -0800821 int getWidthGap() {
822 return mWidthGap;
823 }
824
825 int getHeightGap() {
826 return mHeightGap;
827 }
828
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700829 public void setFixedSize(int width, int height) {
830 mFixedWidth = width;
831 mFixedHeight = height;
832 }
833
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800834 @Override
835 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800836 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800837 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700838 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
839 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700840 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
841 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700842 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Sunny Goyalc6205602015-05-21 20:46:33 -0700843 int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX);
844 int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700845 if (cw != mCellWidth || ch != mCellHeight) {
846 mCellWidth = cw;
847 mCellHeight = ch;
848 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
849 mHeightGap, mCountX, mCountY);
850 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700851 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700852
Winson Chung2d75f122013-09-23 16:53:31 -0700853 int newWidth = childWidthSize;
854 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700855 if (mFixedWidth > 0 && mFixedHeight > 0) {
856 newWidth = mFixedWidth;
857 newHeight = mFixedHeight;
858 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800859 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
860 }
861
Adam Cohend22015c2010-07-26 22:02:18 -0700862 int numWidthGaps = mCountX - 1;
863 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800864
Adam Cohen234c4cd2011-07-17 21:03:04 -0700865 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700866 int hSpace = childWidthSize;
867 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700868 int hFreeSpace = hSpace - (mCountX * mCellWidth);
869 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700870 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
871 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700872 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
873 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700874 } else {
875 mWidthGap = mOriginalWidthGap;
876 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700877 }
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700878
879 // Make the feedback view large enough to hold the blur bitmap.
880 mTouchFeedbackView.measure(
881 MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
882 MeasureSpec.EXACTLY),
883 MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
884 MeasureSpec.EXACTLY));
885
886 mShortcutsAndWidgets.measure(
887 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
888 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
889
890 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
891 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -0700892 if (mFixedWidth > 0 && mFixedHeight > 0) {
893 setMeasuredDimension(maxWidth, maxHeight);
894 } else {
895 setMeasuredDimension(widthSize, heightSize);
896 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800897 }
898
899 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700900 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Tony Wickham26b01422015-11-10 14:44:32 -0800901 boolean isFullscreen = mShortcutsAndWidgets.getChildCount() > 0 &&
902 ((LayoutParams) mShortcutsAndWidgets.getChildAt(0).getLayoutParams()).isFullscreen;
903 int left = getPaddingLeft();
904 if (!isFullscreen) {
Tony Wickhama501d492015-11-03 18:05:01 -0800905 left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Tony Wickham26b01422015-11-10 14:44:32 -0800906 }
Winson Chung38848ca2013-10-08 12:03:44 -0700907 int top = getPaddingTop();
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700908
909 mTouchFeedbackView.layout(left, top,
910 left + mTouchFeedbackView.getMeasuredWidth(),
911 top + mTouchFeedbackView.getMeasuredHeight());
912 mShortcutsAndWidgets.layout(left, top,
913 left + r - l,
914 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800915 }
916
Tony Wickhama501d492015-11-03 18:05:01 -0800917 /**
918 * Returns the amount of space left over after subtracting padding and cells. This space will be
919 * very small, a few pixels at most, and is a result of rounding down when calculating the cell
920 * width in {@link DeviceProfile#calculateCellWidth(int, int)}.
921 */
922 public int getUnusedHorizontalSpace() {
923 return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
924 }
925
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800926 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700927 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
928 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -0700929
930 // Expand the background drawing bounds by the padding baked into the background drawable
Sunny Goyal2805e632015-05-20 15:35:32 -0700931 mBackground.getPadding(mTempRect);
932 mBackground.setBounds(-mTempRect.left, -mTempRect.top,
933 w + mTempRect.right, h + mTempRect.bottom);
Michael Jurkadee05892010-07-27 10:01:56 -0700934 }
935
936 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800937 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700938 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800939 }
940
941 @Override
942 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700943 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800944 }
945
Michael Jurka5f1c5092010-09-03 14:15:02 -0700946 public float getBackgroundAlpha() {
947 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -0700948 }
949
Michael Jurka5f1c5092010-09-03 14:15:02 -0700950 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -0800951 if (mBackgroundAlpha != alpha) {
952 mBackgroundAlpha = alpha;
Sunny Goyal2805e632015-05-20 15:35:32 -0700953 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurkaafaa0502011-12-13 18:22:50 -0800954 }
Michael Jurkadee05892010-07-27 10:01:56 -0700955 }
956
Sunny Goyal2805e632015-05-20 15:35:32 -0700957 @Override
958 protected boolean verifyDrawable(Drawable who) {
959 return super.verifyDrawable(who) || (mIsDragTarget && who == mBackground);
960 }
961
Michael Jurkaa52570f2012-03-20 03:18:20 -0700962 public void setShortcutAndWidgetAlpha(float alpha) {
Sunny Goyal02b50812014-09-10 15:44:42 -0700963 mShortcutsAndWidgets.setAlpha(alpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700964 }
965
Michael Jurkaa52570f2012-03-20 03:18:20 -0700966 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700967 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700968 }
969
Patrick Dubroy440c3602010-07-13 17:50:32 -0700970 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700971 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700972 }
973
Adam Cohen76fc0852011-06-17 13:26:23 -0700974 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800975 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700976 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800977 boolean[][] occupied = mOccupied;
978 if (!permanent) {
979 occupied = mTmpOccupied;
980 }
981
Adam Cohen19f37922012-03-21 11:59:11 -0700982 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700983 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
984 final ItemInfo info = (ItemInfo) child.getTag();
985
986 // We cancel any existing animations
987 if (mReorderAnimators.containsKey(lp)) {
988 mReorderAnimators.get(lp).cancel();
989 mReorderAnimators.remove(lp);
990 }
991
Adam Cohen482ed822012-03-02 14:15:13 -0800992 final int oldX = lp.x;
993 final int oldY = lp.y;
994 if (adjustOccupied) {
995 occupied[lp.cellX][lp.cellY] = false;
996 occupied[cellX][cellY] = true;
997 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700998 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -0800999 if (permanent) {
1000 lp.cellX = info.cellX = cellX;
1001 lp.cellY = info.cellY = cellY;
1002 } else {
1003 lp.tmpCellX = cellX;
1004 lp.tmpCellY = cellY;
1005 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001006 clc.setupLp(lp);
1007 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001008 final int newX = lp.x;
1009 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001010
Adam Cohen76fc0852011-06-17 13:26:23 -07001011 lp.x = oldX;
1012 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001013
Adam Cohen482ed822012-03-02 14:15:13 -08001014 // Exit early if we're not actually moving the view
1015 if (oldX == newX && oldY == newY) {
1016 lp.isLockedToGrid = true;
1017 return true;
1018 }
1019
Michael Jurkaf1ad6082013-03-13 12:55:46 +01001020 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001021 va.setDuration(duration);
1022 mReorderAnimators.put(lp, va);
1023
1024 va.addUpdateListener(new AnimatorUpdateListener() {
1025 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001026 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001027 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001028 lp.x = (int) ((1 - r) * oldX + r * newX);
1029 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001030 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001031 }
1032 });
Adam Cohen482ed822012-03-02 14:15:13 -08001033 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001034 boolean cancelled = false;
1035 public void onAnimationEnd(Animator animation) {
1036 // If the animation was cancelled, it means that another animation
1037 // has interrupted this one, and we don't want to lock the item into
1038 // place just yet.
1039 if (!cancelled) {
1040 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001041 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001042 }
1043 if (mReorderAnimators.containsKey(lp)) {
1044 mReorderAnimators.remove(lp);
1045 }
1046 }
1047 public void onAnimationCancel(Animator animation) {
1048 cancelled = true;
1049 }
1050 });
Adam Cohen482ed822012-03-02 14:15:13 -08001051 va.setStartDelay(delay);
1052 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001053 return true;
1054 }
1055 return false;
1056 }
1057
Tony Wickhama501d492015-11-03 18:05:01 -08001058 void visualizeDropLocation(View v, Bitmap dragOutline, int cellX, int cellY, int spanX,
1059 int spanY, boolean resize, DropTarget.DragObject dragObject) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001060 final int oldDragCellX = mDragCell[0];
1061 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001062
Adam Cohen2801caf2011-05-13 20:57:39 -07001063 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001064 return;
1065 }
1066
Adam Cohen482ed822012-03-02 14:15:13 -08001067 if (cellX != oldDragCellX || cellY != oldDragCellY) {
Sunny Goyale78e3d72015-09-24 11:23:31 -07001068 Point dragOffset = dragObject.dragView.getDragVisualizeOffset();
1069 Rect dragRegion = dragObject.dragView.getDragRegion();
1070
Adam Cohen482ed822012-03-02 14:15:13 -08001071 mDragCell[0] = cellX;
1072 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001073 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001074 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001075 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001076
Joe Onorato4be866d2010-10-10 11:26:02 -07001077 int left = topLeft[0];
1078 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001079
Winson Chungb8c69f32011-10-19 21:36:08 -07001080 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001081 // When drawing the drag outline, it did not account for margin offsets
1082 // added by the view's parent.
1083 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1084 left += lp.leftMargin;
1085 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001086
Adam Cohen99e8b402011-03-25 19:23:43 -07001087 // Offsets due to the size difference between the View and the dragOutline.
1088 // There is a size difference to account for the outer blur, which may lie
1089 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001090 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001091 // We center about the x axis
1092 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1093 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001094 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001095 if (dragOffset != null && dragRegion != null) {
1096 // Center the drag region *horizontally* in the cell and apply a drag
1097 // outline offset
1098 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1099 - dragRegion.width()) / 2;
Winson Chung69737c32013-10-08 17:00:19 -07001100 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1101 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1102 top += dragOffset.y + cellPaddingY;
Winson Chungb8c69f32011-10-19 21:36:08 -07001103 } else {
1104 // Center the drag outline in the cell
1105 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1106 - dragOutline.getWidth()) / 2;
1107 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1108 - dragOutline.getHeight()) / 2;
1109 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001110 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001111 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001112 mDragOutlineAnims[oldIndex].animateOut();
1113 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001114 Rect r = mDragOutlines[mDragOutlineCurrent];
1115 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1116 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001117 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001118 }
Winson Chung150fbab2010-09-29 17:14:26 -07001119
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001120 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1121 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Sunny Goyale78e3d72015-09-24 11:23:31 -07001122
1123 if (dragObject.stateAnnouncer != null) {
1124 String msg;
1125 if (isHotseat()) {
1126 msg = getContext().getString(R.string.move_to_hotseat_position,
1127 Math.max(cellX, cellY) + 1);
1128 } else {
1129 msg = getContext().getString(R.string.move_to_empty_cell,
1130 cellY + 1, cellX + 1);
1131 }
1132 dragObject.stateAnnouncer.announce(msg);
1133 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001134 }
1135 }
1136
Adam Cohene0310962011-04-18 16:15:31 -07001137 public void clearDragOutlines() {
1138 final int oldIndex = mDragOutlineCurrent;
1139 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001140 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001141 }
1142
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001143 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001144 * Find a vacant area that will fit the given bounds nearest the requested
1145 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001146 *
Romain Guy51afc022009-05-04 18:03:43 -07001147 * @param pixelX The X location at which you want to search for a vacant area.
1148 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001149 * @param spanX Horizontal span of the object.
1150 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001151 * @param result Array in which to place the result, or null (in which case a new array will
1152 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001153 * @return The X, Y cell of a vacant area that can contain this object,
1154 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001155 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001156 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
1157 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, spanX, spanY, result, null);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001158 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001159
Michael Jurka6a1435d2010-09-27 17:35:12 -07001160 /**
1161 * Find a vacant area that will fit the given bounds nearest the requested
1162 * cell location. Uses Euclidean distance to score multiple vacant areas.
1163 *
1164 * @param pixelX The X location at which you want to search for a vacant area.
1165 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001166 * @param minSpanX The minimum horizontal span required
1167 * @param minSpanY The minimum vertical span required
1168 * @param spanX Horizontal span of the object.
1169 * @param spanY Vertical span of the object.
1170 * @param result Array in which to place the result, or null (in which case a new array will
1171 * be allocated)
1172 * @return The X, Y cell of a vacant area that can contain this object,
1173 * nearest the requested location.
1174 */
1175 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1176 int spanY, int[] result, int[] resultSpan) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001177 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
Adam Cohend41fbf52012-02-16 23:53:59 -08001178 result, resultSpan);
1179 }
1180
Adam Cohend41fbf52012-02-16 23:53:59 -08001181 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1182 private void lazyInitTempRectStack() {
1183 if (mTempRectStack.isEmpty()) {
1184 for (int i = 0; i < mCountX * mCountY; i++) {
1185 mTempRectStack.push(new Rect());
1186 }
1187 }
1188 }
Adam Cohen482ed822012-03-02 14:15:13 -08001189
Adam Cohend41fbf52012-02-16 23:53:59 -08001190 private void recycleTempRects(Stack<Rect> used) {
1191 while (!used.isEmpty()) {
1192 mTempRectStack.push(used.pop());
1193 }
1194 }
1195
1196 /**
1197 * Find a vacant area that will fit the given bounds nearest the requested
1198 * cell location. Uses Euclidean distance to score multiple vacant areas.
1199 *
1200 * @param pixelX The X location at which you want to search for a vacant area.
1201 * @param pixelY The Y location at which you want to search for a vacant area.
1202 * @param minSpanX The minimum horizontal span required
1203 * @param minSpanY The minimum vertical span required
1204 * @param spanX Horizontal span of the object.
1205 * @param spanY Vertical span of the object.
1206 * @param ignoreOccupied If true, the result can be an occupied cell
1207 * @param result Array in which to place the result, or null (in which case a new array will
1208 * be allocated)
1209 * @return The X, Y cell of a vacant area that can contain this object,
1210 * nearest the requested location.
1211 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001212 private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1213 int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001214 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001215
Adam Cohene3e27a82011-04-15 12:07:39 -07001216 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1217 // to the center of the item, but we are searching based on the top-left cell, so
1218 // we translate the point over to correspond to the top-left.
1219 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1220 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1221
Jeff Sharkey70864282009-04-07 21:08:40 -07001222 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001223 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001224 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001225 final Rect bestRect = new Rect(-1, -1, -1, -1);
1226 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001227
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001228 final int countX = mCountX;
1229 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001230
Adam Cohend41fbf52012-02-16 23:53:59 -08001231 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1232 spanX < minSpanX || spanY < minSpanY) {
1233 return bestXY;
1234 }
1235
1236 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001237 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001238 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1239 int ySize = -1;
1240 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001241 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001242 // First, let's see if this thing fits anywhere
1243 for (int i = 0; i < minSpanX; i++) {
1244 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001245 if (mOccupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001246 continue inner;
1247 }
Michael Jurkac28de512010-08-13 11:27:44 -07001248 }
1249 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001250 xSize = minSpanX;
1251 ySize = minSpanY;
1252
1253 // We know that the item will fit at _some_ acceptable size, now let's see
1254 // how big we can make it. We'll alternate between incrementing x and y spans
1255 // until we hit a limit.
1256 boolean incX = true;
1257 boolean hitMaxX = xSize >= spanX;
1258 boolean hitMaxY = ySize >= spanY;
1259 while (!(hitMaxX && hitMaxY)) {
1260 if (incX && !hitMaxX) {
1261 for (int j = 0; j < ySize; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001262 if (x + xSize > countX -1 || mOccupied[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001263 // We can't move out horizontally
1264 hitMaxX = true;
1265 }
1266 }
1267 if (!hitMaxX) {
1268 xSize++;
1269 }
1270 } else if (!hitMaxY) {
1271 for (int i = 0; i < xSize; i++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001272 if (y + ySize > countY - 1 || mOccupied[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001273 // We can't move out vertically
1274 hitMaxY = true;
1275 }
1276 }
1277 if (!hitMaxY) {
1278 ySize++;
1279 }
1280 }
1281 hitMaxX |= xSize >= spanX;
1282 hitMaxY |= ySize >= spanY;
1283 incX = !incX;
1284 }
1285 incX = true;
1286 hitMaxX = xSize >= spanX;
1287 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001288 }
Sunny Goyal2805e632015-05-20 15:35:32 -07001289 final int[] cellXY = mTmpPoint;
Adam Cohene3e27a82011-04-15 12:07:39 -07001290 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001291
Adam Cohend41fbf52012-02-16 23:53:59 -08001292 // We verify that the current rect is not a sub-rect of any of our previous
1293 // candidates. In this case, the current rect is disqualified in favour of the
1294 // containing rect.
1295 Rect currentRect = mTempRectStack.pop();
1296 currentRect.set(x, y, x + xSize, y + ySize);
1297 boolean contained = false;
1298 for (Rect r : validRegions) {
1299 if (r.contains(currentRect)) {
1300 contained = true;
1301 break;
1302 }
1303 }
1304 validRegions.push(currentRect);
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001305 double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY);
Adam Cohen482ed822012-03-02 14:15:13 -08001306
Adam Cohend41fbf52012-02-16 23:53:59 -08001307 if ((distance <= bestDistance && !contained) ||
1308 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001309 bestDistance = distance;
1310 bestXY[0] = x;
1311 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001312 if (resultSpan != null) {
1313 resultSpan[0] = xSize;
1314 resultSpan[1] = ySize;
1315 }
1316 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001317 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001318 }
1319 }
1320
Adam Cohenc0dcf592011-06-01 15:30:43 -07001321 // Return -1, -1 if no suitable location found
1322 if (bestDistance == Double.MAX_VALUE) {
1323 bestXY[0] = -1;
1324 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001325 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001326 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001327 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001328 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001329
Adam Cohen482ed822012-03-02 14:15:13 -08001330 /**
1331 * Find a vacant area that will fit the given bounds nearest the requested
1332 * cell location, and will also weigh in a suggested direction vector of the
1333 * desired location. This method computers distance based on unit grid distances,
1334 * not pixel distances.
1335 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001336 * @param cellX The X cell nearest to which you want to search for a vacant area.
1337 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001338 * @param spanX Horizontal span of the object.
1339 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001340 * @param direction The favored direction in which the views should move from x, y
1341 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1342 * matches exactly. Otherwise we find the best matching direction.
1343 * @param occoupied The array which represents which cells in the CellLayout are occupied
1344 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001345 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001346 * @param result Array in which to place the result, or null (in which case a new array will
1347 * be allocated)
1348 * @return The X, Y cell of a vacant area that can contain this object,
1349 * nearest the requested location.
1350 */
1351 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001352 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001353 // Keep track of best-scoring drop area
1354 final int[] bestXY = result != null ? result : new int[2];
1355 float bestDistance = Float.MAX_VALUE;
1356 int bestDirectionScore = Integer.MIN_VALUE;
1357
1358 final int countX = mCountX;
1359 final int countY = mCountY;
1360
1361 for (int y = 0; y < countY - (spanY - 1); y++) {
1362 inner:
1363 for (int x = 0; x < countX - (spanX - 1); x++) {
1364 // First, let's see if this thing fits anywhere
1365 for (int i = 0; i < spanX; i++) {
1366 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001367 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001368 continue inner;
1369 }
1370 }
1371 }
1372
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001373 float distance = (float) Math.hypot(x - cellX, y - cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001374 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001375 computeDirectionVector(x - cellX, y - cellY, curDirection);
1376 // The direction score is just the dot product of the two candidate direction
1377 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001378 int curDirectionScore = direction[0] * curDirection[0] +
1379 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001380 boolean exactDirectionOnly = false;
1381 boolean directionMatches = direction[0] == curDirection[0] &&
1382 direction[0] == curDirection[0];
1383 if ((directionMatches || !exactDirectionOnly) &&
1384 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001385 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1386 bestDistance = distance;
1387 bestDirectionScore = curDirectionScore;
1388 bestXY[0] = x;
1389 bestXY[1] = y;
1390 }
1391 }
1392 }
1393
1394 // Return -1, -1 if no suitable location found
1395 if (bestDistance == Float.MAX_VALUE) {
1396 bestXY[0] = -1;
1397 bestXY[1] = -1;
1398 }
1399 return bestXY;
1400 }
1401
1402 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001403 int[] direction, ItemConfiguration currentState) {
1404 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001405 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001406 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001407 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1408
Adam Cohen8baab352012-03-20 17:39:21 -07001409 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001410
1411 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001412 c.x = mTempLocation[0];
1413 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001414 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001415 }
Adam Cohen8baab352012-03-20 17:39:21 -07001416 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001417 return success;
1418 }
1419
Adam Cohenf3900c22012-11-16 18:28:11 -08001420 /**
1421 * This helper class defines a cluster of views. It helps with defining complex edges
1422 * of the cluster and determining how those edges interact with other views. The edges
1423 * essentially define a fine-grained boundary around the cluster of views -- like a more
1424 * precise version of a bounding box.
1425 */
1426 private class ViewCluster {
1427 final static int LEFT = 0;
1428 final static int TOP = 1;
1429 final static int RIGHT = 2;
1430 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001431
Adam Cohenf3900c22012-11-16 18:28:11 -08001432 ArrayList<View> views;
1433 ItemConfiguration config;
1434 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001435
Adam Cohenf3900c22012-11-16 18:28:11 -08001436 int[] leftEdge = new int[mCountY];
1437 int[] rightEdge = new int[mCountY];
1438 int[] topEdge = new int[mCountX];
1439 int[] bottomEdge = new int[mCountX];
1440 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1441
1442 @SuppressWarnings("unchecked")
1443 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1444 this.views = (ArrayList<View>) views.clone();
1445 this.config = config;
1446 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001447 }
1448
Adam Cohenf3900c22012-11-16 18:28:11 -08001449 void resetEdges() {
1450 for (int i = 0; i < mCountX; i++) {
1451 topEdge[i] = -1;
1452 bottomEdge[i] = -1;
1453 }
1454 for (int i = 0; i < mCountY; i++) {
1455 leftEdge[i] = -1;
1456 rightEdge[i] = -1;
1457 }
1458 leftEdgeDirty = true;
1459 rightEdgeDirty = true;
1460 bottomEdgeDirty = true;
1461 topEdgeDirty = true;
1462 boundingRectDirty = true;
1463 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001464
Adam Cohenf3900c22012-11-16 18:28:11 -08001465 void computeEdge(int which, int[] edge) {
1466 int count = views.size();
1467 for (int i = 0; i < count; i++) {
1468 CellAndSpan cs = config.map.get(views.get(i));
1469 switch (which) {
1470 case LEFT:
1471 int left = cs.x;
1472 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1473 if (left < edge[j] || edge[j] < 0) {
1474 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001475 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001476 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001477 break;
1478 case RIGHT:
1479 int right = cs.x + cs.spanX;
1480 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1481 if (right > edge[j]) {
1482 edge[j] = right;
1483 }
1484 }
1485 break;
1486 case TOP:
1487 int top = cs.y;
1488 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1489 if (top < edge[j] || edge[j] < 0) {
1490 edge[j] = top;
1491 }
1492 }
1493 break;
1494 case BOTTOM:
1495 int bottom = cs.y + cs.spanY;
1496 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1497 if (bottom > edge[j]) {
1498 edge[j] = bottom;
1499 }
1500 }
1501 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001502 }
1503 }
1504 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001505
1506 boolean isViewTouchingEdge(View v, int whichEdge) {
1507 CellAndSpan cs = config.map.get(v);
1508
1509 int[] edge = getEdge(whichEdge);
1510
1511 switch (whichEdge) {
1512 case LEFT:
1513 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1514 if (edge[i] == cs.x + cs.spanX) {
1515 return true;
1516 }
1517 }
1518 break;
1519 case RIGHT:
1520 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1521 if (edge[i] == cs.x) {
1522 return true;
1523 }
1524 }
1525 break;
1526 case TOP:
1527 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1528 if (edge[i] == cs.y + cs.spanY) {
1529 return true;
1530 }
1531 }
1532 break;
1533 case BOTTOM:
1534 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1535 if (edge[i] == cs.y) {
1536 return true;
1537 }
1538 }
1539 break;
1540 }
1541 return false;
1542 }
1543
1544 void shift(int whichEdge, int delta) {
1545 for (View v: views) {
1546 CellAndSpan c = config.map.get(v);
1547 switch (whichEdge) {
1548 case LEFT:
1549 c.x -= delta;
1550 break;
1551 case RIGHT:
1552 c.x += delta;
1553 break;
1554 case TOP:
1555 c.y -= delta;
1556 break;
1557 case BOTTOM:
1558 default:
1559 c.y += delta;
1560 break;
1561 }
1562 }
1563 resetEdges();
1564 }
1565
1566 public void addView(View v) {
1567 views.add(v);
1568 resetEdges();
1569 }
1570
1571 public Rect getBoundingRect() {
1572 if (boundingRectDirty) {
1573 boolean first = true;
1574 for (View v: views) {
1575 CellAndSpan c = config.map.get(v);
1576 if (first) {
1577 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1578 first = false;
1579 } else {
1580 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1581 }
1582 }
1583 }
1584 return boundingRect;
1585 }
1586
1587 public int[] getEdge(int which) {
1588 switch (which) {
1589 case LEFT:
1590 return getLeftEdge();
1591 case RIGHT:
1592 return getRightEdge();
1593 case TOP:
1594 return getTopEdge();
1595 case BOTTOM:
1596 default:
1597 return getBottomEdge();
1598 }
1599 }
1600
1601 public int[] getLeftEdge() {
1602 if (leftEdgeDirty) {
1603 computeEdge(LEFT, leftEdge);
1604 }
1605 return leftEdge;
1606 }
1607
1608 public int[] getRightEdge() {
1609 if (rightEdgeDirty) {
1610 computeEdge(RIGHT, rightEdge);
1611 }
1612 return rightEdge;
1613 }
1614
1615 public int[] getTopEdge() {
1616 if (topEdgeDirty) {
1617 computeEdge(TOP, topEdge);
1618 }
1619 return topEdge;
1620 }
1621
1622 public int[] getBottomEdge() {
1623 if (bottomEdgeDirty) {
1624 computeEdge(BOTTOM, bottomEdge);
1625 }
1626 return bottomEdge;
1627 }
1628
1629 PositionComparator comparator = new PositionComparator();
1630 class PositionComparator implements Comparator<View> {
1631 int whichEdge = 0;
1632 public int compare(View left, View right) {
1633 CellAndSpan l = config.map.get(left);
1634 CellAndSpan r = config.map.get(right);
1635 switch (whichEdge) {
1636 case LEFT:
1637 return (r.x + r.spanX) - (l.x + l.spanX);
1638 case RIGHT:
1639 return l.x - r.x;
1640 case TOP:
1641 return (r.y + r.spanY) - (l.y + l.spanY);
1642 case BOTTOM:
1643 default:
1644 return l.y - r.y;
1645 }
1646 }
1647 }
1648
1649 public void sortConfigurationForEdgePush(int edge) {
1650 comparator.whichEdge = edge;
1651 Collections.sort(config.sortedViews, comparator);
1652 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001653 }
1654
Adam Cohenf3900c22012-11-16 18:28:11 -08001655 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1656 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001657
Adam Cohenf3900c22012-11-16 18:28:11 -08001658 ViewCluster cluster = new ViewCluster(views, currentState);
1659 Rect clusterRect = cluster.getBoundingRect();
1660 int whichEdge;
1661 int pushDistance;
1662 boolean fail = false;
1663
1664 // Determine the edge of the cluster that will be leading the push and how far
1665 // the cluster must be shifted.
1666 if (direction[0] < 0) {
1667 whichEdge = ViewCluster.LEFT;
1668 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001669 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001670 whichEdge = ViewCluster.RIGHT;
1671 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1672 } else if (direction[1] < 0) {
1673 whichEdge = ViewCluster.TOP;
1674 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1675 } else {
1676 whichEdge = ViewCluster.BOTTOM;
1677 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001678 }
1679
Adam Cohenf3900c22012-11-16 18:28:11 -08001680 // Break early for invalid push distance.
1681 if (pushDistance <= 0) {
1682 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001683 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001684
1685 // Mark the occupied state as false for the group of views we want to move.
1686 for (View v: views) {
1687 CellAndSpan c = currentState.map.get(v);
1688 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1689 }
1690
1691 // We save the current configuration -- if we fail to find a solution we will revert
1692 // to the initial state. The process of finding a solution modifies the configuration
1693 // in place, hence the need for revert in the failure case.
1694 currentState.save();
1695
1696 // The pushing algorithm is simplified by considering the views in the order in which
1697 // they would be pushed by the cluster. For example, if the cluster is leading with its
1698 // left edge, we consider sort the views by their right edge, from right to left.
1699 cluster.sortConfigurationForEdgePush(whichEdge);
1700
1701 while (pushDistance > 0 && !fail) {
1702 for (View v: currentState.sortedViews) {
1703 // For each view that isn't in the cluster, we see if the leading edge of the
1704 // cluster is contacting the edge of that view. If so, we add that view to the
1705 // cluster.
1706 if (!cluster.views.contains(v) && v != dragView) {
1707 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1708 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1709 if (!lp.canReorder) {
1710 // The push solution includes the all apps button, this is not viable.
1711 fail = true;
1712 break;
1713 }
1714 cluster.addView(v);
1715 CellAndSpan c = currentState.map.get(v);
1716
1717 // Adding view to cluster, mark it as not occupied.
1718 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1719 }
1720 }
1721 }
1722 pushDistance--;
1723
1724 // The cluster has been completed, now we move the whole thing over in the appropriate
1725 // direction.
1726 cluster.shift(whichEdge, 1);
1727 }
1728
1729 boolean foundSolution = false;
1730 clusterRect = cluster.getBoundingRect();
1731
1732 // Due to the nature of the algorithm, the only check required to verify a valid solution
1733 // is to ensure that completed shifted cluster lies completely within the cell layout.
1734 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1735 clusterRect.bottom <= mCountY) {
1736 foundSolution = true;
1737 } else {
1738 currentState.restore();
1739 }
1740
1741 // In either case, we set the occupied array as marked for the location of the views
1742 for (View v: cluster.views) {
1743 CellAndSpan c = currentState.map.get(v);
1744 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1745 }
1746
1747 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001748 }
1749
Adam Cohen482ed822012-03-02 14:15:13 -08001750 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001751 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001752 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001753
Adam Cohen8baab352012-03-20 17:39:21 -07001754 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001755 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001756 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001757 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001758 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001759 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001760 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001761 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001762 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001763 }
1764 }
Adam Cohen8baab352012-03-20 17:39:21 -07001765
Adam Cohen8baab352012-03-20 17:39:21 -07001766 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001767 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001768 CellAndSpan c = currentState.map.get(v);
1769 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1770 }
1771
Adam Cohen47a876d2012-03-19 13:21:41 -07001772 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1773 int top = boundingRect.top;
1774 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001775 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001776 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001777 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001778 CellAndSpan c = currentState.map.get(v);
1779 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001780 }
1781
Adam Cohen482ed822012-03-02 14:15:13 -08001782 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1783
Adam Cohenf3900c22012-11-16 18:28:11 -08001784 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1785 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001786
Adam Cohen8baab352012-03-20 17:39:21 -07001787 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001788 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001789 int deltaX = mTempLocation[0] - boundingRect.left;
1790 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001791 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001792 CellAndSpan c = currentState.map.get(v);
1793 c.x += deltaX;
1794 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001795 }
1796 success = true;
1797 }
Adam Cohen8baab352012-03-20 17:39:21 -07001798
1799 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001800 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001801 CellAndSpan c = currentState.map.get(v);
1802 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001803 }
1804 return success;
1805 }
1806
1807 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1808 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1809 }
1810
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001811 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1812 // to push items in each of the cardinal directions, in an order based on the direction vector
1813 // passed.
1814 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1815 int[] direction, View ignoreView, ItemConfiguration solution) {
1816 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001817 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001818 // separately in each of the components.
1819 int temp = direction[1];
1820 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001821
1822 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001823 ignoreView, solution)) {
1824 return true;
1825 }
1826 direction[1] = temp;
1827 temp = direction[0];
1828 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001829
1830 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001831 ignoreView, solution)) {
1832 return true;
1833 }
1834 // Revert the direction
1835 direction[0] = temp;
1836
1837 // Now we try pushing in each component of the opposite direction
1838 direction[0] *= -1;
1839 direction[1] *= -1;
1840 temp = direction[1];
1841 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001842 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001843 ignoreView, solution)) {
1844 return true;
1845 }
1846
1847 direction[1] = temp;
1848 temp = direction[0];
1849 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001850 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001851 ignoreView, solution)) {
1852 return true;
1853 }
1854 // revert the direction
1855 direction[0] = temp;
1856 direction[0] *= -1;
1857 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001858
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001859 } else {
1860 // If the direction vector has a single non-zero component, we push first in the
1861 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001862 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001863 ignoreView, solution)) {
1864 return true;
1865 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001866 // Then we try the opposite direction
1867 direction[0] *= -1;
1868 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001869 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001870 ignoreView, solution)) {
1871 return true;
1872 }
1873 // Switch the direction back
1874 direction[0] *= -1;
1875 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001876
1877 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001878 // to find a solution by pushing along the perpendicular axis.
1879
1880 // Swap the components
1881 int temp = direction[1];
1882 direction[1] = direction[0];
1883 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001884 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001885 ignoreView, solution)) {
1886 return true;
1887 }
1888
1889 // Then we try the opposite direction
1890 direction[0] *= -1;
1891 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001892 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001893 ignoreView, solution)) {
1894 return true;
1895 }
1896 // Switch the direction back
1897 direction[0] *= -1;
1898 direction[1] *= -1;
1899
1900 // Swap the components back
1901 temp = direction[1];
1902 direction[1] = direction[0];
1903 direction[0] = temp;
1904 }
1905 return false;
1906 }
1907
Adam Cohen482ed822012-03-02 14:15:13 -08001908 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001909 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001910 // Return early if get invalid cell positions
1911 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001912
Adam Cohen8baab352012-03-20 17:39:21 -07001913 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001914 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001915
Adam Cohen8baab352012-03-20 17:39:21 -07001916 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001917 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001918 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001919 if (c != null) {
1920 c.x = cellX;
1921 c.y = cellY;
1922 }
Adam Cohen482ed822012-03-02 14:15:13 -08001923 }
Adam Cohen482ed822012-03-02 14:15:13 -08001924 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1925 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001926 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001927 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001928 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001929 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001930 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001931 if (Rect.intersects(r0, r1)) {
1932 if (!lp.canReorder) {
1933 return false;
1934 }
1935 mIntersectingViews.add(child);
1936 }
1937 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001938
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001939 solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
1940
Winson Chung5f8afe62013-08-12 16:19:28 -07001941 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001942 // we try to find a solution such that no displaced item travels through another item
1943 // without also displacing that item.
1944 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001945 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001946 return true;
1947 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001948
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001949 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08001950 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001951 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001952 return true;
1953 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001954
Adam Cohen482ed822012-03-02 14:15:13 -08001955 // Ok, they couldn't move as a block, let's move them individually
1956 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001957 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001958 return false;
1959 }
1960 }
1961 return true;
1962 }
1963
1964 /*
1965 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1966 * the provided point and the provided cell
1967 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001968 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001969 double angle = Math.atan(((float) deltaY) / deltaX);
1970
1971 result[0] = 0;
1972 result[1] = 0;
1973 if (Math.abs(Math.cos(angle)) > 0.5f) {
1974 result[0] = (int) Math.signum(deltaX);
1975 }
1976 if (Math.abs(Math.sin(angle)) > 0.5f) {
1977 result[1] = (int) Math.signum(deltaY);
1978 }
1979 }
1980
Adam Cohen8baab352012-03-20 17:39:21 -07001981 private void copyOccupiedArray(boolean[][] occupied) {
1982 for (int i = 0; i < mCountX; i++) {
1983 for (int j = 0; j < mCountY; j++) {
1984 occupied[i][j] = mOccupied[i][j];
1985 }
1986 }
1987 }
1988
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001989 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001990 int spanX, int spanY, int[] direction, View dragView, boolean decX,
1991 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001992 // Copy the current state into the solution. This solution will be manipulated as necessary.
1993 copyCurrentStateToSolution(solution, false);
1994 // Copy the current occupied array into the temporary occupied array. This array will be
1995 // manipulated as necessary to find a solution.
1996 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001997
1998 // We find the nearest cell into which we would place the dragged item, assuming there's
1999 // nothing in its way.
2000 int result[] = new int[2];
2001 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2002
2003 boolean success = false;
2004 // First we try the exact nearest position of the item being dragged,
2005 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07002006 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
2007 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002008
2009 if (!success) {
2010 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
2011 // x, then 1 in y etc.
2012 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002013 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
2014 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002015 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002016 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
2017 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002018 }
2019 solution.isSolution = false;
2020 } else {
2021 solution.isSolution = true;
2022 solution.dragViewX = result[0];
2023 solution.dragViewY = result[1];
2024 solution.dragViewSpanX = spanX;
2025 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002026 }
2027 return solution;
2028 }
2029
2030 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002031 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002032 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002033 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002034 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002035 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08002036 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07002037 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002038 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07002039 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002040 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002041 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08002042 }
2043 }
2044
2045 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
2046 for (int i = 0; i < mCountX; i++) {
2047 for (int j = 0; j < mCountY; j++) {
2048 mTmpOccupied[i][j] = false;
2049 }
2050 }
2051
Michael Jurkaa52570f2012-03-20 03:18:20 -07002052 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002053 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002054 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002055 if (child == dragView) continue;
2056 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002057 CellAndSpan c = solution.map.get(child);
2058 if (c != null) {
2059 lp.tmpCellX = c.x;
2060 lp.tmpCellY = c.y;
2061 lp.cellHSpan = c.spanX;
2062 lp.cellVSpan = c.spanY;
2063 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002064 }
2065 }
2066 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2067 solution.dragViewSpanY, mTmpOccupied, true);
2068 }
2069
2070 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2071 commitDragView) {
2072
2073 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2074 for (int i = 0; i < mCountX; i++) {
2075 for (int j = 0; j < mCountY; j++) {
2076 occupied[i][j] = false;
2077 }
2078 }
2079
Michael Jurkaa52570f2012-03-20 03:18:20 -07002080 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002081 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002082 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002083 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002084 CellAndSpan c = solution.map.get(child);
2085 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002086 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2087 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002088 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002089 }
2090 }
2091 if (commitDragView) {
2092 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2093 solution.dragViewSpanY, occupied, true);
2094 }
2095 }
2096
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002097
2098 // This method starts or changes the reorder preview animations
2099 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
2100 View dragView, int delay, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07002101 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002102 for (int i = 0; i < childCount; i++) {
2103 View child = mShortcutsAndWidgets.getChildAt(i);
2104 if (child == dragView) continue;
2105 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002106 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
2107 != null && !solution.intersectingViews.contains(child);
2108
Adam Cohen19f37922012-03-21 11:59:11 -07002109 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002110 if (c != null && !skip) {
2111 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
2112 lp.cellY, c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002113 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002114 }
2115 }
2116 }
2117
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002118 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07002119 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002120 class ReorderPreviewAnimation {
Adam Cohen19f37922012-03-21 11:59:11 -07002121 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002122 float finalDeltaX;
2123 float finalDeltaY;
2124 float initDeltaX;
2125 float initDeltaY;
2126 float finalScale;
2127 float initScale;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002128 int mode;
2129 boolean repeating = false;
2130 private static final int PREVIEW_DURATION = 300;
2131 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
2132
2133 public static final int MODE_HINT = 0;
2134 public static final int MODE_PREVIEW = 1;
2135
Adam Cohene7587d22012-05-24 18:50:02 -07002136 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002137
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002138 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
2139 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07002140 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2141 final int x0 = mTmpPoint[0];
2142 final int y0 = mTmpPoint[1];
2143 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2144 final int x1 = mTmpPoint[0];
2145 final int y1 = mTmpPoint[1];
2146 final int dX = x1 - x0;
2147 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002148 finalDeltaX = 0;
2149 finalDeltaY = 0;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002150 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07002151 if (dX == dY && dX == 0) {
2152 } else {
2153 if (dY == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002154 finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002155 } else if (dX == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002156 finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002157 } else {
2158 double angle = Math.atan( (float) (dY) / dX);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002159 finalDeltaX = (int) (- dir * Math.signum(dX) *
2160 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
2161 finalDeltaY = (int) (- dir * Math.signum(dY) *
2162 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002163 }
2164 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002165 this.mode = mode;
Adam Cohend024f982012-05-23 18:26:45 -07002166 initDeltaX = child.getTranslationX();
2167 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002168 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002169 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002170 this.child = child;
2171 }
2172
Adam Cohend024f982012-05-23 18:26:45 -07002173 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002174 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002175 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002176 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002177 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002178 if (finalDeltaX == 0 && finalDeltaY == 0) {
2179 completeAnimationImmediately();
2180 return;
2181 }
Adam Cohen19f37922012-03-21 11:59:11 -07002182 }
Adam Cohend024f982012-05-23 18:26:45 -07002183 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002184 return;
2185 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002186 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002187 a = va;
Tony Wickham489fc562015-09-02 14:45:39 -07002188
2189 // Animations are disabled in power save mode, causing the repeated animation to jump
2190 // spastically between beginning and end states. Since this looks bad, we don't repeat
2191 // the animation in power save mode.
Tony Wickham112ac952015-11-12 12:31:50 -08002192 if (!Utilities.isPowerSaverOn(getContext())) {
Tony Wickham489fc562015-09-02 14:45:39 -07002193 va.setRepeatMode(ValueAnimator.REVERSE);
2194 va.setRepeatCount(ValueAnimator.INFINITE);
2195 }
2196
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002197 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002198 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002199 va.addUpdateListener(new AnimatorUpdateListener() {
2200 @Override
2201 public void onAnimationUpdate(ValueAnimator animation) {
2202 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002203 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2204 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2205 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002206 child.setTranslationX(x);
2207 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002208 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002209 child.setScaleX(s);
2210 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002211 }
2212 });
2213 va.addListener(new AnimatorListenerAdapter() {
2214 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002215 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002216 initDeltaX = 0;
2217 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002218 initScale = getChildrenScale();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002219 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002220 }
2221 });
Adam Cohen19f37922012-03-21 11:59:11 -07002222 mShakeAnimators.put(child, this);
2223 va.start();
2224 }
2225
Adam Cohend024f982012-05-23 18:26:45 -07002226 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002227 if (a != null) {
2228 a.cancel();
2229 }
Adam Cohen19f37922012-03-21 11:59:11 -07002230 }
Adam Cohene7587d22012-05-24 18:50:02 -07002231
Adam Cohen091440a2015-03-18 14:16:05 -07002232 @Thunk void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002233 if (a != null) {
2234 a.cancel();
2235 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002236
Michael Jurka2ecf9952012-06-18 12:52:28 -07002237 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002238 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002239 s.playTogether(
Adam Cohen307fe232012-08-16 17:55:58 -07002240 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2241 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
Michael Jurka2ecf9952012-06-18 12:52:28 -07002242 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2243 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002244 );
2245 s.setDuration(REORDER_ANIMATION_DURATION);
2246 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2247 s.start();
2248 }
Adam Cohen19f37922012-03-21 11:59:11 -07002249 }
2250
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002251 private void completeAndClearReorderPreviewAnimations() {
2252 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002253 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002254 }
2255 mShakeAnimators.clear();
2256 }
2257
Adam Cohen482ed822012-03-02 14:15:13 -08002258 private void commitTempPlacement() {
2259 for (int i = 0; i < mCountX; i++) {
2260 for (int j = 0; j < mCountY; j++) {
2261 mOccupied[i][j] = mTmpOccupied[i][j];
2262 }
2263 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002264 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002265 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002266 View child = mShortcutsAndWidgets.getChildAt(i);
2267 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2268 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002269 // We do a null check here because the item info can be null in the case of the
2270 // AllApps button in the hotseat.
2271 if (info != null) {
Adam Cohen487f7dd2012-06-28 18:12:10 -07002272 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2273 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2274 info.requiresDbUpdate = true;
2275 }
Adam Cohen2acce882012-03-28 19:03:19 -07002276 info.cellX = lp.cellX = lp.tmpCellX;
2277 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002278 info.spanX = lp.cellHSpan;
2279 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002280 }
Adam Cohen482ed822012-03-02 14:15:13 -08002281 }
Adam Cohen2acce882012-03-28 19:03:19 -07002282 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002283 }
2284
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002285 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002286 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002287 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002288 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002289 lp.useTmpCoords = useTempCoords;
2290 }
2291 }
2292
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002293 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002294 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2295 int[] result = new int[2];
2296 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002297 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002298 resultSpan);
2299 if (result[0] >= 0 && result[1] >= 0) {
2300 copyCurrentStateToSolution(solution, false);
2301 solution.dragViewX = result[0];
2302 solution.dragViewY = result[1];
2303 solution.dragViewSpanX = resultSpan[0];
2304 solution.dragViewSpanY = resultSpan[1];
2305 solution.isSolution = true;
2306 } else {
2307 solution.isSolution = false;
2308 }
2309 return solution;
2310 }
2311
2312 public void prepareChildForDrag(View child) {
2313 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002314 }
2315
Adam Cohen19f37922012-03-21 11:59:11 -07002316 /* This seems like it should be obvious and straight-forward, but when the direction vector
2317 needs to match with the notion of the dragView pushing other views, we have to employ
2318 a slightly more subtle notion of the direction vector. The question is what two points is
2319 the vector between? The center of the dragView and its desired destination? Not quite, as
2320 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2321 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2322 or right, which helps make pushing feel right.
2323 */
2324 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2325 int spanY, View dragView, int[] resultDirection) {
2326 int[] targetDestination = new int[2];
2327
2328 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2329 Rect dragRect = new Rect();
2330 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2331 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2332
2333 Rect dropRegionRect = new Rect();
2334 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2335 dragView, dropRegionRect, mIntersectingViews);
2336
2337 int dropRegionSpanX = dropRegionRect.width();
2338 int dropRegionSpanY = dropRegionRect.height();
2339
2340 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2341 dropRegionRect.height(), dropRegionRect);
2342
2343 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2344 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2345
2346 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2347 deltaX = 0;
2348 }
2349 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2350 deltaY = 0;
2351 }
2352
2353 if (deltaX == 0 && deltaY == 0) {
2354 // No idea what to do, give a random direction.
2355 resultDirection[0] = 1;
2356 resultDirection[1] = 0;
2357 } else {
2358 computeDirectionVector(deltaX, deltaY, resultDirection);
2359 }
2360 }
2361
2362 // For a given cell and span, fetch the set of views intersecting the region.
2363 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2364 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2365 if (boundingRect != null) {
2366 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2367 }
2368 intersectingViews.clear();
2369 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2370 Rect r1 = new Rect();
2371 final int count = mShortcutsAndWidgets.getChildCount();
2372 for (int i = 0; i < count; i++) {
2373 View child = mShortcutsAndWidgets.getChildAt(i);
2374 if (child == dragView) continue;
2375 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2376 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2377 if (Rect.intersects(r0, r1)) {
2378 mIntersectingViews.add(child);
2379 if (boundingRect != null) {
2380 boundingRect.union(r1);
2381 }
2382 }
2383 }
2384 }
2385
2386 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2387 View dragView, int[] result) {
2388 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2389 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2390 mIntersectingViews);
2391 return !mIntersectingViews.isEmpty();
2392 }
2393
2394 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002395 completeAndClearReorderPreviewAnimations();
2396 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2397 final int count = mShortcutsAndWidgets.getChildCount();
2398 for (int i = 0; i < count; i++) {
2399 View child = mShortcutsAndWidgets.getChildAt(i);
2400 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2401 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2402 lp.tmpCellX = lp.cellX;
2403 lp.tmpCellY = lp.cellY;
2404 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2405 0, false, false);
2406 }
Adam Cohen19f37922012-03-21 11:59:11 -07002407 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002408 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002409 }
Adam Cohen19f37922012-03-21 11:59:11 -07002410 }
2411
Adam Cohenbebf0422012-04-11 18:06:28 -07002412 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2413 View dragView, int[] direction, boolean commit) {
2414 int[] pixelXY = new int[2];
2415 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2416
2417 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002418 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002419 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2420
2421 setUseTempCoords(true);
2422 if (swapSolution != null && swapSolution.isSolution) {
2423 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2424 // committing anything or animating anything as we just want to determine if a solution
2425 // exists
2426 copySolutionToTempState(swapSolution, dragView);
2427 setItemPlacementDirty(true);
2428 animateItemsToSolution(swapSolution, dragView, commit);
2429
2430 if (commit) {
2431 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002432 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002433 setItemPlacementDirty(false);
2434 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002435 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2436 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002437 }
2438 mShortcutsAndWidgets.requestLayout();
2439 }
2440 return swapSolution.isSolution;
2441 }
2442
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002443 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002444 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002445 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002446 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002447
2448 if (resultSpan == null) {
2449 resultSpan = new int[2];
2450 }
2451
Adam Cohen19f37922012-03-21 11:59:11 -07002452 // When we are checking drop validity or actually dropping, we don't recompute the
2453 // direction vector, since we want the solution to match the preview, and it's possible
2454 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002455 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2456 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002457 mDirectionVector[0] = mPreviousReorderDirection[0];
2458 mDirectionVector[1] = mPreviousReorderDirection[1];
2459 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002460 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2461 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2462 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002463 }
2464 } else {
2465 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2466 mPreviousReorderDirection[0] = mDirectionVector[0];
2467 mPreviousReorderDirection[1] = mDirectionVector[1];
2468 }
2469
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002470 // Find a solution involving pushing / displacing any items in the way
2471 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002472 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2473
2474 // We attempt the approach which doesn't shuffle views at all
2475 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2476 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2477
2478 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002479
2480 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2481 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002482 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2483 finalSolution = swapSolution;
2484 } else if (noShuffleSolution.isSolution) {
2485 finalSolution = noShuffleSolution;
2486 }
2487
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002488 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002489 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002490 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2491 ReorderPreviewAnimation.MODE_HINT);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002492 result[0] = finalSolution.dragViewX;
2493 result[1] = finalSolution.dragViewY;
2494 resultSpan[0] = finalSolution.dragViewSpanX;
2495 resultSpan[1] = finalSolution.dragViewSpanY;
2496 } else {
2497 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2498 }
2499 return result;
2500 }
2501
Adam Cohen482ed822012-03-02 14:15:13 -08002502 boolean foundSolution = true;
2503 if (!DESTRUCTIVE_REORDER) {
2504 setUseTempCoords(true);
2505 }
2506
2507 if (finalSolution != null) {
2508 result[0] = finalSolution.dragViewX;
2509 result[1] = finalSolution.dragViewY;
2510 resultSpan[0] = finalSolution.dragViewSpanX;
2511 resultSpan[1] = finalSolution.dragViewSpanY;
2512
2513 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2514 // committing anything or animating anything as we just want to determine if a solution
2515 // exists
2516 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2517 if (!DESTRUCTIVE_REORDER) {
2518 copySolutionToTempState(finalSolution, dragView);
2519 }
2520 setItemPlacementDirty(true);
2521 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2522
Adam Cohen19f37922012-03-21 11:59:11 -07002523 if (!DESTRUCTIVE_REORDER &&
2524 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002525 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002526 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002527 setItemPlacementDirty(false);
2528 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002529 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2530 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002531 }
2532 }
2533 } else {
2534 foundSolution = false;
2535 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2536 }
2537
2538 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2539 setUseTempCoords(false);
2540 }
Adam Cohen482ed822012-03-02 14:15:13 -08002541
Michael Jurkaa52570f2012-03-20 03:18:20 -07002542 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002543 return result;
2544 }
2545
Adam Cohen19f37922012-03-21 11:59:11 -07002546 void setItemPlacementDirty(boolean dirty) {
2547 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002548 }
Adam Cohen19f37922012-03-21 11:59:11 -07002549 boolean isItemPlacementDirty() {
2550 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002551 }
2552
Adam Cohen091440a2015-03-18 14:16:05 -07002553 @Thunk class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002554 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002555 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2556 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002557 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002558 boolean isSolution = false;
2559 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2560
Adam Cohenf3900c22012-11-16 18:28:11 -08002561 void save() {
2562 // Copy current state into savedMap
2563 for (View v: map.keySet()) {
2564 map.get(v).copy(savedMap.get(v));
2565 }
2566 }
2567
2568 void restore() {
2569 // Restore current state from savedMap
2570 for (View v: savedMap.keySet()) {
2571 savedMap.get(v).copy(map.get(v));
2572 }
2573 }
2574
2575 void add(View v, CellAndSpan cs) {
2576 map.put(v, cs);
2577 savedMap.put(v, new CellAndSpan());
2578 sortedViews.add(v);
2579 }
2580
Adam Cohen482ed822012-03-02 14:15:13 -08002581 int area() {
2582 return dragViewSpanX * dragViewSpanY;
2583 }
Adam Cohen8baab352012-03-20 17:39:21 -07002584 }
2585
2586 private class CellAndSpan {
2587 int x, y;
2588 int spanX, spanY;
2589
Adam Cohenf3900c22012-11-16 18:28:11 -08002590 public CellAndSpan() {
2591 }
2592
2593 public void copy(CellAndSpan copy) {
2594 copy.x = x;
2595 copy.y = y;
2596 copy.spanX = spanX;
2597 copy.spanY = spanY;
2598 }
2599
Adam Cohen8baab352012-03-20 17:39:21 -07002600 public CellAndSpan(int x, int y, int spanX, int spanY) {
2601 this.x = x;
2602 this.y = y;
2603 this.spanX = spanX;
2604 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002605 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002606
2607 public String toString() {
2608 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2609 }
2610
Adam Cohen482ed822012-03-02 14:15:13 -08002611 }
2612
Adam Cohendf035382011-04-11 17:22:04 -07002613 /**
Adam Cohendf035382011-04-11 17:22:04 -07002614 * Find a starting cell position that will fit the given bounds nearest the requested
2615 * cell location. Uses Euclidean distance to score multiple vacant areas.
2616 *
2617 * @param pixelX The X location at which you want to search for a vacant area.
2618 * @param pixelY The Y location at which you want to search for a vacant area.
2619 * @param spanX Horizontal span of the object.
2620 * @param spanY Vertical span of the object.
2621 * @param ignoreView Considers space occupied by this view as unoccupied
2622 * @param result Previously returned value to possibly recycle.
2623 * @return The X, Y cell of a vacant area that can contain this object,
2624 * nearest the requested location.
2625 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002626 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2627 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002628 }
2629
Michael Jurka0280c3b2010-09-17 15:00:07 -07002630 boolean existsEmptyCell() {
2631 return findCellForSpan(null, 1, 1);
2632 }
2633
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002634 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002635 * Finds the upper-left coordinate of the first rectangle in the grid that can
2636 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2637 * then this method will only return coordinates for rectangles that contain the cell
2638 * (intersectX, intersectY)
2639 *
2640 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2641 * can be found.
2642 * @param spanX The horizontal span of the cell we want to find.
2643 * @param spanY The vertical span of the cell we want to find.
2644 *
2645 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002646 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002647 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Michael Jurka28750fb2010-09-24 17:43:49 -07002648 boolean foundCell = false;
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002649 final int endX = mCountX - (spanX - 1);
2650 final int endY = mCountY - (spanY - 1);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002651
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002652 for (int y = 0; y < endY && !foundCell; y++) {
2653 inner:
2654 for (int x = 0; x < endX; x++) {
2655 for (int i = 0; i < spanX; i++) {
2656 for (int j = 0; j < spanY; j++) {
2657 if (mOccupied[x + i][y + j]) {
2658 // small optimization: we can skip to after the column we just found
2659 // an occupied cell
2660 x += i;
2661 continue inner;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002662 }
2663 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002664 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002665 if (cellXY != null) {
2666 cellXY[0] = x;
2667 cellXY[1] = y;
2668 }
2669 foundCell = true;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002670 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002671 }
2672 }
2673
Michael Jurka28750fb2010-09-24 17:43:49 -07002674 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002675 }
2676
2677 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002678 * A drag event has begun over this layout.
2679 * It may have begun over this layout (in which case onDragChild is called first),
2680 * or it may have begun on another layout.
2681 */
2682 void onDragEnter() {
Winson Chungc07918d2011-07-01 15:35:26 -07002683 mDragging = true;
2684 }
2685
2686 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002687 * Called when drag has left this CellLayout or has been completed (successfully or not)
2688 */
2689 void onDragExit() {
Joe Onorato4be866d2010-10-10 11:26:02 -07002690 // This can actually be called when we aren't in a drag, e.g. when adding a new
2691 // item to this layout via the customize drawer.
2692 // Guard against that case.
2693 if (mDragging) {
2694 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002695 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002696
2697 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002698 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002699 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2700 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002701 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002702 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002703 }
2704
2705 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002706 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002707 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002708 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002709 *
2710 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002711 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002712 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002713 if (child != null) {
2714 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002715 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002716 child.requestLayout();
Tony Wickham1cdb6d02015-09-17 11:08:27 -07002717 markCellsAsOccupiedForView(child);
Romain Guyd94533d2009-08-17 10:01:15 -07002718 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002719 }
2720
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002721 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002722 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002723 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002724 * @param cellX X coordinate of upper left corner expressed as a cell position
2725 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002726 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002727 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002728 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002729 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002730 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002731 final int cellWidth = mCellWidth;
2732 final int cellHeight = mCellHeight;
2733 final int widthGap = mWidthGap;
2734 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002735
Winson Chung4b825dcd2011-06-19 12:41:22 -07002736 final int hStartPadding = getPaddingLeft();
2737 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002738
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002739 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2740 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2741
2742 int x = hStartPadding + cellX * (cellWidth + widthGap);
2743 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002744
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002745 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002746 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002747
Michael Jurka0280c3b2010-09-17 15:00:07 -07002748 private void clearOccupiedCells() {
2749 for (int x = 0; x < mCountX; x++) {
2750 for (int y = 0; y < mCountY; y++) {
2751 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002752 }
2753 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002754 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002755
Adam Cohend4844c32011-02-18 19:25:06 -08002756 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002757 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002758 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002759 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002760 }
2761
Adam Cohend4844c32011-02-18 19:25:06 -08002762 public void markCellsAsUnoccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002763 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002764 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002765 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002766 }
2767
Adam Cohen482ed822012-03-02 14:15:13 -08002768 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2769 boolean value) {
2770 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002771 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2772 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002773 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002774 }
2775 }
2776 }
2777
Adam Cohen2801caf2011-05-13 20:57:39 -07002778 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002779 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002780 (Math.max((mCountX - 1), 0) * mWidthGap);
2781 }
2782
2783 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002784 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002785 (Math.max((mCountY - 1), 0) * mHeightGap);
2786 }
2787
Michael Jurka66d72172011-04-12 16:29:25 -07002788 public boolean isOccupied(int x, int y) {
2789 if (x < mCountX && y < mCountY) {
2790 return mOccupied[x][y];
2791 } else {
2792 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2793 }
2794 }
2795
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002796 @Override
2797 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2798 return new CellLayout.LayoutParams(getContext(), attrs);
2799 }
2800
2801 @Override
2802 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2803 return p instanceof CellLayout.LayoutParams;
2804 }
2805
2806 @Override
2807 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2808 return new CellLayout.LayoutParams(p);
2809 }
2810
2811 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2812 /**
2813 * Horizontal location of the item in the grid.
2814 */
2815 @ViewDebug.ExportedProperty
2816 public int cellX;
2817
2818 /**
2819 * Vertical location of the item in the grid.
2820 */
2821 @ViewDebug.ExportedProperty
2822 public int cellY;
2823
2824 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002825 * Temporary horizontal location of the item in the grid during reorder
2826 */
2827 public int tmpCellX;
2828
2829 /**
2830 * Temporary vertical location of the item in the grid during reorder
2831 */
2832 public int tmpCellY;
2833
2834 /**
2835 * Indicates that the temporary coordinates should be used to layout the items
2836 */
2837 public boolean useTmpCoords;
2838
2839 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002840 * Number of cells spanned horizontally by the item.
2841 */
2842 @ViewDebug.ExportedProperty
2843 public int cellHSpan;
2844
2845 /**
2846 * Number of cells spanned vertically by the item.
2847 */
2848 @ViewDebug.ExportedProperty
2849 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002850
Adam Cohen1b607ed2011-03-03 17:26:50 -08002851 /**
2852 * Indicates whether the item will set its x, y, width and height parameters freely,
2853 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2854 */
Adam Cohend4844c32011-02-18 19:25:06 -08002855 public boolean isLockedToGrid = true;
2856
Adam Cohen482ed822012-03-02 14:15:13 -08002857 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07002858 * Indicates that this item should use the full extents of its parent.
2859 */
2860 public boolean isFullscreen = false;
2861
2862 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002863 * Indicates whether this item can be reordered. Always true except in the case of the
2864 * the AllApps button.
2865 */
2866 public boolean canReorder = true;
2867
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002868 // X coordinate of the view in the layout.
2869 @ViewDebug.ExportedProperty
2870 int x;
2871 // Y coordinate of the view in the layout.
2872 @ViewDebug.ExportedProperty
2873 int y;
2874
Romain Guy84f296c2009-11-04 15:00:44 -08002875 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002876
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002877 public LayoutParams(Context c, AttributeSet attrs) {
2878 super(c, attrs);
2879 cellHSpan = 1;
2880 cellVSpan = 1;
2881 }
2882
2883 public LayoutParams(ViewGroup.LayoutParams source) {
2884 super(source);
2885 cellHSpan = 1;
2886 cellVSpan = 1;
2887 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002888
2889 public LayoutParams(LayoutParams source) {
2890 super(source);
2891 this.cellX = source.cellX;
2892 this.cellY = source.cellY;
2893 this.cellHSpan = source.cellHSpan;
2894 this.cellVSpan = source.cellVSpan;
2895 }
2896
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002897 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002898 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002899 this.cellX = cellX;
2900 this.cellY = cellY;
2901 this.cellHSpan = cellHSpan;
2902 this.cellVSpan = cellVSpan;
2903 }
2904
Adam Cohen2374abf2013-04-16 14:56:57 -07002905 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
2906 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08002907 if (isLockedToGrid) {
2908 final int myCellHSpan = cellHSpan;
2909 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002910 int myCellX = useTmpCoords ? tmpCellX : cellX;
2911 int myCellY = useTmpCoords ? tmpCellY : cellY;
2912
2913 if (invertHorizontally) {
2914 myCellX = colCount - myCellX - cellHSpan;
2915 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002916
Adam Cohend4844c32011-02-18 19:25:06 -08002917 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2918 leftMargin - rightMargin;
2919 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2920 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002921 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2922 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002923 }
2924 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002925
Winson Chungaafa03c2010-06-11 17:34:16 -07002926 public String toString() {
2927 return "(" + this.cellX + ", " + this.cellY + ")";
2928 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002929
2930 public void setWidth(int width) {
2931 this.width = width;
2932 }
2933
2934 public int getWidth() {
2935 return width;
2936 }
2937
2938 public void setHeight(int height) {
2939 this.height = height;
2940 }
2941
2942 public int getHeight() {
2943 return height;
2944 }
2945
2946 public void setX(int x) {
2947 this.x = x;
2948 }
2949
2950 public int getX() {
2951 return x;
2952 }
2953
2954 public void setY(int y) {
2955 this.y = y;
2956 }
2957
2958 public int getY() {
2959 return y;
2960 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002961 }
2962
Michael Jurka0280c3b2010-09-17 15:00:07 -07002963 // This class stores info for two purposes:
2964 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2965 // its spanX, spanY, and the screen it is on
2966 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2967 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2968 // the CellLayout that was long clicked
Sunny Goyal83a8f042015-05-19 12:52:12 -07002969 public static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002970 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07002971 int cellX = -1;
2972 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002973 int spanX;
2974 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07002975 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07002976 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002977
Sunny Goyal83a8f042015-05-19 12:52:12 -07002978 public CellInfo(View v, ItemInfo info) {
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002979 cell = v;
2980 cellX = info.cellX;
2981 cellY = info.cellY;
2982 spanX = info.spanX;
2983 spanY = info.spanY;
2984 screenId = info.screenId;
2985 container = info.container;
2986 }
2987
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002988 @Override
2989 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002990 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2991 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002992 }
2993 }
Michael Jurkad771c962011-08-09 15:00:48 -07002994
Sunny Goyala9116722015-04-29 13:55:58 -07002995 public boolean findVacantCell(int spanX, int spanY, int[] outXY) {
2996 return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied);
2997 }
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002998
2999 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
3000 int x2 = x + spanX - 1;
3001 int y2 = y + spanY - 1;
3002 if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) {
3003 return false;
3004 }
3005 for (int i = x; i <= x2; i++) {
3006 for (int j = y; j <= y2; j++) {
3007 if (mOccupied[i][j]) {
3008 return false;
3009 }
3010 }
3011 }
3012
3013 return true;
3014 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003015}