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