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 dragSurfaceRelinquished()84 boolean dragSurfaceRelinquished() { 85 return mDragState != null && mDragState.mRelinquishDragSurface; 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.setPosition( 183 surfaceControl, touchX - thumbCenterX, touchY - thumbCenterY); 184 transaction.show(surfaceControl); 185 displayContent.reparentToOverlay(transaction, surfaceControl); 186 callingWin.scheduleAnimation(); 187 if (SHOW_LIGHT_TRANSACTIONS) { 188 Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag"); 189 } 190 } else { 191 // Skip surface logic for a drag triggered by an AccessibilityAction 192 mDragState.broadcastDragStartedLocked(touchX, touchY); 193 194 // Timeout for the user to drop the content 195 sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, callingWin.mClient.asBinder(), 196 getAccessibilityManager().getRecommendedTimeoutMillis( 197 A11Y_DRAG_TIMEOUT_DEFAULT_MS, 198 AccessibilityManager.FLAG_CONTENT_CONTROLS)); 199 } 200 } finally { 201 if (surface != null) { 202 surface.release(); 203 } 204 if (mDragState != null && !mDragState.isInProgress()) { 205 mDragState.closeLocked(); 206 } 207 } 208 } 209 return dragToken; // success! 210 } finally { 211 mCallback.get().postPerformDrag(); 212 } 213 } 214 reportDropResult(IWindow window, boolean consumed)215 void reportDropResult(IWindow window, boolean consumed) { 216 IBinder token = window.asBinder(); 217 if (DEBUG_DRAG) { 218 Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token); 219 } 220 221 mCallback.get().preReportDropResult(window, consumed); 222 try { 223 synchronized (mService.mGlobalLock) { 224 if (mDragState == null) { 225 // Most likely the drop recipient ANRed and we ended the drag 226 // out from under it. Log the issue and move on. 227 Slog.w(TAG_WM, "Drop result given but no drag in progress"); 228 return; 229 } 230 231 if (mDragState.mToken != token) { 232 // We're in a drag, but the wrong window has responded. 233 Slog.w(TAG_WM, "Invalid drop-result claim by " + window); 234 throw new IllegalStateException("reportDropResult() by non-recipient"); 235 } 236 237 // The right window has responded, even if it's no longer around, 238 // so be sure to halt the timeout even if the later WindowState 239 // lookup fails. 240 mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder()); 241 WindowState callingWin = mService.windowForClientLocked(null, window, false); 242 if (callingWin == null) { 243 Slog.w(TAG_WM, "Bad result-reporting window " + window); 244 return; // !!! TODO: throw here? 245 } 246 247 mDragState.mDragResult = consumed; 248 mDragState.mRelinquishDragSurface = consumed 249 && mDragState.targetInterceptsGlobalDrag(callingWin); 250 mDragState.endDragLocked(); 251 } 252 } finally { 253 mCallback.get().postReportDropResult(); 254 } 255 } 256 cancelDragAndDrop(IBinder dragToken, boolean skipAnimation)257 void cancelDragAndDrop(IBinder dragToken, boolean skipAnimation) { 258 if (DEBUG_DRAG) { 259 Slog.d(TAG_WM, "cancelDragAndDrop"); 260 } 261 262 mCallback.get().preCancelDragAndDrop(dragToken); 263 try { 264 synchronized (mService.mGlobalLock) { 265 if (mDragState == null) { 266 Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()"); 267 throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()"); 268 } 269 270 if (mDragState.mToken != dragToken) { 271 Slog.w(TAG_WM, 272 "cancelDragAndDrop() does not match prepareDrag()"); 273 throw new IllegalStateException( 274 "cancelDragAndDrop() does not match prepareDrag()"); 275 } 276 277 mDragState.mDragResult = false; 278 mDragState.cancelDragLocked(skipAnimation); 279 } 280 } finally { 281 mCallback.get().postCancelDragAndDrop(); 282 } 283 } 284 285 /** 286 * Handles motion events. 287 * @param keepHandling Whether if the drag operation is continuing or this is the last motion 288 * event. 289 * @param newX X coordinate value in dp in the screen coordinate 290 * @param newY Y coordinate value in dp in the screen coordinate 291 */ handleMotionEvent(boolean keepHandling, float newX, float newY)292 void handleMotionEvent(boolean keepHandling, float newX, float newY) { 293 synchronized (mService.mGlobalLock) { 294 if (!dragDropActiveLocked()) { 295 // The drag has ended but the clean-up message has not been processed by 296 // window manager. Drop events that occur after this until window manager 297 // has a chance to clean-up the input handle. 298 return; 299 } 300 301 mDragState.updateDragSurfaceLocked(keepHandling, newX, newY); 302 } 303 } 304 dragRecipientEntered(IWindow window)305 void dragRecipientEntered(IWindow window) { 306 if (DEBUG_DRAG) { 307 Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder()); 308 } 309 } 310 dragRecipientExited(IWindow window)311 void dragRecipientExited(IWindow window) { 312 if (DEBUG_DRAG) { 313 Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder()); 314 } 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 synchronized (mService.mGlobalLock) { 346 mDragState.reportDropWindowLock(token, x, y); 347 } 348 } 349 dropForAccessibility(IWindow window, float x, float y)350 boolean dropForAccessibility(IWindow window, float x, float y) { 351 synchronized (mService.mGlobalLock) { 352 final boolean isA11yEnabled = getAccessibilityManager().isEnabled(); 353 if (!dragDropActiveLocked()) { 354 return false; 355 } 356 if (mDragState.isAccessibilityDragDrop() && isA11yEnabled) { 357 final WindowState winState = mService.windowForClientLocked( 358 null, window, false); 359 if (!mDragState.isWindowNotified(winState)) { 360 return false; 361 } 362 IBinder token = winState.mInputChannelToken; 363 return mDragState.reportDropWindowLock(token, x, y); 364 } 365 return false; 366 } 367 } 368 getAccessibilityManager()369 AccessibilityManager getAccessibilityManager() { 370 return (AccessibilityManager) mService.mContext.getSystemService( 371 Context.ACCESSIBILITY_SERVICE); 372 } 373 374 private class DragHandler extends Handler { 375 /** 376 * Lock for window manager. 377 */ 378 private final WindowManagerService mService; 379 DragHandler(WindowManagerService service, Looper looper)380 DragHandler(WindowManagerService service, Looper looper) { 381 super(looper); 382 mService = service; 383 } 384 385 @Override handleMessage(Message msg)386 public void handleMessage(Message msg) { 387 switch (msg.what) { 388 case MSG_DRAG_END_TIMEOUT: { 389 final IBinder win = (IBinder) msg.obj; 390 if (DEBUG_DRAG) { 391 Slog.w(TAG_WM, "Timeout ending drag to win " + win); 392 } 393 394 synchronized (mService.mGlobalLock) { 395 // !!! TODO: ANR the drag-receiving app 396 if (mDragState != null) { 397 mDragState.mDragResult = false; 398 mDragState.endDragLocked(); 399 } 400 } 401 break; 402 } 403 404 case MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT: { 405 if (DEBUG_DRAG) 406 Slog.d(TAG_WM, "Drag ending; tearing down input channel"); 407 final DragState.InputInterceptor interceptor = 408 (DragState.InputInterceptor) msg.obj; 409 if (interceptor == null) return; 410 synchronized (mService.mGlobalLock) { 411 interceptor.tearDown(); 412 } 413 break; 414 } 415 416 case MSG_ANIMATION_END: { 417 synchronized (mService.mGlobalLock) { 418 if (mDragState == null) { 419 Slog.wtf(TAG_WM, "mDragState unexpectedly became null while " + 420 "plyaing animation"); 421 return; 422 } 423 mDragState.closeLocked(); 424 } 425 break; 426 } 427 428 case MSG_REMOVE_DRAG_SURFACE_TIMEOUT: { 429 synchronized (mService.mGlobalLock) { 430 mService.mTransactionFactory.get() 431 .reparent((SurfaceControl) msg.obj, null).apply(); 432 } 433 break; 434 } 435 } 436 } 437 } 438 } 439