1 /*
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 
17 package com.android.server.wm;
18 
19 import static android.app.ActivityTaskManager.RESIZE_MODE_USER;
20 import static android.app.ActivityTaskManager.RESIZE_MODE_USER_FORCED;
21 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
22 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
23 
24 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
25 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
26 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
27 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
28 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
29 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
30 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
31 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
32 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
33 import static com.android.server.wm.WindowManagerService.dipToPixel;
34 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
35 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
36 
37 import static java.util.concurrent.CompletableFuture.completedFuture;
38 
39 import android.annotation.NonNull;
40 import android.graphics.Point;
41 import android.graphics.Rect;
42 import android.os.Binder;
43 import android.os.IBinder;
44 import android.os.InputConfig;
45 import android.os.RemoteException;
46 import android.os.Trace;
47 import android.util.DisplayMetrics;
48 import android.util.Slog;
49 import android.view.BatchedInputEventReceiver;
50 import android.view.InputApplicationHandle;
51 import android.view.InputChannel;
52 import android.view.InputDevice;
53 import android.view.InputEvent;
54 import android.view.InputEventReceiver;
55 import android.view.InputWindowHandle;
56 import android.view.MotionEvent;
57 import android.view.WindowManager;
58 
59 import com.android.internal.annotations.VisibleForTesting;
60 import com.android.internal.policy.TaskResizingAlgorithm;
61 import com.android.internal.policy.TaskResizingAlgorithm.CtrlType;
62 import com.android.internal.protolog.common.ProtoLog;
63 
64 import java.util.concurrent.CompletableFuture;
65 
66 class 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
TaskPositioner(WindowManagerService service)107     TaskPositioner(WindowManagerService service) {
108         mService = service;
109     }
110 
onInputEvent(InputEvent event)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
getWindowDragBounds()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      */
register(DisplayContent displayContent, @NonNull WindowState win)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 }
501