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