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