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