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