blob: 36871b1d8c5be20f25f5d7da8b75fea612519fd0 [file] [log] [blame]
Daichi Hironodf5cf622017-09-11 14:59:26 +09001/*
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
17package com.android.server.wm;
18
19import static android.graphics.PixelFormat.TRANSLUCENT;
20import static android.view.SurfaceControl.HIDDEN;
21import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
22import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
23import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
24import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
25
26import android.content.ClipData;
27import android.os.Binder;
28import android.os.Handler;
29import android.os.IBinder;
30import android.os.Message;
31import android.util.Slog;
32import android.view.Display;
33import android.view.IWindow;
34import android.view.Surface;
35import android.view.Surface.OutOfResourcesException;
36import android.view.SurfaceControl;
37import android.view.SurfaceSession;
38import android.view.View;
39import com.android.server.wm.WindowManagerService.H;
40
41/**
42 * Managing drag and drop operations initiated by View#startDragAndDrop.
43 */
44class DragDropController {
45 private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
46 private static final long DRAG_TIMEOUT_MS = 5000;
47
48 IBinder prepareDrag(WindowManagerService service, SurfaceSession session, int callerPid,
49 int callerUid, IWindow window, int flags, int width, int height, Surface outSurface) {
50 if (DEBUG_DRAG) {
51 Slog.d(TAG_WM, "prepare drag surface: w=" + width + " h=" + height
52 + " flags=" + Integer.toHexString(flags) + " win=" + window
53 + " asbinder=" + window.asBinder());
54 }
55
56 IBinder token = null;
57
58 synchronized (service.mWindowMap) {
59 try {
60 if (service.mDragState == null) {
61 // TODO(multi-display): support other displays
62 final DisplayContent displayContent =
63 service.getDefaultDisplayContentLocked();
64 final Display display = displayContent.getDisplay();
65
66 SurfaceControl surface = new SurfaceControl(session, "drag surface",
67 width, height, TRANSLUCENT, HIDDEN);
68 surface.setLayerStack(display.getLayerStack());
69 float alpha = 1;
70 if ((flags & View.DRAG_FLAG_OPAQUE) == 0) {
71 alpha = DRAG_SHADOW_ALPHA_TRANSPARENT;
72 }
73 surface.setAlpha(alpha);
74
75 if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " DRAG "
76 + surface + ": CREATE");
77 outSurface.copyFrom(surface);
78 final IBinder winBinder = window.asBinder();
79 token = new Binder();
80 service.mDragState =
81 new DragState(service, token, surface, flags, winBinder);
82 service.mDragState.mPid = callerPid;
83 service.mDragState.mUid = callerUid;
84 service.mDragState.mOriginalAlpha = alpha;
85 token = service.mDragState.mToken = new Binder();
86
87 // 5 second timeout for this window to actually begin the drag
88 service.mH.removeMessages(H.DRAG_START_TIMEOUT, winBinder);
89 Message msg = service.mH.obtainMessage(H.DRAG_START_TIMEOUT, winBinder);
90 service.mH.sendMessageDelayed(msg, DRAG_TIMEOUT_MS);
91 } else {
92 Slog.w(TAG_WM, "Drag already in progress");
93 }
94 } catch (OutOfResourcesException e) {
95 Slog.e(TAG_WM, "Can't allocate drag surface w=" + width + " h=" + height,
96 e);
97 if (service.mDragState != null) {
98 service.mDragState.reset();
99 service.mDragState = null;
100 }
101 }
102 }
103
104 return token;
105 }
106
107 boolean performDrag(WindowManagerService service, IWindow window, IBinder dragToken,
108 int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
109 ClipData data) {
110 if (DEBUG_DRAG) {
111 Slog.d(TAG_WM, "perform drag: win=" + window + " data=" + data);
112 }
113
114 synchronized (service.mWindowMap) {
115 if (service.mDragState == null) {
116 Slog.w(TAG_WM, "No drag prepared");
117 throw new IllegalStateException("performDrag() without prepareDrag()");
118 }
119
120 if (dragToken != service.mDragState.mToken) {
121 Slog.w(TAG_WM, "Performing mismatched drag");
122 throw new IllegalStateException("performDrag() does not match prepareDrag()");
123 }
124
125 final WindowState callingWin = service.windowForClientLocked(null, window, false);
126 if (callingWin == null) {
127 Slog.w(TAG_WM, "Bad requesting window " + window);
128 return false; // !!! TODO: throw here?
129 }
130
131 // !!! TODO: if input is not still focused on the initiating window, fail
132 // the drag initiation (e.g. an alarm window popped up just as the application
133 // called performDrag()
134
135 service.mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder());
136
137 // !!! TODO: extract the current touch (x, y) in screen coordinates. That
138 // will let us eliminate the (touchX,touchY) parameters from the API.
139
140 // !!! FIXME: put all this heavy stuff onto the mH looper, as well as
141 // the actual drag event dispatch stuff in the dragstate
142
143 final DisplayContent displayContent = callingWin.getDisplayContent();
144 if (displayContent == null) {
145 return false;
146 }
147 Display display = displayContent.getDisplay();
148 service.mDragState.register(display);
149 if (!service.mInputManager.transferTouchFocus(callingWin.mInputChannel,
150 service.mDragState.getInputChannel())) {
151 Slog.e(TAG_WM, "Unable to transfer touch focus");
152 service.mDragState.unregister();
153 service.mDragState.reset();
154 service.mDragState = null;
155 return false;
156 }
157
158 service.mDragState.mDisplayContent = displayContent;
159 service.mDragState.mData = data;
160 service.mDragState.broadcastDragStartedLw(touchX, touchY);
161 service.mDragState.overridePointerIconLw(touchSource);
162
163 // remember the thumb offsets for later
164 service.mDragState.mThumbOffsetX = thumbCenterX;
165 service.mDragState.mThumbOffsetY = thumbCenterY;
166
167 // Make the surface visible at the proper location
168 final SurfaceControl surfaceControl = service.mDragState.mSurfaceControl;
169 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
170 TAG_WM, ">>> OPEN TRANSACTION performDrag");
171 service.openSurfaceTransaction();
172 try {
173 surfaceControl.setPosition(touchX - thumbCenterX,
174 touchY - thumbCenterY);
175 surfaceControl.setLayer(service.mDragState.getDragLayerLw());
176 surfaceControl.setLayerStack(display.getLayerStack());
177 surfaceControl.show();
178 } finally {
179 service.closeSurfaceTransaction();
180 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
181 TAG_WM, "<<< CLOSE TRANSACTION performDrag");
182 }
183
184 service.mDragState.notifyLocationLw(touchX, touchY);
185 }
186
187 return true; // success!
188 }
189
190 void reportDropResult(WindowManagerService service, IWindow window, boolean consumed) {
191 IBinder token = window.asBinder();
192 if (DEBUG_DRAG) {
193 Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token);
194 }
195
196 synchronized (service.mWindowMap) {
197 if (service.mDragState == null) {
198 // Most likely the drop recipient ANRed and we ended the drag
199 // out from under it. Log the issue and move on.
200 Slog.w(TAG_WM, "Drop result given but no drag in progress");
201 return;
202 }
203
204 if (service.mDragState.mToken != token) {
205 // We're in a drag, but the wrong window has responded.
206 Slog.w(TAG_WM, "Invalid drop-result claim by " + window);
207 throw new IllegalStateException("reportDropResult() by non-recipient");
208 }
209
210 // The right window has responded, even if it's no longer around,
211 // so be sure to halt the timeout even if the later WindowState
212 // lookup fails.
213 service.mH.removeMessages(H.DRAG_END_TIMEOUT, window.asBinder());
214 WindowState callingWin = service.windowForClientLocked(null, window, false);
215 if (callingWin == null) {
216 Slog.w(TAG_WM, "Bad result-reporting window " + window);
217 return; // !!! TODO: throw here?
218 }
219
220 service.mDragState.mDragResult = consumed;
221 service.mDragState.endDragLw();
222 }
223 }
224
225 void cancelDragAndDrop(WindowManagerService service, IBinder dragToken) {
226 if (DEBUG_DRAG) {
227 Slog.d(TAG_WM, "cancelDragAndDrop");
228 }
229
230 synchronized (service.mWindowMap) {
231 if (service.mDragState == null) {
232 Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
233 throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
234 }
235
236 if (service.mDragState.mToken != dragToken) {
237 Slog.w(TAG_WM,
238 "cancelDragAndDrop() does not match prepareDrag()");
239 throw new IllegalStateException(
240 "cancelDragAndDrop() does not match prepareDrag()");
241 }
242
243 service.mDragState.mDragResult = false;
244 service.mDragState.cancelDragLw();
245 }
246 }
247
248 void dragRecipientEntered(IWindow window) {
249 if (DEBUG_DRAG) {
250 Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder());
251 }
252 }
253
254 void dragRecipientExited(IWindow window) {
255 if (DEBUG_DRAG) {
256 Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder());
257 }
258 }
259
260 void handleMessage(WindowManagerService service, Message msg) {
261 switch (msg.what) {
262 case H.DRAG_START_TIMEOUT: {
263 IBinder win = (IBinder) msg.obj;
264 if (DEBUG_DRAG) {
265 Slog.w(TAG_WM, "Timeout starting drag by win " + win);
266 }
267 synchronized (service.mWindowMap) {
268 // !!! TODO: ANR the app that has failed to start the drag in time
269 if (service.mDragState != null) {
270 service.mDragState.unregister();
271 service.mDragState.reset();
272 service.mDragState = null;
273 }
274 }
275 break;
276 }
277
278 case H.DRAG_END_TIMEOUT: {
279 IBinder win = (IBinder) msg.obj;
280 if (DEBUG_DRAG) {
281 Slog.w(TAG_WM, "Timeout ending drag to win " + win);
282 }
283 synchronized (service.mWindowMap) {
284 // !!! TODO: ANR the drag-receiving app
285 if (service.mDragState != null) {
286 service.mDragState.mDragResult = false;
287 service.mDragState.endDragLw();
288 }
289 }
290 break;
291 }
292
293 case H.TEAR_DOWN_DRAG_AND_DROP_INPUT: {
294 if (DEBUG_DRAG)
295 Slog.d(TAG_WM, "Drag ending; tearing down input channel");
296 DragState.InputInterceptor interceptor = (DragState.InputInterceptor) msg.obj;
297 if (interceptor != null) {
298 synchronized (service.mWindowMap) {
299 interceptor.tearDown();
300 }
301 }
302 break;
303 }
304 }
305 }
306}