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.SurfaceControl.Transaction; 35 import android.view.SurfaceSession; 36 import android.view.View; 37 38 import com.android.internal.util.Preconditions; 39 import com.android.server.input.InputWindowHandle; 40 import com.android.server.wm.WindowManagerInternal.IDragDropCallback; 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; 75 } 76 getInputWindowHandleLocked()77 InputWindowHandle getInputWindowHandleLocked() { 78 return mDragState.getInputWindowHandle(); 79 } 80 registerCallback(IDragDropCallback callback)81 void registerCallback(IDragDropCallback callback) { 82 Preconditions.checkNotNull(callback); 83 mCallback.set(callback); 84 } 85 DragDropController(WindowManagerService service, Looper looper)86 DragDropController(WindowManagerService service, Looper looper) { 87 mService = service; 88 mHandler = new DragHandler(service, looper); 89 } 90 sendDragStartedIfNeededLocked(WindowState window)91 void sendDragStartedIfNeededLocked(WindowState window) { 92 mDragState.sendDragStartedIfNeededLocked(window); 93 } 94 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)95 IBinder performDrag(SurfaceSession session, int callerPid, int callerUid, IWindow window, 96 int flags, SurfaceControl surface, int touchSource, float touchX, float touchY, 97 float thumbCenterX, float thumbCenterY, ClipData data) { 98 if (DEBUG_DRAG) { 99 Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=" + 100 Integer.toHexString(flags) + " data=" + data); 101 } 102 103 final IBinder dragToken = new Binder(); 104 final boolean callbackResult = mCallback.get().prePerformDrag(window, dragToken, 105 touchSource, touchX, touchY, thumbCenterX, thumbCenterY, data); 106 try { 107 synchronized (mService.mWindowMap) { 108 try { 109 if (!callbackResult) { 110 Slog.w(TAG_WM, "IDragDropCallback rejects the performDrag request"); 111 return null; 112 } 113 114 if (dragDropActiveLocked()) { 115 Slog.w(TAG_WM, "Drag already in progress"); 116 return null; 117 } 118 119 final WindowState callingWin = mService.windowForClientLocked( 120 null, window, false); 121 if (callingWin == null) { 122 Slog.w(TAG_WM, "Bad requesting window " + window); 123 return null; // !!! TODO: throw here? 124 } 125 126 // !!! TODO: if input is not still focused on the initiating window, fail 127 // the drag initiation (e.g. an alarm window popped up just as the application 128 // called performDrag() 129 130 // !!! TODO: extract the current touch (x, y) in screen coordinates. That 131 // will let us eliminate the (touchX,touchY) parameters from the API. 132 133 // !!! FIXME: put all this heavy stuff onto the mHandler looper, as well as 134 // the actual drag event dispatch stuff in the dragstate 135 136 // !!! TODO(multi-display): support other displays 137 138 final DisplayContent displayContent = callingWin.getDisplayContent(); 139 if (displayContent == null) { 140 Slog.w(TAG_WM, "display content is null"); 141 return null; 142 } 143 144 final float alpha = (flags & View.DRAG_FLAG_OPAQUE) == 0 ? 145 DRAG_SHADOW_ALPHA_TRANSPARENT : 1; 146 final IBinder winBinder = window.asBinder(); 147 IBinder token = new Binder(); 148 mDragState = new DragState(mService, this, token, surface, flags, winBinder); 149 surface = null; 150 mDragState.mPid = callerPid; 151 mDragState.mUid = callerUid; 152 mDragState.mOriginalAlpha = alpha; 153 mDragState.mToken = dragToken; 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 mDragState.mDisplayContent = displayContent; 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 final SurfaceControl surfaceControl = mDragState.mSurfaceControl; 173 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag"); 174 175 final SurfaceControl.Transaction transaction = 176 callingWin.getPendingTransaction(); 177 transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha); 178 transaction.setPosition( 179 surfaceControl, touchX - thumbCenterX, touchY - thumbCenterY); 180 transaction.show(surfaceControl); 181 displayContent.reparentToOverlay(transaction, surfaceControl); 182 callingWin.scheduleAnimation(); 183 184 if (SHOW_LIGHT_TRANSACTIONS) { 185 Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag"); 186 } 187 188 mDragState.notifyLocationLocked(touchX, touchY); 189 } finally { 190 if (surface != null) { 191 surface.release(); 192 } 193 if (mDragState != null && !mDragState.isInProgress()) { 194 mDragState.closeLocked(); 195 } 196 } 197 } 198 return dragToken; // success! 199 } finally { 200 mCallback.get().postPerformDrag(); 201 } 202 } 203 reportDropResult(IWindow window, boolean consumed)204 void reportDropResult(IWindow window, boolean consumed) { 205 IBinder token = window.asBinder(); 206 if (DEBUG_DRAG) { 207 Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token); 208 } 209 210 mCallback.get().preReportDropResult(window, consumed); 211 try { 212 synchronized (mService.mWindowMap) { 213 if (mDragState == null) { 214 // Most likely the drop recipient ANRed and we ended the drag 215 // out from under it. Log the issue and move on. 216 Slog.w(TAG_WM, "Drop result given but no drag in progress"); 217 return; 218 } 219 220 if (mDragState.mToken != token) { 221 // We're in a drag, but the wrong window has responded. 222 Slog.w(TAG_WM, "Invalid drop-result claim by " + window); 223 throw new IllegalStateException("reportDropResult() by non-recipient"); 224 } 225 226 // The right window has responded, even if it's no longer around, 227 // so be sure to halt the timeout even if the later WindowState 228 // lookup fails. 229 mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder()); 230 WindowState callingWin = mService.windowForClientLocked(null, window, false); 231 if (callingWin == null) { 232 Slog.w(TAG_WM, "Bad result-reporting window " + window); 233 return; // !!! TODO: throw here? 234 } 235 236 mDragState.mDragResult = consumed; 237 mDragState.endDragLocked(); 238 } 239 } finally { 240 mCallback.get().postReportDropResult(); 241 } 242 } 243 cancelDragAndDrop(IBinder dragToken)244 void cancelDragAndDrop(IBinder dragToken) { 245 if (DEBUG_DRAG) { 246 Slog.d(TAG_WM, "cancelDragAndDrop"); 247 } 248 249 mCallback.get().preCancelDragAndDrop(dragToken); 250 try { 251 synchronized (mService.mWindowMap) { 252 if (mDragState == null) { 253 Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()"); 254 throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()"); 255 } 256 257 if (mDragState.mToken != dragToken) { 258 Slog.w(TAG_WM, 259 "cancelDragAndDrop() does not match prepareDrag()"); 260 throw new IllegalStateException( 261 "cancelDragAndDrop() does not match prepareDrag()"); 262 } 263 264 mDragState.mDragResult = false; 265 mDragState.cancelDragLocked(); 266 } 267 } finally { 268 mCallback.get().postCancelDragAndDrop(); 269 } 270 } 271 272 /** 273 * Handles motion events. 274 * @param keepHandling Whether if the drag operation is continuing or this is the last motion 275 * event. 276 * @param newX X coordinate value in dp in the screen coordinate 277 * @param newY Y coordinate value in dp in the screen coordinate 278 */ handleMotionEvent(boolean keepHandling, float newX, float newY)279 void handleMotionEvent(boolean keepHandling, float newX, float newY) { 280 synchronized (mService.mWindowMap) { 281 if (!dragDropActiveLocked()) { 282 // The drag has ended but the clean-up message has not been processed by 283 // window manager. Drop events that occur after this until window manager 284 // has a chance to clean-up the input handle. 285 return; 286 } 287 288 if (keepHandling) { 289 mDragState.notifyMoveLocked(newX, newY); 290 } else { 291 mDragState.notifyDropLocked(newX, newY); 292 } 293 } 294 } 295 dragRecipientEntered(IWindow window)296 void dragRecipientEntered(IWindow window) { 297 if (DEBUG_DRAG) { 298 Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder()); 299 } 300 } 301 dragRecipientExited(IWindow window)302 void dragRecipientExited(IWindow window) { 303 if (DEBUG_DRAG) { 304 Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder()); 305 } 306 } 307 308 /** 309 * Sends a message to the Handler managed by DragDropController. 310 */ sendHandlerMessage(int what, Object arg)311 void sendHandlerMessage(int what, Object arg) { 312 mHandler.obtainMessage(what, arg).sendToTarget(); 313 } 314 315 /** 316 * Sends a timeout message to the Handler managed by DragDropController. 317 */ sendTimeoutMessage(int what, Object arg)318 void sendTimeoutMessage(int what, Object arg) { 319 mHandler.removeMessages(what, arg); 320 final Message msg = mHandler.obtainMessage(what, arg); 321 mHandler.sendMessageDelayed(msg, DRAG_TIMEOUT_MS); 322 } 323 324 /** 325 * Notifies the current drag state is closed. 326 */ onDragStateClosedLocked(DragState dragState)327 void onDragStateClosedLocked(DragState dragState) { 328 if (mDragState != dragState) { 329 Slog.wtf(TAG_WM, "Unknown drag state is closed"); 330 return; 331 } 332 mDragState = null; 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.mWindowMap) { 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.mWindowMap) { 372 interceptor.tearDown(); 373 } 374 break; 375 } 376 377 case MSG_ANIMATION_END: { 378 synchronized (mService.mWindowMap) { 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 } 390 } 391 } 392