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