[go: nahoru, domu]

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