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