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