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.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; 20 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; 21 import static android.app.ActivityManager.RESIZE_MODE_USER; 22 import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED; 23 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; 24 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 25 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; 26 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION; 27 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING; 28 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; 29 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; 30 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 31 import static com.android.server.wm.WindowManagerService.dipToPixel; 32 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM; 33 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP; 34 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP; 35 36 import android.annotation.IntDef; 37 import android.graphics.Point; 38 import android.graphics.Rect; 39 import android.os.Looper; 40 import android.os.Process; 41 import android.os.RemoteException; 42 import android.os.Trace; 43 import android.util.DisplayMetrics; 44 import android.util.Slog; 45 import android.view.BatchedInputEventReceiver; 46 import android.view.Choreographer; 47 import android.view.Display; 48 import android.view.DisplayInfo; 49 import android.view.InputChannel; 50 import android.view.InputDevice; 51 import android.view.InputEvent; 52 import android.view.MotionEvent; 53 import android.view.WindowManager; 54 55 import com.android.internal.annotations.VisibleForTesting; 56 import com.android.server.input.InputApplicationHandle; 57 import com.android.server.input.InputWindowHandle; 58 import com.android.server.wm.WindowManagerService.H; 59 60 import java.lang.annotation.Retention; 61 import java.lang.annotation.RetentionPolicy; 62 63 class TaskPositioner implements DimLayer.DimLayerUser { 64 private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false; 65 private static final String TAG_LOCAL = "TaskPositioner"; 66 private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM; 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 WindowPositionerEventReceiver mInputEventReceiver; 101 private Display mDisplay; 102 private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); 103 private DimLayer mDimLayer; 104 @CtrlType 105 private int mCurrentDimSide; 106 private Rect mTmpRect = new Rect(); 107 private int mSideMargin; 108 private int mMinVisibleWidth; 109 private int mMinVisibleHeight; 110 111 private Task mTask; 112 private boolean mResizing; 113 private boolean mPreserveOrientation; 114 private boolean mStartOrientationWasLandscape; 115 private final Rect mWindowOriginalBounds = new Rect(); 116 private final Rect mWindowDragBounds = new Rect(); 117 private final Point mMaxVisibleSize = new Point(); 118 private float mStartDragX; 119 private float mStartDragY; 120 @CtrlType 121 private int mCtrlType = CTRL_NONE; 122 private boolean mDragEnded = false; 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, int displayId)136 public void onInputEvent(InputEvent event, int displayId) { 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.mWindowMap) { 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 mService.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.mWindowMap) { 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 mService.mActivityManager.resizeTask( 209 mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED); 210 } 211 212 if (mCurrentDimSide != CTRL_NONE) { 213 final int createMode = mCurrentDimSide == CTRL_LEFT 214 ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT 215 : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; 216 mService.mActivityManager.moveTaskToDockedStack( 217 mTask.mTaskId, createMode, true /*toTop*/, true /* animate */, 218 null /* initialBounds */); 219 } 220 } catch(RemoteException e) {} 221 222 // Post back to WM to handle clean-ups. We still need the input 223 // event handler for the last finishInputEvent()! 224 mService.mH.sendEmptyMessage(H.FINISH_TASK_POSITIONING); 225 } 226 handled = true; 227 } catch (Exception e) { 228 Slog.e(TAG, "Exception caught by drag handleMotion", e); 229 } finally { 230 finishInputEvent(event, handled); 231 } 232 } 233 } 234 TaskPositioner(WindowManagerService service)235 TaskPositioner(WindowManagerService service) { 236 mService = service; 237 } 238 239 @VisibleForTesting getWindowDragBounds()240 Rect getWindowDragBounds() { 241 return mWindowDragBounds; 242 } 243 244 /** 245 * @param display The Display that the window being dragged is on. 246 */ register(Display display)247 void register(Display display) { 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 mDisplay = display; 258 mDisplay.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(null); 269 mDragApplicationHandle.name = TAG; 270 mDragApplicationHandle.dispatchingTimeoutNanos = 271 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; 272 273 mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, null, 274 mDisplay.getDisplayId()); 275 mDragWindowHandle.name = TAG; 276 mDragWindowHandle.inputChannel = mServerChannel; 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 mDisplay.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 mService.pauseRotationLocked(); 308 309 mDimLayer = new DimLayer(mService, this, mDisplay.getDisplayId(), TAG_LOCAL); 310 mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics); 311 mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics); 312 mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics); 313 mDisplay.getRealSize(mMaxVisibleSize); 314 315 mDragEnded = false; 316 } 317 unregister()318 void unregister() { 319 if (DEBUG_TASK_POSITIONING) { 320 Slog.d(TAG, "Unregistering task positioner"); 321 } 322 323 if (mClientChannel == null) { 324 Slog.e(TAG, "Task positioner not registered"); 325 return; 326 } 327 328 mService.mInputManager.unregisterInputChannel(mServerChannel); 329 330 mInputEventReceiver.dispose(); 331 mInputEventReceiver = null; 332 mClientChannel.dispose(); 333 mServerChannel.dispose(); 334 mClientChannel = null; 335 mServerChannel = null; 336 337 mDragWindowHandle = null; 338 mDragApplicationHandle = null; 339 mDisplay = null; 340 341 if (mDimLayer != null) { 342 mDimLayer.destroySurface(); 343 mDimLayer = null; 344 } 345 mCurrentDimSide = CTRL_NONE; 346 mDragEnded = true; 347 348 // Resume rotations after a drag. 349 if (DEBUG_ORIENTATION) { 350 Slog.d(TAG, "Resuming rotation after re-position"); 351 } 352 mService.resumeRotationLocked(); 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 mTask = win.getTask(); 363 // Use the dim bounds, not the original task bounds. The cursor 364 // movement should be calculated relative to the visible bounds. 365 // Also, use the dim bounds of the task which accounts for 366 // multiple app windows. Don't use any bounds from win itself as it 367 // may not be the same size as the task. 368 mTask.getDimBounds(mTmpRect); 369 startDrag(resize, preserveOrientation, startX, startY, mTmpRect); 370 } 371 372 @VisibleForTesting startDrag(boolean resize, boolean preserveOrientation, float startX, float startY, Rect startBounds)373 void startDrag(boolean resize, boolean preserveOrientation, 374 float startX, float startY, Rect startBounds) { 375 mCtrlType = CTRL_NONE; 376 mStartDragX = startX; 377 mStartDragY = startY; 378 mPreserveOrientation = preserveOrientation; 379 380 if (resize) { 381 if (startX < startBounds.left) { 382 mCtrlType |= CTRL_LEFT; 383 } 384 if (startX > startBounds.right) { 385 mCtrlType |= CTRL_RIGHT; 386 } 387 if (startY < startBounds.top) { 388 mCtrlType |= CTRL_TOP; 389 } 390 if (startY > startBounds.bottom) { 391 mCtrlType |= CTRL_BOTTOM; 392 } 393 mResizing = mCtrlType != CTRL_NONE; 394 } 395 396 // In case of !isDockedInEffect we are using the union of all task bounds. These might be 397 // made up out of multiple windows which are only partially overlapping. When that happens, 398 // the orientation from the window of interest to the entire stack might diverge. However 399 // for now we treat them as the same. 400 mStartOrientationWasLandscape = startBounds.width() >= startBounds.height(); 401 mWindowOriginalBounds.set(startBounds); 402 403 // Make sure we always have valid drag bounds even if the drag ends before any move events 404 // have been handled. 405 mWindowDragBounds.set(startBounds); 406 } 407 endDragLocked()408 private void endDragLocked() { 409 mResizing = false; 410 mTask.setDragResizing(false, DRAG_RESIZE_MODE_FREEFORM); 411 } 412 413 /** Returns true if the move operation should be ended. */ notifyMoveLocked(float x, float y)414 private boolean notifyMoveLocked(float x, float y) { 415 if (DEBUG_TASK_POSITIONING) { 416 Slog.d(TAG, "notifyMoveLocked: {" + x + "," + y + "}"); 417 } 418 419 if (mCtrlType != CTRL_NONE) { 420 resizeDrag(x, y); 421 mTask.setDragResizing(true, DRAG_RESIZE_MODE_FREEFORM); 422 return false; 423 } 424 425 // This is a moving or scrolling operation. 426 mTask.mStack.getDimBounds(mTmpRect); 427 428 int nX = (int) x; 429 int nY = (int) y; 430 if (!mTmpRect.contains(nX, nY)) { 431 // For a moving operation we allow the pointer to go out of the stack bounds, but 432 // use the clamped pointer position for the drag bounds computation. 433 nX = Math.min(Math.max(nX, mTmpRect.left), mTmpRect.right); 434 nY = Math.min(Math.max(nY, mTmpRect.top), mTmpRect.bottom); 435 } 436 437 updateWindowDragBounds(nX, nY, mTmpRect); 438 updateDimLayerVisibility(nX); 439 return false; 440 } 441 442 /** 443 * The user is drag - resizing the window. 444 * 445 * @param x The x coordinate of the current drag coordinate. 446 * @param y the y coordinate of the current drag coordinate. 447 */ 448 @VisibleForTesting resizeDrag(float x, float y)449 void resizeDrag(float x, float y) { 450 // This is a resizing operation. 451 // We need to keep various constraints: 452 // 1. mMinVisible[Width/Height] <= [width/height] <= mMaxVisibleSize.[x/y] 453 // 2. The orientation is kept - if required. 454 final int deltaX = Math.round(x - mStartDragX); 455 final int deltaY = Math.round(y - mStartDragY); 456 int left = mWindowOriginalBounds.left; 457 int top = mWindowOriginalBounds.top; 458 int right = mWindowOriginalBounds.right; 459 int bottom = mWindowOriginalBounds.bottom; 460 461 // The aspect which we have to respect. Note that if the orientation does not need to be 462 // preserved the aspect will be calculated as 1.0 which neutralizes the following 463 // computations. 464 final float minAspect = !mPreserveOrientation 465 ? 1.0f 466 : (mStartOrientationWasLandscape ? MIN_ASPECT : (1.0f / MIN_ASPECT)); 467 // Calculate the resulting width and height of the drag operation. 468 int width = right - left; 469 int height = bottom - top; 470 if ((mCtrlType & CTRL_LEFT) != 0) { 471 width = Math.max(mMinVisibleWidth, width - deltaX); 472 } else if ((mCtrlType & CTRL_RIGHT) != 0) { 473 width = Math.max(mMinVisibleWidth, width + deltaX); 474 } 475 if ((mCtrlType & CTRL_TOP) != 0) { 476 height = Math.max(mMinVisibleHeight, height - deltaY); 477 } else if ((mCtrlType & CTRL_BOTTOM) != 0) { 478 height = Math.max(mMinVisibleHeight, height + deltaY); 479 } 480 481 // If we have to preserve the orientation - check that we are doing so. 482 final float aspect = (float) width / (float) height; 483 if (mPreserveOrientation && ((mStartOrientationWasLandscape && aspect < MIN_ASPECT) 484 || (!mStartOrientationWasLandscape && aspect > (1.0 / MIN_ASPECT)))) { 485 // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major 486 // drag axis. What ever is producing the bigger rectangle will be chosen. 487 int width1; 488 int width2; 489 int height1; 490 int height2; 491 if (mStartOrientationWasLandscape) { 492 // Assuming that the width is our target we calculate the height. 493 width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width)); 494 height1 = Math.min(height, Math.round((float)width1 / MIN_ASPECT)); 495 if (height1 < mMinVisibleHeight) { 496 // If the resulting height is too small we adjust to the minimal size. 497 height1 = mMinVisibleHeight; 498 width1 = Math.max(mMinVisibleWidth, 499 Math.min(mMaxVisibleSize.x, Math.round((float)height1 * MIN_ASPECT))); 500 } 501 // Assuming that the height is our target we calculate the width. 502 height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height)); 503 width2 = Math.max(width, Math.round((float)height2 * MIN_ASPECT)); 504 if (width2 < mMinVisibleWidth) { 505 // If the resulting width is too small we adjust to the minimal size. 506 width2 = mMinVisibleWidth; 507 height2 = Math.max(mMinVisibleHeight, 508 Math.min(mMaxVisibleSize.y, Math.round((float)width2 / MIN_ASPECT))); 509 } 510 } else { 511 // Assuming that the width is our target we calculate the height. 512 width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width)); 513 height1 = Math.max(height, Math.round((float)width1 * MIN_ASPECT)); 514 if (height1 < mMinVisibleHeight) { 515 // If the resulting height is too small we adjust to the minimal size. 516 height1 = mMinVisibleHeight; 517 width1 = Math.max(mMinVisibleWidth, 518 Math.min(mMaxVisibleSize.x, Math.round((float)height1 / MIN_ASPECT))); 519 } 520 // Assuming that the height is our target we calculate the width. 521 height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height)); 522 width2 = Math.min(width, Math.round((float)height2 / MIN_ASPECT)); 523 if (width2 < mMinVisibleWidth) { 524 // If the resulting width is too small we adjust to the minimal size. 525 width2 = mMinVisibleWidth; 526 height2 = Math.max(mMinVisibleHeight, 527 Math.min(mMaxVisibleSize.y, Math.round((float)width2 * MIN_ASPECT))); 528 } 529 } 530 531 // Use the bigger of the two rectangles if the major change was positive, otherwise 532 // do the opposite. 533 final boolean grows = width > (right - left) || height > (bottom - top); 534 if (grows == (width1 * height1 > width2 * height2)) { 535 width = width1; 536 height = height1; 537 } else { 538 width = width2; 539 height = height2; 540 } 541 } 542 543 // Update mWindowDragBounds to the new drag size. 544 updateDraggedBounds(left, top, right, bottom, width, height); 545 } 546 547 /** 548 * Given the old coordinates and the new width and height, update the mWindowDragBounds. 549 * 550 * @param left The original left bound before the user started dragging. 551 * @param top The original top bound before the user started dragging. 552 * @param right The original right bound before the user started dragging. 553 * @param bottom The original bottom bound before the user started dragging. 554 * @param newWidth The new dragged width. 555 * @param newHeight The new dragged height. 556 */ updateDraggedBounds(int left, int top, int right, int bottom, int newWidth, int newHeight)557 void updateDraggedBounds(int left, int top, int right, int bottom, int newWidth, 558 int newHeight) { 559 // Generate the final bounds by keeping the opposite drag edge constant. 560 if ((mCtrlType & CTRL_LEFT) != 0) { 561 left = right - newWidth; 562 } else { // Note: The right might have changed - if we pulled at the right or not. 563 right = left + newWidth; 564 } 565 if ((mCtrlType & CTRL_TOP) != 0) { 566 top = bottom - newHeight; 567 } else { // Note: The height might have changed - if we pulled at the bottom or not. 568 bottom = top + newHeight; 569 } 570 571 mWindowDragBounds.set(left, top, right, bottom); 572 573 checkBoundsForOrientationViolations(mWindowDragBounds); 574 } 575 576 /** 577 * Validate bounds against orientation violations (if DEBUG_ORIENTATION_VIOLATIONS is set). 578 * 579 * @param bounds The bounds to be checked. 580 */ checkBoundsForOrientationViolations(Rect bounds)581 private void checkBoundsForOrientationViolations(Rect bounds) { 582 // When using debug check that we are not violating the given constraints. 583 if (DEBUG_ORIENTATION_VIOLATIONS) { 584 if (mStartOrientationWasLandscape != (bounds.width() >= bounds.height())) { 585 Slog.e(TAG, "Orientation violation detected! should be " 586 + (mStartOrientationWasLandscape ? "landscape" : "portrait") 587 + " but is the other"); 588 } else { 589 Slog.v(TAG, "new bounds size: " + bounds.width() + " x " + bounds.height()); 590 } 591 if (mMinVisibleWidth > bounds.width() || mMinVisibleHeight > bounds.height()) { 592 Slog.v(TAG, "Minimum requirement violated: Width(min, is)=(" + mMinVisibleWidth 593 + ", " + bounds.width() + ") Height(min,is)=(" 594 + mMinVisibleHeight + ", " + bounds.height() + ")"); 595 } 596 if (mMaxVisibleSize.x < bounds.width() || mMaxVisibleSize.y < bounds.height()) { 597 Slog.v(TAG, "Maximum requirement violated: Width(min, is)=(" + mMaxVisibleSize.x 598 + ", " + bounds.width() + ") Height(min,is)=(" 599 + mMaxVisibleSize.y + ", " + bounds.height() + ")"); 600 } 601 } 602 } 603 updateWindowDragBounds(int x, int y, Rect stackBounds)604 private void updateWindowDragBounds(int x, int y, Rect stackBounds) { 605 final int offsetX = Math.round(x - mStartDragX); 606 final int offsetY = Math.round(y - mStartDragY); 607 mWindowDragBounds.set(mWindowOriginalBounds); 608 // Horizontally, at least mMinVisibleWidth pixels of the window should remain visible. 609 final int maxLeft = stackBounds.right - mMinVisibleWidth; 610 final int minLeft = stackBounds.left + mMinVisibleWidth - mWindowOriginalBounds.width(); 611 612 // Vertically, the top mMinVisibleHeight of the window should remain visible. 613 // (This assumes that the window caption bar is at the top of the window). 614 final int minTop = stackBounds.top; 615 final int maxTop = stackBounds.bottom - mMinVisibleHeight; 616 617 mWindowDragBounds.offsetTo( 618 Math.min(Math.max(mWindowOriginalBounds.left + offsetX, minLeft), maxLeft), 619 Math.min(Math.max(mWindowOriginalBounds.top + offsetY, minTop), maxTop)); 620 621 if (DEBUG_TASK_POSITIONING) Slog.d(TAG, 622 "updateWindowDragBounds: " + mWindowDragBounds); 623 } 624 updateDimLayerVisibility(int x)625 private void updateDimLayerVisibility(int x) { 626 @CtrlType 627 int dimSide = getDimSide(x); 628 if (dimSide == mCurrentDimSide) { 629 return; 630 } 631 632 mCurrentDimSide = dimSide; 633 634 if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION updateDimLayerVisibility"); 635 mService.openSurfaceTransaction(); 636 if (mCurrentDimSide == CTRL_NONE) { 637 mDimLayer.hide(); 638 } else { 639 showDimLayer(); 640 } 641 mService.closeSurfaceTransaction(); 642 } 643 644 /** 645 * Returns the side of the screen the dim layer should be shown. 646 * @param x horizontal coordinate used to determine if the dim layer should be shown 647 * @return Returns {@link #CTRL_LEFT} if the dim layer should be shown on the left half of the 648 * screen, {@link #CTRL_RIGHT} if on the right side, or {@link #CTRL_NONE} if the dim layer 649 * shouldn't be shown. 650 */ getDimSide(int x)651 private int getDimSide(int x) { 652 if (mTask.mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID 653 || !mTask.mStack.fillsParent() 654 || mTask.mStack.getConfiguration().orientation != ORIENTATION_LANDSCAPE) { 655 return CTRL_NONE; 656 } 657 658 mTask.mStack.getDimBounds(mTmpRect); 659 if (x - mSideMargin <= mTmpRect.left) { 660 return CTRL_LEFT; 661 } 662 if (x + mSideMargin >= mTmpRect.right) { 663 return CTRL_RIGHT; 664 } 665 666 return CTRL_NONE; 667 } 668 showDimLayer()669 private void showDimLayer() { 670 mTask.mStack.getDimBounds(mTmpRect); 671 if (mCurrentDimSide == CTRL_LEFT) { 672 mTmpRect.right = mTmpRect.centerX(); 673 } else if (mCurrentDimSide == CTRL_RIGHT) { 674 mTmpRect.left = mTmpRect.centerX(); 675 } 676 677 mDimLayer.setBounds(mTmpRect); 678 mDimLayer.show(mService.getDragLayerLocked(), RESIZING_HINT_ALPHA, 679 RESIZING_HINT_DURATION_MS); 680 } 681 682 @Override /** {@link DimLayer.DimLayerUser} */ dimFullscreen()683 public boolean dimFullscreen() { 684 return isFullscreen(); 685 } 686 isFullscreen()687 boolean isFullscreen() { 688 return false; 689 } 690 691 @Override /** {@link DimLayer.DimLayerUser} */ getDisplayInfo()692 public DisplayInfo getDisplayInfo() { 693 return mTask.mStack.getDisplayInfo(); 694 } 695 696 @Override isAttachedToDisplay()697 public boolean isAttachedToDisplay() { 698 return mTask != null && mTask.getDisplayContent() != null; 699 } 700 701 @Override getDimBounds(Rect out)702 public void getDimBounds(Rect out) { 703 // This dim layer user doesn't need this. 704 } 705 706 @Override toShortString()707 public String toShortString() { 708 return TAG; 709 } 710 } 711