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