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