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