blob: 6f7b6845372a440be4309c1694dd1c1051a6f000 [file] [log] [blame]
Daniel Sandler325dc232013-06-05 22:57:57 -04001package com.android.launcher3;
Adam Cohend4844c32011-02-18 19:25:06 -08002
Sunny Goyal55bdeed2018-09-18 16:17:22 -07003import static com.android.launcher3.LauncherAnimUtils.LAYOUT_HEIGHT;
4import static com.android.launcher3.LauncherAnimUtils.LAYOUT_WIDTH;
5import static com.android.launcher3.views.BaseDragLayer.LAYOUT_X;
6import static com.android.launcher3.views.BaseDragLayer.LAYOUT_Y;
7
Adam Cohend4844c32011-02-18 19:25:06 -08008import android.animation.AnimatorSet;
9import android.animation.ObjectAnimator;
10import android.animation.PropertyValuesHolder;
Adam Cohen0cf2a7c2011-11-08 15:07:01 -080011import android.appwidget.AppWidgetHostView;
Adam Cohend4844c32011-02-18 19:25:06 -080012import android.appwidget.AppWidgetProviderInfo;
13import android.content.Context;
Sunny Goyal756cd262015-08-20 12:33:21 -070014import android.graphics.Point;
Adam Cohen0cf2a7c2011-11-08 15:07:01 -080015import android.graphics.Rect;
Sunny Goyal08ca40f2016-09-13 21:07:31 -070016import android.util.AttributeSet;
Tony Wickham71255bb2016-02-10 16:18:15 -080017import android.view.KeyEvent;
Sunny Goyal6ad72f02016-09-23 11:01:10 -070018import android.view.MotionEvent;
Tony Wickham71255bb2016-02-10 16:18:15 -080019import android.view.View;
Sunny Goyal37920962017-09-28 13:43:24 -070020import android.view.ViewGroup;
Adam Cohend4844c32011-02-18 19:25:06 -080021
Sunny Goyale78e3d72015-09-24 11:23:31 -070022import com.android.launcher3.accessibility.DragViewStateAnnouncer;
Sunny Goyal08ca40f2016-09-13 21:07:31 -070023import com.android.launcher3.dragndrop.DragLayer;
Tony Wickham71255bb2016-02-10 16:18:15 -080024import com.android.launcher3.util.FocusLogic;
Sunny Goyal73b5a272019-12-09 14:55:56 -080025import com.android.launcher3.util.MainThreadInitializedObject;
Sunny Goyal29947f02017-12-18 13:49:44 -080026import com.android.launcher3.widget.LauncherAppWidgetHostView;
Sunny Goyale78e3d72015-09-24 11:23:31 -070027
Tony Wickhama5fb60a2019-04-03 14:05:08 -070028import java.util.ArrayList;
29import java.util.List;
30
Sunny Goyal37920962017-09-28 13:43:24 -070031public class AppWidgetResizeFrame extends AbstractFloatingView implements View.OnKeyListener {
Sunny Goyalba776d52015-05-18 20:52:57 -070032 private static final int SNAP_DURATION = 150;
33 private static final float DIMMED_HANDLE_ALPHA = 0f;
34 private static final float RESIZE_THRESHOLD = 0.66f;
35
Sunny Goyal756cd262015-08-20 12:33:21 -070036 private static final Rect sTmpRect = new Rect();
37
38 // Represents the cell size on the grid in the two orientations.
Sunny Goyal73b5a272019-12-09 14:55:56 -080039 private static final MainThreadInitializedObject<Point[]> CELL_SIZE =
40 new MainThreadInitializedObject<>(c -> {
41 InvariantDeviceProfile inv = LauncherAppState.getIDP(c);
42 return new Point[] {inv.landscapeProfile.getCellSize(),
43 inv.portraitProfile.getCellSize()};
44 });
Sunny Goyalba776d52015-05-18 20:52:57 -070045
Sunny Goyal08ca40f2016-09-13 21:07:31 -070046 private static final int HANDLE_COUNT = 4;
47 private static final int INDEX_LEFT = 0;
48 private static final int INDEX_TOP = 1;
49 private static final int INDEX_RIGHT = 2;
50 private static final int INDEX_BOTTOM = 3;
51
Sunny Goyalba776d52015-05-18 20:52:57 -070052 private final Launcher mLauncher;
Sunny Goyal08ca40f2016-09-13 21:07:31 -070053 private final DragViewStateAnnouncer mStateAnnouncer;
Sunny Goyalf0b6db72018-08-13 16:10:14 -070054 private final FirstFrameAnimatorHelper mFirstFrameAnimatorHelper;
Sunny Goyalba776d52015-05-18 20:52:57 -070055
Sunny Goyal08ca40f2016-09-13 21:07:31 -070056 private final View[] mDragHandles = new View[HANDLE_COUNT];
Tony Wickhama5fb60a2019-04-03 14:05:08 -070057 private final List<Rect> mSystemGestureExclusionRects = new ArrayList<>(HANDLE_COUNT);
Sunny Goyalba776d52015-05-18 20:52:57 -070058
Sunny Goyal08ca40f2016-09-13 21:07:31 -070059 private LauncherAppWidgetHostView mWidgetView;
60 private CellLayout mCellLayout;
61 private DragLayer mDragLayer;
62
63 private Rect mWidgetPadding;
Sunny Goyalba776d52015-05-18 20:52:57 -070064
65 private final int mBackgroundPadding;
66 private final int mTouchTargetWidth;
67
68 private final int[] mDirectionVector = new int[2];
69 private final int[] mLastDirectionVector = new int[2];
Adam Cohend4844c32011-02-18 19:25:06 -080070
Sunny Goyal08ca40f2016-09-13 21:07:31 -070071 private final IntRange mTempRange1 = new IntRange();
72 private final IntRange mTempRange2 = new IntRange();
73
74 private final IntRange mDeltaXRange = new IntRange();
75 private final IntRange mBaselineX = new IntRange();
76
77 private final IntRange mDeltaYRange = new IntRange();
78 private final IntRange mBaselineY = new IntRange();
Sunny Goyale78e3d72015-09-24 11:23:31 -070079
Adam Cohend4844c32011-02-18 19:25:06 -080080 private boolean mLeftBorderActive;
81 private boolean mRightBorderActive;
82 private boolean mTopBorderActive;
83 private boolean mBottomBorderActive;
84
Adam Cohend4844c32011-02-18 19:25:06 -080085 private int mResizeMode;
Adam Cohen3cba7222011-03-02 19:03:11 -080086
Adam Cohend4844c32011-02-18 19:25:06 -080087 private int mRunningHInc;
88 private int mRunningVInc;
89 private int mMinHSpan;
90 private int mMinVSpan;
91 private int mDeltaX;
92 private int mDeltaY;
Adam Cohenbebf0422012-04-11 18:06:28 -070093 private int mDeltaXAddOn;
94 private int mDeltaYAddOn;
Adam Cohen1b607ed2011-03-03 17:26:50 -080095
Adam Cohen4459d6b2012-07-13 15:59:15 -070096 private int mTopTouchRegionAdjustment = 0;
97 private int mBottomTouchRegionAdjustment = 0;
98
Sunny Goyal6ad72f02016-09-23 11:01:10 -070099 private int mXDown, mYDown;
100
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700101 public AppWidgetResizeFrame(Context context) {
102 this(context, null);
103 }
Adam Cohend4844c32011-02-18 19:25:06 -0800104
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700105 public AppWidgetResizeFrame(Context context, AttributeSet attrs) {
106 this(context, attrs, 0);
107 }
108
109 public AppWidgetResizeFrame(Context context, AttributeSet attrs, int defStyleAttr) {
110 super(context, attrs, defStyleAttr);
111
112 mLauncher = Launcher.getLauncher(context);
113 mStateAnnouncer = DragViewStateAnnouncer.createFor(this);
114
115 mBackgroundPadding = getResources()
116 .getDimensionPixelSize(R.dimen.resize_frame_background_padding);
117 mTouchTargetWidth = 2 * mBackgroundPadding;
Sunny Goyalf0b6db72018-08-13 16:10:14 -0700118 mFirstFrameAnimatorHelper = new FirstFrameAnimatorHelper(this);
Tony Wickhama5fb60a2019-04-03 14:05:08 -0700119
120 for (int i = 0; i < HANDLE_COUNT; i++) {
121 mSystemGestureExclusionRects.add(new Rect());
122 }
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700123 }
124
125 @Override
126 protected void onFinishInflate() {
127 super.onFinishInflate();
128
Sunny Goyal37920962017-09-28 13:43:24 -0700129 ViewGroup content = (ViewGroup) getChildAt(0);
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700130 for (int i = 0; i < HANDLE_COUNT; i ++) {
Sunny Goyal37920962017-09-28 13:43:24 -0700131 mDragHandles[i] = content.getChildAt(i);
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700132 }
133 }
134
Tony Wickhama5fb60a2019-04-03 14:05:08 -0700135 @Override
136 protected void onLayout(boolean changed, int l, int t, int r, int b) {
137 super.onLayout(changed, l, t, r, b);
138 if (Utilities.ATLEAST_Q) {
139 for (int i = 0; i < HANDLE_COUNT; i++) {
140 View dragHandle = mDragHandles[i];
141 mSystemGestureExclusionRects.get(i).set(dragHandle.getLeft(), dragHandle.getTop(),
142 dragHandle.getRight(), dragHandle.getBottom());
143 }
144 setSystemGestureExclusionRects(mSystemGestureExclusionRects);
145 }
146 }
147
Sunny Goyal37920962017-09-28 13:43:24 -0700148 public static void showForWidget(LauncherAppWidgetHostView widget, CellLayout cellLayout) {
149 Launcher launcher = Launcher.getLauncher(cellLayout.getContext());
150 AbstractFloatingView.closeAllOpenViews(launcher);
151
152 DragLayer dl = launcher.getDragLayer();
153 AppWidgetResizeFrame frame = (AppWidgetResizeFrame) launcher.getLayoutInflater()
154 .inflate(R.layout.app_widget_resize_frame, dl, false);
155 frame.setupForWidget(widget, cellLayout, dl);
156 ((DragLayer.LayoutParams) frame.getLayoutParams()).customPosition = true;
157
158 dl.addView(frame);
159 frame.mIsOpen = true;
160 frame.snapToWidget(false);
161 }
162
163 private void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout,
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700164 DragLayer dragLayer) {
Adam Cohend4844c32011-02-18 19:25:06 -0800165 mCellLayout = cellLayout;
166 mWidgetView = widgetView;
Adam Cohen59400422014-03-05 18:07:04 -0800167 LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo)
168 widgetView.getAppWidgetInfo();
169 mResizeMode = info.resizeMode;
Adam Cohen67882692011-03-11 15:29:03 -0800170 mDragLayer = dragLayer;
Adam Cohen3cba7222011-03-02 19:03:11 -0800171
Sunny Goyal233ee962015-08-03 13:05:01 -0700172 mMinHSpan = info.minSpanX;
173 mMinVSpan = info.minSpanY;
Adam Cohend4844c32011-02-18 19:25:06 -0800174
Sunny Goyal952e63d2017-08-16 04:59:08 -0700175 mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(getContext(),
176 widgetView.getAppWidgetInfo().provider, null);
Adam Cohen59400422014-03-05 18:07:04 -0800177
Adam Cohend4844c32011-02-18 19:25:06 -0800178 if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700179 mDragHandles[INDEX_TOP].setVisibility(GONE);
180 mDragHandles[INDEX_BOTTOM].setVisibility(GONE);
Adam Cohend4844c32011-02-18 19:25:06 -0800181 } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700182 mDragHandles[INDEX_LEFT].setVisibility(GONE);
183 mDragHandles[INDEX_RIGHT].setVisibility(GONE);
Adam Cohen3cba7222011-03-02 19:03:11 -0800184 }
185
Adam Cohenbebf0422012-04-11 18:06:28 -0700186 // When we create the resize frame, we first mark all cells as unoccupied. The appropriate
187 // cells (same if not resized, or different) will be marked as occupied when the resize
188 // frame is dismissed.
189 mCellLayout.markCellsAsUnoccupiedForView(mWidgetView);
Tony Wickham71255bb2016-02-10 16:18:15 -0800190
191 setOnKeyListener(this);
Adam Cohend4844c32011-02-18 19:25:06 -0800192 }
193
194 public boolean beginResizeIfPointInRegion(int x, int y) {
195 boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
196 boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
Adam Cohen4459d6b2012-07-13 15:59:15 -0700197
Adam Cohen3cba7222011-03-02 19:03:11 -0800198 mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive;
199 mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive;
Adam Cohen4459d6b2012-07-13 15:59:15 -0700200 mTopBorderActive = (y < mTouchTargetWidth + mTopTouchRegionAdjustment) && verticalActive;
201 mBottomBorderActive = (y > getHeight() - mTouchTargetWidth + mBottomTouchRegionAdjustment)
202 && verticalActive;
Adam Cohend4844c32011-02-18 19:25:06 -0800203
204 boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
205 || mTopBorderActive || mBottomBorderActive;
206
Adam Cohend4844c32011-02-18 19:25:06 -0800207 if (anyBordersActive) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700208 mDragHandles[INDEX_LEFT].setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
209 mDragHandles[INDEX_RIGHT].setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
210 mDragHandles[INDEX_TOP].setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
211 mDragHandles[INDEX_BOTTOM].setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
Adam Cohend4844c32011-02-18 19:25:06 -0800212 }
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700213
214 if (mLeftBorderActive) {
215 mDeltaXRange.set(-getLeft(), getWidth() - 2 * mTouchTargetWidth);
216 } else if (mRightBorderActive) {
217 mDeltaXRange.set(2 * mTouchTargetWidth - getWidth(), mDragLayer.getWidth() - getRight());
218 } else {
219 mDeltaXRange.set(0, 0);
220 }
221 mBaselineX.set(getLeft(), getRight());
222
223 if (mTopBorderActive) {
224 mDeltaYRange.set(-getTop(), getHeight() - 2 * mTouchTargetWidth);
225 } else if (mBottomBorderActive) {
226 mDeltaYRange.set(2 * mTouchTargetWidth - getHeight(), mDragLayer.getHeight() - getBottom());
227 } else {
228 mDeltaYRange.set(0, 0);
229 }
230 mBaselineY.set(getTop(), getBottom());
231
Adam Cohend4844c32011-02-18 19:25:06 -0800232 return anyBordersActive;
233 }
234
Adam Cohen1b607ed2011-03-03 17:26:50 -0800235 /**
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700236 * Based on the deltas, we resize the frame.
Adam Cohen1b607ed2011-03-03 17:26:50 -0800237 */
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700238 public void visualizeResizeForDelta(int deltaX, int deltaY) {
239 mDeltaX = mDeltaXRange.clamp(deltaX);
240 mDeltaY = mDeltaYRange.clamp(deltaY);
Adam Cohend4844c32011-02-18 19:25:06 -0800241
Adam Cohen67882692011-03-11 15:29:03 -0800242 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700243 mDeltaX = mDeltaXRange.clamp(deltaX);
244 mBaselineX.applyDelta(mLeftBorderActive, mRightBorderActive, mDeltaX, mTempRange1);
245 lp.x = mTempRange1.start;
246 lp.width = mTempRange1.size();
Adam Cohen67882692011-03-11 15:29:03 -0800247
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700248 mDeltaY = mDeltaYRange.clamp(deltaY);
249 mBaselineY.applyDelta(mTopBorderActive, mBottomBorderActive, mDeltaY, mTempRange1);
250 lp.y = mTempRange1.start;
251 lp.height = mTempRange1.size();
Adam Cohend4844c32011-02-18 19:25:06 -0800252
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700253 resizeWidgetIfNeeded(false);
Jon Miranda097a7252016-12-28 10:49:55 -0800254
255 // When the widget resizes in multi-window mode, the translation value changes to maintain
256 // a center fit. These overrides ensure the resize frame always aligns with the widget view.
257 getSnappedRectRelativeToDragLayer(sTmpRect);
258 if (mLeftBorderActive) {
259 lp.width = sTmpRect.width() + sTmpRect.left - lp.x;
260 }
261 if (mTopBorderActive) {
262 lp.height = sTmpRect.height() + sTmpRect.top - lp.y;
263 }
264 if (mRightBorderActive) {
265 lp.x = sTmpRect.left;
266 }
267 if (mBottomBorderActive) {
268 lp.y = sTmpRect.top;
269 }
270
Adam Cohend4844c32011-02-18 19:25:06 -0800271 requestLayout();
272 }
273
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700274 private static int getSpanIncrement(float deltaFrac) {
275 return Math.abs(deltaFrac) > RESIZE_THRESHOLD ? Math.round(deltaFrac) : 0;
276 }
277
Adam Cohen1b607ed2011-03-03 17:26:50 -0800278 /**
279 * Based on the current deltas, we determine if and how to resize the widget.
280 */
Adam Cohenbebf0422012-04-11 18:06:28 -0700281 private void resizeWidgetIfNeeded(boolean onDismiss) {
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530282 float xThreshold = mCellLayout.getCellWidth();
283 float yThreshold = mCellLayout.getCellHeight();
Adam Cohend4844c32011-02-18 19:25:06 -0800284
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700285 int hSpanInc = getSpanIncrement((mDeltaX + mDeltaXAddOn) / xThreshold - mRunningHInc);
286 int vSpanInc = getSpanIncrement((mDeltaY + mDeltaYAddOn) / yThreshold - mRunningVInc);
Adam Cohene4b77292011-03-08 18:35:52 -0800287
Adam Cohenbebf0422012-04-11 18:06:28 -0700288 if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return;
Adam Cohend4844c32011-02-18 19:25:06 -0800289
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700290 mDirectionVector[0] = 0;
291 mDirectionVector[1] = 0;
Adam Cohend4844c32011-02-18 19:25:06 -0800292
293 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
Adam Cohen1b607ed2011-03-03 17:26:50 -0800294
Adam Cohenbebf0422012-04-11 18:06:28 -0700295 int spanX = lp.cellHSpan;
296 int spanY = lp.cellVSpan;
297 int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX;
298 int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY;
299
Adam Cohen1b607ed2011-03-03 17:26:50 -0800300 // For each border, we bound the resizing based on the minimum width, and the maximum
301 // expandability.
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700302 mTempRange1.set(cellX, spanX + cellX);
303 int hSpanDelta = mTempRange1.applyDeltaAndBound(mLeftBorderActive, mRightBorderActive,
304 hSpanInc, mMinHSpan, mCellLayout.getCountX(), mTempRange2);
305 cellX = mTempRange2.start;
306 spanX = mTempRange2.size();
307 if (hSpanDelta != 0) {
308 mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
Adam Cohend4844c32011-02-18 19:25:06 -0800309 }
310
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700311 mTempRange1.set(cellY, spanY + cellY);
312 int vSpanDelta = mTempRange1.applyDeltaAndBound(mTopBorderActive, mBottomBorderActive,
313 vSpanInc, mMinVSpan, mCellLayout.getCountY(), mTempRange2);
314 cellY = mTempRange2.start;
315 spanY = mTempRange2.size();
316 if (vSpanDelta != 0) {
317 mDirectionVector[1] = mTopBorderActive ? -1 : 1;
Adam Cohend4844c32011-02-18 19:25:06 -0800318 }
319
Adam Cohenbebf0422012-04-11 18:06:28 -0700320 if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return;
Adam Cohend4844c32011-02-18 19:25:06 -0800321
Adam Cohene0489502012-08-27 15:18:53 -0700322 // We always want the final commit to match the feedback, so we make sure to use the
323 // last used direction vector when committing the resize / reorder.
324 if (onDismiss) {
325 mDirectionVector[0] = mLastDirectionVector[0];
326 mDirectionVector[1] = mLastDirectionVector[1];
327 } else {
328 mLastDirectionVector[0] = mDirectionVector[0];
329 mLastDirectionVector[1] = mDirectionVector[1];
330 }
331
Adam Cohenbebf0422012-04-11 18:06:28 -0700332 if (mCellLayout.createAreaForResize(cellX, cellY, spanX, spanY, mWidgetView,
333 mDirectionVector, onDismiss)) {
Sunny Goyale78e3d72015-09-24 11:23:31 -0700334 if (mStateAnnouncer != null && (lp.cellHSpan != spanX || lp.cellVSpan != spanY) ) {
335 mStateAnnouncer.announce(
336 mLauncher.getString(R.string.widget_resized, spanX, spanY));
337 }
338
Adam Cohenbebf0422012-04-11 18:06:28 -0700339 lp.tmpCellX = cellX;
340 lp.tmpCellY = cellY;
341 lp.cellHSpan = spanX;
342 lp.cellVSpan = spanY;
343 mRunningVInc += vSpanDelta;
344 mRunningHInc += hSpanDelta;
Sunny Goyale78e3d72015-09-24 11:23:31 -0700345
Adam Cohena897f392012-04-27 18:12:05 -0700346 if (!onDismiss) {
347 updateWidgetSizeRanges(mWidgetView, mLauncher, spanX, spanY);
348 }
Adam Cohenbebf0422012-04-11 18:06:28 -0700349 }
Adam Cohen67882692011-03-11 15:29:03 -0800350 mWidgetView.requestLayout();
Adam Cohend4844c32011-02-18 19:25:06 -0800351 }
352
Sunny Goyale396abf2020-04-06 15:11:17 -0700353 public static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher,
Adam Cohena897f392012-04-27 18:12:05 -0700354 int spanX, int spanY) {
Sunny Goyalba776d52015-05-18 20:52:57 -0700355 getWidgetSizeRanges(launcher, spanX, spanY, sTmpRect);
356 widgetView.updateAppWidgetSize(null, sTmpRect.left, sTmpRect.top,
357 sTmpRect.right, sTmpRect.bottom);
Adam Cohen9e05a5e2012-09-10 15:53:09 -0700358 }
359
Sunny Goyal16466f12016-03-10 05:34:30 -0800360 public static Rect getWidgetSizeRanges(Context context, int spanX, int spanY, Rect rect) {
Adam Cohen9e05a5e2012-09-10 15:53:09 -0700361 if (rect == null) {
362 rect = new Rect();
363 }
Sunny Goyal16466f12016-03-10 05:34:30 -0800364 final float density = context.getResources().getDisplayMetrics().density;
Sunny Goyal73b5a272019-12-09 14:55:56 -0800365 final Point[] cellSize = CELL_SIZE.get(context);
Adam Cohena897f392012-04-27 18:12:05 -0700366
367 // Compute landscape size
Sunny Goyal73b5a272019-12-09 14:55:56 -0800368 int landWidth = (int) ((spanX * cellSize[0].x) / density);
369 int landHeight = (int) ((spanY * cellSize[0].y) / density);
Adam Cohena897f392012-04-27 18:12:05 -0700370
371 // Compute portrait size
Sunny Goyal73b5a272019-12-09 14:55:56 -0800372 int portWidth = (int) ((spanX * cellSize[1].x) / density);
373 int portHeight = (int) ((spanY * cellSize[1].y) / density);
Adam Cohen9e05a5e2012-09-10 15:53:09 -0700374 rect.set(portWidth, landHeight, landWidth, portHeight);
375 return rect;
Adam Cohena897f392012-04-27 18:12:05 -0700376 }
377
Sunny Goyal988ad272017-08-18 02:05:01 -0700378 @Override
379 protected void onDetachedFromWindow() {
380 super.onDetachedFromWindow();
381
382 // We are done with resizing the widget. Save the widget size & position to LauncherModel
Adam Cohenbebf0422012-04-11 18:06:28 -0700383 resizeWidgetIfNeeded(true);
Adam Cohenbebf0422012-04-11 18:06:28 -0700384 }
Adam Cohend4844c32011-02-18 19:25:06 -0800385
Sunny Goyal6ad72f02016-09-23 11:01:10 -0700386 private void onTouchUp() {
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530387 int xThreshold = mCellLayout.getCellWidth();
388 int yThreshold = mCellLayout.getCellHeight();
Adam Cohend4844c32011-02-18 19:25:06 -0800389
Jon Miranda097a7252016-12-28 10:49:55 -0800390 mDeltaXAddOn = mRunningHInc * xThreshold;
391 mDeltaYAddOn = mRunningVInc * yThreshold;
Adam Cohenbebf0422012-04-11 18:06:28 -0700392 mDeltaX = 0;
393 mDeltaY = 0;
394
Sunny Goyalf0b6db72018-08-13 16:10:14 -0700395 post(() -> snapToWidget(true));
Adam Cohend4844c32011-02-18 19:25:06 -0800396 }
397
Jon Miranda097a7252016-12-28 10:49:55 -0800398 /**
399 * Returns the rect of this view when the frame is snapped around the widget, with the bounds
400 * relative to the {@link DragLayer}.
401 */
402 private void getSnappedRectRelativeToDragLayer(Rect out) {
Jon Miranda21266912016-12-19 14:12:05 -0800403 float scale = mWidgetView.getScaleToFit();
Adam Cohen37b59ff2011-06-13 17:13:42 -0700404
Jon Miranda097a7252016-12-28 10:49:55 -0800405 mDragLayer.getViewRectRelativeToSelf(mWidgetView, out);
Adam Cohend6e7aa32013-07-09 15:32:37 -0700406
Jon Miranda097a7252016-12-28 10:49:55 -0800407 int width = 2 * mBackgroundPadding
408 + (int) (scale * (out.width() - mWidgetPadding.left - mWidgetPadding.right));
409 int height = 2 * mBackgroundPadding
410 + (int) (scale * (out.height() - mWidgetPadding.top - mWidgetPadding.bottom));
Jon Miranda26e4d172016-12-12 15:43:18 -0800411
Jon Miranda097a7252016-12-28 10:49:55 -0800412 int x = (int) (out.left - mBackgroundPadding + scale * mWidgetPadding.left);
413 int y = (int) (out.top - mBackgroundPadding + scale * mWidgetPadding.top);
414
415 out.left = x;
416 out.top = y;
417 out.right = out.left + width;
418 out.bottom = out.top + height;
419 }
420
Sunny Goyal37920962017-09-28 13:43:24 -0700421 private void snapToWidget(boolean animate) {
Jon Miranda097a7252016-12-28 10:49:55 -0800422 getSnappedRectRelativeToDragLayer(sTmpRect);
423 int newWidth = sTmpRect.width();
424 int newHeight = sTmpRect.height();
425 int newX = sTmpRect.left;
426 int newY = sTmpRect.top;
Adam Cohen3cba7222011-03-02 19:03:11 -0800427
Sunny Goyalba776d52015-05-18 20:52:57 -0700428 // We need to make sure the frame's touchable regions lie fully within the bounds of the
Adam Cohen4459d6b2012-07-13 15:59:15 -0700429 // DragLayer. We allow the actual handles to be clipped, but we shift the touch regions
430 // down accordingly to provide a proper touch target.
Adam Cohen3cba7222011-03-02 19:03:11 -0800431 if (newY < 0) {
Adam Cohen4459d6b2012-07-13 15:59:15 -0700432 // In this case we shift the touch region down to start at the top of the DragLayer
433 mTopTouchRegionAdjustment = -newY;
434 } else {
435 mTopTouchRegionAdjustment = 0;
Adam Cohen3cba7222011-03-02 19:03:11 -0800436 }
Adam Cohen67882692011-03-11 15:29:03 -0800437 if (newY + newHeight > mDragLayer.getHeight()) {
Adam Cohen4459d6b2012-07-13 15:59:15 -0700438 // In this case we shift the touch region up to end at the bottom of the DragLayer
439 mBottomTouchRegionAdjustment = -(newY + newHeight - mDragLayer.getHeight());
440 } else {
441 mBottomTouchRegionAdjustment = 0;
Adam Cohen3cba7222011-03-02 19:03:11 -0800442 }
443
Jon Miranda26e4d172016-12-12 15:43:18 -0800444 final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
Adam Cohend4844c32011-02-18 19:25:06 -0800445 if (!animate) {
446 lp.width = newWidth;
447 lp.height = newHeight;
448 lp.x = newX;
449 lp.y = newY;
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700450 for (int i = 0; i < HANDLE_COUNT; i++) {
451 mDragHandles[i].setAlpha(1.0f);
452 }
Adam Cohend4844c32011-02-18 19:25:06 -0800453 requestLayout();
454 } else {
Sunny Goyalf0b6db72018-08-13 16:10:14 -0700455 ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp,
Sunny Goyal55bdeed2018-09-18 16:17:22 -0700456 PropertyValuesHolder.ofInt(LAYOUT_WIDTH, lp.width, newWidth),
457 PropertyValuesHolder.ofInt(LAYOUT_HEIGHT, lp.height, newHeight),
458 PropertyValuesHolder.ofInt(LAYOUT_X, lp.x, newX),
459 PropertyValuesHolder.ofInt(LAYOUT_Y, lp.y, newY));
Sunny Goyalf0b6db72018-08-13 16:10:14 -0700460 mFirstFrameAnimatorHelper.addTo(oa).addUpdateListener(a -> requestLayout());
Sunny Goyal849c6a22018-08-08 16:33:46 -0700461
462 AnimatorSet set = new AnimatorSet();
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700463 set.play(oa);
464 for (int i = 0; i < HANDLE_COUNT; i++) {
Sunny Goyalf0b6db72018-08-13 16:10:14 -0700465 set.play(mFirstFrameAnimatorHelper.addTo(
466 ObjectAnimator.ofFloat(mDragHandles[i], ALPHA, 1f)));
Adam Cohend4844c32011-02-18 19:25:06 -0800467 }
Adam Cohend4844c32011-02-18 19:25:06 -0800468 set.setDuration(SNAP_DURATION);
469 set.start();
470 }
Tony Wickham71255bb2016-02-10 16:18:15 -0800471
472 setFocusableInTouchMode(true);
473 requestFocus();
474 }
475
476 @Override
477 public boolean onKey(View v, int keyCode, KeyEvent event) {
478 // Clear the frame and give focus to the widget host view when a directional key is pressed.
479 if (FocusLogic.shouldConsume(keyCode)) {
Sunny Goyal37920962017-09-28 13:43:24 -0700480 close(false);
Tony Wickham71255bb2016-02-10 16:18:15 -0800481 mWidgetView.requestFocus();
482 return true;
483 }
484 return false;
Adam Cohend4844c32011-02-18 19:25:06 -0800485 }
Sunny Goyal6ad72f02016-09-23 11:01:10 -0700486
487 private boolean handleTouchDown(MotionEvent ev) {
488 Rect hitRect = new Rect();
489 int x = (int) ev.getX();
490 int y = (int) ev.getY();
491
492 getHitRect(hitRect);
493 if (hitRect.contains(x, y)) {
494 if (beginResizeIfPointInRegion(x - getLeft(), y - getTop())) {
495 mXDown = x;
496 mYDown = y;
497 return true;
498 }
499 }
500 return false;
501 }
502
503 @Override
504 public boolean onControllerTouchEvent(MotionEvent ev) {
505 int action = ev.getAction();
506 int x = (int) ev.getX();
507 int y = (int) ev.getY();
508
509 switch (action) {
510 case MotionEvent.ACTION_DOWN:
511 return handleTouchDown(ev);
512 case MotionEvent.ACTION_MOVE:
513 visualizeResizeForDelta(x - mXDown, y - mYDown);
514 break;
515 case MotionEvent.ACTION_CANCEL:
516 case MotionEvent.ACTION_UP:
517 visualizeResizeForDelta(x - mXDown, y - mYDown);
518 onTouchUp();
519 mXDown = mYDown = 0;
520 break;
521 }
522 return true;
523 }
524
525 @Override
526 public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
527 if (ev.getAction() == MotionEvent.ACTION_DOWN && handleTouchDown(ev)) {
528 return true;
529 }
Sunny Goyal37920962017-09-28 13:43:24 -0700530 close(false);
Sunny Goyal6ad72f02016-09-23 11:01:10 -0700531 return false;
532 }
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700533
Sunny Goyal37920962017-09-28 13:43:24 -0700534 @Override
535 protected void handleClose(boolean animate) {
536 mDragLayer.removeView(this);
537 }
538
539 @Override
540 public void logActionCommand(int command) {
541 // TODO: Log this case.
542 }
543
544 @Override
545 protected boolean isOfType(int type) {
546 return (type & TYPE_WIDGET_RESIZE_FRAME) != 0;
547 }
548
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700549 /**
550 * A mutable class for describing the range of two int values.
551 */
552 private static class IntRange {
553
554 public int start, end;
555
556 public int clamp(int value) {
557 return Utilities.boundToRange(value, start, end);
558 }
559
560 public void set(int s, int e) {
561 start = s;
562 end = e;
563 }
564
565 public int size() {
566 return end - start;
567 }
568
569 /**
570 * Moves either the start or end edge (but never both) by {@param delta} and sets the
571 * result in {@param out}
572 */
573 public void applyDelta(boolean moveStart, boolean moveEnd, int delta, IntRange out) {
574 out.start = moveStart ? start + delta : start;
575 out.end = moveEnd ? end + delta : end;
576 }
577
578 /**
579 * Applies delta similar to {@link #applyDelta(boolean, boolean, int, IntRange)},
580 * with extra conditions.
581 * @param minSize minimum size after with the moving edge should not be shifted any further.
582 * For eg, if delta = -3 when moving the endEdge brings the size to less than
583 * minSize, only delta = -2 will applied
584 * @param maxEnd The maximum value to the end edge (start edge is always restricted to 0)
585 * @return the amount of increase when endEdge was moves and the amount of decrease when
586 * the start edge was moved.
587 */
588 public int applyDeltaAndBound(boolean moveStart, boolean moveEnd, int delta,
589 int minSize, int maxEnd, IntRange out) {
590 applyDelta(moveStart, moveEnd, delta, out);
Jon Miranda9f33cb22017-01-03 09:30:00 -0800591 if (out.start < 0) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700592 out.start = 0;
593 }
Jon Miranda9f33cb22017-01-03 09:30:00 -0800594 if (out.end > maxEnd) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700595 out.end = maxEnd;
596 }
597 if (out.size() < minSize) {
598 if (moveStart) {
599 out.start = out.end - minSize;
600 } else if (moveEnd) {
601 out.end = out.start + minSize;
602 }
603 }
604 return moveEnd ? out.size() - size() : size() - out.size();
605 }
606 }
Adam Cohend4844c32011-02-18 19:25:06 -0800607}