• 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 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 
dragSurfaceRelinquishedToDropTarget()84     boolean dragSurfaceRelinquishedToDropTarget() {
85         return mDragState != null && mDragState.mRelinquishDragSurfaceToDropTarget;
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.show(surfaceControl);
183                         displayContent.reparentToOverlay(transaction, surfaceControl);
184                         mDragState.updateDragSurfaceLocked(true, touchX, touchY);
185                         if (SHOW_LIGHT_TRANSACTIONS) {
186                             Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag");
187                         }
188                     } else {
189                         // Skip surface logic for a drag triggered by an AccessibilityAction
190                         mDragState.broadcastDragStartedLocked(touchX, touchY);
191 
192                         // Timeout for the user to drop the content
193                         sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, callingWin.mClient.asBinder(),
194                                 getAccessibilityManager().getRecommendedTimeoutMillis(
195                                         A11Y_DRAG_TIMEOUT_DEFAULT_MS,
196                                         AccessibilityManager.FLAG_CONTENT_CONTROLS));
197                     }
198                 } finally {
199                     if (surface != null) {
200                         surface.release();
201                     }
202                     if (mDragState != null && !mDragState.isInProgress()) {
203                         mDragState.closeLocked();
204                     }
205                 }
206             }
207             return dragToken;    // success!
208         } finally {
209             mCallback.get().postPerformDrag();
210         }
211     }
212 
reportDropResult(IWindow window, boolean consumed)213     void reportDropResult(IWindow window, boolean consumed) {
214         IBinder token = window.asBinder();
215         if (DEBUG_DRAG) {
216             Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token);
217         }
218 
219         mCallback.get().preReportDropResult(window, consumed);
220         try {
221             synchronized (mService.mGlobalLock) {
222                 if (mDragState == null) {
223                     // Most likely the drop recipient ANRed and we ended the drag
224                     // out from under it.  Log the issue and move on.
225                     Slog.w(TAG_WM, "Drop result given but no drag in progress");
226                     return;
227                 }
228 
229                 if (mDragState.mToken != token) {
230                     // We're in a drag, but the wrong window has responded.
231                     Slog.w(TAG_WM, "Invalid drop-result claim by " + window);
232                     throw new IllegalStateException("reportDropResult() by non-recipient");
233                 }
234 
235                 // The right window has responded, even if it's no longer around,
236                 // so be sure to halt the timeout even if the later WindowState
237                 // lookup fails.
238                 mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder());
239                 WindowState callingWin = mService.windowForClientLocked(null, window, false);
240                 if (callingWin == null) {
241                     Slog.w(TAG_WM, "Bad result-reporting window " + window);
242                     return;  // !!! TODO: throw here?
243                 }
244 
245                 mDragState.mDragResult = consumed;
246                 mDragState.mRelinquishDragSurfaceToDropTarget = consumed
247                         && mDragState.targetInterceptsGlobalDrag(callingWin);
248                 mDragState.endDragLocked();
249             }
250         } finally {
251             mCallback.get().postReportDropResult();
252         }
253     }
254 
cancelDragAndDrop(IBinder dragToken, boolean skipAnimation)255     void cancelDragAndDrop(IBinder dragToken, boolean skipAnimation) {
256         if (DEBUG_DRAG) {
257             Slog.d(TAG_WM, "cancelDragAndDrop");
258         }
259 
260         mCallback.get().preCancelDragAndDrop(dragToken);
261         try {
262             synchronized (mService.mGlobalLock) {
263                 if (mDragState == null) {
264                     Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
265                     throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
266                 }
267 
268                 if (mDragState.mToken != dragToken) {
269                     Slog.w(TAG_WM,
270                             "cancelDragAndDrop() does not match prepareDrag()");
271                     throw new IllegalStateException(
272                             "cancelDragAndDrop() does not match prepareDrag()");
273                 }
274 
275                 mDragState.mDragResult = false;
276                 mDragState.cancelDragLocked(skipAnimation);
277             }
278         } finally {
279             mCallback.get().postCancelDragAndDrop();
280         }
281     }
282 
283     /**
284      * Handles motion events.
285      * @param keepHandling Whether if the drag operation is continuing or this is the last motion
286      *          event.
287      * @param newX X coordinate value in dp in the screen coordinate
288      * @param newY Y coordinate value in dp in the screen coordinate
289      */
handleMotionEvent(boolean keepHandling, float newX, float newY)290     void handleMotionEvent(boolean keepHandling, float newX, float newY) {
291         synchronized (mService.mGlobalLock) {
292             if (!dragDropActiveLocked()) {
293                 // The drag has ended but the clean-up message has not been processed by
294                 // window manager. Drop events that occur after this until window manager
295                 // has a chance to clean-up the input handle.
296                 return;
297             }
298 
299             mDragState.updateDragSurfaceLocked(keepHandling, newX, newY);
300         }
301     }
302 
dragRecipientEntered(IWindow window)303     void dragRecipientEntered(IWindow window) {
304         if (DEBUG_DRAG) {
305             Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder());
306         }
307         mCallback.get().dragRecipientEntered(window);
308     }
309 
dragRecipientExited(IWindow window)310     void dragRecipientExited(IWindow window) {
311         if (DEBUG_DRAG) {
312             Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder());
313         }
314         mCallback.get().dragRecipientExited(window);
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         if (mDragState == null) {
346             Slog.w(TAG_WM, "Drag state is closed.");
347             return;
348         }
349 
350         synchronized (mService.mGlobalLock) {
351             mDragState.reportDropWindowLock(token, x, y);
352         }
353     }
354 
dropForAccessibility(IWindow window, float x, float y)355     boolean dropForAccessibility(IWindow window, float x, float y) {
356         synchronized (mService.mGlobalLock) {
357             final boolean isA11yEnabled = getAccessibilityManager().isEnabled();
358             if (!dragDropActiveLocked()) {
359                 return false;
360             }
361             if (mDragState.isAccessibilityDragDrop() && isA11yEnabled) {
362                 final WindowState winState = mService.windowForClientLocked(
363                         null, window, false);
364                 if (!mDragState.isWindowNotified(winState)) {
365                     return false;
366                 }
367                 IBinder token = winState.mInputChannelToken;
368                 return mDragState.reportDropWindowLock(token, x, y);
369             }
370             return false;
371         }
372     }
373 
getAccessibilityManager()374     AccessibilityManager getAccessibilityManager() {
375         return (AccessibilityManager) mService.mContext.getSystemService(
376                 Context.ACCESSIBILITY_SERVICE);
377     }
378 
379     private class DragHandler extends Handler {
380         /**
381          * Lock for window manager.
382          */
383         private final WindowManagerService mService;
384 
DragHandler(WindowManagerService service, Looper looper)385         DragHandler(WindowManagerService service, Looper looper) {
386             super(looper);
387             mService = service;
388         }
389 
390         @Override
handleMessage(Message msg)391         public void handleMessage(Message msg) {
392             switch (msg.what) {
393                 case MSG_DRAG_END_TIMEOUT: {
394                     final IBinder win = (IBinder) msg.obj;
395                     if (DEBUG_DRAG) {
396                         Slog.w(TAG_WM, "Timeout ending drag to win " + win);
397                     }
398 
399                     synchronized (mService.mGlobalLock) {
400                         // !!! TODO: ANR the drag-receiving app
401                         if (mDragState != null) {
402                             mDragState.mDragResult = false;
403                             mDragState.endDragLocked();
404                         }
405                     }
406                     break;
407                 }
408 
409                 case MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT: {
410                     if (DEBUG_DRAG)
411                         Slog.d(TAG_WM, "Drag ending; tearing down input channel");
412                     final DragState.InputInterceptor interceptor =
413                             (DragState.InputInterceptor) msg.obj;
414                     if (interceptor == null) return;
415                     synchronized (mService.mGlobalLock) {
416                         interceptor.tearDown();
417                     }
418                     break;
419                 }
420 
421                 case MSG_ANIMATION_END: {
422                     synchronized (mService.mGlobalLock) {
423                         if (mDragState == null) {
424                             Slog.wtf(TAG_WM, "mDragState unexpectedly became null while " +
425                                     "playing animation");
426                             return;
427                         }
428                         mDragState.closeLocked();
429                     }
430                     break;
431                 }
432 
433                 case MSG_REMOVE_DRAG_SURFACE_TIMEOUT: {
434                     synchronized (mService.mGlobalLock) {
435                         mService.mTransactionFactory.get()
436                                 .reparent((SurfaceControl) msg.obj, null).apply();
437                     }
438                     break;
439                 }
440             }
441         }
442     }
443 }
444