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