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.Trace.TRACE_TAG_WINDOW_MANAGER;
22 
23 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
24 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
25 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
26 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
27 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
28 import static com.android.server.wm.WindowManagerService.dipToPixel;
29 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
30 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
31 
32 import android.annotation.IntDef;
33 import android.app.IActivityTaskManager;
34 import android.graphics.Point;
35 import android.graphics.Rect;
36 import android.os.Binder;
37 import android.os.IBinder;
38 import android.os.Looper;
39 import android.os.Process;
40 import android.os.RemoteException;
41 import android.os.Trace;
42 import android.util.DisplayMetrics;
43 import android.util.Slog;
44 import android.view.BatchedInputEventReceiver;
45 import android.view.Choreographer;
46 import android.view.Display;
47 import android.view.InputApplicationHandle;
48 import android.view.InputChannel;
49 import android.view.InputDevice;
50 import android.view.InputEvent;
51 import android.view.InputWindowHandle;
52 import android.view.MotionEvent;
53 import android.view.SurfaceControl;
54 import android.view.WindowManager;
55 
56 import com.android.internal.annotations.VisibleForTesting;
57 
58 import java.lang.annotation.Retention;
59 import java.lang.annotation.RetentionPolicy;
60 
61 class TaskPositioner implements IBinder.DeathRecipient {
62     private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false;
63     private static final String TAG_LOCAL = "TaskPositioner";
64     private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
65 
66     private static Factory sFactory;
67 
68     // The margin the pointer position has to be within the side of the screen to be
69     // considered at the side of the screen.
70     static final int SIDE_MARGIN_DIP = 100;
71 
72     @IntDef(flag = true,
73             value = {
74                     CTRL_NONE,
75                     CTRL_LEFT,
76                     CTRL_RIGHT,
77                     CTRL_TOP,
78                     CTRL_BOTTOM
79             })
80     @Retention(RetentionPolicy.SOURCE)
81     @interface CtrlType {}
82 
83     private static final int CTRL_NONE   = 0x0;
84     private static final int CTRL_LEFT   = 0x1;
85     private static final int CTRL_RIGHT  = 0x2;
86     private static final int CTRL_TOP    = 0x4;
87     private static final int CTRL_BOTTOM = 0x8;
88 
89     public static final float RESIZING_HINT_ALPHA = 0.5f;
90 
91     public static final int RESIZING_HINT_DURATION_MS = 0;
92 
93     // The minimal aspect ratio which needs to be met to count as landscape (or 1/.. for portrait).
94     // Note: We do not use the 1.33 from the CDD here since the user is allowed to use what ever
95     // aspect he desires.
96     @VisibleForTesting
97     static final float MIN_ASPECT = 1.2f;
98 
99     private final WindowManagerService mService;
100     private final IActivityTaskManager mActivityManager;
101     private WindowPositionerEventReceiver mInputEventReceiver;
102     private DisplayContent mDisplayContent;
103     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
104     private Rect mTmpRect = new Rect();
105     private int mSideMargin;
106     private int mMinVisibleWidth;
107     private int mMinVisibleHeight;
108 
109     @VisibleForTesting
110     Task mTask;
111     private boolean mResizing;
112     private boolean mPreserveOrientation;
113     private boolean mStartOrientationWasLandscape;
114     private final Rect mWindowOriginalBounds = new Rect();
115     private final Rect mWindowDragBounds = new Rect();
116     private final Point mMaxVisibleSize = new Point();
117     private float mStartDragX;
118     private float mStartDragY;
119     @CtrlType
120     private int mCtrlType = CTRL_NONE;
121     @VisibleForTesting
122     boolean mDragEnded;
123     IBinder mClientCallback;
124 
125     InputChannel mServerChannel;
126     InputChannel mClientChannel;
127     InputApplicationHandle mDragApplicationHandle;
128     InputWindowHandle mDragWindowHandle;
129 
130     private final class WindowPositionerEventReceiver extends BatchedInputEventReceiver {
WindowPositionerEventReceiver( InputChannel inputChannel, Looper looper, Choreographer choreographer)131         public WindowPositionerEventReceiver(
132                 InputChannel inputChannel, Looper looper, Choreographer choreographer) {
133             super(inputChannel, looper, choreographer);
134         }
135 
136         @Override
onInputEvent(InputEvent event)137         public void onInputEvent(InputEvent event) {
138             if (!(event instanceof MotionEvent)
139                     || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
140                 return;
141             }
142             final MotionEvent motionEvent = (MotionEvent) event;
143             boolean handled = false;
144 
145             try {
146                 if (mDragEnded) {
147                     // The drag has ended but the clean-up message has not been processed by
148                     // window manager. Drop events that occur after this until window manager
149                     // has a chance to clean-up the input handle.
150                     handled = true;
151                     return;
152                 }
153 
154                 final float newX = motionEvent.getRawX();
155                 final float newY = motionEvent.getRawY();
156 
157                 switch (motionEvent.getAction()) {
158                     case MotionEvent.ACTION_DOWN: {
159                         if (DEBUG_TASK_POSITIONING) {
160                             Slog.w(TAG, "ACTION_DOWN @ {" + newX + ", " + newY + "}");
161                         }
162                     } break;
163 
164                     case MotionEvent.ACTION_MOVE: {
165                         if (DEBUG_TASK_POSITIONING){
166                             Slog.w(TAG, "ACTION_MOVE @ {" + newX + ", " + newY + "}");
167                         }
168                         synchronized (mService.mGlobalLock) {
169                             mDragEnded = notifyMoveLocked(newX, newY);
170                             mTask.getDimBounds(mTmpRect);
171                         }
172                         if (!mTmpRect.equals(mWindowDragBounds)) {
173                             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
174                                     "wm.TaskPositioner.resizeTask");
175                             try {
176                                 mActivityManager.resizeTask(
177                                         mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER);
178                             } catch (RemoteException e) {
179                             }
180                             Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
181                         }
182                     } break;
183 
184                     case MotionEvent.ACTION_UP: {
185                         if (DEBUG_TASK_POSITIONING) {
186                             Slog.w(TAG, "ACTION_UP @ {" + newX + ", " + newY + "}");
187                         }
188                         mDragEnded = true;
189                     } break;
190 
191                     case MotionEvent.ACTION_CANCEL: {
192                         if (DEBUG_TASK_POSITIONING) {
193                             Slog.w(TAG, "ACTION_CANCEL @ {" + newX + ", " + newY + "}");
194                         }
195                         mDragEnded = true;
196                     } break;
197                 }
198 
199                 if (mDragEnded) {
200                     final boolean wasResizing = mResizing;
201                     synchronized (mService.mGlobalLock) {
202                         endDragLocked();
203                         mTask.getDimBounds(mTmpRect);
204                     }
205                     try {
206                         if (wasResizing && !mTmpRect.equals(mWindowDragBounds)) {
207                             // We were using fullscreen surface during resizing. Request
208                             // resizeTask() one last time to restore surface to window size.
209                             mActivityManager.resizeTask(
210                                     mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED);
211                         }
212                     } catch(RemoteException e) {}
213 
214                     // Post back to WM to handle clean-ups. We still need the input
215                     // event handler for the last finishInputEvent()!
216                     mService.mTaskPositioningController.finishTaskPositioning();
217                 }
218                 handled = true;
219             } catch (Exception e) {
220                 Slog.e(TAG, "Exception caught by drag handleMotion", e);
221             } finally {
222                 finishInputEvent(event, handled);
223             }
224         }
225     }
226 
227     @VisibleForTesting
TaskPositioner(WindowManagerService service, IActivityTaskManager activityManager)228     TaskPositioner(WindowManagerService service, IActivityTaskManager activityManager) {
229         mService = service;
230         mActivityManager = activityManager;
231     }
232 
233     /** Use {@link #create(WindowManagerService)} instead **/
TaskPositioner(WindowManagerService service)234     TaskPositioner(WindowManagerService service) {
235         this(service, service.mActivityTaskManager);
236     }
237 
238     @VisibleForTesting
getWindowDragBounds()239     Rect getWindowDragBounds() {
240         return mWindowDragBounds;
241     }
242 
243     /**
244      * @param displayContent The Display that the window being dragged is on.
245      */
register(DisplayContent displayContent)246     void register(DisplayContent displayContent) {
247         final Display display = displayContent.getDisplay();
248 
249         if (DEBUG_TASK_POSITIONING) {
250             Slog.d(TAG, "Registering task positioner");
251         }
252 
253         if (mClientChannel != null) {
254             Slog.e(TAG, "Task positioner already registered");
255             return;
256         }
257 
258         mDisplayContent = displayContent;
259         display.getMetrics(mDisplayMetrics);
260         final InputChannel[] channels = InputChannel.openInputChannelPair(TAG);
261         mServerChannel = channels[0];
262         mClientChannel = channels[1];
263         mService.mInputManager.registerInputChannel(mServerChannel, null);
264 
265         mInputEventReceiver = new WindowPositionerEventReceiver(
266                 mClientChannel, mService.mAnimationHandler.getLooper(),
267                 mService.mAnimator.getChoreographer());
268 
269         mDragApplicationHandle = new InputApplicationHandle(new Binder());
270         mDragApplicationHandle.name = TAG;
271         mDragApplicationHandle.dispatchingTimeoutNanos =
272                 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
273 
274         mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
275                 display.getDisplayId());
276         mDragWindowHandle.name = TAG;
277         mDragWindowHandle.token = mServerChannel.getToken();
278         mDragWindowHandle.layer = mService.getDragLayerLocked();
279         mDragWindowHandle.layoutParamsFlags = 0;
280         mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
281         mDragWindowHandle.dispatchingTimeoutNanos =
282                 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
283         mDragWindowHandle.visible = true;
284         mDragWindowHandle.canReceiveKeys = false;
285         mDragWindowHandle.hasFocus = true;
286         mDragWindowHandle.hasWallpaper = false;
287         mDragWindowHandle.paused = false;
288         mDragWindowHandle.ownerPid = Process.myPid();
289         mDragWindowHandle.ownerUid = Process.myUid();
290         mDragWindowHandle.inputFeatures = 0;
291         mDragWindowHandle.scaleFactor = 1.0f;
292 
293         // The drag window cannot receive new touches.
294         mDragWindowHandle.touchableRegion.setEmpty();
295 
296         // The drag window covers the entire display
297         mDragWindowHandle.frameLeft = 0;
298         mDragWindowHandle.frameTop = 0;
299         final Point p = new Point();
300         display.getRealSize(p);
301         mDragWindowHandle.frameRight = p.x;
302         mDragWindowHandle.frameBottom = p.y;
303 
304         // Pause rotations before a drag.
305         if (DEBUG_ORIENTATION) {
306             Slog.d(TAG, "Pausing rotation during re-position");
307         }
308         mDisplayContent.pauseRotationLocked();
309 
310         // Notify InputMonitor to take mDragWindowHandle.
311         mDisplayContent.getInputMonitor().updateInputWindowsImmediately();
312         new SurfaceControl.Transaction().syncInputWindows().apply();
313 
314         mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
315         mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
316         mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
317         display.getRealSize(mMaxVisibleSize);
318 
319         mDragEnded = false;
320     }
321 
unregister()322     void unregister() {
323         if (DEBUG_TASK_POSITIONING) {
324             Slog.d(TAG, "Unregistering task positioner");
325         }
326 
327         if (mClientChannel == null) {
328             Slog.e(TAG, "Task positioner not registered");
329             return;
330         }
331 
332         mService.mInputManager.unregisterInputChannel(mServerChannel);
333 
334         mInputEventReceiver.dispose();
335         mInputEventReceiver = null;
336         mClientChannel.dispose();
337         mServerChannel.dispose();
338         mClientChannel = null;
339         mServerChannel = null;
340 
341         mDragWindowHandle = null;
342         mDragApplicationHandle = null;
343         mDragEnded = true;
344 
345         // Notify InputMonitor to remove mDragWindowHandle.
346         mDisplayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
347 
348         // Resume rotations after a drag.
349         if (DEBUG_ORIENTATION) {
350             Slog.d(TAG, "Resuming rotation after re-position");
351         }
352         mDisplayContent.resumeRotationLocked();
353         mDisplayContent = null;
354         mClientCallback.unlinkToDeath(this, 0 /* flags */);
355     }
356 
startDrag(WindowState win, boolean resize, boolean preserveOrientation, float startX, float startY)357     void startDrag(WindowState win, boolean resize, boolean preserveOrientation, float startX,
358                    float startY) {
359         if (DEBUG_TASK_POSITIONING) {
360             Slog.d(TAG, "startDrag: win=" + win + ", resize=" + resize
361                     + ", preserveOrientation=" + preserveOrientation + ", {" + startX + ", "
362                     + startY + "}");
363         }
364         try {
365             mClientCallback = win.mClient.asBinder();
366             mClientCallback.linkToDeath(this, 0 /* flags */);
367         } catch (RemoteException e) {
368             // The caller has died, so clean up TaskPositioningController.
369             mService.mTaskPositioningController.finishTaskPositioning();
370             return;
371         }
372         mTask = win.getTask();
373         // Use the bounds of the task which accounts for
374         // multiple app windows. Don't use any bounds from win itself as it
375         // may not be the same size as the task.
376         mTask.getBounds(mTmpRect);
377         startDrag(resize, preserveOrientation, startX, startY, mTmpRect);
378     }
379 
startDrag(boolean resize, boolean preserveOrientation, float startX, float startY, Rect startBounds)380     protected void startDrag(boolean resize, boolean preserveOrientation,
381                    float startX, float startY, Rect startBounds) {
382         mCtrlType = CTRL_NONE;
383         mStartDragX = startX;
384         mStartDragY = startY;
385         mPreserveOrientation = preserveOrientation;
386 
387         if (resize) {
388             if (startX < startBounds.left) {
389                 mCtrlType |= CTRL_LEFT;
390             }
391             if (startX > startBounds.right) {
392                 mCtrlType |= CTRL_RIGHT;
393             }
394             if (startY < startBounds.top) {
395                 mCtrlType |= CTRL_TOP;
396             }
397             if (startY > startBounds.bottom) {
398                 mCtrlType |= CTRL_BOTTOM;
399             }
400             mResizing = mCtrlType != CTRL_NONE;
401         }
402 
403         // In case of !isDockedInEffect we are using the union of all task bounds. These might be
404         // made up out of multiple windows which are only partially overlapping. When that happens,
405         // the orientation from the window of interest to the entire stack might diverge. However
406         // for now we treat them as the same.
407         mStartOrientationWasLandscape = startBounds.width() >= startBounds.height();
408         mWindowOriginalBounds.set(startBounds);
409 
410         // Notify the app that resizing has started, even though we haven't received any new
411         // bounds yet. This will guarantee that the app starts the backdrop renderer before
412         // configuration changes which could cause an activity restart.
413         if (mResizing) {
414             synchronized (mService.mGlobalLock) {
415                 notifyMoveLocked(startX, startY);
416             }
417 
418             // Perform the resize on the WMS handler thread when we don't have the WMS lock held
419             // to ensure that we don't deadlock WMS and AMS. Note that WindowPositionerEventReceiver
420             // callbacks are delivered on the same handler so this initial resize is always
421             // guaranteed to happen before subsequent drag resizes.
422             mService.mH.post(() -> {
423                 try {
424                     mActivityManager.resizeTask(
425                             mTask.mTaskId, startBounds, RESIZE_MODE_USER_FORCED);
426                 } catch (RemoteException e) {
427                 }
428             });
429         }
430 
431         // Make sure we always have valid drag bounds even if the drag ends before any move events
432         // have been handled.
433         mWindowDragBounds.set(startBounds);
434     }
435 
endDragLocked()436     private void endDragLocked() {
437         mResizing = false;
438         mTask.setDragResizing(false, DRAG_RESIZE_MODE_FREEFORM);
439     }
440 
441     /** Returns true if the move operation should be ended. */
notifyMoveLocked(float x, float y)442     private boolean notifyMoveLocked(float x, float y) {
443         if (DEBUG_TASK_POSITIONING) {
444             Slog.d(TAG, "notifyMoveLocked: {" + x + "," + y + "}");
445         }
446 
447         if (mCtrlType != CTRL_NONE) {
448             resizeDrag(x, y);
449             mTask.setDragResizing(true, DRAG_RESIZE_MODE_FREEFORM);
450             return false;
451         }
452 
453         // This is a moving or scrolling operation.
454         mTask.mStack.getDimBounds(mTmpRect);
455         // If a target window is covered by system bar, there is no way to move it again by touch.
456         // So we exclude them from stack bounds. and then it will be shown inside stable area.
457         Rect stableBounds = new Rect();
458         mDisplayContent.getStableRect(stableBounds);
459         mTmpRect.intersect(stableBounds);
460 
461         int nX = (int) x;
462         int nY = (int) y;
463         if (!mTmpRect.contains(nX, nY)) {
464             // For a moving operation we allow the pointer to go out of the stack bounds, but
465             // use the clamped pointer position for the drag bounds computation.
466             nX = Math.min(Math.max(nX, mTmpRect.left), mTmpRect.right);
467             nY = Math.min(Math.max(nY, mTmpRect.top), mTmpRect.bottom);
468         }
469 
470         updateWindowDragBounds(nX, nY, mTmpRect);
471         return false;
472     }
473 
474     /**
475      * The user is drag - resizing the window.
476      *
477      * @param x The x coordinate of the current drag coordinate.
478      * @param y the y coordinate of the current drag coordinate.
479      */
480     @VisibleForTesting
resizeDrag(float x, float y)481     void resizeDrag(float x, float y) {
482         // This is a resizing operation.
483         // We need to keep various constraints:
484         // 1. mMinVisible[Width/Height] <= [width/height] <= mMaxVisibleSize.[x/y]
485         // 2. The orientation is kept - if required.
486         final int deltaX = Math.round(x - mStartDragX);
487         final int deltaY = Math.round(y - mStartDragY);
488         int left = mWindowOriginalBounds.left;
489         int top = mWindowOriginalBounds.top;
490         int right = mWindowOriginalBounds.right;
491         int bottom = mWindowOriginalBounds.bottom;
492 
493         // The aspect which we have to respect. Note that if the orientation does not need to be
494         // preserved the aspect will be calculated as 1.0 which neutralizes the following
495         // computations.
496         final float minAspect = !mPreserveOrientation
497                 ? 1.0f
498                 : (mStartOrientationWasLandscape ? MIN_ASPECT : (1.0f / MIN_ASPECT));
499         // Calculate the resulting width and height of the drag operation.
500         int width = right - left;
501         int height = bottom - top;
502         if ((mCtrlType & CTRL_LEFT) != 0) {
503             width = Math.max(mMinVisibleWidth, width - deltaX);
504         } else if ((mCtrlType & CTRL_RIGHT) != 0) {
505             width = Math.max(mMinVisibleWidth, width + deltaX);
506         }
507         if ((mCtrlType & CTRL_TOP) != 0) {
508             height = Math.max(mMinVisibleHeight, height - deltaY);
509         } else if ((mCtrlType & CTRL_BOTTOM) != 0) {
510             height = Math.max(mMinVisibleHeight, height + deltaY);
511         }
512 
513         // If we have to preserve the orientation - check that we are doing so.
514         final float aspect = (float) width / (float) height;
515         if (mPreserveOrientation && ((mStartOrientationWasLandscape && aspect < MIN_ASPECT)
516                 || (!mStartOrientationWasLandscape && aspect > (1.0 / MIN_ASPECT)))) {
517             // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major
518             // drag axis. What ever is producing the bigger rectangle will be chosen.
519             int width1;
520             int width2;
521             int height1;
522             int height2;
523             if (mStartOrientationWasLandscape) {
524                 // Assuming that the width is our target we calculate the height.
525                 width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width));
526                 height1 = Math.min(height, Math.round((float)width1 / MIN_ASPECT));
527                 if (height1 < mMinVisibleHeight) {
528                     // If the resulting height is too small we adjust to the minimal size.
529                     height1 = mMinVisibleHeight;
530                     width1 = Math.max(mMinVisibleWidth,
531                             Math.min(mMaxVisibleSize.x, Math.round((float)height1 * MIN_ASPECT)));
532                 }
533                 // Assuming that the height is our target we calculate the width.
534                 height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height));
535                 width2 = Math.max(width, Math.round((float)height2 * MIN_ASPECT));
536                 if (width2 < mMinVisibleWidth) {
537                     // If the resulting width is too small we adjust to the minimal size.
538                     width2 = mMinVisibleWidth;
539                     height2 = Math.max(mMinVisibleHeight,
540                             Math.min(mMaxVisibleSize.y, Math.round((float)width2 / MIN_ASPECT)));
541                 }
542             } else {
543                 // Assuming that the width is our target we calculate the height.
544                 width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width));
545                 height1 = Math.max(height, Math.round((float)width1 * MIN_ASPECT));
546                 if (height1 < mMinVisibleHeight) {
547                     // If the resulting height is too small we adjust to the minimal size.
548                     height1 = mMinVisibleHeight;
549                     width1 = Math.max(mMinVisibleWidth,
550                             Math.min(mMaxVisibleSize.x, Math.round((float)height1 / MIN_ASPECT)));
551                 }
552                 // Assuming that the height is our target we calculate the width.
553                 height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height));
554                 width2 = Math.min(width, Math.round((float)height2 / MIN_ASPECT));
555                 if (width2 < mMinVisibleWidth) {
556                     // If the resulting width is too small we adjust to the minimal size.
557                     width2 = mMinVisibleWidth;
558                     height2 = Math.max(mMinVisibleHeight,
559                             Math.min(mMaxVisibleSize.y, Math.round((float)width2 * MIN_ASPECT)));
560                 }
561             }
562 
563             // Use the bigger of the two rectangles if the major change was positive, otherwise
564             // do the opposite.
565             final boolean grows = width > (right - left) || height > (bottom - top);
566             if (grows == (width1 * height1 > width2 * height2)) {
567                 width = width1;
568                 height = height1;
569             } else {
570                 width = width2;
571                 height = height2;
572             }
573         }
574 
575         // Update mWindowDragBounds to the new drag size.
576         updateDraggedBounds(left, top, right, bottom, width, height);
577     }
578 
579     /**
580      * Given the old coordinates and the new width and height, update the mWindowDragBounds.
581      *
582      * @param left      The original left bound before the user started dragging.
583      * @param top       The original top bound before the user started dragging.
584      * @param right     The original right bound before the user started dragging.
585      * @param bottom    The original bottom bound before the user started dragging.
586      * @param newWidth  The new dragged width.
587      * @param newHeight The new dragged height.
588      */
updateDraggedBounds(int left, int top, int right, int bottom, int newWidth, int newHeight)589     void updateDraggedBounds(int left, int top, int right, int bottom, int newWidth,
590                              int newHeight) {
591         // Generate the final bounds by keeping the opposite drag edge constant.
592         if ((mCtrlType & CTRL_LEFT) != 0) {
593             left = right - newWidth;
594         } else { // Note: The right might have changed - if we pulled at the right or not.
595             right = left + newWidth;
596         }
597         if ((mCtrlType & CTRL_TOP) != 0) {
598             top = bottom - newHeight;
599         } else { // Note: The height might have changed - if we pulled at the bottom or not.
600             bottom = top + newHeight;
601         }
602 
603         mWindowDragBounds.set(left, top, right, bottom);
604 
605         checkBoundsForOrientationViolations(mWindowDragBounds);
606     }
607 
608     /**
609      * Validate bounds against orientation violations (if DEBUG_ORIENTATION_VIOLATIONS is set).
610      *
611      * @param bounds The bounds to be checked.
612      */
checkBoundsForOrientationViolations(Rect bounds)613     private void checkBoundsForOrientationViolations(Rect bounds) {
614         // When using debug check that we are not violating the given constraints.
615         if (DEBUG_ORIENTATION_VIOLATIONS) {
616             if (mStartOrientationWasLandscape != (bounds.width() >= bounds.height())) {
617                 Slog.e(TAG, "Orientation violation detected! should be "
618                         + (mStartOrientationWasLandscape ? "landscape" : "portrait")
619                         + " but is the other");
620             } else {
621                 Slog.v(TAG, "new bounds size: " + bounds.width() + " x " + bounds.height());
622             }
623             if (mMinVisibleWidth > bounds.width() || mMinVisibleHeight > bounds.height()) {
624                 Slog.v(TAG, "Minimum requirement violated: Width(min, is)=(" + mMinVisibleWidth
625                         + ", " + bounds.width() + ") Height(min,is)=("
626                         + mMinVisibleHeight + ", " + bounds.height() + ")");
627             }
628             if (mMaxVisibleSize.x < bounds.width() || mMaxVisibleSize.y < bounds.height()) {
629                 Slog.v(TAG, "Maximum requirement violated: Width(min, is)=(" + mMaxVisibleSize.x
630                         + ", " + bounds.width() + ") Height(min,is)=("
631                         + mMaxVisibleSize.y + ", " + bounds.height() + ")");
632             }
633         }
634     }
635 
updateWindowDragBounds(int x, int y, Rect stackBounds)636     private void updateWindowDragBounds(int x, int y, Rect stackBounds) {
637         final int offsetX = Math.round(x - mStartDragX);
638         final int offsetY = Math.round(y - mStartDragY);
639         mWindowDragBounds.set(mWindowOriginalBounds);
640         // Horizontally, at least mMinVisibleWidth pixels of the window should remain visible.
641         final int maxLeft = stackBounds.right - mMinVisibleWidth;
642         final int minLeft = stackBounds.left + mMinVisibleWidth - mWindowOriginalBounds.width();
643 
644         // Vertically, the top mMinVisibleHeight of the window should remain visible.
645         // (This assumes that the window caption bar is at the top of the window).
646         final int minTop = stackBounds.top;
647         final int maxTop = stackBounds.bottom - mMinVisibleHeight;
648 
649         mWindowDragBounds.offsetTo(
650                 Math.min(Math.max(mWindowOriginalBounds.left + offsetX, minLeft), maxLeft),
651                 Math.min(Math.max(mWindowOriginalBounds.top + offsetY, minTop), maxTop));
652 
653         if (DEBUG_TASK_POSITIONING) Slog.d(TAG,
654                 "updateWindowDragBounds: " + mWindowDragBounds);
655     }
656 
toShortString()657     public String toShortString() {
658         return TAG;
659     }
660 
setFactory(Factory factory)661     static void setFactory(Factory factory) {
662         sFactory = factory;
663     }
664 
create(WindowManagerService service)665     static TaskPositioner create(WindowManagerService service) {
666         if (sFactory == null) {
667             sFactory = new Factory() {};
668         }
669 
670         return sFactory.create(service);
671     }
672 
673     @Override
binderDied()674     public void binderDied() {
675         mService.mTaskPositioningController.finishTaskPositioning();
676     }
677 
678     interface Factory {
create(WindowManagerService service)679         default TaskPositioner create(WindowManagerService service) {
680             return new TaskPositioner(service);
681         }
682     }
683 }
684