• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
20 import static android.view.View.DRAG_FLAG_GLOBAL;
21 import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION;
22 import static android.view.View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG;
23 
24 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
25 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
26 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
27 
28 import android.annotation.NonNull;
29 import android.content.ClipData;
30 import android.content.Context;
31 import android.hardware.display.DisplayTopology;
32 import android.hardware.input.InputManagerGlobal;
33 import android.os.Binder;
34 import android.os.Handler;
35 import android.os.HandlerExecutor;
36 import android.os.IBinder;
37 import android.os.Looper;
38 import android.os.Message;
39 import android.os.RemoteException;
40 import android.os.Trace;
41 import android.util.Slog;
42 import android.view.Display;
43 import android.view.DragEvent;
44 import android.view.IWindow;
45 import android.view.InputDevice;
46 import android.view.PointerIcon;
47 import android.view.SurfaceControl;
48 import android.view.View;
49 import android.view.accessibility.AccessibilityManager;
50 import android.window.IGlobalDragListener;
51 import android.window.IUnhandledDragCallback;
52 
53 import com.android.internal.annotations.VisibleForTesting;
54 import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
55 import com.android.window.flags.Flags;
56 
57 import java.util.Objects;
58 import java.util.Random;
59 import java.util.concurrent.CompletableFuture;
60 import java.util.concurrent.TimeUnit;
61 import java.util.concurrent.atomic.AtomicReference;
62 import java.util.function.Consumer;
63 
64 /**
65  * Managing drag and drop operations initiated by View#startDragAndDrop.
66  */
67 class DragDropController {
68     private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
69     static final long DRAG_TIMEOUT_MS = 5000;
70     private static final int A11Y_DRAG_TIMEOUT_DEFAULT_MS = 60000;
71 
72     // Messages for Handler.
73     static final int MSG_DRAG_END_TIMEOUT = 0;
74     static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 1;
75     static final int MSG_ANIMATION_END = 2;
76     static final int MSG_REMOVE_DRAG_SURFACE_TIMEOUT = 3;
77     static final int MSG_UNHANDLED_DROP_LISTENER_TIMEOUT = 4;
78 
79     /**
80      * Drag state per operation.
81      * Needs a lock of {@code WindowManagerService#mWindowMap} to read this. Needs both locks of
82      * {@code mWriteLock} and {@code WindowManagerService#mWindowMap} to update this.
83      * The variable is cleared by {@code #onDragStateClosedLocked} which is invoked by DragState
84      * itself, thus the variable can be null after calling DragState's methods.
85      */
86     private DragState mDragState;
87 
88     private WindowManagerService mService;
89     private final Handler mHandler;
90     private final Consumer<DisplayTopology> mDisplayTopologyListener =
91             this::handleDisplayTopologyChange;
92 
93     // The global drag listener for handling cross-window drags
94     private IGlobalDragListener mGlobalDragListener;
95     private final IBinder.DeathRecipient mGlobalDragListenerDeathRecipient =
96             new IBinder.DeathRecipient() {
97         @Override
98         public void binderDied() {
99             synchronized (mService.mGlobalLock) {
100                 if (hasPendingUnhandledDropCallback()) {
101                     onUnhandledDropCallback(false /* consumedByListeners */);
102                 }
103                 setGlobalDragListener(null);
104             }
105         }
106     };
107 
108     /**
109      * Callback which is used to sync drag state with the vendor-specific code.
110      */
111     @NonNull private AtomicReference<IDragDropCallback> mCallback = new AtomicReference<>(
112             new IDragDropCallback() {});
113 
DragDropController(WindowManagerService service, Looper looper)114     DragDropController(WindowManagerService service, Looper looper) {
115         mService = service;
116         mHandler = new DragHandler(service, looper);
117         if (Flags.enableConnectedDisplaysDnd()) {
118             mService.mDisplayManager.registerTopologyListener(
119                     new HandlerExecutor(mService.mH), mDisplayTopologyListener);
120         }
121     }
122 
123     @VisibleForTesting
getHandler()124     Handler getHandler() {
125         return mHandler;
126     }
127 
dragDropActiveLocked()128     boolean dragDropActiveLocked() {
129         return mDragState != null && !mDragState.isClosing();
130     }
131 
132     @VisibleForTesting
dragSurfaceRelinquishedToDropTarget()133     boolean dragSurfaceRelinquishedToDropTarget() {
134         return mDragState != null && mDragState.mRelinquishDragSurfaceToDropTarget;
135     }
136 
registerCallback(IDragDropCallback callback)137     void registerCallback(IDragDropCallback callback) {
138         Objects.requireNonNull(callback);
139         mCallback.set(callback);
140     }
141 
142     /**
143      * Sets the listener for unhandled cross-window drags.
144      */
setGlobalDragListener(IGlobalDragListener listener)145     public void setGlobalDragListener(IGlobalDragListener listener) {
146         if (mGlobalDragListener != null && mGlobalDragListener.asBinder() != null) {
147             mGlobalDragListener.asBinder().unlinkToDeath(
148                     mGlobalDragListenerDeathRecipient, 0);
149         }
150         mGlobalDragListener = listener;
151         if (listener != null && listener.asBinder() != null) {
152             try {
153                 mGlobalDragListener.asBinder().linkToDeath(
154                         mGlobalDragListenerDeathRecipient, 0);
155             } catch (RemoteException e) {
156                 mGlobalDragListener = null;
157             }
158         }
159     }
160 
sendDragStartedIfNeededLocked(WindowState window)161     void sendDragStartedIfNeededLocked(WindowState window) {
162         mDragState.sendDragStartedIfNeededLocked(window);
163     }
164 
performDrag(int callerPid, int callerUid, IWindow window, int flags, SurfaceControl surface, int touchSource, int touchDeviceId, int touchPointerId, float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data)165     IBinder performDrag(int callerPid, int callerUid, IWindow window, int flags,
166             SurfaceControl surface, int touchSource, int touchDeviceId, int touchPointerId,
167             float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) {
168         if (DEBUG_DRAG) {
169             Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=0x" +
170                     Integer.toHexString(flags) + " data=" + data + " touch(" + touchX + ","
171                     + touchY + ") thumb center(" + thumbCenterX + "," + thumbCenterY + ")");
172         }
173 
174         final IBinder dragToken = new Binder();
175         final boolean callbackResult = mCallback.get().prePerformDrag(window, dragToken,
176                 touchSource, touchX, touchY, thumbCenterX, thumbCenterY, data);
177         try {
178             DisplayContent displayContent = null;
179             CompletableFuture<Boolean> touchFocusTransferredFuture = null;
180             synchronized (mService.mGlobalLock) {
181                 try {
182                     if (!callbackResult) {
183                         Slog.w(TAG_WM, "IDragDropCallback rejects the performDrag request");
184                         return null;
185                     }
186 
187                     if (dragDropActiveLocked()) {
188                         Slog.w(TAG_WM, "Drag already in progress");
189                         return null;
190                     }
191 
192                     final WindowState callingWin = mService.windowForClientLocked(
193                             null, window, false);
194                     if (callingWin == null || !callingWin.canReceiveTouchInput()) {
195                         Slog.w(TAG_WM, "Bad requesting window " + window);
196                         return null;  // !!! TODO: throw here?
197                     }
198 
199                     // !!! TODO: if input is not still focused on the initiating window, fail
200                     // the drag initiation (e.g. an alarm window popped up just as the application
201                     // called performDrag()
202 
203                     // !!! TODO: extract the current touch (x, y) in screen coordinates.  That
204                     // will let us eliminate the (touchX,touchY) parameters from the API.
205 
206                     // !!! FIXME: put all this heavy stuff onto the mHandler looper, as well as
207                     // the actual drag event dispatch stuff in the dragstate
208 
209                     // !!! TODO(multi-display): support other displays
210 
211                     displayContent = callingWin.getDisplayContent();
212                     if (displayContent == null) {
213                         Slog.w(TAG_WM, "display content is null");
214                         return null;
215                     }
216 
217                     final float alpha = (flags & View.DRAG_FLAG_OPAQUE) == 0 ?
218                             DRAG_SHADOW_ALPHA_TRANSPARENT : 1;
219                     final IBinder winBinder = window.asBinder();
220                     IBinder token = new Binder();
221                     mDragState = new DragState(mService, this, token, surface, flags, winBinder);
222                     surface = null;
223                     mDragState.mPid = callerPid;
224                     mDragState.mUid = callerUid;
225                     mDragState.mStartDragAlpha = alpha;
226                     mDragState.mAnimatedScale = callingWin.mGlobalScale;
227                     mDragState.mToken = dragToken;
228                     mDragState.mStartDragDisplayContent = displayContent;
229                     mDragState.mCurrentDisplayContent = displayContent;
230                     mDragState.mData = data;
231                     mDragState.mCallingTaskIdToHide = shouldMoveCallingTaskToBack(callingWin,
232                             flags);
233                     if (DEBUG_DRAG) {
234                         Slog.d(TAG_WM, "Calling task to hide=" + mDragState.mCallingTaskIdToHide);
235                     }
236 
237                     if ((flags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) == 0) {
238                         final Display display = displayContent.getDisplay();
239                         touchFocusTransferredFuture = mCallback.get().registerInputChannel(
240                                 mDragState, display, mService.mInputManager,
241                                 callingWin.mInputChannelToken);
242                     } else {
243                         // Skip surface logic for a drag triggered by an AccessibilityAction
244                         mDragState.broadcastDragStartedLocked(touchX, touchY);
245 
246                         // Timeout for the user to drop the content
247                         sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, callingWin.mClient.asBinder(),
248                                 getAccessibilityManager().getRecommendedTimeoutMillis(
249                                         A11Y_DRAG_TIMEOUT_DEFAULT_MS,
250                                         AccessibilityManager.FLAG_CONTENT_CONTROLS));
251 
252                         return dragToken;
253                     }
254                 } finally {
255                     if (surface != null) {
256                         try (final SurfaceControl.Transaction transaction =
257                                 mService.mTransactionFactory.get()) {
258                             transaction.remove(surface);
259                             transaction.apply();
260                         }
261                     }
262                 }
263             }
264 
265             boolean touchFocusTransferred = false;
266             try {
267                 touchFocusTransferred = touchFocusTransferredFuture.get(DRAG_TIMEOUT_MS,
268                         TimeUnit.MILLISECONDS);
269             } catch (Exception exception) {
270                 Slog.e(TAG_WM, "Exception thrown while waiting for touch focus transfer",
271                         exception);
272             }
273 
274             synchronized (mService.mGlobalLock) {
275                 if (!touchFocusTransferred) {
276                     Slog.e(TAG_WM, "Unable to transfer touch focus");
277                     mDragState.closeLocked();
278                     return null;
279                 }
280 
281                 final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
282                 mDragState.broadcastDragStartedLocked(touchX, touchY);
283                 if ((touchSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
284                     InputManagerGlobal.getInstance().setPointerIcon(
285                             PointerIcon.getSystemIcon(
286                                     mService.mContext, PointerIcon.TYPE_GRABBING),
287                             mDragState.mCurrentDisplayContent.getDisplayId(), touchDeviceId,
288                             touchPointerId, mDragState.getInputToken());
289                 }
290                 // remember the thumb offsets for later
291                 mDragState.mThumbOffsetX = thumbCenterX;
292                 mDragState.mThumbOffsetY = thumbCenterY;
293 
294                 // Make the surface visible at the proper location
295                 if (SHOW_LIGHT_TRANSACTIONS) {
296                     Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag");
297                 }
298 
299                 final SurfaceControl.Transaction transaction = mDragState.mTransaction;
300                 transaction.setAlpha(surfaceControl, mDragState.mStartDragAlpha);
301                 transaction.show(surfaceControl);
302                 displayContent.reparentToOverlay(transaction, surfaceControl);
303                 mDragState.updateDragSurfaceLocked(true /* keepHandling */,
304                         displayContent.getDisplayId(), touchX, touchY);
305                 if (SHOW_LIGHT_TRANSACTIONS) {
306                     Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag");
307                 }
308             }
309             return dragToken;    // success!
310         } finally {
311             mCallback.get().postPerformDrag();
312         }
313     }
314 
315     /**
316      * This is called from the drop target window that received ACTION_DROP
317      * (see DragState#reportDropWindowLock()).
318      */
reportDropResult(IWindow window, boolean consumed)319     void reportDropResult(IWindow window, boolean consumed) {
320         IBinder token = window.asBinder();
321         if (DEBUG_DRAG) {
322             Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token);
323         }
324 
325         mCallback.get().preReportDropResult(window, consumed);
326         try {
327             synchronized (mService.mGlobalLock) {
328                 if (mDragState == null) {
329                     // Most likely the drop recipient ANRed and we ended the drag
330                     // out from under it.  Log the issue and move on.
331                     Slog.w(TAG_WM, "Drop result given but no drag in progress");
332                     return;
333                 }
334 
335                 if (mDragState.mToken != token) {
336                     // We're in a drag, but the wrong window has responded.
337                     Slog.w(TAG_WM, "Invalid drop-result claim by " + window);
338                     throw new IllegalStateException("reportDropResult() by non-recipient");
339                 }
340 
341                 // The right window has responded, even if it's no longer around,
342                 // so be sure to halt the timeout even if the later WindowState
343                 // lookup fails.
344                 mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder());
345 
346                 WindowState callingWin = mService.windowForClientLocked(null, window, false);
347                 if (callingWin == null) {
348                     Slog.w(TAG_WM, "Bad result-reporting window " + window);
349                     return;  // !!! TODO: throw here?
350                 }
351 
352                 // If the drop was not consumed by the target window, then check if it should be
353                 // consumed by the system unhandled drag listener
354                 if (!consumed && notifyUnhandledDrop(mDragState.mUnhandledDropEvent,
355                         "window-drop")) {
356                     // If the unhandled drag listener is notified, then defer ending the drag until
357                     // the listener calls back
358                     return;
359                 }
360 
361                 final boolean relinquishDragSurfaceToDropTarget =
362                         consumed && mDragState.targetInterceptsGlobalDrag(callingWin);
363                 final boolean isCrossWindowDrag = !mDragState.mLocalWin.equals(token);
364                 mDragState.endDragLocked(consumed, relinquishDragSurfaceToDropTarget);
365 
366                 final Task droppedWindowTask = callingWin.getTask();
367                 if (com.android.window.flags.Flags.delegateUnhandledDrags()
368                         && mGlobalDragListener != null && droppedWindowTask != null && consumed
369                         && isCrossWindowDrag) {
370                     try {
371                         mGlobalDragListener.onCrossWindowDrop(droppedWindowTask.getTaskInfo());
372                     } catch (RemoteException e) {
373                         Slog.e(TAG_WM, "Failed to call global drag listener for cross-window "
374                                 + "drop", e);
375                     }
376                 }
377             }
378         } finally {
379             mCallback.get().postReportDropResult();
380         }
381     }
382 
383     /**
384      * If the calling window's task should be hidden for the duration of the drag, this returns the
385      * task id of the task (or -1 otherwise).
386      */
shouldMoveCallingTaskToBack(WindowState callingWin, int flags)387     private int shouldMoveCallingTaskToBack(WindowState callingWin, int flags) {
388         if ((flags & View.DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START) == 0) {
389             // Not requested by the app
390             return -1;
391         }
392         final ActivityRecord callingActivity = callingWin.getActivityRecord();
393         if (callingActivity == null || callingActivity.getTask() == null) {
394             // Not an activity
395             return -1;
396         }
397         return callingActivity.getTask().mTaskId;
398     }
399 
400     /**
401      * Notifies the unhandled drag listener if needed.
402      * @return whether the listener was notified and subsequent drag completion should be deferred
403      *         until the listener calls back
404      */
notifyUnhandledDrop(DragEvent dropEvent, String reason)405     boolean notifyUnhandledDrop(DragEvent dropEvent, String reason) {
406         final boolean isLocalDrag =
407                 (mDragState.mFlags & (DRAG_FLAG_GLOBAL_SAME_APPLICATION | DRAG_FLAG_GLOBAL)) == 0;
408         final boolean shouldDelegateUnhandledDrag =
409                 (mDragState.mFlags & DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) != 0;
410         if (!com.android.window.flags.Flags.delegateUnhandledDrags()
411                 || mGlobalDragListener == null
412                 || !shouldDelegateUnhandledDrag
413                 || isLocalDrag) {
414             // Skip if the flag is disabled, there is no unhandled-drag listener, or if this is a
415             // purely local drag
416             if (DEBUG_DRAG) Slog.d(TAG_WM, "Skipping unhandled listener "
417                     + "(listener=" + mGlobalDragListener + ", flags=" + mDragState.mFlags + ")");
418             return false;
419         }
420         final int traceCookie = new Random().nextInt();
421         Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "DragDropController#notifyUnhandledDrop",
422                 traceCookie);
423         if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DROP to unhandled listener (" + reason + ")");
424         try {
425             // Schedule timeout for the unhandled drag listener to call back
426             sendTimeoutMessage(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT, null, DRAG_TIMEOUT_MS);
427             mGlobalDragListener.onUnhandledDrop(dropEvent, new IUnhandledDragCallback.Stub() {
428                 @Override
429                 public void notifyUnhandledDropComplete(boolean consumedByListener) {
430                     if (DEBUG_DRAG) Slog.d(TAG_WM, "Unhandled listener finished handling DROP");
431                     synchronized (mService.mGlobalLock) {
432                         onUnhandledDropCallback(consumedByListener);
433                         Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER,
434                                 "DragDropController#notifyUnhandledDrop", traceCookie);
435                     }
436                 }
437             });
438             return true;
439         } catch (RemoteException e) {
440             Slog.e(TAG_WM, "Failed to call global drag listener for unhandled drop", e);
441             return false;
442         }
443     }
444 
445     /**
446      * Called when the unhandled drag listener has completed handling the drop
447      * (if it was notififed).
448      */
449     @VisibleForTesting
onUnhandledDropCallback(boolean consumedByListener)450     void onUnhandledDropCallback(boolean consumedByListener) {
451         mHandler.removeMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT, null);
452         // If handled, then the listeners assume responsibility of cleaning up the drag surface
453         mDragState.mDragResult = consumedByListener;
454         mDragState.mRelinquishDragSurfaceToDropTarget = consumedByListener;
455         mDragState.closeLocked();
456     }
457 
458     /**
459      * Returns whether we are currently waiting for the unhandled drag listener to callback after
460      * it was notified of an unhandled drag.
461      */
hasPendingUnhandledDropCallback()462     boolean hasPendingUnhandledDropCallback() {
463         return mHandler.hasMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT);
464     }
465 
cancelDragAndDrop(IBinder dragToken, boolean skipAnimation)466     void cancelDragAndDrop(IBinder dragToken, boolean skipAnimation) {
467         if (DEBUG_DRAG) {
468             Slog.d(TAG_WM, "cancelDragAndDrop");
469         }
470 
471         mCallback.get().preCancelDragAndDrop(dragToken);
472         try {
473             synchronized (mService.mGlobalLock) {
474                 if (mDragState == null) {
475                     Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
476                     throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
477                 }
478 
479                 if (mDragState.mToken != dragToken) {
480                     Slog.w(TAG_WM,
481                             "cancelDragAndDrop() does not match prepareDrag()");
482                     throw new IllegalStateException(
483                             "cancelDragAndDrop() does not match prepareDrag()");
484                 }
485 
486                 mDragState.mDragResult = false;
487                 mDragState.cancelDragLocked(skipAnimation);
488             }
489         } finally {
490             mCallback.get().postCancelDragAndDrop();
491         }
492     }
493 
494     @VisibleForTesting
handleDisplayTopologyChange(DisplayTopology unused)495     void handleDisplayTopologyChange(DisplayTopology unused) {
496         synchronized (mService.mGlobalLock) {
497             if (mDragState == null) {
498                 return;
499             }
500             if (DEBUG_DRAG) {
501                 Slog.d(TAG_WM, "DisplayTopology changed, cancelling DragAndDrop");
502             }
503             cancelDragAndDrop(mDragState.mToken, true /* skipAnimation */);
504         }
505     }
506 
507     /**
508      * Handles motion events.
509      * @param keepHandling Whether if the drag operation is continuing or this is the last motion
510      *          event.
511      * @param displayId id of the display the X,Y coordinate is n.
512      * @param newX X coordinate value in dp in the screen coordinate
513      * @param newY Y coordinate value in dp in the screen coordinate
514      */
handleMotionEvent(boolean keepHandling, int displayId, float newX, float newY)515     void handleMotionEvent(boolean keepHandling, int displayId, float newX, float newY) {
516         synchronized (mService.mGlobalLock) {
517             if (!dragDropActiveLocked()) {
518                 // The drag has ended but the clean-up message has not been processed by
519                 // window manager. Drop events that occur after this until window manager
520                 // has a chance to clean-up the input handle.
521                 return;
522             }
523 
524             mDragState.updateDragSurfaceLocked(keepHandling, displayId, newX, newY);
525         }
526     }
527 
dragRecipientEntered(IWindow window)528     void dragRecipientEntered(IWindow window) {
529         if (DEBUG_DRAG) {
530             Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder());
531         }
532         mCallback.get().dragRecipientEntered(window);
533     }
534 
dragRecipientExited(IWindow window)535     void dragRecipientExited(IWindow window) {
536         if (DEBUG_DRAG) {
537             Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder());
538         }
539         mCallback.get().dragRecipientExited(window);
540     }
541 
542     /**
543      * Sends a message to the Handler managed by DragDropController.
544      */
sendHandlerMessage(int what, Object arg)545     void sendHandlerMessage(int what, Object arg) {
546         mHandler.obtainMessage(what, arg).sendToTarget();
547     }
548 
549     /**
550      * Sends a timeout message to the Handler managed by DragDropController.
551      */
sendTimeoutMessage(int what, Object arg, long timeoutMs)552     void sendTimeoutMessage(int what, Object arg, long timeoutMs) {
553         mHandler.removeMessages(what, arg);
554         final Message msg = mHandler.obtainMessage(what, arg);
555         mHandler.sendMessageDelayed(msg, timeoutMs);
556     }
557 
558     /**
559      * Notifies the current drag state is closed.
560      */
onDragStateClosedLocked(DragState dragState)561     void onDragStateClosedLocked(DragState dragState) {
562         if (mDragState != dragState) {
563             Slog.wtf(TAG_WM, "Unknown drag state is closed");
564             return;
565         }
566         mDragState = null;
567     }
568 
reportDropWindow(IBinder token, float x, float y)569     void reportDropWindow(IBinder token, float x, float y) {
570         if (mDragState == null) {
571             Slog.w(TAG_WM, "Drag state is closed.");
572             return;
573         }
574 
575         synchronized (mService.mGlobalLock) {
576             mDragState.reportDropWindowLock(token, x, y);
577         }
578     }
579 
dropForAccessibility(IWindow window, float x, float y)580     boolean dropForAccessibility(IWindow window, float x, float y) {
581         synchronized (mService.mGlobalLock) {
582             final boolean isA11yEnabled = getAccessibilityManager().isEnabled();
583             if (!dragDropActiveLocked()) {
584                 return false;
585             }
586             if (mDragState.isAccessibilityDragDrop() && isA11yEnabled) {
587                 final WindowState winState = mService.windowForClientLocked(
588                         null, window, false);
589                 if (!mDragState.isWindowNotified(winState)) {
590                     return false;
591                 }
592                 IBinder token = winState.mInputChannelToken;
593                 return mDragState.reportDropWindowLock(token, x, y);
594             }
595             return false;
596         }
597     }
598 
getAccessibilityManager()599     AccessibilityManager getAccessibilityManager() {
600         return (AccessibilityManager) mService.mContext.getSystemService(
601                 Context.ACCESSIBILITY_SERVICE);
602     }
603 
604     private class DragHandler extends Handler {
605         /**
606          * Lock for window manager.
607          */
608         private final WindowManagerService mService;
609 
DragHandler(WindowManagerService service, Looper looper)610         DragHandler(WindowManagerService service, Looper looper) {
611             super(looper);
612             mService = service;
613         }
614 
615         @Override
handleMessage(Message msg)616         public void handleMessage(Message msg) {
617             switch (msg.what) {
618                 case MSG_DRAG_END_TIMEOUT: {
619                     final IBinder win = (IBinder) msg.obj;
620                     if (DEBUG_DRAG) {
621                         Slog.w(TAG_WM, "Timeout ending drag to win " + win);
622                     }
623 
624                     synchronized (mService.mGlobalLock) {
625                         // !!! TODO: ANR the drag-receiving app
626                         if (mDragState != null) {
627                             mDragState.endDragLocked(false /* consumed */,
628                                     false /* relinquishDragSurfaceToDropTarget */);
629                         }
630                     }
631                     break;
632                 }
633 
634                 case MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT: {
635                     if (DEBUG_DRAG)
636                         Slog.d(TAG_WM, "Drag ending; tearing down input channel");
637                     final DragState.InputInterceptor interceptor =
638                             (DragState.InputInterceptor) msg.obj;
639                     if (interceptor == null) return;
640                     synchronized (mService.mGlobalLock) {
641                         interceptor.tearDown();
642                     }
643                     break;
644                 }
645 
646                 case MSG_ANIMATION_END: {
647                     synchronized (mService.mGlobalLock) {
648                         if (mDragState == null) {
649                             Slog.wtf(TAG_WM, "mDragState unexpectedly became null while " +
650                                     "playing animation");
651                             return;
652                         }
653                         mDragState.closeLocked();
654                     }
655                     break;
656                 }
657 
658                 case MSG_REMOVE_DRAG_SURFACE_TIMEOUT: {
659                     synchronized (mService.mGlobalLock) {
660                         mService.mTransactionFactory.get().remove((SurfaceControl) msg.obj).apply();
661                     }
662                     break;
663                 }
664 
665                 case MSG_UNHANDLED_DROP_LISTENER_TIMEOUT: {
666                     synchronized (mService.mGlobalLock) {
667                         onUnhandledDropCallback(false /* consumedByListener */);
668                     }
669                     break;
670                 }
671             }
672         }
673     }
674 }
675