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