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.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; 22 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; 23 24 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM; 25 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT; 26 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE; 27 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT; 28 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP; 29 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; 30 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING; 31 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; 32 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 33 import static com.android.server.wm.WindowManagerService.dipToPixel; 34 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP; 35 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP; 36 37 import static java.util.concurrent.CompletableFuture.completedFuture; 38 39 import android.annotation.NonNull; 40 import android.graphics.Point; 41 import android.graphics.Rect; 42 import android.os.Binder; 43 import android.os.IBinder; 44 import android.os.InputConfig; 45 import android.os.RemoteException; 46 import android.os.Trace; 47 import android.util.DisplayMetrics; 48 import android.util.Slog; 49 import android.view.BatchedInputEventReceiver; 50 import android.view.InputApplicationHandle; 51 import android.view.InputChannel; 52 import android.view.InputDevice; 53 import android.view.InputEvent; 54 import android.view.InputEventReceiver; 55 import android.view.InputWindowHandle; 56 import android.view.MotionEvent; 57 import android.view.WindowManager; 58 59 import com.android.internal.annotations.VisibleForTesting; 60 import com.android.internal.policy.TaskResizingAlgorithm; 61 import com.android.internal.policy.TaskResizingAlgorithm.CtrlType; 62 import com.android.internal.protolog.common.ProtoLog; 63 64 import java.util.concurrent.CompletableFuture; 65 66 class TaskPositioner implements IBinder.DeathRecipient { 67 private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false; 68 private static final String TAG_LOCAL = "TaskPositioner"; 69 private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM; 70 71 private static Factory sFactory; 72 73 public static final float RESIZING_HINT_ALPHA = 0.5f; 74 75 public static final int RESIZING_HINT_DURATION_MS = 0; 76 77 private final WindowManagerService mService; 78 private InputEventReceiver mInputEventReceiver; 79 private DisplayContent mDisplayContent; 80 private Rect mTmpRect = new Rect(); 81 private int mMinVisibleWidth; 82 private int mMinVisibleHeight; 83 84 @VisibleForTesting 85 Task mTask; 86 WindowState mWindow; 87 private boolean mResizing; 88 private boolean mPreserveOrientation; 89 private boolean mStartOrientationWasLandscape; 90 private final Rect mWindowOriginalBounds = new Rect(); 91 private final Rect mWindowDragBounds = new Rect(); 92 private final Point mMaxVisibleSize = new Point(); 93 private float mStartDragX; 94 private float mStartDragY; 95 @CtrlType 96 private int mCtrlType = CTRL_NONE; 97 @VisibleForTesting 98 boolean mDragEnded; 99 IBinder mClientCallback; 100 101 InputChannel mClientChannel; 102 InputApplicationHandle mDragApplicationHandle; 103 InputWindowHandle mDragWindowHandle; 104 105 /** Use {@link #create(WindowManagerService)} instead. */ 106 @VisibleForTesting TaskPositioner(WindowManagerService service)107 TaskPositioner(WindowManagerService service) { 108 mService = service; 109 } 110 onInputEvent(InputEvent event)111 private boolean onInputEvent(InputEvent event) { 112 // All returns need to be in the try block to make sure the finishInputEvent is 113 // called correctly. 114 if (!(event instanceof MotionEvent) 115 || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) { 116 return false; 117 } 118 final MotionEvent motionEvent = (MotionEvent) event; 119 if (mDragEnded) { 120 // The drag has ended but the clean-up message has not been processed by 121 // window manager. Drop events that occur after this until window manager 122 // has a chance to clean-up the input handle. 123 return true; 124 } 125 126 final float newX = motionEvent.getRawX(); 127 final float newY = motionEvent.getRawY(); 128 129 switch (motionEvent.getAction()) { 130 case MotionEvent.ACTION_DOWN: { 131 if (DEBUG_TASK_POSITIONING) { 132 Slog.w(TAG, "ACTION_DOWN @ {" + newX + ", " + newY + "}"); 133 } 134 } 135 break; 136 137 case MotionEvent.ACTION_MOVE: { 138 if (DEBUG_TASK_POSITIONING) { 139 Slog.w(TAG, "ACTION_MOVE @ {" + newX + ", " + newY + "}"); 140 } 141 synchronized (mService.mGlobalLock) { 142 mDragEnded = notifyMoveLocked(newX, newY); 143 mTask.getDimBounds(mTmpRect); 144 } 145 if (!mTmpRect.equals(mWindowDragBounds)) { 146 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, 147 "wm.TaskPositioner.resizeTask"); 148 mService.mAtmService.resizeTask( 149 mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER); 150 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); 151 } 152 } 153 break; 154 155 case MotionEvent.ACTION_UP: { 156 if (DEBUG_TASK_POSITIONING) { 157 Slog.w(TAG, "ACTION_UP @ {" + newX + ", " + newY + "}"); 158 } 159 mDragEnded = true; 160 } 161 break; 162 163 case MotionEvent.ACTION_CANCEL: { 164 if (DEBUG_TASK_POSITIONING) { 165 Slog.w(TAG, "ACTION_CANCEL @ {" + newX + ", " + newY + "}"); 166 } 167 mDragEnded = true; 168 } 169 break; 170 } 171 172 if (mDragEnded) { 173 final boolean wasResizing = mResizing; 174 synchronized (mService.mGlobalLock) { 175 endDragLocked(); 176 mTask.getDimBounds(mTmpRect); 177 } 178 if (wasResizing && !mTmpRect.equals(mWindowDragBounds)) { 179 // We were using fullscreen surface during resizing. Request 180 // resizeTask() one last time to restore surface to window size. 181 mService.mAtmService.resizeTask( 182 mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED); 183 } 184 185 // Post back to WM to handle clean-ups. We still need the input 186 // event handler for the last finishInputEvent()! 187 mService.mTaskPositioningController.finishTaskPositioning(); 188 } 189 return true; 190 } 191 192 @VisibleForTesting getWindowDragBounds()193 Rect getWindowDragBounds() { 194 return mWindowDragBounds; 195 } 196 197 /** 198 * @param displayContent The Display that the window being dragged is on. 199 * @param win The window which will be dragged. 200 */ register(DisplayContent displayContent, @NonNull WindowState win)201 CompletableFuture<Void> register(DisplayContent displayContent, @NonNull WindowState win) { 202 if (DEBUG_TASK_POSITIONING) { 203 Slog.d(TAG, "Registering task positioner"); 204 } 205 206 if (mClientChannel != null) { 207 Slog.e(TAG, "Task positioner already registered"); 208 return completedFuture(null); 209 } 210 211 mDisplayContent = displayContent; 212 mClientChannel = mService.mInputManager.createInputChannel(TAG); 213 214 mInputEventReceiver = new BatchedInputEventReceiver.SimpleBatchedInputEventReceiver( 215 mClientChannel, mService.mAnimationHandler.getLooper(), 216 mService.mAnimator.getChoreographer(), this::onInputEvent); 217 218 mDragApplicationHandle = new InputApplicationHandle(new Binder(), TAG, 219 DEFAULT_DISPATCHING_TIMEOUT_MILLIS); 220 221 mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, 222 displayContent.getDisplayId()); 223 mDragWindowHandle.name = TAG; 224 mDragWindowHandle.token = mClientChannel.getToken(); 225 mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG; 226 mDragWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS; 227 mDragWindowHandle.ownerPid = WindowManagerService.MY_PID; 228 mDragWindowHandle.ownerUid = WindowManagerService.MY_UID; 229 mDragWindowHandle.scaleFactor = 1.0f; 230 // When dragging the window around, we do not want to steal focus for the window. 231 mDragWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE; 232 233 // The drag window cannot receive new touches. 234 mDragWindowHandle.touchableRegion.setEmpty(); 235 236 // Pause rotations before a drag. 237 ProtoLog.d(WM_DEBUG_ORIENTATION, "Pausing rotation during re-position"); 238 mDisplayContent.getDisplayRotation().pause(); 239 240 // Notify InputMonitor to take mDragWindowHandle. 241 return mService.mTaskPositioningController.showInputSurface(win.getDisplayId()) 242 .thenRun(() -> { 243 // The global lock is held by the callers of register but released before the async 244 // results are waited on. We must acquire the lock in this callback to ensure thread 245 // safety. 246 synchronized (mService.mGlobalLock) { 247 final Rect displayBounds = mTmpRect; 248 displayContent.getBounds(displayBounds); 249 final DisplayMetrics displayMetrics = displayContent.getDisplayMetrics(); 250 mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, displayMetrics); 251 mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, displayMetrics); 252 mMaxVisibleSize.set(displayBounds.width(), displayBounds.height()); 253 254 mDragEnded = false; 255 256 try { 257 mClientCallback = win.mClient.asBinder(); 258 mClientCallback.linkToDeath(this, 0 /* flags */); 259 } catch (RemoteException e) { 260 // The caller has died, so clean up TaskPositioningController. 261 mService.mTaskPositioningController.finishTaskPositioning(); 262 return; 263 } 264 mWindow = win; 265 mTask = win.getTask(); 266 } 267 }); 268 } 269 270 void unregister() { 271 if (DEBUG_TASK_POSITIONING) { 272 Slog.d(TAG, "Unregistering task positioner"); 273 } 274 275 if (mClientChannel == null) { 276 Slog.e(TAG, "Task positioner not registered"); 277 return; 278 } 279 280 mService.mTaskPositioningController.hideInputSurface(mDisplayContent.getDisplayId()); 281 mService.mInputManager.removeInputChannel(mClientChannel.getToken()); 282 283 mInputEventReceiver.dispose(); 284 mInputEventReceiver = null; 285 mClientChannel.dispose(); 286 mClientChannel = null; 287 288 mDragWindowHandle = null; 289 mDragApplicationHandle = null; 290 mDragEnded = true; 291 292 // Notify InputMonitor to remove mDragWindowHandle. 293 mDisplayContent.getInputMonitor().updateInputWindowsLw(true /*force*/); 294 295 // Resume rotations after a drag. 296 ProtoLog.d(WM_DEBUG_ORIENTATION, "Resuming rotation after re-position"); 297 mDisplayContent.getDisplayRotation().resume(); 298 mDisplayContent = null; 299 if (mClientCallback != null) { 300 mClientCallback.unlinkToDeath(this, 0 /* flags */); 301 } 302 mWindow = null; 303 } 304 305 /** 306 * Starts moving or resizing the task. This method should be only called from 307 * {@link TaskPositioningController#startPositioningLocked} or unit tests. 308 */ 309 void startDrag(boolean resize, boolean preserveOrientation, float startX, float startY) { 310 if (DEBUG_TASK_POSITIONING) { 311 Slog.d(TAG, "startDrag: win=" + mWindow + ", resize=" + resize 312 + ", preserveOrientation=" + preserveOrientation + ", {" + startX + ", " 313 + startY + "}"); 314 } 315 // Use the bounds of the task which accounts for 316 // multiple app windows. Don't use any bounds from win itself as it 317 // may not be the same size as the task. 318 final Rect startBounds = mTmpRect; 319 mTask.getBounds(startBounds); 320 321 mCtrlType = CTRL_NONE; 322 mStartDragX = startX; 323 mStartDragY = startY; 324 mPreserveOrientation = preserveOrientation; 325 326 if (resize) { 327 if (startX < startBounds.left) { 328 mCtrlType |= CTRL_LEFT; 329 } 330 if (startX > startBounds.right) { 331 mCtrlType |= CTRL_RIGHT; 332 } 333 if (startY < startBounds.top) { 334 mCtrlType |= CTRL_TOP; 335 } 336 if (startY > startBounds.bottom) { 337 mCtrlType |= CTRL_BOTTOM; 338 } 339 mResizing = mCtrlType != CTRL_NONE; 340 } 341 342 // In case of !isDockedInEffect we are using the union of all task bounds. These might be 343 // made up out of multiple windows which are only partially overlapping. When that happens, 344 // the orientation from the window of interest to the entire stack might diverge. However 345 // for now we treat them as the same. 346 mStartOrientationWasLandscape = startBounds.width() >= startBounds.height(); 347 mWindowOriginalBounds.set(startBounds); 348 349 // Notify the app that resizing has started, even though we haven't received any new 350 // bounds yet. This will guarantee that the app starts the backdrop renderer before 351 // configuration changes which could cause an activity restart. 352 if (mResizing) { 353 notifyMoveLocked(startX, startY); 354 355 // The WindowPositionerEventReceiver callbacks are delivered on the same handler so this 356 // initial resize is always guaranteed to happen before subsequent drag resizes. 357 mService.mH.post(() -> { 358 mService.mAtmService.resizeTask( 359 mTask.mTaskId, startBounds, RESIZE_MODE_USER_FORCED); 360 }); 361 } 362 363 // Make sure we always have valid drag bounds even if the drag ends before any move events 364 // have been handled. 365 mWindowDragBounds.set(startBounds); 366 } 367 368 private void endDragLocked() { 369 mResizing = false; 370 mTask.setDragResizing(false); 371 } 372 373 /** Returns true if the move operation should be ended. */ 374 @VisibleForTesting 375 boolean notifyMoveLocked(float x, float y) { 376 if (DEBUG_TASK_POSITIONING) { 377 Slog.d(TAG, "notifyMoveLocked: {" + x + "," + y + "}"); 378 } 379 380 if (mCtrlType != CTRL_NONE) { 381 resizeDrag(x, y); 382 mTask.setDragResizing(true); 383 return false; 384 } 385 386 // This is a moving or scrolling operation. 387 // Only allow to move in stable area so the target window won't be covered by system bar. 388 // Though {@link Task#resolveOverrideConfiguration} should also avoid the case. 389 mDisplayContent.getStableRect(mTmpRect); 390 // The task may be put in a limited display area. 391 mTmpRect.intersect(mTask.getRootTask().getParent().getBounds()); 392 393 int nX = (int) x; 394 int nY = (int) y; 395 if (!mTmpRect.contains(nX, nY)) { 396 // For a moving operation we allow the pointer to go out of the stack bounds, but 397 // use the clamped pointer position for the drag bounds computation. 398 nX = Math.min(Math.max(nX, mTmpRect.left), mTmpRect.right); 399 nY = Math.min(Math.max(nY, mTmpRect.top), mTmpRect.bottom); 400 } 401 402 updateWindowDragBounds(nX, nY, mTmpRect); 403 return false; 404 } 405 406 /** 407 * The user is drag - resizing the window. 408 * 409 * @param x The x coordinate of the current drag coordinate. 410 * @param y the y coordinate of the current drag coordinate. 411 */ 412 @VisibleForTesting 413 void resizeDrag(float x, float y) { 414 updateDraggedBounds(TaskResizingAlgorithm.resizeDrag(x, y, mStartDragX, mStartDragY, 415 mWindowOriginalBounds, mCtrlType, mMinVisibleWidth, mMinVisibleHeight, 416 mMaxVisibleSize, mPreserveOrientation, mStartOrientationWasLandscape)); 417 } 418 419 private void updateDraggedBounds(Rect newBounds) { 420 mWindowDragBounds.set(newBounds); 421 422 checkBoundsForOrientationViolations(mWindowDragBounds); 423 } 424 425 /** 426 * Validate bounds against orientation violations (if DEBUG_ORIENTATION_VIOLATIONS is set). 427 * 428 * @param bounds The bounds to be checked. 429 */ 430 private void checkBoundsForOrientationViolations(Rect bounds) { 431 // When using debug check that we are not violating the given constraints. 432 if (DEBUG_ORIENTATION_VIOLATIONS) { 433 if (mStartOrientationWasLandscape != (bounds.width() >= bounds.height())) { 434 Slog.e(TAG, "Orientation violation detected! should be " 435 + (mStartOrientationWasLandscape ? "landscape" : "portrait") 436 + " but is the other"); 437 } else { 438 Slog.v(TAG, "new bounds size: " + bounds.width() + " x " + bounds.height()); 439 } 440 if (mMinVisibleWidth > bounds.width() || mMinVisibleHeight > bounds.height()) { 441 Slog.v(TAG, "Minimum requirement violated: Width(min, is)=(" + mMinVisibleWidth 442 + ", " + bounds.width() + ") Height(min,is)=(" 443 + mMinVisibleHeight + ", " + bounds.height() + ")"); 444 } 445 if (mMaxVisibleSize.x < bounds.width() || mMaxVisibleSize.y < bounds.height()) { 446 Slog.v(TAG, "Maximum requirement violated: Width(min, is)=(" + mMaxVisibleSize.x 447 + ", " + bounds.width() + ") Height(min,is)=(" 448 + mMaxVisibleSize.y + ", " + bounds.height() + ")"); 449 } 450 } 451 } 452 453 private void updateWindowDragBounds(int x, int y, Rect rootTaskBounds) { 454 final int offsetX = Math.round(x - mStartDragX); 455 final int offsetY = Math.round(y - mStartDragY); 456 mWindowDragBounds.set(mWindowOriginalBounds); 457 // Horizontally, at least mMinVisibleWidth pixels of the window should remain visible. 458 final int maxLeft = rootTaskBounds.right - mMinVisibleWidth; 459 final int minLeft = rootTaskBounds.left + mMinVisibleWidth - mWindowOriginalBounds.width(); 460 461 // Vertically, the top mMinVisibleHeight of the window should remain visible. 462 // (This assumes that the window caption bar is at the top of the window). 463 final int minTop = rootTaskBounds.top; 464 final int maxTop = rootTaskBounds.bottom - mMinVisibleHeight; 465 466 mWindowDragBounds.offsetTo( 467 Math.min(Math.max(mWindowOriginalBounds.left + offsetX, minLeft), maxLeft), 468 Math.min(Math.max(mWindowOriginalBounds.top + offsetY, minTop), maxTop)); 469 470 if (DEBUG_TASK_POSITIONING) Slog.d(TAG, 471 "updateWindowDragBounds: " + mWindowDragBounds); 472 } 473 474 public String toShortString() { 475 return TAG; 476 } 477 478 static void setFactory(Factory factory) { 479 sFactory = factory; 480 } 481 482 static TaskPositioner create(WindowManagerService service) { 483 if (sFactory == null) { 484 sFactory = new Factory() {}; 485 } 486 487 return sFactory.create(service); 488 } 489 490 @Override 491 public void binderDied() { 492 mService.mTaskPositioningController.finishTaskPositioning(); 493 } 494 495 interface Factory { 496 default TaskPositioner create(WindowManagerService service) { 497 return new TaskPositioner(service); 498 } 499 } 500 } 501