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 17package com.android.server.wm; 18 19import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; 20import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; 21import static android.app.ActivityManager.RESIZE_MODE_USER; 22import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED; 23import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; 24import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 25import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; 26import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION; 27import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING; 28import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; 29import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; 30import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 31import static com.android.server.wm.WindowManagerService.dipToPixel; 32import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM; 33import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP; 34import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP; 35 36import android.annotation.IntDef; 37import android.graphics.Point; 38import android.graphics.Rect; 39import android.os.Looper; 40import android.os.Process; 41import android.os.RemoteException; 42import android.os.Trace; 43import android.util.DisplayMetrics; 44import android.util.Slog; 45import android.view.BatchedInputEventReceiver; 46import android.view.Choreographer; 47import android.view.Display; 48import android.view.DisplayInfo; 49import android.view.InputChannel; 50import android.view.InputDevice; 51import android.view.InputEvent; 52import android.view.MotionEvent; 53import android.view.SurfaceControl; 54import android.view.WindowManager; 55 56import com.android.server.input.InputApplicationHandle; 57import com.android.server.input.InputWindowHandle; 58import com.android.server.wm.WindowManagerService.H; 59 60import java.lang.annotation.Retention; 61import java.lang.annotation.RetentionPolicy; 62 63class TaskPositioner implements DimLayer.DimLayerUser { 64 private static final String TAG_LOCAL = "TaskPositioner"; 65 private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM; 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 private final WindowManagerService mService; 93 private WindowPositionerEventReceiver mInputEventReceiver; 94 private Display mDisplay; 95 private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); 96 private DimLayer mDimLayer; 97 @CtrlType 98 private int mCurrentDimSide; 99 private Rect mTmpRect = new Rect(); 100 private int mSideMargin; 101 private int mMinVisibleWidth; 102 private int mMinVisibleHeight; 103 104 private Task mTask; 105 private boolean mResizing; 106 private final Rect mWindowOriginalBounds = new Rect(); 107 private final Rect mWindowDragBounds = new Rect(); 108 private float mStartDragX; 109 private float mStartDragY; 110 @CtrlType 111 private int mCtrlType = CTRL_NONE; 112 private boolean mDragEnded = false; 113 114 InputChannel mServerChannel; 115 InputChannel mClientChannel; 116 InputApplicationHandle mDragApplicationHandle; 117 InputWindowHandle mDragWindowHandle; 118 119 private final class WindowPositionerEventReceiver extends BatchedInputEventReceiver { 120 public WindowPositionerEventReceiver( 121 InputChannel inputChannel, Looper looper, Choreographer choreographer) { 122 super(inputChannel, looper, choreographer); 123 } 124 125 @Override 126 public void onInputEvent(InputEvent event) { 127 if (!(event instanceof MotionEvent) 128 || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) { 129 return; 130 } 131 final MotionEvent motionEvent = (MotionEvent) event; 132 boolean handled = false; 133 134 try { 135 if (mDragEnded) { 136 // The drag has ended but the clean-up message has not been processed by 137 // window manager. Drop events that occur after this until window manager 138 // has a chance to clean-up the input handle. 139 handled = true; 140 return; 141 } 142 143 final float newX = motionEvent.getRawX(); 144 final float newY = motionEvent.getRawY(); 145 146 switch (motionEvent.getAction()) { 147 case MotionEvent.ACTION_DOWN: { 148 if (DEBUG_TASK_POSITIONING) { 149 Slog.w(TAG, "ACTION_DOWN @ {" + newX + ", " + newY + "}"); 150 } 151 } break; 152 153 case MotionEvent.ACTION_MOVE: { 154 if (DEBUG_TASK_POSITIONING){ 155 Slog.w(TAG, "ACTION_MOVE @ {" + newX + ", " + newY + "}"); 156 } 157 synchronized (mService.mWindowMap) { 158 mDragEnded = notifyMoveLocked(newX, newY); 159 mTask.getDimBounds(mTmpRect); 160 } 161 if (!mTmpRect.equals(mWindowDragBounds)) { 162 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, 163 "wm.TaskPositioner.resizeTask"); 164 try { 165 mService.mActivityManager.resizeTask( 166 mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER); 167 } catch (RemoteException e) { 168 } 169 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); 170 } 171 } break; 172 173 case MotionEvent.ACTION_UP: { 174 if (DEBUG_TASK_POSITIONING) { 175 Slog.w(TAG, "ACTION_UP @ {" + newX + ", " + newY + "}"); 176 } 177 mDragEnded = true; 178 } break; 179 180 case MotionEvent.ACTION_CANCEL: { 181 if (DEBUG_TASK_POSITIONING) { 182 Slog.w(TAG, "ACTION_CANCEL @ {" + newX + ", " + newY + "}"); 183 } 184 mDragEnded = true; 185 } break; 186 } 187 188 if (mDragEnded) { 189 final boolean wasResizing = mResizing; 190 synchronized (mService.mWindowMap) { 191 endDragLocked(); 192 } 193 try { 194 if (wasResizing) { 195 // We were using fullscreen surface during resizing. Request 196 // resizeTask() one last time to restore surface to window size. 197 mService.mActivityManager.resizeTask( 198 mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED); 199 } 200 201 if (mCurrentDimSide != CTRL_NONE) { 202 final int createMode = mCurrentDimSide == CTRL_LEFT 203 ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT 204 : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; 205 mService.mActivityManager.moveTaskToDockedStack( 206 mTask.mTaskId, createMode, true /*toTop*/, true /* animate */, 207 null /* initialBounds */, false /* moveHomeStackFront */); 208 } 209 } catch(RemoteException e) {} 210 211 // Post back to WM to handle clean-ups. We still need the input 212 // event handler for the last finishInputEvent()! 213 mService.mH.sendEmptyMessage(H.FINISH_TASK_POSITIONING); 214 } 215 handled = true; 216 } catch (Exception e) { 217 Slog.e(TAG, "Exception caught by drag handleMotion", e); 218 } finally { 219 finishInputEvent(event, handled); 220 } 221 } 222 } 223 224 TaskPositioner(WindowManagerService service) { 225 mService = service; 226 } 227 228 /** 229 * @param display The Display that the window being dragged is on. 230 */ 231 void register(Display display) { 232 if (DEBUG_TASK_POSITIONING) { 233 Slog.d(TAG, "Registering task positioner"); 234 } 235 236 if (mClientChannel != null) { 237 Slog.e(TAG, "Task positioner already registered"); 238 return; 239 } 240 241 mDisplay = display; 242 mDisplay.getMetrics(mDisplayMetrics); 243 final InputChannel[] channels = InputChannel.openInputChannelPair(TAG); 244 mServerChannel = channels[0]; 245 mClientChannel = channels[1]; 246 mService.mInputManager.registerInputChannel(mServerChannel, null); 247 248 mInputEventReceiver = new WindowPositionerEventReceiver( 249 mClientChannel, mService.mH.getLooper(), mService.mChoreographer); 250 251 mDragApplicationHandle = new InputApplicationHandle(null); 252 mDragApplicationHandle.name = TAG; 253 mDragApplicationHandle.dispatchingTimeoutNanos = 254 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; 255 256 mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, 257 mDisplay.getDisplayId()); 258 mDragWindowHandle.name = TAG; 259 mDragWindowHandle.inputChannel = mServerChannel; 260 mDragWindowHandle.layer = mService.getDragLayerLocked(); 261 mDragWindowHandle.layoutParamsFlags = 0; 262 mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG; 263 mDragWindowHandle.dispatchingTimeoutNanos = 264 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; 265 mDragWindowHandle.visible = true; 266 mDragWindowHandle.canReceiveKeys = false; 267 mDragWindowHandle.hasFocus = true; 268 mDragWindowHandle.hasWallpaper = false; 269 mDragWindowHandle.paused = false; 270 mDragWindowHandle.ownerPid = Process.myPid(); 271 mDragWindowHandle.ownerUid = Process.myUid(); 272 mDragWindowHandle.inputFeatures = 0; 273 mDragWindowHandle.scaleFactor = 1.0f; 274 275 // The drag window cannot receive new touches. 276 mDragWindowHandle.touchableRegion.setEmpty(); 277 278 // The drag window covers the entire display 279 mDragWindowHandle.frameLeft = 0; 280 mDragWindowHandle.frameTop = 0; 281 final Point p = new Point(); 282 mDisplay.getRealSize(p); 283 mDragWindowHandle.frameRight = p.x; 284 mDragWindowHandle.frameBottom = p.y; 285 286 // Pause rotations before a drag. 287 if (DEBUG_ORIENTATION) { 288 Slog.d(TAG, "Pausing rotation during re-position"); 289 } 290 mService.pauseRotationLocked(); 291 292 mDimLayer = new DimLayer(mService, this, mDisplay.getDisplayId(), TAG_LOCAL); 293 mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics); 294 mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics); 295 mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics); 296 297 mDragEnded = false; 298 } 299 300 void unregister() { 301 if (DEBUG_TASK_POSITIONING) { 302 Slog.d(TAG, "Unregistering task positioner"); 303 } 304 305 if (mClientChannel == null) { 306 Slog.e(TAG, "Task positioner not registered"); 307 return; 308 } 309 310 mService.mInputManager.unregisterInputChannel(mServerChannel); 311 312 mInputEventReceiver.dispose(); 313 mInputEventReceiver = null; 314 mClientChannel.dispose(); 315 mServerChannel.dispose(); 316 mClientChannel = null; 317 mServerChannel = null; 318 319 mDragWindowHandle = null; 320 mDragApplicationHandle = null; 321 mDisplay = null; 322 323 if (mDimLayer != null) { 324 mDimLayer.destroySurface(); 325 mDimLayer = null; 326 } 327 mCurrentDimSide = CTRL_NONE; 328 mDragEnded = true; 329 330 // Resume rotations after a drag. 331 if (DEBUG_ORIENTATION) { 332 Slog.d(TAG, "Resuming rotation after re-position"); 333 } 334 mService.resumeRotationLocked(); 335 } 336 337 void startDragLocked(WindowState win, boolean resize, float startX, float startY) { 338 if (DEBUG_TASK_POSITIONING) { 339 Slog.d(TAG, "startDragLocked: win=" + win + ", resize=" + resize 340 + ", {" + startX + ", " + startY + "}"); 341 } 342 mCtrlType = CTRL_NONE; 343 mTask = win.getTask(); 344 mStartDragX = startX; 345 mStartDragY = startY; 346 347 if (mTask.isDockedInEffect()) { 348 // If this is a docked task or if task size is affected by docked stack changing size, 349 // we can only be here if the task is not resizeable and we're handling a two-finger 350 // scrolling. Use the original task bounds to position the task, the dim bounds 351 // is cropped and doesn't move. 352 mTask.getBounds(mTmpRect); 353 } else { 354 // Use the dim bounds, not the original task bounds. The cursor 355 // movement should be calculated relative to the visible bounds. 356 // Also, use the dim bounds of the task which accounts for 357 // multiple app windows. Don't use any bounds from win itself as it 358 // may not be the same size as the task. 359 mTask.getDimBounds(mTmpRect); 360 } 361 362 if (resize) { 363 if (startX < mTmpRect.left) { 364 mCtrlType |= CTRL_LEFT; 365 } 366 if (startX > mTmpRect.right) { 367 mCtrlType |= CTRL_RIGHT; 368 } 369 if (startY < mTmpRect.top) { 370 mCtrlType |= CTRL_TOP; 371 } 372 if (startY > mTmpRect.bottom) { 373 mCtrlType |= CTRL_BOTTOM; 374 } 375 mResizing = true; 376 } 377 378 mWindowOriginalBounds.set(mTmpRect); 379 } 380 381 private void endDragLocked() { 382 mResizing = false; 383 mTask.setDragResizing(false, DRAG_RESIZE_MODE_FREEFORM); 384 } 385 386 /** Returns true if the move operation should be ended. */ 387 private boolean notifyMoveLocked(float x, float y) { 388 if (DEBUG_TASK_POSITIONING) { 389 Slog.d(TAG, "notifyMoveLocked: {" + x + "," + y + "}"); 390 } 391 392 if (mCtrlType != CTRL_NONE) { 393 // This is a resizing operation. 394 final int deltaX = Math.round(x - mStartDragX); 395 final int deltaY = Math.round(y - mStartDragY); 396 int left = mWindowOriginalBounds.left; 397 int top = mWindowOriginalBounds.top; 398 int right = mWindowOriginalBounds.right; 399 int bottom = mWindowOriginalBounds.bottom; 400 if ((mCtrlType & CTRL_LEFT) != 0) { 401 left = Math.min(left + deltaX, right - mMinVisibleWidth); 402 } 403 if ((mCtrlType & CTRL_TOP) != 0) { 404 top = Math.min(top + deltaY, bottom - mMinVisibleHeight); 405 } 406 if ((mCtrlType & CTRL_RIGHT) != 0) { 407 right = Math.max(left + mMinVisibleWidth, right + deltaX); 408 } 409 if ((mCtrlType & CTRL_BOTTOM) != 0) { 410 bottom = Math.max(top + mMinVisibleHeight, bottom + deltaY); 411 } 412 mWindowDragBounds.set(left, top, right, bottom); 413 mTask.setDragResizing(true, DRAG_RESIZE_MODE_FREEFORM); 414 return false; 415 } 416 417 // This is a moving operation. 418 mTask.mStack.getDimBounds(mTmpRect); 419 420 // If this is a non-resizeable task put into side-by-side mode, we are 421 // handling a two-finger scrolling action. No need to shrink the bounds. 422 if (!mTask.isDockedInEffect()) { 423 mTmpRect.inset(mMinVisibleWidth, mMinVisibleHeight); 424 } 425 426 boolean dragEnded = false; 427 final int nX = (int) x; 428 final int nY = (int) y; 429 if (!mTmpRect.contains(nX, nY)) { 430 // We end the moving operation if position is outside the stack bounds. 431 // In this case we need to clamp the position to stack bounds and calculate 432 // the final window drag bounds. 433 x = Math.min(Math.max(x, mTmpRect.left), mTmpRect.right); 434 y = Math.min(Math.max(y, mTmpRect.top), mTmpRect.bottom); 435 dragEnded = true; 436 } 437 438 updateWindowDragBounds(nX, nY); 439 updateDimLayerVisibility(nX); 440 return dragEnded; 441 } 442 443 private void updateWindowDragBounds(int x, int y) { 444 mWindowDragBounds.set(mWindowOriginalBounds); 445 if (mTask.isDockedInEffect()) { 446 // Offset the bounds without clamp, the bounds will be shifted later 447 // by window manager before applying the scrolling. 448 if (mService.mCurConfiguration.orientation == ORIENTATION_LANDSCAPE) { 449 mWindowDragBounds.offset(Math.round(x - mStartDragX), 0); 450 } else { 451 mWindowDragBounds.offset(0, Math.round(y - mStartDragY)); 452 } 453 } else { 454 mWindowDragBounds.offset(Math.round(x - mStartDragX), Math.round(y - mStartDragY)); 455 } 456 if (DEBUG_TASK_POSITIONING) Slog.d(TAG, 457 "updateWindowDragBounds: " + mWindowDragBounds); 458 } 459 460 private void updateDimLayerVisibility(int x) { 461 @CtrlType 462 int dimSide = getDimSide(x); 463 if (dimSide == mCurrentDimSide) { 464 return; 465 } 466 467 mCurrentDimSide = dimSide; 468 469 if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION updateDimLayerVisibility"); 470 SurfaceControl.openTransaction(); 471 if (mCurrentDimSide == CTRL_NONE) { 472 mDimLayer.hide(); 473 } else { 474 showDimLayer(); 475 } 476 SurfaceControl.closeTransaction(); 477 } 478 479 /** 480 * Returns the side of the screen the dim layer should be shown. 481 * @param x horizontal coordinate used to determine if the dim layer should be shown 482 * @return Returns {@link #CTRL_LEFT} if the dim layer should be shown on the left half of the 483 * screen, {@link #CTRL_RIGHT} if on the right side, or {@link #CTRL_NONE} if the dim layer 484 * shouldn't be shown. 485 */ 486 private int getDimSide(int x) { 487 if (mTask.mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID 488 || !mTask.mStack.isFullscreen() 489 || mService.mCurConfiguration.orientation != ORIENTATION_LANDSCAPE) { 490 return CTRL_NONE; 491 } 492 493 mTask.mStack.getDimBounds(mTmpRect); 494 if (x - mSideMargin <= mTmpRect.left) { 495 return CTRL_LEFT; 496 } 497 if (x + mSideMargin >= mTmpRect.right) { 498 return CTRL_RIGHT; 499 } 500 501 return CTRL_NONE; 502 } 503 504 private void showDimLayer() { 505 mTask.mStack.getDimBounds(mTmpRect); 506 if (mCurrentDimSide == CTRL_LEFT) { 507 mTmpRect.right = mTmpRect.centerX(); 508 } else if (mCurrentDimSide == CTRL_RIGHT) { 509 mTmpRect.left = mTmpRect.centerX(); 510 } 511 512 mDimLayer.setBounds(mTmpRect); 513 mDimLayer.show(mService.getDragLayerLocked(), RESIZING_HINT_ALPHA, 514 RESIZING_HINT_DURATION_MS); 515 } 516 517 @Override /** {@link DimLayer.DimLayerUser} */ 518 public boolean dimFullscreen() { 519 return isFullscreen(); 520 } 521 522 boolean isFullscreen() { 523 return false; 524 } 525 526 @Override /** {@link DimLayer.DimLayerUser} */ 527 public DisplayInfo getDisplayInfo() { 528 return mTask.mStack.getDisplayInfo(); 529 } 530 531 @Override 532 public void getDimBounds(Rect out) { 533 // This dim layer user doesn't need this. 534 } 535 536 @Override 537 public String toShortString() { 538 return TAG; 539 } 540} 541