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