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