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.SurfaceControl.Transaction;
35 import android.view.SurfaceSession;
36 import android.view.View;
37 
38 import com.android.internal.util.Preconditions;
39 import com.android.server.input.InputWindowHandle;
40 import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
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     private static final long DRAG_TIMEOUT_MS = 5000;
49 
50     // Messages for Handler.
51     static final int MSG_DRAG_END_TIMEOUT = 0;
52     static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 1;
53     static final int MSG_ANIMATION_END = 2;
54 
55     /**
56      * Drag state per operation.
57      * Needs a lock of {@code WindowManagerService#mWindowMap} to read this. Needs both locks of
58      * {@code mWriteLock} and {@code WindowManagerService#mWindowMap} to update this.
59      * The variable is cleared by {@code #onDragStateClosedLocked} which is invoked by DragState
60      * itself, thus the variable can be null after calling DragState's methods.
61      */
62     private DragState mDragState;
63 
64     private WindowManagerService mService;
65     private final Handler mHandler;
66 
67     /**
68      * Callback which is used to sync drag state with the vendor-specific code.
69      */
70     @NonNull private AtomicReference<IDragDropCallback> mCallback = new AtomicReference<>(
71             new IDragDropCallback() {});
72 
dragDropActiveLocked()73     boolean dragDropActiveLocked() {
74         return mDragState != null;
75     }
76 
getInputWindowHandleLocked()77     InputWindowHandle getInputWindowHandleLocked() {
78         return mDragState.getInputWindowHandle();
79     }
80 
registerCallback(IDragDropCallback callback)81     void registerCallback(IDragDropCallback callback) {
82         Preconditions.checkNotNull(callback);
83         mCallback.set(callback);
84     }
85 
DragDropController(WindowManagerService service, Looper looper)86     DragDropController(WindowManagerService service, Looper looper) {
87         mService = service;
88         mHandler = new DragHandler(service, looper);
89     }
90 
sendDragStartedIfNeededLocked(WindowState window)91     void sendDragStartedIfNeededLocked(WindowState window) {
92         mDragState.sendDragStartedIfNeededLocked(window);
93     }
94 
performDrag(SurfaceSession session, int callerPid, int callerUid, IWindow window, int flags, SurfaceControl surface, int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data)95     IBinder performDrag(SurfaceSession session, int callerPid, int callerUid, IWindow window,
96             int flags, SurfaceControl surface, int touchSource, float touchX, float touchY,
97             float thumbCenterX, float thumbCenterY, ClipData data) {
98         if (DEBUG_DRAG) {
99             Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=" +
100                             Integer.toHexString(flags) + " data=" + data);
101         }
102 
103         final IBinder dragToken = new Binder();
104         final boolean callbackResult = mCallback.get().prePerformDrag(window, dragToken,
105                 touchSource, touchX, touchY, thumbCenterX, thumbCenterY, data);
106         try {
107             synchronized (mService.mWindowMap) {
108                 try {
109                     if (!callbackResult) {
110                         Slog.w(TAG_WM, "IDragDropCallback rejects the performDrag request");
111                         return null;
112                     }
113 
114                     if (dragDropActiveLocked()) {
115                         Slog.w(TAG_WM, "Drag already in progress");
116                         return null;
117                     }
118 
119                     final WindowState callingWin = mService.windowForClientLocked(
120                             null, window, false);
121                     if (callingWin == null) {
122                         Slog.w(TAG_WM, "Bad requesting window " + window);
123                         return null;  // !!! TODO: throw here?
124                     }
125 
126                     // !!! TODO: if input is not still focused on the initiating window, fail
127                     // the drag initiation (e.g. an alarm window popped up just as the application
128                     // called performDrag()
129 
130                     // !!! TODO: extract the current touch (x, y) in screen coordinates.  That
131                     // will let us eliminate the (touchX,touchY) parameters from the API.
132 
133                     // !!! FIXME: put all this heavy stuff onto the mHandler looper, as well as
134                     // the actual drag event dispatch stuff in the dragstate
135 
136                     // !!! TODO(multi-display): support other displays
137 
138                     final DisplayContent displayContent = callingWin.getDisplayContent();
139                     if (displayContent == null) {
140                         Slog.w(TAG_WM, "display content is null");
141                         return null;
142                     }
143 
144                     final float alpha = (flags & View.DRAG_FLAG_OPAQUE) == 0 ?
145                             DRAG_SHADOW_ALPHA_TRANSPARENT : 1;
146                     final IBinder winBinder = window.asBinder();
147                     IBinder token = new Binder();
148                     mDragState = new DragState(mService, this, token, surface, flags, winBinder);
149                     surface = null;
150                     mDragState.mPid = callerPid;
151                     mDragState.mUid = callerUid;
152                     mDragState.mOriginalAlpha = alpha;
153                     mDragState.mToken = dragToken;
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                     mDragState.mDisplayContent = displayContent;
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                     final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
173                     if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag");
174 
175                     final SurfaceControl.Transaction transaction =
176                             callingWin.getPendingTransaction();
177                     transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha);
178                     transaction.setPosition(
179                             surfaceControl, touchX - thumbCenterX, touchY - thumbCenterY);
180                     transaction.show(surfaceControl);
181                     displayContent.reparentToOverlay(transaction, surfaceControl);
182                     callingWin.scheduleAnimation();
183 
184                     if (SHOW_LIGHT_TRANSACTIONS) {
185                         Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag");
186                     }
187 
188                     mDragState.notifyLocationLocked(touchX, touchY);
189                 } finally {
190                     if (surface != null) {
191                         surface.release();
192                     }
193                     if (mDragState != null && !mDragState.isInProgress()) {
194                         mDragState.closeLocked();
195                     }
196                 }
197             }
198             return dragToken;    // success!
199         } finally {
200             mCallback.get().postPerformDrag();
201         }
202     }
203 
reportDropResult(IWindow window, boolean consumed)204     void reportDropResult(IWindow window, boolean consumed) {
205         IBinder token = window.asBinder();
206         if (DEBUG_DRAG) {
207             Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token);
208         }
209 
210         mCallback.get().preReportDropResult(window, consumed);
211         try {
212             synchronized (mService.mWindowMap) {
213                 if (mDragState == null) {
214                     // Most likely the drop recipient ANRed and we ended the drag
215                     // out from under it.  Log the issue and move on.
216                     Slog.w(TAG_WM, "Drop result given but no drag in progress");
217                     return;
218                 }
219 
220                 if (mDragState.mToken != token) {
221                     // We're in a drag, but the wrong window has responded.
222                     Slog.w(TAG_WM, "Invalid drop-result claim by " + window);
223                     throw new IllegalStateException("reportDropResult() by non-recipient");
224                 }
225 
226                 // The right window has responded, even if it's no longer around,
227                 // so be sure to halt the timeout even if the later WindowState
228                 // lookup fails.
229                 mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder());
230                 WindowState callingWin = mService.windowForClientLocked(null, window, false);
231                 if (callingWin == null) {
232                     Slog.w(TAG_WM, "Bad result-reporting window " + window);
233                     return;  // !!! TODO: throw here?
234                 }
235 
236                 mDragState.mDragResult = consumed;
237                 mDragState.endDragLocked();
238             }
239         } finally {
240             mCallback.get().postReportDropResult();
241         }
242     }
243 
cancelDragAndDrop(IBinder dragToken)244     void cancelDragAndDrop(IBinder dragToken) {
245         if (DEBUG_DRAG) {
246             Slog.d(TAG_WM, "cancelDragAndDrop");
247         }
248 
249         mCallback.get().preCancelDragAndDrop(dragToken);
250         try {
251             synchronized (mService.mWindowMap) {
252                 if (mDragState == null) {
253                     Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
254                     throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
255                 }
256 
257                 if (mDragState.mToken != dragToken) {
258                     Slog.w(TAG_WM,
259                             "cancelDragAndDrop() does not match prepareDrag()");
260                     throw new IllegalStateException(
261                             "cancelDragAndDrop() does not match prepareDrag()");
262                 }
263 
264                 mDragState.mDragResult = false;
265                 mDragState.cancelDragLocked();
266             }
267         } finally {
268             mCallback.get().postCancelDragAndDrop();
269         }
270     }
271 
272     /**
273      * Handles motion events.
274      * @param keepHandling Whether if the drag operation is continuing or this is the last motion
275      *          event.
276      * @param newX X coordinate value in dp in the screen coordinate
277      * @param newY Y coordinate value in dp in the screen coordinate
278      */
handleMotionEvent(boolean keepHandling, float newX, float newY)279     void handleMotionEvent(boolean keepHandling, float newX, float newY) {
280         synchronized (mService.mWindowMap) {
281             if (!dragDropActiveLocked()) {
282                 // The drag has ended but the clean-up message has not been processed by
283                 // window manager. Drop events that occur after this until window manager
284                 // has a chance to clean-up the input handle.
285                 return;
286             }
287 
288             if (keepHandling) {
289                 mDragState.notifyMoveLocked(newX, newY);
290             } else {
291                 mDragState.notifyDropLocked(newX, newY);
292             }
293         }
294     }
295 
dragRecipientEntered(IWindow window)296     void dragRecipientEntered(IWindow window) {
297         if (DEBUG_DRAG) {
298             Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder());
299         }
300     }
301 
dragRecipientExited(IWindow window)302     void dragRecipientExited(IWindow window) {
303         if (DEBUG_DRAG) {
304             Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder());
305         }
306     }
307 
308     /**
309      * Sends a message to the Handler managed by DragDropController.
310      */
sendHandlerMessage(int what, Object arg)311     void sendHandlerMessage(int what, Object arg) {
312         mHandler.obtainMessage(what, arg).sendToTarget();
313     }
314 
315     /**
316      * Sends a timeout message to the Handler managed by DragDropController.
317      */
sendTimeoutMessage(int what, Object arg)318     void sendTimeoutMessage(int what, Object arg) {
319         mHandler.removeMessages(what, arg);
320         final Message msg = mHandler.obtainMessage(what, arg);
321         mHandler.sendMessageDelayed(msg, DRAG_TIMEOUT_MS);
322     }
323 
324     /**
325      * Notifies the current drag state is closed.
326      */
onDragStateClosedLocked(DragState dragState)327     void onDragStateClosedLocked(DragState dragState) {
328         if (mDragState != dragState) {
329             Slog.wtf(TAG_WM, "Unknown drag state is closed");
330             return;
331         }
332         mDragState = null;
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.mWindowMap) {
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.mWindowMap) {
372                         interceptor.tearDown();
373                     }
374                     break;
375                 }
376 
377                 case MSG_ANIMATION_END: {
378                     synchronized (mService.mWindowMap) {
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         }
390     }
391 }
392