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