blob: 0b076559ae367d0b82a714e1aeb71fdaa49860e5 [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;
32import android.os.Looper;
33import android.provider.DeviceConfig;
34import android.util.DisplayMetrics;
35import android.view.InputChannel;
36import android.view.InputEvent;
37import android.view.InputEventReceiver;
38import android.view.InputMonitor;
39import android.view.MotionEvent;
40
41import com.android.internal.policy.TaskResizingAlgorithm;
42import com.android.systemui.R;
43import com.android.systemui.pip.PipBoundsHandler;
Ben Lin7d6b8e72020-02-27 17:48:16 -080044import com.android.systemui.pip.PipTaskOrganizer;
Beverly660d0a72020-02-26 12:32:42 -050045import com.android.systemui.util.DeviceConfigProxy;
Ben Lin04c83f62019-12-20 10:56:45 -080046
47import java.util.concurrent.Executor;
48
49/**
50 * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
51 * trigger dynamic resize.
52 */
53public class PipResizeGestureHandler {
54
55 private static final String TAG = "PipResizeGestureHandler";
56
57 private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
58 private final PipBoundsHandler mPipBoundsHandler;
59 private final PipTouchHandler mPipTouchHandler;
60 private final PipMotionHelper mMotionHelper;
61 private final int mDisplayId;
62 private final Executor mMainExecutor;
63 private final Region mTmpRegion = new Region();
64
65 private final PointF mDownPoint = new PointF();
66 private final Point mMaxSize = new Point();
67 private final Point mMinSize = new Point();
Ben Lin094c7752020-03-11 12:10:51 -070068 private final Rect mLastResizeBounds = new Rect();
Ben Lin75ba9c32020-03-19 17:55:12 -070069 private final Rect mLastDownBounds = new Rect();
Ben Lin04c83f62019-12-20 10:56:45 -080070 private final Rect mTmpBounds = new Rect();
71 private final int mDelta;
72
73 private boolean mAllowGesture = false;
74 private boolean mIsAttached;
75 private boolean mIsEnabled;
76 private boolean mEnablePipResize;
77
78 private InputMonitor mInputMonitor;
79 private InputEventReceiver mInputEventReceiver;
Ben Lin7d6b8e72020-02-27 17:48:16 -080080 private PipTaskOrganizer mPipTaskOrganizer;
Ben Lin04c83f62019-12-20 10:56:45 -080081
82 private int mCtrlType;
83
84 public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler,
Beverly660d0a72020-02-26 12:32:42 -050085 PipTouchHandler pipTouchHandler, PipMotionHelper motionHelper,
Ben Lin7d6b8e72020-02-27 17:48:16 -080086 DeviceConfigProxy deviceConfig, PipTaskOrganizer pipTaskOrganizer) {
Ben Lin04c83f62019-12-20 10:56:45 -080087 final Resources res = context.getResources();
88 context.getDisplay().getMetrics(mDisplayMetrics);
89 mDisplayId = context.getDisplayId();
90 mMainExecutor = context.getMainExecutor();
91 mPipBoundsHandler = pipBoundsHandler;
92 mPipTouchHandler = pipTouchHandler;
93 mMotionHelper = motionHelper;
Ben Lin7d6b8e72020-02-27 17:48:16 -080094 mPipTaskOrganizer = pipTaskOrganizer;
Ben Lin04c83f62019-12-20 10:56:45 -080095
96 context.getDisplay().getRealSize(mMaxSize);
97 mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size);
98
99 mEnablePipResize = DeviceConfig.getBoolean(
100 DeviceConfig.NAMESPACE_SYSTEMUI,
101 PIP_USER_RESIZE,
Ben Lin9ee8b132020-03-19 15:35:21 -0700102 /* defaultValue = */ true);
Beverly660d0a72020-02-26 12:32:42 -0500103 deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mMainExecutor,
Ben Lin04c83f62019-12-20 10:56:45 -0800104 new DeviceConfig.OnPropertiesChangedListener() {
105 @Override
106 public void onPropertiesChanged(DeviceConfig.Properties properties) {
107 if (properties.getKeyset().contains(PIP_USER_RESIZE)) {
108 mEnablePipResize = properties.getBoolean(
Ben Lin9ee8b132020-03-19 15:35:21 -0700109 PIP_USER_RESIZE, /* defaultValue = */ true);
Ben Lin04c83f62019-12-20 10:56:45 -0800110 }
111 }
112 });
113 }
114
115 private void disposeInputChannel() {
116 if (mInputEventReceiver != null) {
117 mInputEventReceiver.dispose();
118 mInputEventReceiver = null;
119 }
120 if (mInputMonitor != null) {
121 mInputMonitor.dispose();
122 mInputMonitor = null;
123 }
124 }
125
126 void onActivityPinned() {
127 mIsAttached = true;
128 updateIsEnabled();
129 }
130
131 void onActivityUnpinned() {
132 mIsAttached = false;
133 updateIsEnabled();
134 }
135
136 private void updateIsEnabled() {
137 boolean isEnabled = mIsAttached && mEnablePipResize;
138 if (isEnabled == mIsEnabled) {
139 return;
140 }
141 mIsEnabled = isEnabled;
142 disposeInputChannel();
143
144 if (mIsEnabled) {
145 // Register input event receiver
146 mInputMonitor = InputManager.getInstance().monitorGestureInput(
147 "pip-resize", mDisplayId);
148 mInputEventReceiver = new SysUiInputEventReceiver(
149 mInputMonitor.getInputChannel(), Looper.getMainLooper());
150 }
151 }
152
153 private void onInputEvent(InputEvent ev) {
154 if (ev instanceof MotionEvent) {
155 onMotionEvent((MotionEvent) ev);
156 }
157 }
158
159 private boolean isWithinTouchRegion(int x, int y) {
160 final Rect currentPipBounds = mMotionHelper.getBounds();
161 if (currentPipBounds == null) {
162 return false;
163 }
164
165 mTmpBounds.set(currentPipBounds);
166 mTmpBounds.inset(-mDelta, -mDelta);
167
168 mTmpRegion.set(mTmpBounds);
169 mTmpRegion.op(currentPipBounds, Region.Op.DIFFERENCE);
170
171 if (mTmpRegion.contains(x, y)) {
172 if (x < currentPipBounds.left) {
173 mCtrlType |= CTRL_LEFT;
174 }
175 if (x > currentPipBounds.right) {
176 mCtrlType |= CTRL_RIGHT;
177 }
178 if (y < currentPipBounds.top) {
179 mCtrlType |= CTRL_TOP;
180 }
181 if (y > currentPipBounds.bottom) {
182 mCtrlType |= CTRL_BOTTOM;
183 }
184 return true;
185 }
186 return false;
187 }
188
189 private void onMotionEvent(MotionEvent ev) {
190 int action = ev.getActionMasked();
191 if (action == MotionEvent.ACTION_DOWN) {
Ben Lin094c7752020-03-11 12:10:51 -0700192 mLastResizeBounds.setEmpty();
Ben Lin04c83f62019-12-20 10:56:45 -0800193 mAllowGesture = isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
194 if (mAllowGesture) {
195 mDownPoint.set(ev.getX(), ev.getY());
Ben Lin75ba9c32020-03-19 17:55:12 -0700196 mLastDownBounds.set(mMotionHelper.getBounds());
Ben Lin04c83f62019-12-20 10:56:45 -0800197 }
198
199 } else if (mAllowGesture) {
Ben Lin04c83f62019-12-20 10:56:45 -0800200 switch (action) {
201 case MotionEvent.ACTION_POINTER_DOWN:
202 // We do not support multi touch for resizing via drag
203 mAllowGesture = false;
204 break;
205 case MotionEvent.ACTION_MOVE:
206 // Capture inputs
207 mInputMonitor.pilferPointers();
Ben Lin094c7752020-03-11 12:10:51 -0700208 final Rect currentPipBounds = mMotionHelper.getBounds();
209 mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(ev.getX(), ev.getY(),
210 mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x,
Ben Lind0039232020-03-23 16:05:42 -0700211 mMinSize.y, mMaxSize, true,
212 mLastDownBounds.width() > mLastDownBounds.height()));
Ben Lin094c7752020-03-11 12:10:51 -0700213 mPipBoundsHandler.transformBoundsToAspectRatio(mLastResizeBounds);
Ben Lin75ba9c32020-03-19 17:55:12 -0700214 mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds,
215 null);
Ben Lin04c83f62019-12-20 10:56:45 -0800216 break;
217 case MotionEvent.ACTION_UP:
218 case MotionEvent.ACTION_CANCEL:
Ben Lin094c7752020-03-11 12:10:51 -0700219 mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds);
Ben Lin06dc41f2020-03-05 00:39:27 +0000220 mMotionHelper.synchronizePinnedStackBounds();
Ben Lin04c83f62019-12-20 10:56:45 -0800221 mCtrlType = CTRL_NONE;
222 mAllowGesture = false;
223 break;
224 }
225 }
226 }
227
228 void updateMaxSize(int maxX, int maxY) {
229 mMaxSize.set(maxX, maxY);
230 }
231
Ben Lin094c7752020-03-11 12:10:51 -0700232 void updateMinSize(int minX, int minY) {
Ben Lin04c83f62019-12-20 10:56:45 -0800233 mMinSize.set(minX, minY);
234 }
235
236 class SysUiInputEventReceiver extends InputEventReceiver {
237 SysUiInputEventReceiver(InputChannel channel, Looper looper) {
238 super(channel, looper);
239 }
240
241 public void onInputEvent(InputEvent event) {
242 PipResizeGestureHandler.this.onInputEvent(event);
243 finishInputEvent(event, true);
244 }
245 }
246}