1 /* 2 * Copyright (C) 2011 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.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY; 20 import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; 21 import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK; 22 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; 23 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP; 24 25 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; 26 import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; 27 import static com.android.server.wm.DragDropController.MSG_ANIMATION_END; 28 import static com.android.server.wm.DragDropController.MSG_DRAG_END_TIMEOUT; 29 import static com.android.server.wm.DragDropController.MSG_REMOVE_DRAG_SURFACE_TIMEOUT; 30 import static com.android.server.wm.DragDropController.MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT; 31 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG; 32 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; 33 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 34 35 import android.animation.Animator; 36 import android.animation.PropertyValuesHolder; 37 import android.animation.ValueAnimator; 38 import android.annotation.Nullable; 39 import android.content.ClipData; 40 import android.content.ClipDescription; 41 import android.graphics.Point; 42 import android.graphics.Rect; 43 import android.hardware.input.InputManager; 44 import android.os.Binder; 45 import android.os.Build; 46 import android.os.IBinder; 47 import android.os.Process; 48 import android.os.RemoteException; 49 import android.os.UserHandle; 50 import android.os.UserManager; 51 import android.util.Slog; 52 import android.view.Display; 53 import android.view.DragEvent; 54 import android.view.InputApplicationHandle; 55 import android.view.InputChannel; 56 import android.view.InputDevice; 57 import android.view.InputWindowHandle; 58 import android.view.PointerIcon; 59 import android.view.SurfaceControl; 60 import android.view.View; 61 import android.view.WindowManager; 62 import android.view.animation.DecelerateInterpolator; 63 import android.view.animation.Interpolator; 64 65 import com.android.internal.protolog.common.ProtoLog; 66 import com.android.internal.view.IDragAndDropPermissions; 67 import com.android.server.LocalServices; 68 import com.android.server.pm.UserManagerInternal; 69 70 import java.util.ArrayList; 71 72 /** 73 * Drag/drop state 74 */ 75 class DragState { 76 private static final long MIN_ANIMATION_DURATION_MS = 195; 77 private static final long MAX_ANIMATION_DURATION_MS = 375; 78 79 private static final int DRAG_FLAGS_URI_ACCESS = View.DRAG_FLAG_GLOBAL_URI_READ | 80 View.DRAG_FLAG_GLOBAL_URI_WRITE; 81 82 private static final int DRAG_FLAGS_URI_PERMISSIONS = DRAG_FLAGS_URI_ACCESS | 83 View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION | 84 View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION; 85 86 // Property names for animations 87 private static final String ANIMATED_PROPERTY_X = "x"; 88 private static final String ANIMATED_PROPERTY_Y = "y"; 89 private static final String ANIMATED_PROPERTY_ALPHA = "alpha"; 90 private static final String ANIMATED_PROPERTY_SCALE = "scale"; 91 92 final WindowManagerService mService; 93 final DragDropController mDragDropController; 94 IBinder mToken; 95 /** 96 * Do not use the variable from the out of animation thread while mAnimator is not null. 97 */ 98 SurfaceControl mSurfaceControl; 99 int mFlags; 100 IBinder mLocalWin; 101 int mPid; 102 int mUid; 103 int mSourceUserId; 104 boolean mCrossProfileCopyAllowed; 105 ClipData mData; 106 ClipDescription mDataDescription; 107 int mTouchSource; 108 boolean mDragResult; 109 boolean mRelinquishDragSurface; 110 float mOriginalAlpha; 111 float mOriginalX, mOriginalY; 112 float mCurrentX, mCurrentY; 113 float mThumbOffsetX, mThumbOffsetY; 114 InputInterceptor mInputInterceptor; 115 ArrayList<WindowState> mNotifiedWindows; 116 boolean mDragInProgress; 117 /** 118 * Whether if animation is completed. Needs to be volatile to update from the animation thread 119 * without having a WM lock. 120 */ 121 volatile boolean mAnimationCompleted = false; 122 /** 123 * The display on which the drag is happening. If it goes into a different display this will 124 * be updated. 125 */ 126 DisplayContent mDisplayContent; 127 128 @Nullable private ValueAnimator mAnimator; 129 private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f); 130 private Point mDisplaySize = new Point(); 131 132 // A surface used to catch input events for the drag-and-drop operation. 133 SurfaceControl mInputSurface; 134 135 final SurfaceControl.Transaction mTransaction; 136 137 private final Rect mTmpClipRect = new Rect(); 138 139 /** 140 * Whether we are finishing this drag and drop. This starts with {@code false}, and is set to 141 * {@code true} when {@link #closeLocked()} is called. 142 */ 143 private boolean mIsClosing; 144 DragState(WindowManagerService service, DragDropController controller, IBinder token, SurfaceControl surface, int flags, IBinder localWin)145 DragState(WindowManagerService service, DragDropController controller, IBinder token, 146 SurfaceControl surface, int flags, IBinder localWin) { 147 mService = service; 148 mDragDropController = controller; 149 mToken = token; 150 mSurfaceControl = surface; 151 mFlags = flags; 152 mLocalWin = localWin; 153 mNotifiedWindows = new ArrayList<>(); 154 mTransaction = service.mTransactionFactory.get(); 155 } 156 isClosing()157 boolean isClosing() { 158 return mIsClosing; 159 } 160 showInputSurface()161 private void showInputSurface() { 162 if (mInputSurface == null) { 163 mInputSurface = mService.makeSurfaceBuilder( 164 mService.mRoot.getDisplayContent(mDisplayContent.getDisplayId()).getSession()) 165 .setContainerLayer() 166 .setName("Drag and Drop Input Consumer") 167 .setCallsite("DragState.showInputSurface") 168 .build(); 169 } 170 final InputWindowHandle h = getInputWindowHandle(); 171 if (h == null) { 172 Slog.w(TAG_WM, "Drag is in progress but there is no " 173 + "drag window handle."); 174 return; 175 } 176 177 mTransaction.show(mInputSurface); 178 mTransaction.setInputWindowInfo(mInputSurface, h); 179 mTransaction.setLayer(mInputSurface, Integer.MAX_VALUE); 180 181 mTmpClipRect.set(0, 0, mDisplaySize.x, mDisplaySize.y); 182 mTransaction.setWindowCrop(mInputSurface, mTmpClipRect); 183 184 // syncInputWindows here to ensure the input window info is sent before the 185 // transferTouchFocus is called. 186 mTransaction.syncInputWindows(); 187 mTransaction.apply(true); 188 } 189 190 /** 191 * After calling this, DragDropController#onDragStateClosedLocked is invoked, which causes 192 * DragDropController#mDragState becomes null. 193 */ closeLocked()194 void closeLocked() { 195 mIsClosing = true; 196 // Unregister the input interceptor. 197 if (mInputInterceptor != null) { 198 if (DEBUG_DRAG) 199 Slog.d(TAG_WM, "unregistering drag input channel"); 200 201 // Input channel should be disposed on the thread where the input is being handled. 202 mDragDropController.sendHandlerMessage( 203 MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT, mInputInterceptor); 204 mInputInterceptor = null; 205 } 206 207 // Send drag end broadcast if drag start has been sent. 208 if (mDragInProgress) { 209 final int myPid = Process.myPid(); 210 211 if (DEBUG_DRAG) { 212 Slog.d(TAG_WM, "broadcasting DRAG_ENDED"); 213 } 214 for (WindowState ws : mNotifiedWindows) { 215 float x = 0; 216 float y = 0; 217 if (!mDragResult && (ws.mSession.mPid == mPid)) { 218 // Report unconsumed drop location back to the app that started the drag. 219 x = mCurrentX; 220 y = mCurrentY; 221 } 222 DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, 223 x, y, mThumbOffsetX, mThumbOffsetY, null, null, null, null, null, 224 mDragResult); 225 try { 226 ws.mClient.dispatchDragEvent(event); 227 } catch (RemoteException e) { 228 Slog.w(TAG_WM, "Unable to drag-end window " + ws); 229 } 230 // if the current window is in the same process, 231 // the dispatch has already recycled the event 232 if (myPid != ws.mSession.mPid) { 233 event.recycle(); 234 } 235 } 236 mNotifiedWindows.clear(); 237 mDragInProgress = false; 238 } 239 240 // Take the cursor back if it has been changed. 241 if (isFromSource(InputDevice.SOURCE_MOUSE)) { 242 mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY); 243 mTouchSource = 0; 244 } 245 246 // Clear the internal variables. 247 if (mInputSurface != null) { 248 mTransaction.remove(mInputSurface).apply(); 249 mInputSurface = null; 250 } 251 if (mSurfaceControl != null) { 252 if (!mRelinquishDragSurface) { 253 mTransaction.reparent(mSurfaceControl, null).apply(); 254 } else { 255 mDragDropController.sendTimeoutMessage(MSG_REMOVE_DRAG_SURFACE_TIMEOUT, 256 mSurfaceControl, DragDropController.DRAG_TIMEOUT_MS); 257 } 258 mSurfaceControl = null; 259 } 260 if (mAnimator != null && !mAnimationCompleted) { 261 Slog.wtf(TAG_WM, 262 "Unexpectedly destroying mSurfaceControl while animation is running"); 263 } 264 mFlags = 0; 265 mLocalWin = null; 266 mToken = null; 267 mData = null; 268 mThumbOffsetX = mThumbOffsetY = 0; 269 mNotifiedWindows = null; 270 271 // Notifies the controller that the drag state is closed. 272 mDragDropController.onDragStateClosedLocked(this); 273 } 274 275 /** 276 * Notify the drop target and tells it about the data. If the drop event is not sent to the 277 * target, invokes {@code endDragLocked} immediately. 278 */ reportDropWindowLock(IBinder token, float x, float y)279 boolean reportDropWindowLock(IBinder token, float x, float y) { 280 if (mAnimator != null) { 281 return false; 282 } 283 284 final WindowState touchedWin = mService.mInputToWindowMap.get(token); 285 if (!isWindowNotified(touchedWin)) { 286 // "drop" outside a valid window -- no recipient to apply a 287 // timeout to, and we can send the drag-ended message immediately. 288 mDragResult = false; 289 endDragLocked(); 290 if (DEBUG_DRAG) Slog.d(TAG_WM, "Drop outside a valid window " + touchedWin); 291 return false; 292 } 293 294 if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin); 295 296 final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid()); 297 298 final DragAndDropPermissionsHandler dragAndDropPermissions; 299 if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0 300 && mData != null) { 301 dragAndDropPermissions = new DragAndDropPermissionsHandler(mService.mGlobalLock, 302 mData, 303 mUid, 304 touchedWin.getOwningPackage(), 305 mFlags & DRAG_FLAGS_URI_PERMISSIONS, 306 mSourceUserId, 307 targetUserId); 308 } else { 309 dragAndDropPermissions = null; 310 } 311 if (mSourceUserId != targetUserId) { 312 if (mData != null) { 313 mData.fixUris(mSourceUserId); 314 } 315 } 316 final int myPid = Process.myPid(); 317 final IBinder clientToken = touchedWin.mClient.asBinder(); 318 final DragEvent event = obtainDragEvent(DragEvent.ACTION_DROP, x, y, 319 mData, targetInterceptsGlobalDrag(touchedWin), 320 dragAndDropPermissions); 321 try { 322 touchedWin.mClient.dispatchDragEvent(event); 323 324 // 5 second timeout for this window to respond to the drop 325 mDragDropController.sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, clientToken, 326 DragDropController.DRAG_TIMEOUT_MS); 327 } catch (RemoteException e) { 328 Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin); 329 endDragLocked(); 330 return false; 331 } finally { 332 if (myPid != touchedWin.mSession.mPid) { 333 event.recycle(); 334 } 335 } 336 mToken = clientToken; 337 return true; 338 } 339 340 class InputInterceptor { 341 InputChannel mClientChannel; 342 DragInputEventReceiver mInputEventReceiver; 343 InputApplicationHandle mDragApplicationHandle; 344 InputWindowHandle mDragWindowHandle; 345 InputInterceptor(Display display)346 InputInterceptor(Display display) { 347 mClientChannel = mService.mInputManager.createInputChannel("drag"); 348 mInputEventReceiver = new DragInputEventReceiver(mClientChannel, 349 mService.mH.getLooper(), mDragDropController); 350 351 mDragApplicationHandle = new InputApplicationHandle(new Binder(), "drag", 352 DEFAULT_DISPATCHING_TIMEOUT_MILLIS); 353 354 mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, 355 display.getDisplayId()); 356 mDragWindowHandle.name = "drag"; 357 mDragWindowHandle.token = mClientChannel.getToken(); 358 mDragWindowHandle.layoutParamsFlags = 0; 359 mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG; 360 mDragWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS; 361 mDragWindowHandle.visible = true; 362 // Allows the system to consume keys when dragging is active. This can also be used to 363 // modify the drag state on key press. Example, cancel drag on escape key. 364 mDragWindowHandle.focusable = true; 365 mDragWindowHandle.hasWallpaper = false; 366 mDragWindowHandle.paused = false; 367 mDragWindowHandle.ownerPid = Process.myPid(); 368 mDragWindowHandle.ownerUid = Process.myUid(); 369 mDragWindowHandle.inputFeatures = 0; 370 mDragWindowHandle.scaleFactor = 1.0f; 371 372 // The drag window cannot receive new touches. 373 mDragWindowHandle.touchableRegion.setEmpty(); 374 375 // The drag window covers the entire display 376 mDragWindowHandle.frameLeft = 0; 377 mDragWindowHandle.frameTop = 0; 378 mDragWindowHandle.frameRight = mDisplaySize.x; 379 mDragWindowHandle.frameBottom = mDisplaySize.y; 380 381 // Pause rotations before a drag. 382 ProtoLog.d(WM_DEBUG_ORIENTATION, "Pausing rotation during drag"); 383 mService.mRoot.forAllDisplays(dc -> { 384 dc.getDisplayRotation().pause(); 385 }); 386 } 387 tearDown()388 void tearDown() { 389 mService.mInputManager.removeInputChannel(mClientChannel.getToken()); 390 mInputEventReceiver.dispose(); 391 mInputEventReceiver = null; 392 mClientChannel.dispose(); 393 mClientChannel = null; 394 395 mDragWindowHandle = null; 396 mDragApplicationHandle = null; 397 398 // Resume rotations after a drag. 399 ProtoLog.d(WM_DEBUG_ORIENTATION, "Resuming rotation after drag"); 400 mService.mRoot.forAllDisplays(dc -> { 401 dc.getDisplayRotation().resume(); 402 }); 403 } 404 } 405 getInputChannel()406 InputChannel getInputChannel() { 407 return mInputInterceptor == null ? null : mInputInterceptor.mClientChannel; 408 } 409 getInputWindowHandle()410 InputWindowHandle getInputWindowHandle() { 411 return mInputInterceptor == null ? null : mInputInterceptor.mDragWindowHandle; 412 } 413 414 /** 415 * @param display The Display that the window being dragged is on. 416 */ register(Display display)417 void register(Display display) { 418 display.getRealSize(mDisplaySize); 419 if (DEBUG_DRAG) Slog.d(TAG_WM, "registering drag input channel"); 420 if (mInputInterceptor != null) { 421 Slog.e(TAG_WM, "Duplicate register of drag input channel"); 422 } else { 423 mInputInterceptor = new InputInterceptor(display); 424 showInputSurface(); 425 } 426 } 427 428 /* call out to each visible window/session informing it about the drag 429 */ broadcastDragStartedLocked(final float touchX, final float touchY)430 void broadcastDragStartedLocked(final float touchX, final float touchY) { 431 mOriginalX = mCurrentX = touchX; 432 mOriginalY = mCurrentY = touchY; 433 434 // Cache a base-class instance of the clip metadata so that parceling 435 // works correctly in calling out to the apps. 436 mDataDescription = (mData != null) ? mData.getDescription() : null; 437 mNotifiedWindows.clear(); 438 mDragInProgress = true; 439 440 mSourceUserId = UserHandle.getUserId(mUid); 441 442 final UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class); 443 mCrossProfileCopyAllowed = !userManager.getUserRestriction( 444 mSourceUserId, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE); 445 446 if (DEBUG_DRAG) { 447 Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")"); 448 } 449 450 final boolean containsAppExtras = containsApplicationExtras(mDataDescription); 451 mService.mRoot.forAllWindows(w -> { 452 sendDragStartedLocked(w, touchX, touchY, containsAppExtras); 453 }, false /* traverseTopToBottom */); 454 } 455 456 /* helper - send a ACTION_DRAG_STARTED event, if the 457 * designated window is potentially a drop recipient. There are race situations 458 * around DRAG_ENDED broadcast, so we make sure that once we've declared that 459 * the drag has ended, we never send out another DRAG_STARTED for this drag action. 460 * 461 * This method clones the 'event' parameter if it's being delivered to the same 462 * process, so it's safe for the caller to call recycle() on the event afterwards. 463 */ sendDragStartedLocked(WindowState newWin, float touchX, float touchY, boolean containsAppExtras)464 private void sendDragStartedLocked(WindowState newWin, float touchX, float touchY, 465 boolean containsAppExtras) { 466 final boolean interceptsGlobalDrag = targetInterceptsGlobalDrag(newWin); 467 if (mDragInProgress && isValidDropTarget(newWin, containsAppExtras, interceptsGlobalDrag)) { 468 // Only allow the extras to be dispatched to a global-intercepting drag target 469 ClipData data = interceptsGlobalDrag ? mData.copyForTransferWithActivityInfo() : null; 470 DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED, 471 newWin.translateToWindowX(touchX), newWin.translateToWindowY(touchY), 472 data, false /* includeDragSurface */, 473 null /* dragAndDropPermission */); 474 try { 475 newWin.mClient.dispatchDragEvent(event); 476 // track each window that we've notified that the drag is starting 477 mNotifiedWindows.add(newWin); 478 } catch (RemoteException e) { 479 Slog.w(TAG_WM, "Unable to drag-start window " + newWin); 480 } finally { 481 // if the callee was local, the dispatch has already recycled the event 482 if (Process.myPid() != newWin.mSession.mPid) { 483 event.recycle(); 484 } 485 } 486 } 487 } 488 489 /** 490 * Returns true if this is a drag of an application mime type. 491 */ containsApplicationExtras(ClipDescription desc)492 private boolean containsApplicationExtras(ClipDescription desc) { 493 if (desc == null) { 494 return false; 495 } 496 return desc.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY) 497 || desc.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT) 498 || desc.hasMimeType(MIMETYPE_APPLICATION_TASK); 499 } 500 isValidDropTarget(WindowState targetWin, boolean containsAppExtras, boolean interceptsGlobalDrag)501 private boolean isValidDropTarget(WindowState targetWin, boolean containsAppExtras, 502 boolean interceptsGlobalDrag) { 503 if (targetWin == null) { 504 return false; 505 } 506 final boolean isLocalWindow = mLocalWin == targetWin.mClient.asBinder(); 507 if (!isLocalWindow && !interceptsGlobalDrag && containsAppExtras) { 508 // App-drags can only go to local windows or windows that can intercept global drag, and 509 // not to other app windows 510 return false; 511 } 512 if (!targetWin.isPotentialDragTarget(interceptsGlobalDrag)) { 513 return false; 514 } 515 if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0 || !targetWindowSupportsGlobalDrag(targetWin)) { 516 // Drag is limited to the current window. 517 if (!isLocalWindow) { 518 return false; 519 } 520 } 521 522 return interceptsGlobalDrag 523 || mCrossProfileCopyAllowed 524 || mSourceUserId == UserHandle.getUserId(targetWin.getOwningUid()); 525 } 526 targetWindowSupportsGlobalDrag(WindowState targetWin)527 private boolean targetWindowSupportsGlobalDrag(WindowState targetWin) { 528 // Global drags are limited to system windows, and windows for apps that are targeting N and 529 // above. 530 return targetWin.mActivityRecord == null 531 || targetWin.mActivityRecord.mTargetSdk >= Build.VERSION_CODES.N; 532 } 533 534 /** 535 * @return whether the given window {@param targetWin} can intercept global drags. 536 */ targetInterceptsGlobalDrag(WindowState targetWin)537 public boolean targetInterceptsGlobalDrag(WindowState targetWin) { 538 return (targetWin.mAttrs.privateFlags & PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP) != 0; 539 } 540 541 /* helper - send a ACTION_DRAG_STARTED event only if the window has not 542 * previously been notified, i.e. it became visible after the drag operation 543 * was begun. This is a rare case. 544 */ sendDragStartedIfNeededLocked(WindowState newWin)545 void sendDragStartedIfNeededLocked(WindowState newWin) { 546 if (mDragInProgress) { 547 // If we have sent the drag-started, we needn't do so again 548 if (isWindowNotified(newWin)) { 549 return; 550 } 551 if (DEBUG_DRAG) { 552 Slog.d(TAG_WM, "need to send DRAG_STARTED to new window " + newWin); 553 } 554 sendDragStartedLocked(newWin, mCurrentX, mCurrentY, 555 containsApplicationExtras(mDataDescription)); 556 } 557 } 558 isWindowNotified(WindowState newWin)559 boolean isWindowNotified(WindowState newWin) { 560 for (WindowState ws : mNotifiedWindows) { 561 if (ws == newWin) { 562 return true; 563 } 564 } 565 return false; 566 } 567 endDragLocked()568 void endDragLocked() { 569 if (mAnimator != null) { 570 return; 571 } 572 if (!mDragResult) { 573 if (!isAccessibilityDragDrop()) { 574 mAnimator = createReturnAnimationLocked(); 575 return; // Will call closeLocked() when the animation is done. 576 } 577 } 578 closeLocked(); 579 } 580 cancelDragLocked(boolean skipAnimation)581 void cancelDragLocked(boolean skipAnimation) { 582 if (mAnimator != null) { 583 return; 584 } 585 if (!mDragInProgress || skipAnimation || isAccessibilityDragDrop()) { 586 // mDragInProgress is false if an app invokes Session#cancelDragAndDrop before 587 // Session#performDrag. Reset the drag state without playing the cancel animation 588 // because H.DRAG_START_TIMEOUT may be sent to WindowManagerService, which will cause 589 // DragState#reset() while playing the cancel animation. 590 // skipAnimation is true when a caller requests to skip the drag cancel animation. 591 closeLocked(); 592 return; 593 } 594 mAnimator = createCancelAnimationLocked(); 595 } 596 updateDragSurfaceLocked(boolean keepHandling, float x, float y)597 void updateDragSurfaceLocked(boolean keepHandling, float x, float y) { 598 if (mAnimator != null) { 599 return; 600 } 601 mCurrentX = x; 602 mCurrentY = y; 603 604 if (!keepHandling) { 605 return; 606 } 607 608 // Move the surface to the given touch 609 if (SHOW_LIGHT_TRANSACTIONS) { 610 Slog.i(TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked"); 611 } 612 mTransaction.setPosition(mSurfaceControl, x - mThumbOffsetX, y - mThumbOffsetY).apply(); 613 ProtoLog.i(WM_SHOW_TRANSACTIONS, "DRAG %s: pos=(%d,%d)", mSurfaceControl, 614 (int) (x - mThumbOffsetX), (int) (y - mThumbOffsetY)); 615 } 616 617 /** 618 * Returns true if it has sent DRAG_STARTED broadcast out but has not been sent DRAG_END 619 * broadcast. 620 */ isInProgress()621 boolean isInProgress() { 622 return mDragInProgress; 623 } 624 obtainDragEvent(int action, float x, float y, ClipData data, boolean includeDragSurface, IDragAndDropPermissions dragAndDropPermissions)625 private DragEvent obtainDragEvent(int action, float x, float y, ClipData data, 626 boolean includeDragSurface, IDragAndDropPermissions dragAndDropPermissions) { 627 return DragEvent.obtain(action, x, y, mThumbOffsetX, mThumbOffsetY, 628 null /* localState */, mDataDescription, data, 629 includeDragSurface ? mSurfaceControl : null, 630 dragAndDropPermissions, false /* result */); 631 } 632 createReturnAnimationLocked()633 private ValueAnimator createReturnAnimationLocked() { 634 final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder( 635 PropertyValuesHolder.ofFloat( 636 ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, 637 mOriginalX - mThumbOffsetX), 638 PropertyValuesHolder.ofFloat( 639 ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, 640 mOriginalY - mThumbOffsetY), 641 PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, 1, 1), 642 PropertyValuesHolder.ofFloat( 643 ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, mOriginalAlpha / 2)); 644 645 final float translateX = mOriginalX - mCurrentX; 646 final float translateY = mOriginalY - mCurrentY; 647 // Adjust the duration to the travel distance. 648 final double travelDistance = Math.sqrt(translateX * translateX + translateY * translateY); 649 final double displayDiagonal = 650 Math.sqrt(mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y); 651 final long duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal 652 * (MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS)); 653 final AnimationListener listener = new AnimationListener(); 654 animator.setDuration(duration); 655 animator.setInterpolator(mCubicEaseOutInterpolator); 656 animator.addListener(listener); 657 animator.addUpdateListener(listener); 658 659 mService.mAnimationHandler.post(() -> animator.start()); 660 return animator; 661 } 662 createCancelAnimationLocked()663 private ValueAnimator createCancelAnimationLocked() { 664 final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder( 665 PropertyValuesHolder.ofFloat( 666 ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, mCurrentX), 667 PropertyValuesHolder.ofFloat( 668 ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, mCurrentY), 669 PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, 1, 0), 670 PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0)); 671 final AnimationListener listener = new AnimationListener(); 672 animator.setDuration(MIN_ANIMATION_DURATION_MS); 673 animator.setInterpolator(mCubicEaseOutInterpolator); 674 animator.addListener(listener); 675 animator.addUpdateListener(listener); 676 677 mService.mAnimationHandler.post(() -> animator.start()); 678 return animator; 679 } 680 isFromSource(int source)681 private boolean isFromSource(int source) { 682 return (mTouchSource & source) == source; 683 } 684 overridePointerIconLocked(int touchSource)685 void overridePointerIconLocked(int touchSource) { 686 mTouchSource = touchSource; 687 if (isFromSource(InputDevice.SOURCE_MOUSE)) { 688 InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_GRABBING); 689 } 690 } 691 692 private class AnimationListener 693 implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener { 694 @Override onAnimationUpdate(ValueAnimator animation)695 public void onAnimationUpdate(ValueAnimator animation) { 696 try (SurfaceControl.Transaction transaction = 697 mService.mTransactionFactory.get()) { 698 transaction.setPosition( 699 mSurfaceControl, 700 (float) animation.getAnimatedValue(ANIMATED_PROPERTY_X), 701 (float) animation.getAnimatedValue(ANIMATED_PROPERTY_Y)); 702 transaction.setAlpha( 703 mSurfaceControl, 704 (float) animation.getAnimatedValue(ANIMATED_PROPERTY_ALPHA)); 705 transaction.setMatrix( 706 mSurfaceControl, 707 (float) animation.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0, 708 0, (float) animation.getAnimatedValue(ANIMATED_PROPERTY_SCALE)); 709 transaction.apply(); 710 } 711 } 712 713 @Override onAnimationStart(Animator animator)714 public void onAnimationStart(Animator animator) {} 715 716 @Override onAnimationCancel(Animator animator)717 public void onAnimationCancel(Animator animator) {} 718 719 @Override onAnimationRepeat(Animator animator)720 public void onAnimationRepeat(Animator animator) {} 721 722 @Override onAnimationEnd(Animator animator)723 public void onAnimationEnd(Animator animator) { 724 mAnimationCompleted = true; 725 // Updating mDragState requires the WM lock so continues it on the out of 726 // AnimationThread. 727 mDragDropController.sendHandlerMessage(MSG_ANIMATION_END, null); 728 } 729 } 730 isAccessibilityDragDrop()731 boolean isAccessibilityDragDrop() { 732 return (mFlags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) != 0; 733 } 734 } 735