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 73 boolean dragDropActiveLocked() { 74 return mDragState != null; 75 } 76 77 InputWindowHandle getInputWindowHandleLocked() { 78 return mDragState.getInputWindowHandle(); 79 } 80 81 void registerCallback(IDragDropCallback callback) { 82 Preconditions.checkNotNull(callback); 83 mCallback.set(callback); 84 } 85 86 DragDropController(WindowManagerService service, Looper looper) { 87 mService = service; 88 mHandler = new DragHandler(service, looper); 89 } 90 91 void sendDragStartedIfNeededLocked(WindowState window) { 92 mDragState.sendDragStartedIfNeededLocked(window); 93 } 94 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 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 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 */ 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 296 void dragRecipientEntered(IWindow window) { 297 if (DEBUG_DRAG) { 298 Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder()); 299 } 300 } 301 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 */ 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 */ 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 */ 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 341 DragHandler(WindowManagerService service, Looper looper) { 342 super(looper); 343 mService = service; 344 } 345 346 @Override 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