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.concurrent.atomic.AtomicReference; 41 42 /** 43 * Managing drag and drop operations initiated by View#startDragAndDrop. 44 */ 45 class DragDropController { 46 private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f; 47 private static final long DRAG_TIMEOUT_MS = 5000; 48 49 // Messages for Handler. 50 static final int MSG_DRAG_END_TIMEOUT = 0; 51 static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 1; 52 static final int MSG_ANIMATION_END = 2; 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 dragDropActiveLocked()72 boolean dragDropActiveLocked() { 73 return mDragState != null && !mDragState.isClosing(); 74 } 75 registerCallback(IDragDropCallback callback)76 void registerCallback(IDragDropCallback callback) { 77 Preconditions.checkNotNull(callback); 78 mCallback.set(callback); 79 } 80 DragDropController(WindowManagerService service, Looper looper)81 DragDropController(WindowManagerService service, Looper looper) { 82 mService = service; 83 mHandler = new DragHandler(service, looper); 84 } 85 sendDragStartedIfNeededLocked(WindowState window)86 void sendDragStartedIfNeededLocked(WindowState window) { 87 mDragState.sendDragStartedIfNeededLocked(window); 88 } 89 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)90 IBinder performDrag(SurfaceSession session, int callerPid, int callerUid, IWindow window, 91 int flags, SurfaceControl surface, int touchSource, float touchX, float touchY, 92 float thumbCenterX, float thumbCenterY, ClipData data) { 93 if (DEBUG_DRAG) { 94 Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=" + 95 Integer.toHexString(flags) + " data=" + data); 96 } 97 98 final IBinder dragToken = new Binder(); 99 final boolean callbackResult = mCallback.get().prePerformDrag(window, dragToken, 100 touchSource, touchX, touchY, thumbCenterX, thumbCenterY, data); 101 try { 102 synchronized (mService.mGlobalLock) { 103 try { 104 if (!callbackResult) { 105 Slog.w(TAG_WM, "IDragDropCallback rejects the performDrag request"); 106 return null; 107 } 108 109 if (dragDropActiveLocked()) { 110 Slog.w(TAG_WM, "Drag already in progress"); 111 return null; 112 } 113 114 final WindowState callingWin = mService.windowForClientLocked( 115 null, window, false); 116 if (callingWin == null) { 117 Slog.w(TAG_WM, "Bad requesting window " + window); 118 return null; // !!! TODO: throw here? 119 } 120 121 // !!! TODO: if input is not still focused on the initiating window, fail 122 // the drag initiation (e.g. an alarm window popped up just as the application 123 // called performDrag() 124 125 // !!! TODO: extract the current touch (x, y) in screen coordinates. That 126 // will let us eliminate the (touchX,touchY) parameters from the API. 127 128 // !!! FIXME: put all this heavy stuff onto the mHandler looper, as well as 129 // the actual drag event dispatch stuff in the dragstate 130 131 // !!! TODO(multi-display): support other displays 132 133 final DisplayContent displayContent = callingWin.getDisplayContent(); 134 if (displayContent == null) { 135 Slog.w(TAG_WM, "display content is null"); 136 return null; 137 } 138 139 final float alpha = (flags & View.DRAG_FLAG_OPAQUE) == 0 ? 140 DRAG_SHADOW_ALPHA_TRANSPARENT : 1; 141 final IBinder winBinder = window.asBinder(); 142 IBinder token = new Binder(); 143 mDragState = new DragState(mService, this, token, surface, flags, winBinder); 144 surface = null; 145 mDragState.mPid = callerPid; 146 mDragState.mUid = callerUid; 147 mDragState.mOriginalAlpha = alpha; 148 mDragState.mToken = dragToken; 149 mDragState.mDisplayContent = displayContent; 150 151 final Display display = displayContent.getDisplay(); 152 if (!mCallback.get().registerInputChannel( 153 mDragState, display, mService.mInputManager, 154 callingWin.mInputChannel)) { 155 Slog.e(TAG_WM, "Unable to transfer touch focus"); 156 return null; 157 } 158 159 mDragState.mData = data; 160 mDragState.broadcastDragStartedLocked(touchX, touchY); 161 mDragState.overridePointerIconLocked(touchSource); 162 // remember the thumb offsets for later 163 mDragState.mThumbOffsetX = thumbCenterX; 164 mDragState.mThumbOffsetY = thumbCenterY; 165 166 // Make the surface visible at the proper location 167 final SurfaceControl surfaceControl = mDragState.mSurfaceControl; 168 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag"); 169 170 final SurfaceControl.Transaction transaction = 171 callingWin.getPendingTransaction(); 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