1 /*
2  * Copyright (C) 2011 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 com.android.server.input.InputApplicationHandle;
20 import com.android.server.input.InputWindowHandle;
21 import com.android.server.wm.WindowManagerService.DragInputEventReceiver;
22 import com.android.server.wm.WindowManagerService.H;
23 
24 import android.content.ClipData;
25 import android.content.ClipDescription;
26 import android.graphics.Point;
27 import android.graphics.Rect;
28 import android.graphics.Region;
29 import android.os.IBinder;
30 import android.os.Message;
31 import android.os.Process;
32 import android.os.RemoteException;
33 import android.util.Slog;
34 import android.view.Display;
35 import android.view.DragEvent;
36 import android.view.InputChannel;
37 import android.view.SurfaceControl;
38 import android.view.View;
39 import android.view.WindowManager;
40 
41 import java.util.ArrayList;
42 
43 /**
44  * Drag/drop state
45  */
46 class DragState {
47     final WindowManagerService mService;
48     IBinder mToken;
49     SurfaceControl mSurfaceControl;
50     int mFlags;
51     IBinder mLocalWin;
52     ClipData mData;
53     ClipDescription mDataDescription;
54     boolean mDragResult;
55     float mCurrentX, mCurrentY;
56     float mThumbOffsetX, mThumbOffsetY;
57     InputChannel mServerChannel, mClientChannel;
58     DragInputEventReceiver mInputEventReceiver;
59     InputApplicationHandle mDragApplicationHandle;
60     InputWindowHandle mDragWindowHandle;
61     WindowState mTargetWindow;
62     ArrayList<WindowState> mNotifiedWindows;
63     boolean mDragInProgress;
64     Display mDisplay;
65 
66     private final Region mTmpRegion = new Region();
67     private final Rect mTmpRect = new Rect();
68 
DragState(WindowManagerService service, IBinder token, SurfaceControl surface, int flags, IBinder localWin)69     DragState(WindowManagerService service, IBinder token, SurfaceControl surface,
70             int flags, IBinder localWin) {
71         mService = service;
72         mToken = token;
73         mSurfaceControl = surface;
74         mFlags = flags;
75         mLocalWin = localWin;
76         mNotifiedWindows = new ArrayList<WindowState>();
77     }
78 
reset()79     void reset() {
80         if (mSurfaceControl != null) {
81             mSurfaceControl.destroy();
82         }
83         mSurfaceControl = null;
84         mFlags = 0;
85         mLocalWin = null;
86         mToken = null;
87         mData = null;
88         mThumbOffsetX = mThumbOffsetY = 0;
89         mNotifiedWindows = null;
90     }
91 
92     /**
93      * @param display The Display that the window being dragged is on.
94      */
register(Display display)95     void register(Display display) {
96         mDisplay = display;
97         if (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.TAG, "registering drag input channel");
98         if (mClientChannel != null) {
99             Slog.e(WindowManagerService.TAG, "Duplicate register of drag input channel");
100         } else {
101             InputChannel[] channels = InputChannel.openInputChannelPair("drag");
102             mServerChannel = channels[0];
103             mClientChannel = channels[1];
104             mService.mInputManager.registerInputChannel(mServerChannel, null);
105             mInputEventReceiver = mService.new DragInputEventReceiver(mClientChannel,
106                     mService.mH.getLooper());
107 
108             mDragApplicationHandle = new InputApplicationHandle(null);
109             mDragApplicationHandle.name = "drag";
110             mDragApplicationHandle.dispatchingTimeoutNanos =
111                     WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
112 
113             mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
114                     mDisplay.getDisplayId());
115             mDragWindowHandle.name = "drag";
116             mDragWindowHandle.inputChannel = mServerChannel;
117             mDragWindowHandle.layer = getDragLayerLw();
118             mDragWindowHandle.layoutParamsFlags = 0;
119             mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
120             mDragWindowHandle.dispatchingTimeoutNanos =
121                     WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
122             mDragWindowHandle.visible = true;
123             mDragWindowHandle.canReceiveKeys = false;
124             mDragWindowHandle.hasFocus = true;
125             mDragWindowHandle.hasWallpaper = false;
126             mDragWindowHandle.paused = false;
127             mDragWindowHandle.ownerPid = Process.myPid();
128             mDragWindowHandle.ownerUid = Process.myUid();
129             mDragWindowHandle.inputFeatures = 0;
130             mDragWindowHandle.scaleFactor = 1.0f;
131 
132             // The drag window cannot receive new touches.
133             mDragWindowHandle.touchableRegion.setEmpty();
134 
135             // The drag window covers the entire display
136             mDragWindowHandle.frameLeft = 0;
137             mDragWindowHandle.frameTop = 0;
138             Point p = new Point();
139             mDisplay.getRealSize(p);
140             mDragWindowHandle.frameRight = p.x;
141             mDragWindowHandle.frameBottom = p.y;
142 
143             // Pause rotations before a drag.
144             if (WindowManagerService.DEBUG_ORIENTATION) {
145                 Slog.d(WindowManagerService.TAG, "Pausing rotation during drag");
146             }
147             mService.pauseRotationLocked();
148         }
149     }
150 
unregister()151     void unregister() {
152         if (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.TAG, "unregistering drag input channel");
153         if (mClientChannel == null) {
154             Slog.e(WindowManagerService.TAG, "Unregister of nonexistent drag input channel");
155         } else {
156             mService.mInputManager.unregisterInputChannel(mServerChannel);
157             mInputEventReceiver.dispose();
158             mInputEventReceiver = null;
159             mClientChannel.dispose();
160             mServerChannel.dispose();
161             mClientChannel = null;
162             mServerChannel = null;
163 
164             mDragWindowHandle = null;
165             mDragApplicationHandle = null;
166 
167             // Resume rotations after a drag.
168             if (WindowManagerService.DEBUG_ORIENTATION) {
169                 Slog.d(WindowManagerService.TAG, "Resuming rotation after drag");
170             }
171             mService.resumeRotationLocked();
172         }
173     }
174 
getDragLayerLw()175     int getDragLayerLw() {
176         return mService.mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_DRAG)
177                 * WindowManagerService.TYPE_LAYER_MULTIPLIER
178                 + WindowManagerService.TYPE_LAYER_OFFSET;
179     }
180 
181     /* call out to each visible window/session informing it about the drag
182      */
broadcastDragStartedLw(final float touchX, final float touchY)183     void broadcastDragStartedLw(final float touchX, final float touchY) {
184         // Cache a base-class instance of the clip metadata so that parceling
185         // works correctly in calling out to the apps.
186         mDataDescription = (mData != null) ? mData.getDescription() : null;
187         mNotifiedWindows.clear();
188         mDragInProgress = true;
189 
190         if (WindowManagerService.DEBUG_DRAG) {
191             Slog.d(WindowManagerService.TAG, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")");
192         }
193 
194         final WindowList windows = mService.getWindowListLocked(mDisplay);
195         if (windows != null) {
196             final int N = windows.size();
197             for (int i = 0; i < N; i++) {
198                 sendDragStartedLw(windows.get(i), touchX, touchY, mDataDescription);
199             }
200         }
201     }
202 
203     /* helper - send a caller-provided event, presumed to be DRAG_STARTED, if the
204      * designated window is potentially a drop recipient.  There are race situations
205      * around DRAG_ENDED broadcast, so we make sure that once we've declared that
206      * the drag has ended, we never send out another DRAG_STARTED for this drag action.
207      *
208      * This method clones the 'event' parameter if it's being delivered to the same
209      * process, so it's safe for the caller to call recycle() on the event afterwards.
210      */
sendDragStartedLw(WindowState newWin, float touchX, float touchY, ClipDescription desc)211     private void sendDragStartedLw(WindowState newWin, float touchX, float touchY,
212             ClipDescription desc) {
213         // Don't actually send the event if the drag is supposed to be pinned
214         // to the originating window but 'newWin' is not that window.
215         if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) {
216             final IBinder winBinder = newWin.mClient.asBinder();
217             if (winBinder != mLocalWin) {
218                 if (WindowManagerService.DEBUG_DRAG) {
219                     Slog.d(WindowManagerService.TAG, "Not dispatching local DRAG_STARTED to " + newWin);
220                 }
221                 return;
222             }
223         }
224 
225         if (mDragInProgress && newWin.isPotentialDragTarget()) {
226             DragEvent event = obtainDragEvent(newWin, DragEvent.ACTION_DRAG_STARTED,
227                     touchX, touchY, null, desc, null, false);
228             try {
229                 newWin.mClient.dispatchDragEvent(event);
230                 // track each window that we've notified that the drag is starting
231                 mNotifiedWindows.add(newWin);
232             } catch (RemoteException e) {
233                 Slog.w(WindowManagerService.TAG, "Unable to drag-start window " + newWin);
234             } finally {
235                 // if the callee was local, the dispatch has already recycled the event
236                 if (Process.myPid() != newWin.mSession.mPid) {
237                     event.recycle();
238                 }
239             }
240         }
241     }
242 
243     /* helper - construct and send a DRAG_STARTED event only if the window has not
244      * previously been notified, i.e. it became visible after the drag operation
245      * was begun.  This is a rare case.
246      */
sendDragStartedIfNeededLw(WindowState newWin)247     void sendDragStartedIfNeededLw(WindowState newWin) {
248         if (mDragInProgress) {
249             // If we have sent the drag-started, we needn't do so again
250             for (WindowState ws : mNotifiedWindows) {
251                 if (ws == newWin) {
252                     return;
253                 }
254             }
255             if (WindowManagerService.DEBUG_DRAG) {
256                 Slog.d(WindowManagerService.TAG, "need to send DRAG_STARTED to new window " + newWin);
257             }
258             sendDragStartedLw(newWin, mCurrentX, mCurrentY, mDataDescription);
259         }
260     }
261 
broadcastDragEndedLw()262     void broadcastDragEndedLw() {
263         if (WindowManagerService.DEBUG_DRAG) {
264             Slog.d(WindowManagerService.TAG, "broadcasting DRAG_ENDED");
265         }
266         DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
267                 0, 0, null, null, null, mDragResult);
268         for (WindowState ws: mNotifiedWindows) {
269             try {
270                 ws.mClient.dispatchDragEvent(evt);
271             } catch (RemoteException e) {
272                 Slog.w(WindowManagerService.TAG, "Unable to drag-end window " + ws);
273             }
274         }
275         mNotifiedWindows.clear();
276         mDragInProgress = false;
277         evt.recycle();
278     }
279 
endDragLw()280     void endDragLw() {
281         mService.mDragState.broadcastDragEndedLw();
282 
283         // stop intercepting input
284         mService.mDragState.unregister();
285         mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
286 
287         // free our resources and drop all the object references
288         mService.mDragState.reset();
289         mService.mDragState = null;
290     }
291 
notifyMoveLw(float x, float y)292     void notifyMoveLw(float x, float y) {
293         final int myPid = Process.myPid();
294 
295         // Move the surface to the given touch
296         if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(
297                 WindowManagerService.TAG, ">>> OPEN TRANSACTION notifyMoveLw");
298         SurfaceControl.openTransaction();
299         try {
300             mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
301             if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, "  DRAG "
302                     + mSurfaceControl + ": pos=(" +
303                     (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
304         } finally {
305             SurfaceControl.closeTransaction();
306             if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(
307                     WindowManagerService.TAG, "<<< CLOSE TRANSACTION notifyMoveLw");
308         }
309 
310         // Tell the affected window
311         WindowState touchedWin = getTouchedWinAtPointLw(x, y);
312         if (touchedWin == null) {
313             if (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.TAG, "No touched win at x=" + x + " y=" + y);
314             return;
315         }
316         if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) {
317             final IBinder touchedBinder = touchedWin.mClient.asBinder();
318             if (touchedBinder != mLocalWin) {
319                 // This drag is pinned only to the originating window, but the drag
320                 // point is outside that window.  Pretend it's over empty space.
321                 touchedWin = null;
322             }
323         }
324         try {
325             // have we dragged over a new window?
326             if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) {
327                 if (WindowManagerService.DEBUG_DRAG) {
328                     Slog.d(WindowManagerService.TAG, "sending DRAG_EXITED to " + mTargetWindow);
329                 }
330                 // force DRAG_EXITED_EVENT if appropriate
331                 DragEvent evt = obtainDragEvent(mTargetWindow, DragEvent.ACTION_DRAG_EXITED,
332                         x, y, null, null, null, false);
333                 mTargetWindow.mClient.dispatchDragEvent(evt);
334                 if (myPid != mTargetWindow.mSession.mPid) {
335                     evt.recycle();
336                 }
337             }
338             if (touchedWin != null) {
339                 if (false && WindowManagerService.DEBUG_DRAG) {
340                     Slog.d(WindowManagerService.TAG, "sending DRAG_LOCATION to " + touchedWin);
341                 }
342                 DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DRAG_LOCATION,
343                         x, y, null, null, null, false);
344                 touchedWin.mClient.dispatchDragEvent(evt);
345                 if (myPid != touchedWin.mSession.mPid) {
346                     evt.recycle();
347                 }
348             }
349         } catch (RemoteException e) {
350             Slog.w(WindowManagerService.TAG, "can't send drag notification to windows");
351         }
352         mTargetWindow = touchedWin;
353     }
354 
355     // Tell the drop target about the data.  Returns 'true' if we can immediately
356     // dispatch the global drag-ended message, 'false' if we need to wait for a
357     // result from the recipient.
notifyDropLw(float x, float y)358     boolean notifyDropLw(float x, float y) {
359         WindowState touchedWin = getTouchedWinAtPointLw(x, y);
360         if (touchedWin == null) {
361             // "drop" outside a valid window -- no recipient to apply a
362             // timeout to, and we can send the drag-ended message immediately.
363             mDragResult = false;
364             return true;
365         }
366 
367         if (WindowManagerService.DEBUG_DRAG) {
368             Slog.d(WindowManagerService.TAG, "sending DROP to " + touchedWin);
369         }
370         final int myPid = Process.myPid();
371         final IBinder token = touchedWin.mClient.asBinder();
372         DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y,
373                 null, null, mData, false);
374         try {
375             touchedWin.mClient.dispatchDragEvent(evt);
376 
377             // 5 second timeout for this window to respond to the drop
378             mService.mH.removeMessages(H.DRAG_END_TIMEOUT, token);
379             Message msg = mService.mH.obtainMessage(H.DRAG_END_TIMEOUT, token);
380             mService.mH.sendMessageDelayed(msg, 5000);
381         } catch (RemoteException e) {
382             Slog.w(WindowManagerService.TAG, "can't send drop notification to win " + touchedWin);
383             return true;
384         } finally {
385             if (myPid != touchedWin.mSession.mPid) {
386                 evt.recycle();
387             }
388         }
389         mToken = token;
390         return false;
391     }
392 
393     // Find the visible, touch-deliverable window under the given point
getTouchedWinAtPointLw(float xf, float yf)394     private WindowState getTouchedWinAtPointLw(float xf, float yf) {
395         WindowState touchedWin = null;
396         final int x = (int) xf;
397         final int y = (int) yf;
398 
399         final WindowList windows = mService.getWindowListLocked(mDisplay);
400         if (windows == null) {
401             return null;
402         }
403         final int N = windows.size();
404         for (int i = N - 1; i >= 0; i--) {
405             WindowState child = windows.get(i);
406             final int flags = child.mAttrs.flags;
407             if (!child.isVisibleLw()) {
408                 // not visible == don't tell about drags
409                 continue;
410             }
411             if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
412                 // not touchable == don't tell about drags
413                 continue;
414             }
415 
416             child.getStackBounds(mTmpRect);
417             if (!mTmpRect.contains(x, y)) {
418                 // outside of this window's activity stack == don't tell about drags
419                 continue;
420             }
421 
422             child.getTouchableRegion(mTmpRegion);
423 
424             final int touchFlags = flags &
425                     (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
426                             | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
427             if (mTmpRegion.contains(x, y) || touchFlags == 0) {
428                 // Found it
429                 touchedWin = child;
430                 break;
431             }
432         }
433 
434         return touchedWin;
435     }
436 
obtainDragEvent(WindowState win, int action, float x, float y, Object localState, ClipDescription description, ClipData data, boolean result)437     private static DragEvent obtainDragEvent(WindowState win, int action,
438             float x, float y, Object localState,
439             ClipDescription description, ClipData data, boolean result) {
440         float winX = x - win.mFrame.left;
441         float winY = y - win.mFrame.top;
442         if (win.mEnforceSizeCompat) {
443             winX *= win.mGlobalScale;
444             winY *= win.mGlobalScale;
445         }
446         return DragEvent.obtain(action, winX, winY, localState, description, data, result);
447     }
448 }