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