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.content.Context;
26 import android.os.Binder;
27 import android.os.Handler;
28 import android.os.IBinder;
29 import android.os.Looper;
30 import android.os.Message;
31 import android.util.Slog;
32 import android.view.Display;
33 import android.view.IWindow;
34 import android.view.SurfaceControl;
35 import android.view.View;
36 import android.view.accessibility.AccessibilityManager;
37 
38 import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
39 
40 import java.util.Objects;
41 import java.util.concurrent.CompletableFuture;
42 import java.util.concurrent.TimeUnit;
43 import java.util.concurrent.atomic.AtomicReference;
44 
45 /**
46  * Managing drag and drop operations initiated by View#startDragAndDrop.
47  */
48 class DragDropController {
49     private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
50     static final long DRAG_TIMEOUT_MS = 5000;
51     private static final int A11Y_DRAG_TIMEOUT_DEFAULT_MS = 60000;
52 
53     // Messages for Handler.
54     static final int MSG_DRAG_END_TIMEOUT = 0;
55     static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 1;
56     static final int MSG_ANIMATION_END = 2;
57     static final int MSG_REMOVE_DRAG_SURFACE_TIMEOUT = 3;
58 
59     /**
60      * Drag state per operation.
61      * Needs a lock of {@code WindowManagerService#mWindowMap} to read this. Needs both locks of
62      * {@code mWriteLock} and {@code WindowManagerService#mWindowMap} to update this.
63      * The variable is cleared by {@code #onDragStateClosedLocked} which is invoked by DragState
64      * itself, thus the variable can be null after calling DragState's methods.
65      */
66     private DragState mDragState;
67 
68     private WindowManagerService mService;
69     private final Handler mHandler;
70 
71     /**
72      * Callback which is used to sync drag state with the vendor-specific code.
73      */
74     @NonNull private AtomicReference<IDragDropCallback> mCallback = new AtomicReference<>(
75             new IDragDropCallback() {});
76 
DragDropController(WindowManagerService service, Looper looper)77     DragDropController(WindowManagerService service, Looper looper) {
78         mService = service;
79         mHandler = new DragHandler(service, looper);
80     }
81 
dragDropActiveLocked()82     boolean dragDropActiveLocked() {
83         return mDragState != null && !mDragState.isClosing();
84     }
85 
dragSurfaceRelinquishedToDropTarget()86     boolean dragSurfaceRelinquishedToDropTarget() {
87         return mDragState != null && mDragState.mRelinquishDragSurfaceToDropTarget;
88     }
89 
registerCallback(IDragDropCallback callback)90     void registerCallback(IDragDropCallback callback) {
91         Objects.requireNonNull(callback);
92         mCallback.set(callback);
93     }
94 
sendDragStartedIfNeededLocked(WindowState window)95     void sendDragStartedIfNeededLocked(WindowState window) {
96         mDragState.sendDragStartedIfNeededLocked(window);
97     }
98 
performDrag(int callerPid, int callerUid, IWindow window, int flags, SurfaceControl surface, int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data)99     IBinder performDrag(int callerPid, int callerUid, IWindow window, int flags,
100             SurfaceControl surface, int touchSource, float touchX, float touchY,
101             float thumbCenterX, float thumbCenterY, ClipData data) {
102         if (DEBUG_DRAG) {
103             Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=" +
104                     Integer.toHexString(flags) + " data=" + data + " touch(" + touchX + ","
105                     + touchY + ") thumb center(" + thumbCenterX + "," + thumbCenterY + ")");
106         }
107 
108         final IBinder dragToken = new Binder();
109         final boolean callbackResult = mCallback.get().prePerformDrag(window, dragToken,
110                 touchSource, touchX, touchY, thumbCenterX, thumbCenterY, data);
111         try {
112             DisplayContent displayContent = null;
113             CompletableFuture<Boolean> touchFocusTransferredFuture = null;
114             synchronized (mService.mGlobalLock) {
115                 try {
116                     if (!callbackResult) {
117                         Slog.w(TAG_WM, "IDragDropCallback rejects the performDrag request");
118                         return null;
119                     }
120 
121                     if (dragDropActiveLocked()) {
122                         Slog.w(TAG_WM, "Drag already in progress");
123                         return null;
124                     }
125 
126                     final WindowState callingWin = mService.windowForClientLocked(
127                             null, window, false);
128                     if (callingWin == null || !callingWin.canReceiveTouchInput()) {
129                         Slog.w(TAG_WM, "Bad requesting window " + window);
130                         return null;  // !!! TODO: throw here?
131                     }
132 
133                     // !!! TODO: if input is not still focused on the initiating window, fail
134                     // the drag initiation (e.g. an alarm window popped up just as the application
135                     // called performDrag()
136 
137                     // !!! TODO: extract the current touch (x, y) in screen coordinates.  That
138                     // will let us eliminate the (touchX,touchY) parameters from the API.
139 
140                     // !!! FIXME: put all this heavy stuff onto the mHandler looper, as well as
141                     // the actual drag event dispatch stuff in the dragstate
142 
143                     // !!! TODO(multi-display): support other displays
144 
145                     displayContent = callingWin.getDisplayContent();
146                     if (displayContent == null) {
147                         Slog.w(TAG_WM, "display content is null");
148                         return null;
149                     }
150 
151                     final float alpha = (flags & View.DRAG_FLAG_OPAQUE) == 0 ?
152                             DRAG_SHADOW_ALPHA_TRANSPARENT : 1;
153                     final IBinder winBinder = window.asBinder();
154                     IBinder token = new Binder();
155                     mDragState = new DragState(mService, this, token, surface, flags, winBinder);
156                     surface = null;
157                     mDragState.mPid = callerPid;
158                     mDragState.mUid = callerUid;
159                     mDragState.mOriginalAlpha = alpha;
160                     mDragState.mAnimatedScale = callingWin.mGlobalScale;
161                     mDragState.mToken = dragToken;
162                     mDragState.mDisplayContent = displayContent;
163                     mDragState.mData = data;
164 
165                     if ((flags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) == 0) {
166                         final Display display = displayContent.getDisplay();
167                         touchFocusTransferredFuture = mCallback.get().registerInputChannel(
168                                 mDragState, display, mService.mInputManager,
169                                 callingWin.mInputChannel);
170                     } else {
171                         // Skip surface logic for a drag triggered by an AccessibilityAction
172                         mDragState.broadcastDragStartedLocked(touchX, touchY);
173 
174                         // Timeout for the user to drop the content
175                         sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, callingWin.mClient.asBinder(),
176                                 getAccessibilityManager().getRecommendedTimeoutMillis(
177                                         A11Y_DRAG_TIMEOUT_DEFAULT_MS,
178                                         AccessibilityManager.FLAG_CONTENT_CONTROLS));
179 
180                         return dragToken;
181                     }
182                 } finally {
183                     if (surface != null) {
184                         try (final SurfaceControl.Transaction transaction =
185                                 mService.mTransactionFactory.get()) {
186                             transaction.remove(surface);
187                             transaction.apply();
188                         }
189                     }
190                 }
191             }
192 
193             boolean touchFocusTransferred = false;
194             try {
195                 touchFocusTransferred = touchFocusTransferredFuture.get(DRAG_TIMEOUT_MS,
196                         TimeUnit.MILLISECONDS);
197             } catch (Exception exception) {
198                 Slog.e(TAG_WM, "Exception thrown while waiting for touch focus transfer",
199                         exception);
200             }
201 
202             synchronized (mService.mGlobalLock) {
203                 if (!touchFocusTransferred) {
204                     Slog.e(TAG_WM, "Unable to transfer touch focus");
205                     mDragState.closeLocked();
206                     return null;
207                 }
208 
209                 final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
210                 mDragState.broadcastDragStartedLocked(touchX, touchY);
211                 mDragState.overridePointerIconLocked(touchSource);
212                 // remember the thumb offsets for later
213                 mDragState.mThumbOffsetX = thumbCenterX;
214                 mDragState.mThumbOffsetY = thumbCenterY;
215 
216                 // Make the surface visible at the proper location
217                 if (SHOW_LIGHT_TRANSACTIONS) {
218                     Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag");
219                 }
220 
221                 final SurfaceControl.Transaction transaction = mDragState.mTransaction;
222                 transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha);
223                 transaction.show(surfaceControl);
224                 displayContent.reparentToOverlay(transaction, surfaceControl);
225                 mDragState.updateDragSurfaceLocked(true, touchX, touchY);
226                 if (SHOW_LIGHT_TRANSACTIONS) {
227                     Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag");
228                 }
229             }
230             return dragToken;    // success!
231         } finally {
232             mCallback.get().postPerformDrag();
233         }
234     }
235 
reportDropResult(IWindow window, boolean consumed)236     void reportDropResult(IWindow window, boolean consumed) {
237         IBinder token = window.asBinder();
238         if (DEBUG_DRAG) {
239             Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token);
240         }
241 
242         mCallback.get().preReportDropResult(window, consumed);
243         try {
244             synchronized (mService.mGlobalLock) {
245                 if (mDragState == null) {
246                     // Most likely the drop recipient ANRed and we ended the drag
247                     // out from under it.  Log the issue and move on.
248                     Slog.w(TAG_WM, "Drop result given but no drag in progress");
249                     return;
250                 }
251 
252                 if (mDragState.mToken != token) {
253                     // We're in a drag, but the wrong window has responded.
254                     Slog.w(TAG_WM, "Invalid drop-result claim by " + window);
255                     throw new IllegalStateException("reportDropResult() by non-recipient");
256                 }
257 
258                 // The right window has responded, even if it's no longer around,
259                 // so be sure to halt the timeout even if the later WindowState
260                 // lookup fails.
261                 mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder());
262                 WindowState callingWin = mService.windowForClientLocked(null, window, false);
263                 if (callingWin == null) {
264                     Slog.w(TAG_WM, "Bad result-reporting window " + window);
265                     return;  // !!! TODO: throw here?
266                 }
267 
268                 mDragState.mDragResult = consumed;
269                 mDragState.mRelinquishDragSurfaceToDropTarget = consumed
270                         && mDragState.targetInterceptsGlobalDrag(callingWin);
271                 mDragState.endDragLocked();
272             }
273         } finally {
274             mCallback.get().postReportDropResult();
275         }
276     }
277 
cancelDragAndDrop(IBinder dragToken, boolean skipAnimation)278     void cancelDragAndDrop(IBinder dragToken, boolean skipAnimation) {
279         if (DEBUG_DRAG) {
280             Slog.d(TAG_WM, "cancelDragAndDrop");
281         }
282 
283         mCallback.get().preCancelDragAndDrop(dragToken);
284         try {
285             synchronized (mService.mGlobalLock) {
286                 if (mDragState == null) {
287                     Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
288                     throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
289                 }
290 
291                 if (mDragState.mToken != dragToken) {
292                     Slog.w(TAG_WM,
293                             "cancelDragAndDrop() does not match prepareDrag()");
294                     throw new IllegalStateException(
295                             "cancelDragAndDrop() does not match prepareDrag()");
296                 }
297 
298                 mDragState.mDragResult = false;
299                 mDragState.cancelDragLocked(skipAnimation);
300             }
301         } finally {
302             mCallback.get().postCancelDragAndDrop();
303         }
304     }
305 
306     /**
307      * Handles motion events.
308      * @param keepHandling Whether if the drag operation is continuing or this is the last motion
309      *          event.
310      * @param newX X coordinate value in dp in the screen coordinate
311      * @param newY Y coordinate value in dp in the screen coordinate
312      */
handleMotionEvent(boolean keepHandling, float newX, float newY)313     void handleMotionEvent(boolean keepHandling, float newX, float newY) {
314         synchronized (mService.mGlobalLock) {
315             if (!dragDropActiveLocked()) {
316                 // The drag has ended but the clean-up message has not been processed by
317                 // window manager. Drop events that occur after this until window manager
318                 // has a chance to clean-up the input handle.
319                 return;
320             }
321 
322             mDragState.updateDragSurfaceLocked(keepHandling, newX, newY);
323         }
324     }
325 
dragRecipientEntered(IWindow window)326     void dragRecipientEntered(IWindow window) {
327         if (DEBUG_DRAG) {
328             Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder());
329         }
330         mCallback.get().dragRecipientEntered(window);
331     }
332 
dragRecipientExited(IWindow window)333     void dragRecipientExited(IWindow window) {
334         if (DEBUG_DRAG) {
335             Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder());
336         }
337         mCallback.get().dragRecipientExited(window);
338     }
339 
340     /**
341      * Sends a message to the Handler managed by DragDropController.
342      */
sendHandlerMessage(int what, Object arg)343     void sendHandlerMessage(int what, Object arg) {
344         mHandler.obtainMessage(what, arg).sendToTarget();
345     }
346 
347     /**
348      * Sends a timeout message to the Handler managed by DragDropController.
349      */
sendTimeoutMessage(int what, Object arg, long timeoutMs)350     void sendTimeoutMessage(int what, Object arg, long timeoutMs) {
351         mHandler.removeMessages(what, arg);
352         final Message msg = mHandler.obtainMessage(what, arg);
353         mHandler.sendMessageDelayed(msg, timeoutMs);
354     }
355 
356     /**
357      * Notifies the current drag state is closed.
358      */
onDragStateClosedLocked(DragState dragState)359     void onDragStateClosedLocked(DragState dragState) {
360         if (mDragState != dragState) {
361             Slog.wtf(TAG_WM, "Unknown drag state is closed");
362             return;
363         }
364         mDragState = null;
365     }
366 
reportDropWindow(IBinder token, float x, float y)367     void reportDropWindow(IBinder token, float x, float y) {
368         if (mDragState == null) {
369             Slog.w(TAG_WM, "Drag state is closed.");
370             return;
371         }
372 
373         synchronized (mService.mGlobalLock) {
374             mDragState.reportDropWindowLock(token, x, y);
375         }
376     }
377 
dropForAccessibility(IWindow window, float x, float y)378     boolean dropForAccessibility(IWindow window, float x, float y) {
379         synchronized (mService.mGlobalLock) {
380             final boolean isA11yEnabled = getAccessibilityManager().isEnabled();
381             if (!dragDropActiveLocked()) {
382                 return false;
383             }
384             if (mDragState.isAccessibilityDragDrop() && isA11yEnabled) {
385                 final WindowState winState = mService.windowForClientLocked(
386                         null, window, false);
387                 if (!mDragState.isWindowNotified(winState)) {
388                     return false;
389                 }
390                 IBinder token = winState.mInputChannelToken;
391                 return mDragState.reportDropWindowLock(token, x, y);
392             }
393             return false;
394         }
395     }
396 
getAccessibilityManager()397     AccessibilityManager getAccessibilityManager() {
398         return (AccessibilityManager) mService.mContext.getSystemService(
399                 Context.ACCESSIBILITY_SERVICE);
400     }
401 
402     private class DragHandler extends Handler {
403         /**
404          * Lock for window manager.
405          */
406         private final WindowManagerService mService;
407 
DragHandler(WindowManagerService service, Looper looper)408         DragHandler(WindowManagerService service, Looper looper) {
409             super(looper);
410             mService = service;
411         }
412 
413         @Override
handleMessage(Message msg)414         public void handleMessage(Message msg) {
415             switch (msg.what) {
416                 case MSG_DRAG_END_TIMEOUT: {
417                     final IBinder win = (IBinder) msg.obj;
418                     if (DEBUG_DRAG) {
419                         Slog.w(TAG_WM, "Timeout ending drag to win " + win);
420                     }
421 
422                     synchronized (mService.mGlobalLock) {
423                         // !!! TODO: ANR the drag-receiving app
424                         if (mDragState != null) {
425                             mDragState.mDragResult = false;
426                             mDragState.endDragLocked();
427                         }
428                     }
429                     break;
430                 }
431 
432                 case MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT: {
433                     if (DEBUG_DRAG)
434                         Slog.d(TAG_WM, "Drag ending; tearing down input channel");
435                     final DragState.InputInterceptor interceptor =
436                             (DragState.InputInterceptor) msg.obj;
437                     if (interceptor == null) return;
438                     synchronized (mService.mGlobalLock) {
439                         interceptor.tearDown();
440                     }
441                     break;
442                 }
443 
444                 case MSG_ANIMATION_END: {
445                     synchronized (mService.mGlobalLock) {
446                         if (mDragState == null) {
447                             Slog.wtf(TAG_WM, "mDragState unexpectedly became null while " +
448                                     "playing animation");
449                             return;
450                         }
451                         mDragState.closeLocked();
452                     }
453                     break;
454                 }
455 
456                 case MSG_REMOVE_DRAG_SURFACE_TIMEOUT: {
457                     synchronized (mService.mGlobalLock) {
458                         mService.mTransactionFactory.get()
459                                 .reparent((SurfaceControl) msg.obj, null).apply();
460                     }
461                     break;
462                 }
463             }
464         }
465     }
466 }
467