blob: 9b3fb6b881c4a0937aa392acf2c1ce22659cd643 [file] [log] [blame]
Aurimas Liutikas88c7ff12023-08-10 12:42:26 -07001/*
2 * Copyright (C) 2015 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
17package com.android.server.wm;
18
19import static android.app.ActivityTaskManager.RESIZE_MODE_USER;
20import static android.app.ActivityTaskManager.RESIZE_MODE_USER_FORCED;
21import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
22import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
23
24import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
25import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
26import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
27import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
28import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
29import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
30import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
31import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
32import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
33import static com.android.server.wm.WindowManagerService.dipToPixel;
34import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
35import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
36
37import static java.util.concurrent.CompletableFuture.completedFuture;
38
39import android.annotation.NonNull;
40import android.graphics.Point;
41import android.graphics.Rect;
42import android.os.Binder;
43import android.os.IBinder;
44import android.os.InputConfig;
45import android.os.RemoteException;
46import android.os.Trace;
47import android.util.DisplayMetrics;
48import android.util.Slog;
49import android.view.BatchedInputEventReceiver;
50import android.view.InputApplicationHandle;
51import android.view.InputChannel;
52import android.view.InputDevice;
53import android.view.InputEvent;
54import android.view.InputEventReceiver;
55import android.view.InputWindowHandle;
56import android.view.MotionEvent;
57import android.view.WindowManager;
58
59import com.android.internal.annotations.VisibleForTesting;
60import com.android.internal.policy.TaskResizingAlgorithm;
61import com.android.internal.policy.TaskResizingAlgorithm.CtrlType;
62import com.android.internal.protolog.common.ProtoLog;
63
64import java.util.concurrent.CompletableFuture;
65
66class TaskPositioner implements IBinder.DeathRecipient {
67 private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false;
68 private static final String TAG_LOCAL = "TaskPositioner";
69 private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
70
71 private static Factory sFactory;
72
73 public static final float RESIZING_HINT_ALPHA = 0.5f;
74
75 public static final int RESIZING_HINT_DURATION_MS = 0;
76
77 private final WindowManagerService mService;
78 private InputEventReceiver mInputEventReceiver;
79 private DisplayContent mDisplayContent;
80 private Rect mTmpRect = new Rect();
81 private int mMinVisibleWidth;
82 private int mMinVisibleHeight;
83
84 @VisibleForTesting
85 Task mTask;
86 WindowState mWindow;
87 private boolean mResizing;
88 private boolean mPreserveOrientation;
89 private boolean mStartOrientationWasLandscape;
90 private final Rect mWindowOriginalBounds = new Rect();
91 private final Rect mWindowDragBounds = new Rect();
92 private final Point mMaxVisibleSize = new Point();
93 private float mStartDragX;
94 private float mStartDragY;
95 @CtrlType
96 private int mCtrlType = CTRL_NONE;
97 @VisibleForTesting
98 boolean mDragEnded;
99 IBinder mClientCallback;
100
101 InputChannel mClientChannel;
102 InputApplicationHandle mDragApplicationHandle;
103 InputWindowHandle mDragWindowHandle;
104
105 /** Use {@link #create(WindowManagerService)} instead. */
106 @VisibleForTesting
107 TaskPositioner(WindowManagerService service) {
108 mService = service;
109 }
110
111 private boolean onInputEvent(InputEvent event) {
112 // All returns need to be in the try block to make sure the finishInputEvent is
113 // called correctly.
114 if (!(event instanceof MotionEvent)
115 || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
116 return false;
117 }
118 final MotionEvent motionEvent = (MotionEvent) event;
119 if (mDragEnded) {
120 // The drag has ended but the clean-up message has not been processed by
121 // window manager. Drop events that occur after this until window manager
122 // has a chance to clean-up the input handle.
123 return true;
124 }
125
126 final float newX = motionEvent.getRawX();
127 final float newY = motionEvent.getRawY();
128
129 switch (motionEvent.getAction()) {
130 case MotionEvent.ACTION_DOWN: {
131 if (DEBUG_TASK_POSITIONING) {
132 Slog.w(TAG, "ACTION_DOWN @ {" + newX + ", " + newY + "}");
133 }
134 }
135 break;
136
137 case MotionEvent.ACTION_MOVE: {
138 if (DEBUG_TASK_POSITIONING) {
139 Slog.w(TAG, "ACTION_MOVE @ {" + newX + ", " + newY + "}");
140 }
141 synchronized (mService.mGlobalLock) {
142 mDragEnded = notifyMoveLocked(newX, newY);
143 mTask.getDimBounds(mTmpRect);
144 }
145 if (!mTmpRect.equals(mWindowDragBounds)) {
146 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
147 "wm.TaskPositioner.resizeTask");
148 mService.mAtmService.resizeTask(
149 mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER);
150 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
151 }
152 }
153 break;
154
155 case MotionEvent.ACTION_UP: {
156 if (DEBUG_TASK_POSITIONING) {
157 Slog.w(TAG, "ACTION_UP @ {" + newX + ", " + newY + "}");
158 }
159 mDragEnded = true;
160 }
161 break;
162
163 case MotionEvent.ACTION_CANCEL: {
164 if (DEBUG_TASK_POSITIONING) {
165 Slog.w(TAG, "ACTION_CANCEL @ {" + newX + ", " + newY + "}");
166 }
167 mDragEnded = true;
168 }
169 break;
170 }
171
172 if (mDragEnded) {
173 final boolean wasResizing = mResizing;
174 synchronized (mService.mGlobalLock) {
175 endDragLocked();
176 mTask.getDimBounds(mTmpRect);
177 }
178 if (wasResizing && !mTmpRect.equals(mWindowDragBounds)) {
179 // We were using fullscreen surface during resizing. Request
180 // resizeTask() one last time to restore surface to window size.
181 mService.mAtmService.resizeTask(
182 mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED);
183 }
184
185 // Post back to WM to handle clean-ups. We still need the input
186 // event handler for the last finishInputEvent()!
187 mService.mTaskPositioningController.finishTaskPositioning();
188 }
189 return true;
190 }
191
192 @VisibleForTesting
193 Rect getWindowDragBounds() {
194 return mWindowDragBounds;
195 }
196
197 /**
198 * @param displayContent The Display that the window being dragged is on.
199 * @param win The window which will be dragged.
200 */
201 CompletableFuture<Void> register(DisplayContent displayContent, @NonNull WindowState win) {
202 if (DEBUG_TASK_POSITIONING) {
203 Slog.d(TAG, "Registering task positioner");
204 }
205
206 if (mClientChannel != null) {
207 Slog.e(TAG, "Task positioner already registered");
208 return completedFuture(null);
209 }
210
211 mDisplayContent = displayContent;
212 mClientChannel = mService.mInputManager.createInputChannel(TAG);
213
214 mInputEventReceiver = new BatchedInputEventReceiver.SimpleBatchedInputEventReceiver(
215 mClientChannel, mService.mAnimationHandler.getLooper(),
216 mService.mAnimator.getChoreographer(), this::onInputEvent);
217
218 mDragApplicationHandle = new InputApplicationHandle(new Binder(), TAG,
219 DEFAULT_DISPATCHING_TIMEOUT_MILLIS);
220
221 mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle,
222 displayContent.getDisplayId());
223 mDragWindowHandle.name = TAG;
224 mDragWindowHandle.token = mClientChannel.getToken();
225 mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
226 mDragWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
227 mDragWindowHandle.ownerPid = WindowManagerService.MY_PID;
228 mDragWindowHandle.ownerUid = WindowManagerService.MY_UID;
229 mDragWindowHandle.scaleFactor = 1.0f;
230 // When dragging the window around, we do not want to steal focus for the window.
231 mDragWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE;
232
233 // The drag window cannot receive new touches.
234 mDragWindowHandle.touchableRegion.setEmpty();
235
236 // Pause rotations before a drag.
237 ProtoLog.d(WM_DEBUG_ORIENTATION, "Pausing rotation during re-position");
238 mDisplayContent.getDisplayRotation().pause();
239
240 // Notify InputMonitor to take mDragWindowHandle.
241 return mService.mTaskPositioningController.showInputSurface(win.getDisplayId())
242 .thenRun(() -> {
243 // The global lock is held by the callers of register but released before the async
244 // results are waited on. We must acquire the lock in this callback to ensure thread
245 // safety.
246 synchronized (mService.mGlobalLock) {
247 final Rect displayBounds = mTmpRect;
248 displayContent.getBounds(displayBounds);
249 final DisplayMetrics displayMetrics = displayContent.getDisplayMetrics();
250 mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, displayMetrics);
251 mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, displayMetrics);
252 mMaxVisibleSize.set(displayBounds.width(), displayBounds.height());
253
254 mDragEnded = false;
255
256 try {
257 mClientCallback = win.mClient.asBinder();
258 mClientCallback.linkToDeath(this, 0 /* flags */);
259 } catch (RemoteException e) {
260 // The caller has died, so clean up TaskPositioningController.
261 mService.mTaskPositioningController.finishTaskPositioning();
262 return;
263 }
264 mWindow = win;
265 mTask = win.getTask();
266 }
267 });
268 }
269
270 void unregister() {
271 if (DEBUG_TASK_POSITIONING) {
272 Slog.d(TAG, "Unregistering task positioner");
273 }
274
275 if (mClientChannel == null) {
276 Slog.e(TAG, "Task positioner not registered");
277 return;
278 }
279
280 mService.mTaskPositioningController.hideInputSurface(mDisplayContent.getDisplayId());
281 mService.mInputManager.removeInputChannel(mClientChannel.getToken());
282
283 mInputEventReceiver.dispose();
284 mInputEventReceiver = null;
285 mClientChannel.dispose();
286 mClientChannel = null;
287
288 mDragWindowHandle = null;
289 mDragApplicationHandle = null;
290 mDragEnded = true;
291
292 // Notify InputMonitor to remove mDragWindowHandle.
293 mDisplayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
294
295 // Resume rotations after a drag.
296 ProtoLog.d(WM_DEBUG_ORIENTATION, "Resuming rotation after re-position");
297 mDisplayContent.getDisplayRotation().resume();
298 mDisplayContent = null;
299 if (mClientCallback != null) {
300 mClientCallback.unlinkToDeath(this, 0 /* flags */);
301 }
302 mWindow = null;
303 }
304
305 /**
306 * Starts moving or resizing the task. This method should be only called from
307 * {@link TaskPositioningController#startPositioningLocked} or unit tests.
308 */
309 void startDrag(boolean resize, boolean preserveOrientation, float startX, float startY) {
310 if (DEBUG_TASK_POSITIONING) {
311 Slog.d(TAG, "startDrag: win=" + mWindow + ", resize=" + resize
312 + ", preserveOrientation=" + preserveOrientation + ", {" + startX + ", "
313 + startY + "}");
314 }
315 // Use the bounds of the task which accounts for
316 // multiple app windows. Don't use any bounds from win itself as it
317 // may not be the same size as the task.
318 final Rect startBounds = mTmpRect;
319 mTask.getBounds(startBounds);
320
321 mCtrlType = CTRL_NONE;
322 mStartDragX = startX;
323 mStartDragY = startY;
324 mPreserveOrientation = preserveOrientation;
325
326 if (resize) {
327 if (startX < startBounds.left) {
328 mCtrlType |= CTRL_LEFT;
329 }
330 if (startX > startBounds.right) {
331 mCtrlType |= CTRL_RIGHT;
332 }
333 if (startY < startBounds.top) {
334 mCtrlType |= CTRL_TOP;
335 }
336 if (startY > startBounds.bottom) {
337 mCtrlType |= CTRL_BOTTOM;
338 }
339 mResizing = mCtrlType != CTRL_NONE;
340 }
341
342 // In case of !isDockedInEffect we are using the union of all task bounds. These might be
343 // made up out of multiple windows which are only partially overlapping. When that happens,
344 // the orientation from the window of interest to the entire stack might diverge. However
345 // for now we treat them as the same.
346 mStartOrientationWasLandscape = startBounds.width() >= startBounds.height();
347 mWindowOriginalBounds.set(startBounds);
348
349 // Notify the app that resizing has started, even though we haven't received any new
350 // bounds yet. This will guarantee that the app starts the backdrop renderer before
351 // configuration changes which could cause an activity restart.
352 if (mResizing) {
353 notifyMoveLocked(startX, startY);
354
355 // The WindowPositionerEventReceiver callbacks are delivered on the same handler so this
356 // initial resize is always guaranteed to happen before subsequent drag resizes.
357 mService.mH.post(() -> {
358 mService.mAtmService.resizeTask(
359 mTask.mTaskId, startBounds, RESIZE_MODE_USER_FORCED);
360 });
361 }
362
363 // Make sure we always have valid drag bounds even if the drag ends before any move events
364 // have been handled.
365 mWindowDragBounds.set(startBounds);
366 }
367
368 private void endDragLocked() {
369 mResizing = false;
370 mTask.setDragResizing(false);
371 }
372
373 /** Returns true if the move operation should be ended. */
374 @VisibleForTesting
375 boolean notifyMoveLocked(float x, float y) {
376 if (DEBUG_TASK_POSITIONING) {
377 Slog.d(TAG, "notifyMoveLocked: {" + x + "," + y + "}");
378 }
379
380 if (mCtrlType != CTRL_NONE) {
381 resizeDrag(x, y);
382 mTask.setDragResizing(true);
383 return false;
384 }
385
386 // This is a moving or scrolling operation.
387 // Only allow to move in stable area so the target window won't be covered by system bar.
388 // Though {@link Task#resolveOverrideConfiguration} should also avoid the case.
389 mDisplayContent.getStableRect(mTmpRect);
390 // The task may be put in a limited display area.
391 mTmpRect.intersect(mTask.getRootTask().getParent().getBounds());
392
393 int nX = (int) x;
394 int nY = (int) y;
395 if (!mTmpRect.contains(nX, nY)) {
396 // For a moving operation we allow the pointer to go out of the stack bounds, but
397 // use the clamped pointer position for the drag bounds computation.
398 nX = Math.min(Math.max(nX, mTmpRect.left), mTmpRect.right);
399 nY = Math.min(Math.max(nY, mTmpRect.top), mTmpRect.bottom);
400 }
401
402 updateWindowDragBounds(nX, nY, mTmpRect);
403 return false;
404 }
405
406 /**
407 * The user is drag - resizing the window.
408 *
409 * @param x The x coordinate of the current drag coordinate.
410 * @param y the y coordinate of the current drag coordinate.
411 */
412 @VisibleForTesting
413 void resizeDrag(float x, float y) {
414 updateDraggedBounds(TaskResizingAlgorithm.resizeDrag(x, y, mStartDragX, mStartDragY,
415 mWindowOriginalBounds, mCtrlType, mMinVisibleWidth, mMinVisibleHeight,
416 mMaxVisibleSize, mPreserveOrientation, mStartOrientationWasLandscape));
417 }
418
419 private void updateDraggedBounds(Rect newBounds) {
420 mWindowDragBounds.set(newBounds);
421
422 checkBoundsForOrientationViolations(mWindowDragBounds);
423 }
424
425 /**
426 * Validate bounds against orientation violations (if DEBUG_ORIENTATION_VIOLATIONS is set).
427 *
428 * @param bounds The bounds to be checked.
429 */
430 private void checkBoundsForOrientationViolations(Rect bounds) {
431 // When using debug check that we are not violating the given constraints.
432 if (DEBUG_ORIENTATION_VIOLATIONS) {
433 if (mStartOrientationWasLandscape != (bounds.width() >= bounds.height())) {
434 Slog.e(TAG, "Orientation violation detected! should be "
435 + (mStartOrientationWasLandscape ? "landscape" : "portrait")
436 + " but is the other");
437 } else {
438 Slog.v(TAG, "new bounds size: " + bounds.width() + " x " + bounds.height());
439 }
440 if (mMinVisibleWidth > bounds.width() || mMinVisibleHeight > bounds.height()) {
441 Slog.v(TAG, "Minimum requirement violated: Width(min, is)=(" + mMinVisibleWidth
442 + ", " + bounds.width() + ") Height(min,is)=("
443 + mMinVisibleHeight + ", " + bounds.height() + ")");
444 }
445 if (mMaxVisibleSize.x < bounds.width() || mMaxVisibleSize.y < bounds.height()) {
446 Slog.v(TAG, "Maximum requirement violated: Width(min, is)=(" + mMaxVisibleSize.x
447 + ", " + bounds.width() + ") Height(min,is)=("
448 + mMaxVisibleSize.y + ", " + bounds.height() + ")");
449 }
450 }
451 }
452
453 private void updateWindowDragBounds(int x, int y, Rect rootTaskBounds) {
454 final int offsetX = Math.round(x - mStartDragX);
455 final int offsetY = Math.round(y - mStartDragY);
456 mWindowDragBounds.set(mWindowOriginalBounds);
457 // Horizontally, at least mMinVisibleWidth pixels of the window should remain visible.
458 final int maxLeft = rootTaskBounds.right - mMinVisibleWidth;
459 final int minLeft = rootTaskBounds.left + mMinVisibleWidth - mWindowOriginalBounds.width();
460
461 // Vertically, the top mMinVisibleHeight of the window should remain visible.
462 // (This assumes that the window caption bar is at the top of the window).
463 final int minTop = rootTaskBounds.top;
464 final int maxTop = rootTaskBounds.bottom - mMinVisibleHeight;
465
466 mWindowDragBounds.offsetTo(
467 Math.min(Math.max(mWindowOriginalBounds.left + offsetX, minLeft), maxLeft),
468 Math.min(Math.max(mWindowOriginalBounds.top + offsetY, minTop), maxTop));
469
470 if (DEBUG_TASK_POSITIONING) Slog.d(TAG,
471 "updateWindowDragBounds: " + mWindowDragBounds);
472 }
473
474 public String toShortString() {
475 return TAG;
476 }
477
478 static void setFactory(Factory factory) {
479 sFactory = factory;
480 }
481
482 static TaskPositioner create(WindowManagerService service) {
483 if (sFactory == null) {
484 sFactory = new Factory() {};
485 }
486
487 return sFactory.create(service);
488 }
489
490 @Override
491 public void binderDied() {
492 mService.mTaskPositioningController.finishTaskPositioning();
493 }
494
495 interface Factory {
496 default TaskPositioner create(WindowManagerService service) {
497 return new TaskPositioner(service);
498 }
499 }
500}