DragState.java revision fa25bf5382467b1018bd9af7f1cb30a23d7d59f7
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.DragEvent;
33import android.view.InputChannel;
34import android.view.Surface;
35import android.view.View;
36import android.view.WindowManager;
37
38import java.util.ArrayList;
39
40/**
41 * Drag/drop state
42 */
43class 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
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
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
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.mDisplayInfo.logicalWidth;
129            mDragWindowHandle.frameBottom = mService.mDisplayInfo.logicalHeight;
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
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
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     */
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     */
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     */
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
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
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
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.
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
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
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}