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