1 /* 2 * Copyright (C) 2017 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 com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG; 20 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; 21 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 22 23 import android.annotation.NonNull; 24 import android.content.ClipData; 25 import android.content.Context; 26 import android.os.Binder; 27 import android.os.Handler; 28 import android.os.IBinder; 29 import android.os.Looper; 30 import android.os.Message; 31 import android.util.Slog; 32 import android.view.Display; 33 import android.view.IWindow; 34 import android.view.SurfaceControl; 35 import android.view.View; 36 import android.view.accessibility.AccessibilityManager; 37 38 import com.android.server.wm.WindowManagerInternal.IDragDropCallback; 39 40 import java.util.Objects; 41 import java.util.concurrent.CompletableFuture; 42 import java.util.concurrent.TimeUnit; 43 import java.util.concurrent.atomic.AtomicReference; 44 45 /** 46 * Managing drag and drop operations initiated by View#startDragAndDrop. 47 */ 48 class DragDropController { 49 private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f; 50 static final long DRAG_TIMEOUT_MS = 5000; 51 private static final int A11Y_DRAG_TIMEOUT_DEFAULT_MS = 60000; 52 53 // Messages for Handler. 54 static final int MSG_DRAG_END_TIMEOUT = 0; 55 static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 1; 56 static final int MSG_ANIMATION_END = 2; 57 static final int MSG_REMOVE_DRAG_SURFACE_TIMEOUT = 3; 58 59 /** 60 * Drag state per operation. 61 * Needs a lock of {@code WindowManagerService#mWindowMap} to read this. Needs both locks of 62 * {@code mWriteLock} and {@code WindowManagerService#mWindowMap} to update this. 63 * The variable is cleared by {@code #onDragStateClosedLocked} which is invoked by DragState 64 * itself, thus the variable can be null after calling DragState's methods. 65 */ 66 private DragState mDragState; 67 68 private WindowManagerService mService; 69 private final Handler mHandler; 70 71 /** 72 * Callback which is used to sync drag state with the vendor-specific code. 73 */ 74 @NonNull private AtomicReference<IDragDropCallback> mCallback = new AtomicReference<>( 75 new IDragDropCallback() {}); 76 DragDropController(WindowManagerService service, Looper looper)77 DragDropController(WindowManagerService service, Looper looper) { 78 mService = service; 79 mHandler = new DragHandler(service, looper); 80 } 81 dragDropActiveLocked()82 boolean dragDropActiveLocked() { 83 return mDragState != null && !mDragState.isClosing(); 84 } 85 dragSurfaceRelinquishedToDropTarget()86 boolean dragSurfaceRelinquishedToDropTarget() { 87 return mDragState != null && mDragState.mRelinquishDragSurfaceToDropTarget; 88 } 89 registerCallback(IDragDropCallback callback)90 void registerCallback(IDragDropCallback callback) { 91 Objects.requireNonNull(callback); 92 mCallback.set(callback); 93 } 94 sendDragStartedIfNeededLocked(WindowState window)95 void sendDragStartedIfNeededLocked(WindowState window) { 96 mDragState.sendDragStartedIfNeededLocked(window); 97 } 98 performDrag(int callerPid, int callerUid, IWindow window, int flags, SurfaceControl surface, int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data)99 IBinder performDrag(int callerPid, int callerUid, IWindow window, int flags, 100 SurfaceControl surface, int touchSource, float touchX, float touchY, 101 float thumbCenterX, float thumbCenterY, ClipData data) { 102 if (DEBUG_DRAG) { 103 Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=" + 104 Integer.toHexString(flags) + " data=" + data + " touch(" + touchX + "," 105 + touchY + ") thumb center(" + thumbCenterX + "," + thumbCenterY + ")"); 106 } 107 108 final IBinder dragToken = new Binder(); 109 final boolean callbackResult = mCallback.get().prePerformDrag(window, dragToken, 110 touchSource, touchX, touchY, thumbCenterX, thumbCenterY, data); 111 try { 112 DisplayContent displayContent = null; 113 CompletableFuture<Boolean> touchFocusTransferredFuture = null; 114 synchronized (mService.mGlobalLock) { 115 try { 116 if (!callbackResult) { 117 Slog.w(TAG_WM, "IDragDropCallback rejects the performDrag request"); 118 return null; 119 } 120 121 if (dragDropActiveLocked()) { 122 Slog.w(TAG_WM, "Drag already in progress"); 123 return null; 124 } 125 126 final WindowState callingWin = mService.windowForClientLocked( 127 null, window, false); 128 if (callingWin == null || !callingWin.canReceiveTouchInput()) { 129 Slog.w(TAG_WM, "Bad requesting window " + window); 130 return null; // !!! TODO: throw here? 131 } 132 133 // !!! TODO: if input is not still focused on the initiating window, fail 134 // the drag initiation (e.g. an alarm window popped up just as the application 135 // called performDrag() 136 137 // !!! TODO: extract the current touch (x, y) in screen coordinates. That 138 // will let us eliminate the (touchX,touchY) parameters from the API. 139 140 // !!! FIXME: put all this heavy stuff onto the mHandler looper, as well as 141 // the actual drag event dispatch stuff in the dragstate 142 143 // !!! TODO(multi-display): support other displays 144 145 displayContent = callingWin.getDisplayContent(); 146 if (displayContent == null) { 147 Slog.w(TAG_WM, "display content is null"); 148 return null; 149 } 150 151 final float alpha = (flags & View.DRAG_FLAG_OPAQUE) == 0 ? 152 DRAG_SHADOW_ALPHA_TRANSPARENT : 1; 153 final IBinder winBinder = window.asBinder(); 154 IBinder token = new Binder(); 155 mDragState = new DragState(mService, this, token, surface, flags, winBinder); 156 surface = null; 157 mDragState.mPid = callerPid; 158 mDragState.mUid = callerUid; 159 mDragState.mOriginalAlpha = alpha; 160 mDragState.mAnimatedScale = callingWin.mGlobalScale; 161 mDragState.mToken = dragToken; 162 mDragState.mDisplayContent = displayContent; 163 mDragState.mData = data; 164 165 if ((flags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) == 0) { 166 final Display display = displayContent.getDisplay(); 167 touchFocusTransferredFuture = mCallback.get().registerInputChannel( 168 mDragState, display, mService.mInputManager, 169 callingWin.mInputChannel); 170 } else { 171 // Skip surface logic for a drag triggered by an AccessibilityAction 172 mDragState.broadcastDragStartedLocked(touchX, touchY); 173 174 // Timeout for the user to drop the content 175 sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, callingWin.mClient.asBinder(), 176 getAccessibilityManager().getRecommendedTimeoutMillis( 177 A11Y_DRAG_TIMEOUT_DEFAULT_MS, 178 AccessibilityManager.FLAG_CONTENT_CONTROLS)); 179 180 return dragToken; 181 } 182 } finally { 183 if (surface != null) { 184 try (final SurfaceControl.Transaction transaction = 185 mService.mTransactionFactory.get()) { 186 transaction.remove(surface); 187 transaction.apply(); 188 } 189 } 190 } 191 } 192 193 boolean touchFocusTransferred = false; 194 try { 195 touchFocusTransferred = touchFocusTransferredFuture.get(DRAG_TIMEOUT_MS, 196 TimeUnit.MILLISECONDS); 197 } catch (Exception exception) { 198 Slog.e(TAG_WM, "Exception thrown while waiting for touch focus transfer", 199 exception); 200 } 201 202 synchronized (mService.mGlobalLock) { 203 if (!touchFocusTransferred) { 204 Slog.e(TAG_WM, "Unable to transfer touch focus"); 205 mDragState.closeLocked(); 206 return null; 207 } 208 209 final SurfaceControl surfaceControl = mDragState.mSurfaceControl; 210 mDragState.broadcastDragStartedLocked(touchX, touchY); 211 mDragState.overridePointerIconLocked(touchSource); 212 // remember the thumb offsets for later 213 mDragState.mThumbOffsetX = thumbCenterX; 214 mDragState.mThumbOffsetY = thumbCenterY; 215 216 // Make the surface visible at the proper location 217 if (SHOW_LIGHT_TRANSACTIONS) { 218 Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag"); 219 } 220 221 final SurfaceControl.Transaction transaction = mDragState.mTransaction; 222 transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha); 223 transaction.show(surfaceControl); 224 displayContent.reparentToOverlay(transaction, surfaceControl); 225 mDragState.updateDragSurfaceLocked(true, touchX, touchY); 226 if (SHOW_LIGHT_TRANSACTIONS) { 227 Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag"); 228 } 229 } 230 return dragToken; // success! 231 } finally { 232 mCallback.get().postPerformDrag(); 233 } 234 } 235 reportDropResult(IWindow window, boolean consumed)236 void reportDropResult(IWindow window, boolean consumed) { 237 IBinder token = window.asBinder(); 238 if (DEBUG_DRAG) { 239 Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token); 240 } 241 242 mCallback.get().preReportDropResult(window, consumed); 243 try { 244 synchronized (mService.mGlobalLock) { 245 if (mDragState == null) { 246 // Most likely the drop recipient ANRed and we ended the drag 247 // out from under it. Log the issue and move on. 248 Slog.w(TAG_WM, "Drop result given but no drag in progress"); 249 return; 250 } 251 252 if (mDragState.mToken != token) { 253 // We're in a drag, but the wrong window has responded. 254 Slog.w(TAG_WM, "Invalid drop-result claim by " + window); 255 throw new IllegalStateException("reportDropResult() by non-recipient"); 256 } 257 258 // The right window has responded, even if it's no longer around, 259 // so be sure to halt the timeout even if the later WindowState 260 // lookup fails. 261 mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder()); 262 WindowState callingWin = mService.windowForClientLocked(null, window, false); 263 if (callingWin == null) { 264 Slog.w(TAG_WM, "Bad result-reporting window " + window); 265 return; // !!! TODO: throw here? 266 } 267 268 mDragState.mDragResult = consumed; 269 mDragState.mRelinquishDragSurfaceToDropTarget = consumed 270 && mDragState.targetInterceptsGlobalDrag(callingWin); 271 mDragState.endDragLocked(); 272 } 273 } finally { 274 mCallback.get().postReportDropResult(); 275 } 276 } 277 cancelDragAndDrop(IBinder dragToken, boolean skipAnimation)278 void cancelDragAndDrop(IBinder dragToken, boolean skipAnimation) { 279 if (DEBUG_DRAG) { 280 Slog.d(TAG_WM, "cancelDragAndDrop"); 281 } 282 283 mCallback.get().preCancelDragAndDrop(dragToken); 284 try { 285 synchronized (mService.mGlobalLock) { 286 if (mDragState == null) { 287 Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()"); 288 throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()"); 289 } 290 291 if (mDragState.mToken != dragToken) { 292 Slog.w(TAG_WM, 293 "cancelDragAndDrop() does not match prepareDrag()"); 294 throw new IllegalStateException( 295 "cancelDragAndDrop() does not match prepareDrag()"); 296 } 297 298 mDragState.mDragResult = false; 299 mDragState.cancelDragLocked(skipAnimation); 300 } 301 } finally { 302 mCallback.get().postCancelDragAndDrop(); 303 } 304 } 305 306 /** 307 * Handles motion events. 308 * @param keepHandling Whether if the drag operation is continuing or this is the last motion 309 * event. 310 * @param newX X coordinate value in dp in the screen coordinate 311 * @param newY Y coordinate value in dp in the screen coordinate 312 */ handleMotionEvent(boolean keepHandling, float newX, float newY)313 void handleMotionEvent(boolean keepHandling, float newX, float newY) { 314 synchronized (mService.mGlobalLock) { 315 if (!dragDropActiveLocked()) { 316 // The drag has ended but the clean-up message has not been processed by 317 // window manager. Drop events that occur after this until window manager 318 // has a chance to clean-up the input handle. 319 return; 320 } 321 322 mDragState.updateDragSurfaceLocked(keepHandling, newX, newY); 323 } 324 } 325 dragRecipientEntered(IWindow window)326 void dragRecipientEntered(IWindow window) { 327 if (DEBUG_DRAG) { 328 Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder()); 329 } 330 mCallback.get().dragRecipientEntered(window); 331 } 332 dragRecipientExited(IWindow window)333 void dragRecipientExited(IWindow window) { 334 if (DEBUG_DRAG) { 335 Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder()); 336 } 337 mCallback.get().dragRecipientExited(window); 338 } 339 340 /** 341 * Sends a message to the Handler managed by DragDropController. 342 */ sendHandlerMessage(int what, Object arg)343 void sendHandlerMessage(int what, Object arg) { 344 mHandler.obtainMessage(what, arg).sendToTarget(); 345 } 346 347 /** 348 * Sends a timeout message to the Handler managed by DragDropController. 349 */ sendTimeoutMessage(int what, Object arg, long timeoutMs)350 void sendTimeoutMessage(int what, Object arg, long timeoutMs) { 351 mHandler.removeMessages(what, arg); 352 final Message msg = mHandler.obtainMessage(what, arg); 353 mHandler.sendMessageDelayed(msg, timeoutMs); 354 } 355 356 /** 357 * Notifies the current drag state is closed. 358 */ onDragStateClosedLocked(DragState dragState)359 void onDragStateClosedLocked(DragState dragState) { 360 if (mDragState != dragState) { 361 Slog.wtf(TAG_WM, "Unknown drag state is closed"); 362 return; 363 } 364 mDragState = null; 365 } 366 reportDropWindow(IBinder token, float x, float y)367 void reportDropWindow(IBinder token, float x, float y) { 368 if (mDragState == null) { 369 Slog.w(TAG_WM, "Drag state is closed."); 370 return; 371 } 372 373 synchronized (mService.mGlobalLock) { 374 mDragState.reportDropWindowLock(token, x, y); 375 } 376 } 377 dropForAccessibility(IWindow window, float x, float y)378 boolean dropForAccessibility(IWindow window, float x, float y) { 379 synchronized (mService.mGlobalLock) { 380 final boolean isA11yEnabled = getAccessibilityManager().isEnabled(); 381 if (!dragDropActiveLocked()) { 382 return false; 383 } 384 if (mDragState.isAccessibilityDragDrop() && isA11yEnabled) { 385 final WindowState winState = mService.windowForClientLocked( 386 null, window, false); 387 if (!mDragState.isWindowNotified(winState)) { 388 return false; 389 } 390 IBinder token = winState.mInputChannelToken; 391 return mDragState.reportDropWindowLock(token, x, y); 392 } 393 return false; 394 } 395 } 396 getAccessibilityManager()397 AccessibilityManager getAccessibilityManager() { 398 return (AccessibilityManager) mService.mContext.getSystemService( 399 Context.ACCESSIBILITY_SERVICE); 400 } 401 402 private class DragHandler extends Handler { 403 /** 404 * Lock for window manager. 405 */ 406 private final WindowManagerService mService; 407 DragHandler(WindowManagerService service, Looper looper)408 DragHandler(WindowManagerService service, Looper looper) { 409 super(looper); 410 mService = service; 411 } 412 413 @Override handleMessage(Message msg)414 public void handleMessage(Message msg) { 415 switch (msg.what) { 416 case MSG_DRAG_END_TIMEOUT: { 417 final IBinder win = (IBinder) msg.obj; 418 if (DEBUG_DRAG) { 419 Slog.w(TAG_WM, "Timeout ending drag to win " + win); 420 } 421 422 synchronized (mService.mGlobalLock) { 423 // !!! TODO: ANR the drag-receiving app 424 if (mDragState != null) { 425 mDragState.mDragResult = false; 426 mDragState.endDragLocked(); 427 } 428 } 429 break; 430 } 431 432 case MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT: { 433 if (DEBUG_DRAG) 434 Slog.d(TAG_WM, "Drag ending; tearing down input channel"); 435 final DragState.InputInterceptor interceptor = 436 (DragState.InputInterceptor) msg.obj; 437 if (interceptor == null) return; 438 synchronized (mService.mGlobalLock) { 439 interceptor.tearDown(); 440 } 441 break; 442 } 443 444 case MSG_ANIMATION_END: { 445 synchronized (mService.mGlobalLock) { 446 if (mDragState == null) { 447 Slog.wtf(TAG_WM, "mDragState unexpectedly became null while " + 448 "playing animation"); 449 return; 450 } 451 mDragState.closeLocked(); 452 } 453 break; 454 } 455 456 case MSG_REMOVE_DRAG_SURFACE_TIMEOUT: { 457 synchronized (mService.mGlobalLock) { 458 mService.mTransactionFactory.get() 459 .reparent((SurfaceControl) msg.obj, null).apply(); 460 } 461 break; 462 } 463 } 464 } 465 } 466 } 467