DragState.java revision 598d40d93713470b7a4b02036cbea8ba9ee7ef90
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 static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
20import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
21import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
22import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
23import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
24
25import android.content.ClipData;
26import android.content.ClipDescription;
27import android.content.Context;
28import android.graphics.Matrix;
29import android.graphics.Point;
30import android.hardware.input.InputManager;
31import android.os.IBinder;
32import android.os.Message;
33import android.os.Process;
34import android.os.RemoteException;
35import android.os.ServiceManager;
36import android.os.UserHandle;
37import android.os.UserManager;
38import android.os.IUserManager;
39import android.util.Slog;
40import android.view.Display;
41import android.view.DragEvent;
42import android.view.InputChannel;
43import android.view.InputDevice;
44import android.view.PointerIcon;
45import android.view.SurfaceControl;
46import android.view.View;
47import android.view.WindowManager;
48import android.view.animation.AlphaAnimation;
49import android.view.animation.Animation;
50import android.view.animation.AnimationSet;
51import android.view.animation.DecelerateInterpolator;
52import android.view.animation.Interpolator;
53import android.view.animation.ScaleAnimation;
54import android.view.animation.Transformation;
55import android.view.animation.TranslateAnimation;
56
57import com.android.server.input.InputApplicationHandle;
58import com.android.server.input.InputWindowHandle;
59import com.android.server.wm.WindowManagerService.DragInputEventReceiver;
60import com.android.server.wm.WindowManagerService.H;
61
62import com.android.internal.view.IDropPermissions;
63
64import java.util.ArrayList;
65
66/**
67 * Drag/drop state
68 */
69class DragState {
70    private static final long ANIMATION_DURATION_MS = 500;
71
72    private static final int DRAG_FLAGS_URI_ACCESS = View.DRAG_FLAG_GLOBAL_URI_READ |
73            View.DRAG_FLAG_GLOBAL_URI_WRITE;
74
75    private static final int DRAG_FLAGS_URI_PERMISSIONS = DRAG_FLAGS_URI_ACCESS |
76            View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION |
77            View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION;
78
79    final WindowManagerService mService;
80    IBinder mToken;
81    SurfaceControl mSurfaceControl;
82    int mFlags;
83    IBinder mLocalWin;
84    int mPid;
85    int mUid;
86    int mSourceUserId;
87    boolean mCrossProfileCopyAllowed;
88    ClipData mData;
89    ClipDescription mDataDescription;
90    int mTouchSource;
91    boolean mDragResult;
92    float mOriginalAlpha;
93    float mOriginalX, mOriginalY;
94    float mCurrentX, mCurrentY;
95    float mThumbOffsetX, mThumbOffsetY;
96    InputChannel mServerChannel, mClientChannel;
97    DragInputEventReceiver mInputEventReceiver;
98    InputApplicationHandle mDragApplicationHandle;
99    InputWindowHandle mDragWindowHandle;
100    WindowState mTargetWindow;
101    ArrayList<WindowState> mNotifiedWindows;
102    boolean mDragInProgress;
103    DisplayContent mDisplayContent;
104
105    private Animation mAnimation;
106    final Transformation mTransformation = new Transformation();
107    private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
108
109    DragState(WindowManagerService service, IBinder token, SurfaceControl surface,
110            int flags, IBinder localWin) {
111        mService = service;
112        mToken = token;
113        mSurfaceControl = surface;
114        mFlags = flags;
115        mLocalWin = localWin;
116        mNotifiedWindows = new ArrayList<WindowState>();
117    }
118
119    void reset() {
120        if (mSurfaceControl != null) {
121            mSurfaceControl.destroy();
122        }
123        mSurfaceControl = null;
124        mFlags = 0;
125        mLocalWin = null;
126        mToken = null;
127        mData = null;
128        mThumbOffsetX = mThumbOffsetY = 0;
129        mNotifiedWindows = null;
130    }
131
132    /**
133     * @param display The Display that the window being dragged is on.
134     */
135    void register(Display display) {
136        if (DEBUG_DRAG) Slog.d(TAG_WM, "registering drag input channel");
137        if (mClientChannel != null) {
138            Slog.e(TAG_WM, "Duplicate register of drag input channel");
139        } else {
140            mDisplayContent = mService.getDisplayContentLocked(display.getDisplayId());
141
142            InputChannel[] channels = InputChannel.openInputChannelPair("drag");
143            mServerChannel = channels[0];
144            mClientChannel = channels[1];
145            mService.mInputManager.registerInputChannel(mServerChannel, null);
146            mInputEventReceiver = mService.new DragInputEventReceiver(mClientChannel,
147                    mService.mH.getLooper());
148
149            mDragApplicationHandle = new InputApplicationHandle(null);
150            mDragApplicationHandle.name = "drag";
151            mDragApplicationHandle.dispatchingTimeoutNanos =
152                    WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
153
154            mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
155                    display.getDisplayId());
156            mDragWindowHandle.name = "drag";
157            mDragWindowHandle.inputChannel = mServerChannel;
158            mDragWindowHandle.layer = getDragLayerLw();
159            mDragWindowHandle.layoutParamsFlags = 0;
160            mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
161            mDragWindowHandle.dispatchingTimeoutNanos =
162                    WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
163            mDragWindowHandle.visible = true;
164            mDragWindowHandle.canReceiveKeys = false;
165            mDragWindowHandle.hasFocus = true;
166            mDragWindowHandle.hasWallpaper = false;
167            mDragWindowHandle.paused = false;
168            mDragWindowHandle.ownerPid = Process.myPid();
169            mDragWindowHandle.ownerUid = Process.myUid();
170            mDragWindowHandle.inputFeatures = 0;
171            mDragWindowHandle.scaleFactor = 1.0f;
172
173            // The drag window cannot receive new touches.
174            mDragWindowHandle.touchableRegion.setEmpty();
175
176            // The drag window covers the entire display
177            mDragWindowHandle.frameLeft = 0;
178            mDragWindowHandle.frameTop = 0;
179            Point p = new Point();
180            display.getRealSize(p);
181            mDragWindowHandle.frameRight = p.x;
182            mDragWindowHandle.frameBottom = p.y;
183
184            // Pause rotations before a drag.
185            if (DEBUG_ORIENTATION) {
186                Slog.d(TAG_WM, "Pausing rotation during drag");
187            }
188            mService.pauseRotationLocked();
189        }
190    }
191
192    void unregister() {
193        if (DEBUG_DRAG) Slog.d(TAG_WM, "unregistering drag input channel");
194        if (mClientChannel == null) {
195            Slog.e(TAG_WM, "Unregister of nonexistent drag input channel");
196        } else {
197            mService.mInputManager.unregisterInputChannel(mServerChannel);
198            mInputEventReceiver.dispose();
199            mInputEventReceiver = null;
200            mClientChannel.dispose();
201            mServerChannel.dispose();
202            mClientChannel = null;
203            mServerChannel = null;
204
205            mDragWindowHandle = null;
206            mDragApplicationHandle = null;
207
208            // Resume rotations after a drag.
209            if (DEBUG_ORIENTATION) {
210                Slog.d(TAG_WM, "Resuming rotation after drag");
211            }
212            mService.resumeRotationLocked();
213        }
214    }
215
216    int getDragLayerLw() {
217        return mService.mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_DRAG)
218                * WindowManagerService.TYPE_LAYER_MULTIPLIER
219                + WindowManagerService.TYPE_LAYER_OFFSET;
220    }
221
222    /* call out to each visible window/session informing it about the drag
223     */
224    void broadcastDragStartedLw(final float touchX, final float touchY) {
225        mOriginalX = mCurrentX = touchX;
226        mOriginalY = mCurrentY = touchY;
227
228        // Cache a base-class instance of the clip metadata so that parceling
229        // works correctly in calling out to the apps.
230        mDataDescription = (mData != null) ? mData.getDescription() : null;
231        mNotifiedWindows.clear();
232        mDragInProgress = true;
233
234        mSourceUserId = UserHandle.getUserId(mUid);
235
236        final IUserManager userManager =
237                (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
238        try {
239            mCrossProfileCopyAllowed = !userManager.getUserRestrictions(mSourceUserId).getBoolean(
240                    UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
241        } catch (RemoteException e) {
242            Slog.e(TAG_WM, "Remote Exception calling UserManager: " + e);
243            mCrossProfileCopyAllowed = false;
244        }
245
246        if (DEBUG_DRAG) {
247            Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")");
248        }
249
250        final WindowList windows = mDisplayContent.getWindowList();
251        final int N = windows.size();
252        for (int i = 0; i < N; i++) {
253            sendDragStartedLw(windows.get(i), touchX, touchY, mDataDescription);
254        }
255    }
256
257    /* helper - send a ACTION_DRAG_STARTED event, if the
258     * designated window is potentially a drop recipient.  There are race situations
259     * around DRAG_ENDED broadcast, so we make sure that once we've declared that
260     * the drag has ended, we never send out another DRAG_STARTED for this drag action.
261     *
262     * This method clones the 'event' parameter if it's being delivered to the same
263     * process, so it's safe for the caller to call recycle() on the event afterwards.
264     */
265    private void sendDragStartedLw(WindowState newWin, float touchX, float touchY,
266            ClipDescription desc) {
267        if (mDragInProgress && isValidDropTarget(newWin)) {
268            DragEvent event = obtainDragEvent(newWin, DragEvent.ACTION_DRAG_STARTED,
269                    touchX, touchY, null, desc, null, null, false);
270            try {
271                newWin.mClient.dispatchDragEvent(event);
272                // track each window that we've notified that the drag is starting
273                mNotifiedWindows.add(newWin);
274            } catch (RemoteException e) {
275                Slog.w(TAG_WM, "Unable to drag-start window " + newWin);
276            } finally {
277                // if the callee was local, the dispatch has already recycled the event
278                if (Process.myPid() != newWin.mSession.mPid) {
279                    event.recycle();
280                }
281            }
282        }
283    }
284
285    private boolean isValidDropTarget(WindowState targetWin) {
286        if (targetWin == null) {
287            return false;
288        }
289        if (!targetWin.isPotentialDragTarget()) {
290            return false;
291        }
292        if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) {
293            // Drag is limited to the current window.
294            if (mLocalWin != targetWin.mClient.asBinder()) {
295                return false;
296            }
297        }
298
299        return mCrossProfileCopyAllowed ||
300                mSourceUserId == UserHandle.getUserId(targetWin.getOwningUid());
301    }
302
303    /* helper - send a ACTION_DRAG_STARTED event only if the window has not
304     * previously been notified, i.e. it became visible after the drag operation
305     * was begun.  This is a rare case.
306     */
307    void sendDragStartedIfNeededLw(WindowState newWin) {
308        if (mDragInProgress) {
309            // If we have sent the drag-started, we needn't do so again
310            if (isWindowNotified(newWin)) {
311                return;
312            }
313            if (DEBUG_DRAG) {
314                Slog.d(TAG_WM, "need to send DRAG_STARTED to new window " + newWin);
315            }
316            sendDragStartedLw(newWin, mCurrentX, mCurrentY, mDataDescription);
317        }
318    }
319
320    private boolean isWindowNotified(WindowState newWin) {
321        for (WindowState ws : mNotifiedWindows) {
322            if (ws == newWin) {
323                return true;
324            }
325        }
326        return false;
327    }
328
329    private void broadcastDragEndedLw() {
330        final int myPid = Process.myPid();
331
332        if (DEBUG_DRAG) {
333            Slog.d(TAG_WM, "broadcasting DRAG_ENDED");
334        }
335        for (WindowState ws : mNotifiedWindows) {
336            float x = 0;
337            float y = 0;
338            if (!mDragResult && (ws.mSession.mPid == mPid)) {
339                // Report unconsumed drop location back to the app that started the drag.
340                x = mCurrentX;
341                y = mCurrentY;
342            }
343            DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
344                    x, y, null, null, null, null, mDragResult);
345            try {
346                ws.mClient.dispatchDragEvent(evt);
347            } catch (RemoteException e) {
348                Slog.w(TAG_WM, "Unable to drag-end window " + ws);
349            }
350            // if the current window is in the same process,
351            // the dispatch has already recycled the event
352            if (myPid != ws.mSession.mPid) {
353                evt.recycle();
354            }
355        }
356        mNotifiedWindows.clear();
357        mDragInProgress = false;
358    }
359
360    void endDragLw() {
361        if (mAnimation != null) {
362            return;
363        }
364        if (!mDragResult) {
365            mAnimation = createReturnAnimationLocked();
366            mService.scheduleAnimationLocked();
367            return;  // Will call cleanUpDragLw when the animation is done.
368        }
369        cleanUpDragLw();
370    }
371
372    void cancelDragLw() {
373        if (mAnimation != null) {
374            return;
375        }
376        mAnimation = createCancelAnimationLocked();
377        mService.scheduleAnimationLocked();
378    }
379
380    private void cleanUpDragLw() {
381        broadcastDragEndedLw();
382        if (isFromSource(InputDevice.SOURCE_MOUSE)) {
383            mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
384        }
385
386        // stop intercepting input
387        unregister();
388
389        // free our resources and drop all the object references
390        reset();
391        mService.mDragState = null;
392
393        mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
394    }
395
396    void notifyMoveLw(float x, float y) {
397        if (mAnimation != null) {
398            return;
399        }
400        mCurrentX = x;
401        mCurrentY = y;
402
403        // Move the surface to the given touch
404        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
405                TAG_WM, ">>> OPEN TRANSACTION notifyMoveLw");
406        SurfaceControl.openTransaction();
407        try {
408            mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
409            if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, "  DRAG "
410                    + mSurfaceControl + ": pos=(" +
411                    (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
412        } finally {
413            SurfaceControl.closeTransaction();
414            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
415                    TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLw");
416        }
417        notifyLocationLw(x, y);
418    }
419
420    void notifyLocationLw(float x, float y) {
421        // Tell the affected window
422        WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
423        if (touchedWin != null && !isWindowNotified(touchedWin)) {
424            // The drag point is over a window which was not notified about a drag start.
425            // Pretend it's over empty space.
426            touchedWin = null;
427        }
428
429        try {
430            final int myPid = Process.myPid();
431
432            // have we dragged over a new window?
433            if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) {
434                if (DEBUG_DRAG) {
435                    Slog.d(TAG_WM, "sending DRAG_EXITED to " + mTargetWindow);
436                }
437                // force DRAG_EXITED_EVENT if appropriate
438                DragEvent evt = obtainDragEvent(mTargetWindow, DragEvent.ACTION_DRAG_EXITED,
439                        0, 0, null, null, null, null, false);
440                mTargetWindow.mClient.dispatchDragEvent(evt);
441                if (myPid != mTargetWindow.mSession.mPid) {
442                    evt.recycle();
443                }
444            }
445            if (touchedWin != null) {
446                if (false && DEBUG_DRAG) {
447                    Slog.d(TAG_WM, "sending DRAG_LOCATION to " + touchedWin);
448                }
449                DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DRAG_LOCATION,
450                        x, y, null, null, null, null, false);
451                touchedWin.mClient.dispatchDragEvent(evt);
452                if (myPid != touchedWin.mSession.mPid) {
453                    evt.recycle();
454                }
455            }
456        } catch (RemoteException e) {
457            Slog.w(TAG_WM, "can't send drag notification to windows");
458        }
459        mTargetWindow = touchedWin;
460    }
461
462    // Find the drop target and tell it about the data.  Returns 'true' if we can immediately
463    // dispatch the global drag-ended message, 'false' if we need to wait for a
464    // result from the recipient.
465    boolean notifyDropLw(float x, float y) {
466        if (mAnimation != null) {
467            return false;
468        }
469        mCurrentX = x;
470        mCurrentY = y;
471
472        WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
473
474        if (!isWindowNotified(touchedWin)) {
475            // "drop" outside a valid window -- no recipient to apply a
476            // timeout to, and we can send the drag-ended message immediately.
477            mDragResult = false;
478            return true;
479        }
480
481        if (DEBUG_DRAG) {
482            Slog.d(TAG_WM, "sending DROP to " + touchedWin);
483        }
484
485        final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
486
487        DropPermissionsHandler dropPermissions = null;
488        if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 &&
489                (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
490            dropPermissions = new DropPermissionsHandler(
491                    mData,
492                    mUid,
493                    touchedWin.getOwningPackage(),
494                    mFlags & DRAG_FLAGS_URI_PERMISSIONS,
495                    mSourceUserId,
496                    targetUserId);
497        }
498        if (mSourceUserId != targetUserId){
499            mData.fixUris(mSourceUserId);
500        }
501        final int myPid = Process.myPid();
502        final IBinder token = touchedWin.mClient.asBinder();
503        DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y,
504                null, null, mData, dropPermissions, false);
505        try {
506            touchedWin.mClient.dispatchDragEvent(evt);
507
508            // 5 second timeout for this window to respond to the drop
509            mService.mH.removeMessages(H.DRAG_END_TIMEOUT, token);
510            Message msg = mService.mH.obtainMessage(H.DRAG_END_TIMEOUT, token);
511            mService.mH.sendMessageDelayed(msg, 5000);
512        } catch (RemoteException e) {
513            Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin);
514            return true;
515        } finally {
516            if (myPid != touchedWin.mSession.mPid) {
517                evt.recycle();
518            }
519        }
520        mToken = token;
521        return false;
522    }
523
524    private static DragEvent obtainDragEvent(WindowState win, int action,
525            float x, float y, Object localState,
526            ClipDescription description, ClipData data,
527            IDropPermissions dropPermissions,
528            boolean result) {
529        final float winX = win.translateToWindowX(x);
530        final float winY = win.translateToWindowY(y);
531        return DragEvent.obtain(action, winX, winY, localState, description, data,
532                dropPermissions, result);
533    }
534
535    boolean stepAnimationLocked(long currentTimeMs) {
536        if (mAnimation == null) {
537            return false;
538        }
539
540        mTransformation.clear();
541        if (!mAnimation.getTransformation(currentTimeMs, mTransformation)) {
542            cleanUpDragLw();
543            return false;
544        }
545
546        mTransformation.getMatrix().postTranslate(
547                mCurrentX - mThumbOffsetX, mCurrentY - mThumbOffsetY);
548        final float tmpFloats[] = mService.mTmpFloats;
549        mTransformation.getMatrix().getValues(tmpFloats);
550        mSurfaceControl.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
551        mSurfaceControl.setAlpha(mTransformation.getAlpha());
552        mSurfaceControl.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
553                tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
554        return true;
555    }
556
557    private Animation createReturnAnimationLocked() {
558        final AnimationSet set = new AnimationSet(false);
559        set.addAnimation(new TranslateAnimation(
560                0, mOriginalX - mCurrentX, 0, mOriginalY - mCurrentY));
561        set.addAnimation(new AlphaAnimation(mOriginalAlpha, mOriginalAlpha / 2));
562        set.setDuration(ANIMATION_DURATION_MS);
563        set.setInterpolator(mCubicEaseOutInterpolator);
564        set.initialize(0, 0, 0, 0);
565        set.start();  // Will start on the first call to getTransformation.
566        return set;
567    }
568
569    private Animation createCancelAnimationLocked() {
570        final AnimationSet set = new AnimationSet(false);
571        set.addAnimation(new ScaleAnimation(1, 0, 1, 0, mThumbOffsetX, mThumbOffsetY));
572        set.addAnimation(new AlphaAnimation(mOriginalAlpha, 0));
573        set.setDuration(ANIMATION_DURATION_MS);
574        set.setInterpolator(mCubicEaseOutInterpolator);
575        set.initialize(0, 0, 0, 0);
576        set.start();  // Will start on the first call to getTransformation.
577        return set;
578    }
579
580    private boolean isFromSource(int source) {
581        return (mTouchSource & source) == source;
582    }
583
584    void overridePointerIconLw(int touchSource) {
585        mTouchSource = touchSource;
586        if (isFromSource(InputDevice.SOURCE_MOUSE)) {
587            InputManager.getInstance().setPointerIconShape(PointerIcon.STYLE_GRABBING);
588        }
589    }
590}
591