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