blob: d80f18a983ee8376073261ee04639e8e68dde8fd [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;
Ben Lin04c83f62019-12-20 10:56:45 -080059 private final PipMotionHelper mMotionHelper;
60 private final int mDisplayId;
61 private final Executor mMainExecutor;
62 private final Region mTmpRegion = new Region();
63
64 private final PointF mDownPoint = new PointF();
65 private final Point mMaxSize = new Point();
66 private final Point mMinSize = new Point();
Ben Lin094c7752020-03-11 12:10:51 -070067 private final Rect mLastResizeBounds = new Rect();
Ben Lin75ba9c32020-03-19 17:55:12 -070068 private final Rect mLastDownBounds = new Rect();
Ben Lin04c83f62019-12-20 10:56:45 -080069 private final Rect mTmpBounds = new Rect();
70 private final int mDelta;
71
Hongwei Wang081ecb32020-04-13 16:49:03 -070072 private boolean mAllowGesture;
Ben Lin04c83f62019-12-20 10:56:45 -080073 private boolean mIsAttached;
74 private boolean mIsEnabled;
Hongwei Wang081ecb32020-04-13 16:49:03 -070075 private boolean mEnableUserResize;
Ben Lin04c83f62019-12-20 10:56:45 -080076
77 private InputMonitor mInputMonitor;
78 private InputEventReceiver mInputEventReceiver;
Ben Lin7d6b8e72020-02-27 17:48:16 -080079 private PipTaskOrganizer mPipTaskOrganizer;
Ben Lin04c83f62019-12-20 10:56:45 -080080
81 private int mCtrlType;
82
83 public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler,
Hongwei Wang081ecb32020-04-13 16:49:03 -070084 PipMotionHelper motionHelper, DeviceConfigProxy deviceConfig,
85 PipTaskOrganizer pipTaskOrganizer) {
Ben Lin04c83f62019-12-20 10:56:45 -080086 final Resources res = context.getResources();
87 context.getDisplay().getMetrics(mDisplayMetrics);
88 mDisplayId = context.getDisplayId();
89 mMainExecutor = context.getMainExecutor();
90 mPipBoundsHandler = pipBoundsHandler;
Ben Lin04c83f62019-12-20 10:56:45 -080091 mMotionHelper = motionHelper;
Ben Lin7d6b8e72020-02-27 17:48:16 -080092 mPipTaskOrganizer = pipTaskOrganizer;
Ben Lin04c83f62019-12-20 10:56:45 -080093
94 context.getDisplay().getRealSize(mMaxSize);
95 mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size);
96
Hongwei Wang081ecb32020-04-13 16:49:03 -070097 mEnableUserResize = DeviceConfig.getBoolean(
Ben Lin04c83f62019-12-20 10:56:45 -080098 DeviceConfig.NAMESPACE_SYSTEMUI,
99 PIP_USER_RESIZE,
Ben Lin9ee8b132020-03-19 15:35:21 -0700100 /* defaultValue = */ true);
Beverly660d0a72020-02-26 12:32:42 -0500101 deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mMainExecutor,
Ben Lin04c83f62019-12-20 10:56:45 -0800102 new DeviceConfig.OnPropertiesChangedListener() {
103 @Override
104 public void onPropertiesChanged(DeviceConfig.Properties properties) {
105 if (properties.getKeyset().contains(PIP_USER_RESIZE)) {
Hongwei Wang081ecb32020-04-13 16:49:03 -0700106 mEnableUserResize = properties.getBoolean(
Ben Lin9ee8b132020-03-19 15:35:21 -0700107 PIP_USER_RESIZE, /* defaultValue = */ true);
Ben Lin04c83f62019-12-20 10:56:45 -0800108 }
109 }
110 });
111 }
112
113 private void disposeInputChannel() {
114 if (mInputEventReceiver != null) {
115 mInputEventReceiver.dispose();
116 mInputEventReceiver = null;
117 }
118 if (mInputMonitor != null) {
119 mInputMonitor.dispose();
120 mInputMonitor = null;
121 }
122 }
123
124 void onActivityPinned() {
125 mIsAttached = true;
126 updateIsEnabled();
127 }
128
129 void onActivityUnpinned() {
130 mIsAttached = false;
131 updateIsEnabled();
132 }
133
134 private void updateIsEnabled() {
Hongwei Wang081ecb32020-04-13 16:49:03 -0700135 boolean isEnabled = mIsAttached && mEnableUserResize;
Ben Lin04c83f62019-12-20 10:56:45 -0800136 if (isEnabled == mIsEnabled) {
137 return;
138 }
139 mIsEnabled = isEnabled;
140 disposeInputChannel();
141
142 if (mIsEnabled) {
143 // Register input event receiver
144 mInputMonitor = InputManager.getInstance().monitorGestureInput(
145 "pip-resize", mDisplayId);
146 mInputEventReceiver = new SysUiInputEventReceiver(
147 mInputMonitor.getInputChannel(), Looper.getMainLooper());
148 }
149 }
150
151 private void onInputEvent(InputEvent ev) {
152 if (ev instanceof MotionEvent) {
153 onMotionEvent((MotionEvent) ev);
154 }
155 }
156
157 private boolean isWithinTouchRegion(int x, int y) {
158 final Rect currentPipBounds = mMotionHelper.getBounds();
159 if (currentPipBounds == null) {
160 return false;
161 }
162
163 mTmpBounds.set(currentPipBounds);
164 mTmpBounds.inset(-mDelta, -mDelta);
165
166 mTmpRegion.set(mTmpBounds);
167 mTmpRegion.op(currentPipBounds, Region.Op.DIFFERENCE);
168
169 if (mTmpRegion.contains(x, y)) {
170 if (x < currentPipBounds.left) {
171 mCtrlType |= CTRL_LEFT;
172 }
173 if (x > currentPipBounds.right) {
174 mCtrlType |= CTRL_RIGHT;
175 }
176 if (y < currentPipBounds.top) {
177 mCtrlType |= CTRL_TOP;
178 }
179 if (y > currentPipBounds.bottom) {
180 mCtrlType |= CTRL_BOTTOM;
181 }
182 return true;
183 }
184 return false;
185 }
186
187 private void onMotionEvent(MotionEvent ev) {
188 int action = ev.getActionMasked();
189 if (action == MotionEvent.ACTION_DOWN) {
Ben Lin094c7752020-03-11 12:10:51 -0700190 mLastResizeBounds.setEmpty();
Ben Lin04c83f62019-12-20 10:56:45 -0800191 mAllowGesture = isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
192 if (mAllowGesture) {
193 mDownPoint.set(ev.getX(), ev.getY());
Ben Lin75ba9c32020-03-19 17:55:12 -0700194 mLastDownBounds.set(mMotionHelper.getBounds());
Ben Lin04c83f62019-12-20 10:56:45 -0800195 }
196
197 } else if (mAllowGesture) {
Ben Lin04c83f62019-12-20 10:56:45 -0800198 switch (action) {
199 case MotionEvent.ACTION_POINTER_DOWN:
200 // We do not support multi touch for resizing via drag
201 mAllowGesture = false;
202 break;
203 case MotionEvent.ACTION_MOVE:
204 // Capture inputs
205 mInputMonitor.pilferPointers();
Ben Lin094c7752020-03-11 12:10:51 -0700206 final Rect currentPipBounds = mMotionHelper.getBounds();
207 mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(ev.getX(), ev.getY(),
208 mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x,
Ben Lind0039232020-03-23 16:05:42 -0700209 mMinSize.y, mMaxSize, true,
210 mLastDownBounds.width() > mLastDownBounds.height()));
Ben Lin094c7752020-03-11 12:10:51 -0700211 mPipBoundsHandler.transformBoundsToAspectRatio(mLastResizeBounds);
Ben Lin75ba9c32020-03-19 17:55:12 -0700212 mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds,
213 null);
Ben Lin04c83f62019-12-20 10:56:45 -0800214 break;
215 case MotionEvent.ACTION_UP:
216 case MotionEvent.ACTION_CANCEL:
Ben Lin094c7752020-03-11 12:10:51 -0700217 mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds);
Ben Lin06dc41f2020-03-05 00:39:27 +0000218 mMotionHelper.synchronizePinnedStackBounds();
Ben Lin04c83f62019-12-20 10:56:45 -0800219 mCtrlType = CTRL_NONE;
220 mAllowGesture = false;
221 break;
222 }
223 }
224 }
225
226 void updateMaxSize(int maxX, int maxY) {
227 mMaxSize.set(maxX, maxY);
228 }
229
Ben Lin094c7752020-03-11 12:10:51 -0700230 void updateMinSize(int minX, int minY) {
Ben Lin04c83f62019-12-20 10:56:45 -0800231 mMinSize.set(minX, minY);
232 }
233
234 class SysUiInputEventReceiver extends InputEventReceiver {
235 SysUiInputEventReceiver(InputChannel channel, Looper looper) {
236 super(channel, looper);
237 }
238
239 public void onInputEvent(InputEvent event) {
240 PipResizeGestureHandler.this.onInputEvent(event);
241 finishInputEvent(event, true);
242 }
243 }
244}