blob: 5f0bf562df4ec1557725a0e43d28281e9df5bb17 [file] [log] [blame]
Ben Lin04c83f62019-12-20 10:56:45 -08001/*
2 * Copyright (C) 2020 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 */
16package com.android.systemui.pip.phone;
17
18import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_USER_RESIZE;
19import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
20import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
21import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
22import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
23import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
24
25import android.content.Context;
26import android.content.res.Resources;
27import android.graphics.Point;
28import android.graphics.PointF;
29import android.graphics.Rect;
30import android.graphics.Region;
31import android.hardware.input.InputManager;
Ben Lin4a8e2572020-05-08 15:55:15 -070032import android.os.Handler;
Ben Lin04c83f62019-12-20 10:56:45 -080033import android.os.Looper;
34import android.provider.DeviceConfig;
35import android.util.DisplayMetrics;
36import android.view.InputChannel;
37import android.view.InputEvent;
38import android.view.InputEventReceiver;
39import android.view.InputMonitor;
40import android.view.MotionEvent;
41
42import com.android.internal.policy.TaskResizingAlgorithm;
43import com.android.systemui.R;
44import com.android.systemui.pip.PipBoundsHandler;
Ben Lin7d6b8e72020-02-27 17:48:16 -080045import com.android.systemui.pip.PipTaskOrganizer;
Beverly660d0a72020-02-26 12:32:42 -050046import com.android.systemui.util.DeviceConfigProxy;
Ben Lin04c83f62019-12-20 10:56:45 -080047
48import java.util.concurrent.Executor;
Ben Lin4a8e2572020-05-08 15:55:15 -070049import java.util.function.Supplier;
Ben Lin04c83f62019-12-20 10:56:45 -080050
51/**
52 * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
53 * trigger dynamic resize.
54 */
55public class PipResizeGestureHandler {
56
57 private static final String TAG = "PipResizeGestureHandler";
58
59 private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
60 private final PipBoundsHandler mPipBoundsHandler;
Ben Lin04c83f62019-12-20 10:56:45 -080061 private final PipMotionHelper mMotionHelper;
62 private final int mDisplayId;
63 private final Executor mMainExecutor;
64 private final Region mTmpRegion = new Region();
65
66 private final PointF mDownPoint = new PointF();
67 private final Point mMaxSize = new Point();
68 private final Point mMinSize = new Point();
Ben Lin094c7752020-03-11 12:10:51 -070069 private final Rect mLastResizeBounds = new Rect();
Ben Lin75ba9c32020-03-19 17:55:12 -070070 private final Rect mLastDownBounds = new Rect();
Ben Lin4a8e2572020-05-08 15:55:15 -070071 private final Rect mDragCornerSize = new Rect();
72 private final Rect mTmpTopLeftCorner = new Rect();
73 private final Rect mTmpTopRightCorner = new Rect();
74 private final Rect mTmpBottomLeftCorner = new Rect();
75 private final Rect mTmpBottomRightCorner = new Rect();
76 private final Rect mDisplayBounds = new Rect();
Ben Lin04c83f62019-12-20 10:56:45 -080077 private final int mDelta;
Ben Lin4a8e2572020-05-08 15:55:15 -070078 private final Supplier<Rect> mMovementBoundsSupplier;
79 private final Runnable mUpdateMovementBoundsRunnable;
Ben Lin04c83f62019-12-20 10:56:45 -080080
Hongwei Wang081ecb32020-04-13 16:49:03 -070081 private boolean mAllowGesture;
Ben Lin04c83f62019-12-20 10:56:45 -080082 private boolean mIsAttached;
83 private boolean mIsEnabled;
Hongwei Wang081ecb32020-04-13 16:49:03 -070084 private boolean mEnableUserResize;
Ben Lin04c83f62019-12-20 10:56:45 -080085
86 private InputMonitor mInputMonitor;
87 private InputEventReceiver mInputEventReceiver;
Ben Lin7d6b8e72020-02-27 17:48:16 -080088 private PipTaskOrganizer mPipTaskOrganizer;
Ben Lin04c83f62019-12-20 10:56:45 -080089
90 private int mCtrlType;
91
92 public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler,
Hongwei Wang081ecb32020-04-13 16:49:03 -070093 PipMotionHelper motionHelper, DeviceConfigProxy deviceConfig,
Ben Lin4a8e2572020-05-08 15:55:15 -070094 PipTaskOrganizer pipTaskOrganizer, Supplier<Rect> movementBoundsSupplier,
95 Runnable updateMovementBoundsRunnable) {
Ben Lin04c83f62019-12-20 10:56:45 -080096 final Resources res = context.getResources();
97 context.getDisplay().getMetrics(mDisplayMetrics);
98 mDisplayId = context.getDisplayId();
99 mMainExecutor = context.getMainExecutor();
100 mPipBoundsHandler = pipBoundsHandler;
Ben Lin04c83f62019-12-20 10:56:45 -0800101 mMotionHelper = motionHelper;
Ben Lin7d6b8e72020-02-27 17:48:16 -0800102 mPipTaskOrganizer = pipTaskOrganizer;
Ben Lin4a8e2572020-05-08 15:55:15 -0700103 mMovementBoundsSupplier = movementBoundsSupplier;
104 mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
Ben Lin04c83f62019-12-20 10:56:45 -0800105
106 context.getDisplay().getRealSize(mMaxSize);
107 mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size);
108
Hongwei Wang081ecb32020-04-13 16:49:03 -0700109 mEnableUserResize = DeviceConfig.getBoolean(
Ben Lin04c83f62019-12-20 10:56:45 -0800110 DeviceConfig.NAMESPACE_SYSTEMUI,
111 PIP_USER_RESIZE,
Ben Lin9ee8b132020-03-19 15:35:21 -0700112 /* defaultValue = */ true);
Beverly660d0a72020-02-26 12:32:42 -0500113 deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mMainExecutor,
Ben Lin04c83f62019-12-20 10:56:45 -0800114 new DeviceConfig.OnPropertiesChangedListener() {
115 @Override
116 public void onPropertiesChanged(DeviceConfig.Properties properties) {
117 if (properties.getKeyset().contains(PIP_USER_RESIZE)) {
Hongwei Wang081ecb32020-04-13 16:49:03 -0700118 mEnableUserResize = properties.getBoolean(
Ben Lin9ee8b132020-03-19 15:35:21 -0700119 PIP_USER_RESIZE, /* defaultValue = */ true);
Ben Lin04c83f62019-12-20 10:56:45 -0800120 }
121 }
122 });
123 }
124
Ben Lin4a8e2572020-05-08 15:55:15 -0700125 private void resetDragCorners() {
126 mDragCornerSize.set(0, 0, mDelta, mDelta);
127 mTmpTopLeftCorner.set(mDragCornerSize);
128 mTmpTopRightCorner.set(mDragCornerSize);
129 mTmpBottomLeftCorner.set(mDragCornerSize);
130 mTmpBottomRightCorner.set(mDragCornerSize);
131 }
132
Ben Lin04c83f62019-12-20 10:56:45 -0800133 private void disposeInputChannel() {
134 if (mInputEventReceiver != null) {
135 mInputEventReceiver.dispose();
136 mInputEventReceiver = null;
137 }
138 if (mInputMonitor != null) {
139 mInputMonitor.dispose();
140 mInputMonitor = null;
141 }
142 }
143
144 void onActivityPinned() {
145 mIsAttached = true;
146 updateIsEnabled();
147 }
148
149 void onActivityUnpinned() {
150 mIsAttached = false;
151 updateIsEnabled();
152 }
153
154 private void updateIsEnabled() {
Hongwei Wang081ecb32020-04-13 16:49:03 -0700155 boolean isEnabled = mIsAttached && mEnableUserResize;
Ben Lin04c83f62019-12-20 10:56:45 -0800156 if (isEnabled == mIsEnabled) {
157 return;
158 }
159 mIsEnabled = isEnabled;
160 disposeInputChannel();
161
162 if (mIsEnabled) {
163 // Register input event receiver
164 mInputMonitor = InputManager.getInstance().monitorGestureInput(
165 "pip-resize", mDisplayId);
166 mInputEventReceiver = new SysUiInputEventReceiver(
167 mInputMonitor.getInputChannel(), Looper.getMainLooper());
168 }
169 }
170
171 private void onInputEvent(InputEvent ev) {
172 if (ev instanceof MotionEvent) {
173 onMotionEvent((MotionEvent) ev);
174 }
175 }
176
Ben Lin7f12e212020-05-11 16:54:13 -0700177 /**
178 * Check whether the current x,y coordinate is within the region in which drag-resize should
179 * start.
180 * This consists of 4 small squares on the 4 corners of the PIP window, a quarter of which
181 * overlaps with the PIP window while the rest goes outside of the PIP window.
182 * _ _ _ _
183 * |_|_|_________|_|_|
184 * |_|_| |_|_|
185 * | PIP |
186 * | WINDOW |
187 * _|_ _|_
188 * |_|_|_________|_|_|
189 * |_|_| |_|_|
190 */
191 public boolean isWithinTouchRegion(int x, int y) {
Ben Lin04c83f62019-12-20 10:56:45 -0800192 final Rect currentPipBounds = mMotionHelper.getBounds();
193 if (currentPipBounds == null) {
194 return false;
195 }
Ben Lin4a8e2572020-05-08 15:55:15 -0700196 resetDragCorners();
197 mTmpTopLeftCorner.offset(currentPipBounds.left - mDelta / 2,
198 currentPipBounds.top - mDelta / 2);
199 mTmpTopRightCorner.offset(currentPipBounds.right - mDelta / 2,
200 currentPipBounds.top - mDelta / 2);
201 mTmpBottomLeftCorner.offset(currentPipBounds.left - mDelta / 2,
202 currentPipBounds.bottom - mDelta / 2);
203 mTmpBottomRightCorner.offset(currentPipBounds.right - mDelta / 2,
204 currentPipBounds.bottom - mDelta / 2);
Ben Lin04c83f62019-12-20 10:56:45 -0800205
Ben Lin4a8e2572020-05-08 15:55:15 -0700206 mTmpRegion.setEmpty();
207 mTmpRegion.op(mTmpTopLeftCorner, Region.Op.UNION);
208 mTmpRegion.op(mTmpTopRightCorner, Region.Op.UNION);
209 mTmpRegion.op(mTmpBottomLeftCorner, Region.Op.UNION);
210 mTmpRegion.op(mTmpBottomRightCorner, Region.Op.UNION);
Ben Lin04c83f62019-12-20 10:56:45 -0800211
Ben Lin7f12e212020-05-11 16:54:13 -0700212 return mTmpRegion.contains(x, y);
213 }
214
215 private void setCtrlType(int x, int y) {
216 final Rect currentPipBounds = mMotionHelper.getBounds();
217
Ben Lin4a8e2572020-05-08 15:55:15 -0700218 Rect movementBounds = mMovementBoundsSupplier.get();
219 mDisplayBounds.set(movementBounds.left,
220 movementBounds.top,
221 movementBounds.right + currentPipBounds.width(),
222 movementBounds.bottom + currentPipBounds.height());
Ben Lin7f12e212020-05-11 16:54:13 -0700223
224 if (mTmpTopLeftCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top
225 && currentPipBounds.left != mDisplayBounds.left) {
226 mCtrlType |= CTRL_LEFT;
227 mCtrlType |= CTRL_TOP;
Ben Lin04c83f62019-12-20 10:56:45 -0800228 }
Ben Lin7f12e212020-05-11 16:54:13 -0700229 if (mTmpTopRightCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top
230 && currentPipBounds.right != mDisplayBounds.right) {
231 mCtrlType |= CTRL_RIGHT;
232 mCtrlType |= CTRL_TOP;
233 }
234 if (mTmpBottomRightCorner.contains(x, y)
235 && currentPipBounds.bottom != mDisplayBounds.bottom
236 && currentPipBounds.right != mDisplayBounds.right) {
237 mCtrlType |= CTRL_RIGHT;
238 mCtrlType |= CTRL_BOTTOM;
239 }
240 if (mTmpBottomLeftCorner.contains(x, y)
241 && currentPipBounds.bottom != mDisplayBounds.bottom
242 && currentPipBounds.left != mDisplayBounds.left) {
243 mCtrlType |= CTRL_LEFT;
244 mCtrlType |= CTRL_BOTTOM;
245 }
Ben Lin04c83f62019-12-20 10:56:45 -0800246 }
247
248 private void onMotionEvent(MotionEvent ev) {
249 int action = ev.getActionMasked();
250 if (action == MotionEvent.ACTION_DOWN) {
Ben Lin094c7752020-03-11 12:10:51 -0700251 mLastResizeBounds.setEmpty();
Ben Lin04c83f62019-12-20 10:56:45 -0800252 mAllowGesture = isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
253 if (mAllowGesture) {
Ben Lin7f12e212020-05-11 16:54:13 -0700254 setCtrlType((int) ev.getX(), (int) ev.getY());
Ben Lin04c83f62019-12-20 10:56:45 -0800255 mDownPoint.set(ev.getX(), ev.getY());
Ben Lin75ba9c32020-03-19 17:55:12 -0700256 mLastDownBounds.set(mMotionHelper.getBounds());
Ben Lin04c83f62019-12-20 10:56:45 -0800257 }
258
259 } else if (mAllowGesture) {
Ben Lin04c83f62019-12-20 10:56:45 -0800260 switch (action) {
261 case MotionEvent.ACTION_POINTER_DOWN:
262 // We do not support multi touch for resizing via drag
263 mAllowGesture = false;
264 break;
265 case MotionEvent.ACTION_MOVE:
266 // Capture inputs
267 mInputMonitor.pilferPointers();
Ben Lin094c7752020-03-11 12:10:51 -0700268 final Rect currentPipBounds = mMotionHelper.getBounds();
269 mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(ev.getX(), ev.getY(),
270 mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x,
Ben Lind0039232020-03-23 16:05:42 -0700271 mMinSize.y, mMaxSize, true,
272 mLastDownBounds.width() > mLastDownBounds.height()));
Ben Lin094c7752020-03-11 12:10:51 -0700273 mPipBoundsHandler.transformBoundsToAspectRatio(mLastResizeBounds);
Ben Lin75ba9c32020-03-19 17:55:12 -0700274 mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds,
275 null);
Ben Lin04c83f62019-12-20 10:56:45 -0800276 break;
277 case MotionEvent.ACTION_UP:
278 case MotionEvent.ACTION_CANCEL:
Ben Lin4a8e2572020-05-08 15:55:15 -0700279 mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds, (Rect bounds) -> {
280 new Handler(Looper.getMainLooper()).post(() -> {
281 mMotionHelper.synchronizePinnedStackBounds();
282 mUpdateMovementBoundsRunnable.run();
283 mCtrlType = CTRL_NONE;
284 mAllowGesture = false;
285 });
286 });
Ben Lin04c83f62019-12-20 10:56:45 -0800287 break;
288 }
289 }
290 }
291
292 void updateMaxSize(int maxX, int maxY) {
293 mMaxSize.set(maxX, maxY);
294 }
295
Ben Lin094c7752020-03-11 12:10:51 -0700296 void updateMinSize(int minX, int minY) {
Ben Lin04c83f62019-12-20 10:56:45 -0800297 mMinSize.set(minX, minY);
298 }
299
300 class SysUiInputEventReceiver extends InputEventReceiver {
301 SysUiInputEventReceiver(InputChannel channel, Looper looper) {
302 super(channel, looper);
303 }
304
305 public void onInputEvent(InputEvent event) {
306 PipResizeGestureHandler.this.onInputEvent(event);
307 finishInputEvent(event, true);
308 }
309 }
310}