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 
17 package com.android.server.wm;
18 
19 import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
20 import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
21 import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
22 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
23 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
24 
25 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
26 import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
27 import static com.android.server.wm.DragDropController.MSG_ANIMATION_END;
28 import static com.android.server.wm.DragDropController.MSG_DRAG_END_TIMEOUT;
29 import static com.android.server.wm.DragDropController.MSG_REMOVE_DRAG_SURFACE_TIMEOUT;
30 import static com.android.server.wm.DragDropController.MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT;
31 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
32 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
33 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
34 
35 import android.animation.Animator;
36 import android.animation.PropertyValuesHolder;
37 import android.animation.ValueAnimator;
38 import android.annotation.Nullable;
39 import android.content.ClipData;
40 import android.content.ClipDescription;
41 import android.graphics.Point;
42 import android.graphics.Rect;
43 import android.hardware.input.InputManager;
44 import android.os.Binder;
45 import android.os.Build;
46 import android.os.IBinder;
47 import android.os.Process;
48 import android.os.RemoteException;
49 import android.os.UserHandle;
50 import android.os.UserManager;
51 import android.util.Slog;
52 import android.view.Display;
53 import android.view.DragEvent;
54 import android.view.InputApplicationHandle;
55 import android.view.InputChannel;
56 import android.view.InputDevice;
57 import android.view.InputWindowHandle;
58 import android.view.PointerIcon;
59 import android.view.SurfaceControl;
60 import android.view.View;
61 import android.view.WindowManager;
62 import android.view.animation.DecelerateInterpolator;
63 import android.view.animation.Interpolator;
64 
65 import com.android.internal.protolog.common.ProtoLog;
66 import com.android.internal.view.IDragAndDropPermissions;
67 import com.android.server.LocalServices;
68 import com.android.server.pm.UserManagerInternal;
69 
70 import java.util.ArrayList;
71 
72 /**
73  * Drag/drop state
74  */
75 class DragState {
76     private static final long MIN_ANIMATION_DURATION_MS = 195;
77     private static final long MAX_ANIMATION_DURATION_MS = 375;
78 
79     private static final int DRAG_FLAGS_URI_ACCESS = View.DRAG_FLAG_GLOBAL_URI_READ |
80             View.DRAG_FLAG_GLOBAL_URI_WRITE;
81 
82     private static final int DRAG_FLAGS_URI_PERMISSIONS = DRAG_FLAGS_URI_ACCESS |
83             View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION |
84             View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION;
85 
86     // Property names for animations
87     private static final String ANIMATED_PROPERTY_X = "x";
88     private static final String ANIMATED_PROPERTY_Y = "y";
89     private static final String ANIMATED_PROPERTY_ALPHA = "alpha";
90     private static final String ANIMATED_PROPERTY_SCALE = "scale";
91 
92     final WindowManagerService mService;
93     final DragDropController mDragDropController;
94     IBinder mToken;
95     /**
96      * Do not use the variable from the out of animation thread while mAnimator is not null.
97      */
98     SurfaceControl mSurfaceControl;
99     int mFlags;
100     IBinder mLocalWin;
101     int mPid;
102     int mUid;
103     int mSourceUserId;
104     boolean mCrossProfileCopyAllowed;
105     ClipData mData;
106     ClipDescription mDataDescription;
107     int mTouchSource;
108     boolean mDragResult;
109     boolean mRelinquishDragSurface;
110     float mOriginalAlpha;
111     float mOriginalX, mOriginalY;
112     float mCurrentX, mCurrentY;
113     float mThumbOffsetX, mThumbOffsetY;
114     InputInterceptor mInputInterceptor;
115     ArrayList<WindowState> mNotifiedWindows;
116     boolean mDragInProgress;
117     /**
118      * Whether if animation is completed. Needs to be volatile to update from the animation thread
119      * without having a WM lock.
120      */
121     volatile boolean mAnimationCompleted = false;
122     /**
123      * The display on which the drag is happening. If it goes into a different display this will
124      * be updated.
125      */
126     DisplayContent mDisplayContent;
127 
128     @Nullable private ValueAnimator mAnimator;
129     private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
130     private Point mDisplaySize = new Point();
131 
132     // A surface used to catch input events for the drag-and-drop operation.
133     SurfaceControl mInputSurface;
134 
135     final SurfaceControl.Transaction mTransaction;
136 
137     private final Rect mTmpClipRect = new Rect();
138 
139     /**
140      * Whether we are finishing this drag and drop. This starts with {@code false}, and is set to
141      * {@code true} when {@link #closeLocked()} is called.
142      */
143     private boolean mIsClosing;
144 
DragState(WindowManagerService service, DragDropController controller, IBinder token, SurfaceControl surface, int flags, IBinder localWin)145     DragState(WindowManagerService service, DragDropController controller, IBinder token,
146             SurfaceControl surface, int flags, IBinder localWin) {
147         mService = service;
148         mDragDropController = controller;
149         mToken = token;
150         mSurfaceControl = surface;
151         mFlags = flags;
152         mLocalWin = localWin;
153         mNotifiedWindows = new ArrayList<>();
154         mTransaction = service.mTransactionFactory.get();
155     }
156 
isClosing()157     boolean isClosing() {
158         return mIsClosing;
159     }
160 
showInputSurface()161     private void showInputSurface() {
162         if (mInputSurface == null) {
163             mInputSurface = mService.makeSurfaceBuilder(
164                     mService.mRoot.getDisplayContent(mDisplayContent.getDisplayId()).getSession())
165                     .setContainerLayer()
166                     .setName("Drag and Drop Input Consumer")
167                     .setCallsite("DragState.showInputSurface")
168                     .build();
169         }
170         final InputWindowHandle h = getInputWindowHandle();
171         if (h == null) {
172             Slog.w(TAG_WM, "Drag is in progress but there is no "
173                     + "drag window handle.");
174             return;
175         }
176 
177         mTransaction.show(mInputSurface);
178         mTransaction.setInputWindowInfo(mInputSurface, h);
179         mTransaction.setLayer(mInputSurface, Integer.MAX_VALUE);
180 
181         mTmpClipRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
182         mTransaction.setWindowCrop(mInputSurface, mTmpClipRect);
183 
184         // syncInputWindows here to ensure the input window info is sent before the
185         // transferTouchFocus is called.
186         mTransaction.syncInputWindows();
187         mTransaction.apply(true);
188     }
189 
190     /**
191      * After calling this, DragDropController#onDragStateClosedLocked is invoked, which causes
192      * DragDropController#mDragState becomes null.
193      */
closeLocked()194     void closeLocked() {
195         mIsClosing = true;
196         // Unregister the input interceptor.
197         if (mInputInterceptor != null) {
198             if (DEBUG_DRAG)
199                 Slog.d(TAG_WM, "unregistering drag input channel");
200 
201             // Input channel should be disposed on the thread where the input is being handled.
202             mDragDropController.sendHandlerMessage(
203                     MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT, mInputInterceptor);
204             mInputInterceptor = null;
205         }
206 
207         // Send drag end broadcast if drag start has been sent.
208         if (mDragInProgress) {
209             final int myPid = Process.myPid();
210 
211             if (DEBUG_DRAG) {
212                 Slog.d(TAG_WM, "broadcasting DRAG_ENDED");
213             }
214             for (WindowState ws : mNotifiedWindows) {
215                 float x = 0;
216                 float y = 0;
217                 if (!mDragResult && (ws.mSession.mPid == mPid)) {
218                     // Report unconsumed drop location back to the app that started the drag.
219                     x = mCurrentX;
220                     y = mCurrentY;
221                 }
222                 DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
223                         x, y, mThumbOffsetX, mThumbOffsetY, null, null, null, null, null,
224                         mDragResult);
225                 try {
226                     ws.mClient.dispatchDragEvent(event);
227                 } catch (RemoteException e) {
228                     Slog.w(TAG_WM, "Unable to drag-end window " + ws);
229                 }
230                 // if the current window is in the same process,
231                 // the dispatch has already recycled the event
232                 if (myPid != ws.mSession.mPid) {
233                     event.recycle();
234                 }
235             }
236             mNotifiedWindows.clear();
237             mDragInProgress = false;
238         }
239 
240         // Take the cursor back if it has been changed.
241         if (isFromSource(InputDevice.SOURCE_MOUSE)) {
242             mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
243             mTouchSource = 0;
244         }
245 
246         // Clear the internal variables.
247         if (mInputSurface != null) {
248             mTransaction.remove(mInputSurface).apply();
249             mInputSurface = null;
250         }
251         if (mSurfaceControl != null) {
252             if (!mRelinquishDragSurface) {
253                 mTransaction.reparent(mSurfaceControl, null).apply();
254             } else {
255                 mDragDropController.sendTimeoutMessage(MSG_REMOVE_DRAG_SURFACE_TIMEOUT,
256                         mSurfaceControl, DragDropController.DRAG_TIMEOUT_MS);
257             }
258             mSurfaceControl = null;
259         }
260         if (mAnimator != null && !mAnimationCompleted) {
261             Slog.wtf(TAG_WM,
262                     "Unexpectedly destroying mSurfaceControl while animation is running");
263         }
264         mFlags = 0;
265         mLocalWin = null;
266         mToken = null;
267         mData = null;
268         mThumbOffsetX = mThumbOffsetY = 0;
269         mNotifiedWindows = null;
270 
271         // Notifies the controller that the drag state is closed.
272         mDragDropController.onDragStateClosedLocked(this);
273     }
274 
275     /**
276      * Notify the drop target and tells it about the data. If the drop event is not sent to the
277      * target, invokes {@code endDragLocked} immediately.
278      */
reportDropWindowLock(IBinder token, float x, float y)279     boolean reportDropWindowLock(IBinder token, float x, float y) {
280         if (mAnimator != null) {
281             return false;
282         }
283 
284         final WindowState touchedWin = mService.mInputToWindowMap.get(token);
285         if (!isWindowNotified(touchedWin)) {
286             // "drop" outside a valid window -- no recipient to apply a
287             // timeout to, and we can send the drag-ended message immediately.
288             mDragResult = false;
289             endDragLocked();
290             if (DEBUG_DRAG) Slog.d(TAG_WM, "Drop outside a valid window " + touchedWin);
291             return false;
292         }
293 
294         if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin);
295 
296         final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
297 
298         final DragAndDropPermissionsHandler dragAndDropPermissions;
299         if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0
300                 && mData != null) {
301             dragAndDropPermissions = new DragAndDropPermissionsHandler(mService.mGlobalLock,
302                     mData,
303                     mUid,
304                     touchedWin.getOwningPackage(),
305                     mFlags & DRAG_FLAGS_URI_PERMISSIONS,
306                     mSourceUserId,
307                     targetUserId);
308         } else {
309             dragAndDropPermissions = null;
310         }
311         if (mSourceUserId != targetUserId) {
312             if (mData != null) {
313                 mData.fixUris(mSourceUserId);
314             }
315         }
316         final int myPid = Process.myPid();
317         final IBinder clientToken = touchedWin.mClient.asBinder();
318         final DragEvent event = obtainDragEvent(DragEvent.ACTION_DROP, x, y,
319                 mData, targetInterceptsGlobalDrag(touchedWin),
320                 dragAndDropPermissions);
321         try {
322             touchedWin.mClient.dispatchDragEvent(event);
323 
324             // 5 second timeout for this window to respond to the drop
325             mDragDropController.sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, clientToken,
326                     DragDropController.DRAG_TIMEOUT_MS);
327         } catch (RemoteException e) {
328             Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin);
329             endDragLocked();
330             return false;
331         } finally {
332             if (myPid != touchedWin.mSession.mPid) {
333                 event.recycle();
334             }
335         }
336         mToken = clientToken;
337         return true;
338     }
339 
340     class InputInterceptor {
341         InputChannel mClientChannel;
342         DragInputEventReceiver mInputEventReceiver;
343         InputApplicationHandle mDragApplicationHandle;
344         InputWindowHandle mDragWindowHandle;
345 
InputInterceptor(Display display)346         InputInterceptor(Display display) {
347             mClientChannel = mService.mInputManager.createInputChannel("drag");
348             mInputEventReceiver = new DragInputEventReceiver(mClientChannel,
349                     mService.mH.getLooper(), mDragDropController);
350 
351             mDragApplicationHandle = new InputApplicationHandle(new Binder(), "drag",
352                     DEFAULT_DISPATCHING_TIMEOUT_MILLIS);
353 
354             mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle,
355                     display.getDisplayId());
356             mDragWindowHandle.name = "drag";
357             mDragWindowHandle.token = mClientChannel.getToken();
358             mDragWindowHandle.layoutParamsFlags = 0;
359             mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
360             mDragWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
361             mDragWindowHandle.visible = true;
362             // Allows the system to consume keys when dragging is active. This can also be used to
363             // modify the drag state on key press. Example, cancel drag on escape key.
364             mDragWindowHandle.focusable = true;
365             mDragWindowHandle.hasWallpaper = false;
366             mDragWindowHandle.paused = false;
367             mDragWindowHandle.ownerPid = Process.myPid();
368             mDragWindowHandle.ownerUid = Process.myUid();
369             mDragWindowHandle.inputFeatures = 0;
370             mDragWindowHandle.scaleFactor = 1.0f;
371 
372             // The drag window cannot receive new touches.
373             mDragWindowHandle.touchableRegion.setEmpty();
374 
375             // The drag window covers the entire display
376             mDragWindowHandle.frameLeft = 0;
377             mDragWindowHandle.frameTop = 0;
378             mDragWindowHandle.frameRight = mDisplaySize.x;
379             mDragWindowHandle.frameBottom = mDisplaySize.y;
380 
381             // Pause rotations before a drag.
382             ProtoLog.d(WM_DEBUG_ORIENTATION, "Pausing rotation during drag");
383             mService.mRoot.forAllDisplays(dc -> {
384                 dc.getDisplayRotation().pause();
385             });
386         }
387 
tearDown()388         void tearDown() {
389             mService.mInputManager.removeInputChannel(mClientChannel.getToken());
390             mInputEventReceiver.dispose();
391             mInputEventReceiver = null;
392             mClientChannel.dispose();
393             mClientChannel = null;
394 
395             mDragWindowHandle = null;
396             mDragApplicationHandle = null;
397 
398             // Resume rotations after a drag.
399             ProtoLog.d(WM_DEBUG_ORIENTATION, "Resuming rotation after drag");
400             mService.mRoot.forAllDisplays(dc -> {
401                 dc.getDisplayRotation().resume();
402             });
403         }
404     }
405 
getInputChannel()406     InputChannel getInputChannel() {
407         return mInputInterceptor == null ? null : mInputInterceptor.mClientChannel;
408     }
409 
getInputWindowHandle()410     InputWindowHandle getInputWindowHandle() {
411         return mInputInterceptor == null ? null : mInputInterceptor.mDragWindowHandle;
412     }
413 
414     /**
415      * @param display The Display that the window being dragged is on.
416      */
register(Display display)417     void register(Display display) {
418         display.getRealSize(mDisplaySize);
419         if (DEBUG_DRAG) Slog.d(TAG_WM, "registering drag input channel");
420         if (mInputInterceptor != null) {
421             Slog.e(TAG_WM, "Duplicate register of drag input channel");
422         } else {
423             mInputInterceptor = new InputInterceptor(display);
424             showInputSurface();
425         }
426     }
427 
428     /* call out to each visible window/session informing it about the drag
429      */
broadcastDragStartedLocked(final float touchX, final float touchY)430     void broadcastDragStartedLocked(final float touchX, final float touchY) {
431         mOriginalX = mCurrentX = touchX;
432         mOriginalY = mCurrentY = touchY;
433 
434         // Cache a base-class instance of the clip metadata so that parceling
435         // works correctly in calling out to the apps.
436         mDataDescription = (mData != null) ? mData.getDescription() : null;
437         mNotifiedWindows.clear();
438         mDragInProgress = true;
439 
440         mSourceUserId = UserHandle.getUserId(mUid);
441 
442         final UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class);
443         mCrossProfileCopyAllowed = !userManager.getUserRestriction(
444                 mSourceUserId, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
445 
446         if (DEBUG_DRAG) {
447             Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")");
448         }
449 
450         final boolean containsAppExtras = containsApplicationExtras(mDataDescription);
451         mService.mRoot.forAllWindows(w -> {
452             sendDragStartedLocked(w, touchX, touchY, containsAppExtras);
453         }, false /* traverseTopToBottom */);
454     }
455 
456     /* helper - send a ACTION_DRAG_STARTED event, if the
457      * designated window is potentially a drop recipient.  There are race situations
458      * around DRAG_ENDED broadcast, so we make sure that once we've declared that
459      * the drag has ended, we never send out another DRAG_STARTED for this drag action.
460      *
461      * This method clones the 'event' parameter if it's being delivered to the same
462      * process, so it's safe for the caller to call recycle() on the event afterwards.
463      */
sendDragStartedLocked(WindowState newWin, float touchX, float touchY, boolean containsAppExtras)464     private void sendDragStartedLocked(WindowState newWin, float touchX, float touchY,
465             boolean containsAppExtras) {
466         final boolean interceptsGlobalDrag = targetInterceptsGlobalDrag(newWin);
467         if (mDragInProgress && isValidDropTarget(newWin, containsAppExtras, interceptsGlobalDrag)) {
468             // Only allow the extras to be dispatched to a global-intercepting drag target
469             ClipData data = interceptsGlobalDrag ? mData.copyForTransferWithActivityInfo() : null;
470             DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED,
471                     newWin.translateToWindowX(touchX), newWin.translateToWindowY(touchY),
472                     data, false /* includeDragSurface */,
473                     null /* dragAndDropPermission */);
474             try {
475                 newWin.mClient.dispatchDragEvent(event);
476                 // track each window that we've notified that the drag is starting
477                 mNotifiedWindows.add(newWin);
478             } catch (RemoteException e) {
479                 Slog.w(TAG_WM, "Unable to drag-start window " + newWin);
480             } finally {
481                 // if the callee was local, the dispatch has already recycled the event
482                 if (Process.myPid() != newWin.mSession.mPid) {
483                     event.recycle();
484                 }
485             }
486         }
487     }
488 
489     /**
490      * Returns true if this is a drag of an application mime type.
491      */
containsApplicationExtras(ClipDescription desc)492     private boolean containsApplicationExtras(ClipDescription desc) {
493         if (desc == null) {
494             return false;
495         }
496         return desc.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY)
497                 || desc.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT)
498                 || desc.hasMimeType(MIMETYPE_APPLICATION_TASK);
499     }
500 
isValidDropTarget(WindowState targetWin, boolean containsAppExtras, boolean interceptsGlobalDrag)501     private boolean isValidDropTarget(WindowState targetWin, boolean containsAppExtras,
502             boolean interceptsGlobalDrag) {
503         if (targetWin == null) {
504             return false;
505         }
506         final boolean isLocalWindow = mLocalWin == targetWin.mClient.asBinder();
507         if (!isLocalWindow && !interceptsGlobalDrag && containsAppExtras) {
508             // App-drags can only go to local windows or windows that can intercept global drag, and
509             // not to other app windows
510             return false;
511         }
512         if (!targetWin.isPotentialDragTarget(interceptsGlobalDrag)) {
513             return false;
514         }
515         if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0 || !targetWindowSupportsGlobalDrag(targetWin)) {
516             // Drag is limited to the current window.
517             if (!isLocalWindow) {
518                 return false;
519             }
520         }
521 
522         return interceptsGlobalDrag
523                 || mCrossProfileCopyAllowed
524                 || mSourceUserId == UserHandle.getUserId(targetWin.getOwningUid());
525     }
526 
targetWindowSupportsGlobalDrag(WindowState targetWin)527     private boolean targetWindowSupportsGlobalDrag(WindowState targetWin) {
528         // Global drags are limited to system windows, and windows for apps that are targeting N and
529         // above.
530         return targetWin.mActivityRecord == null
531                 || targetWin.mActivityRecord.mTargetSdk >= Build.VERSION_CODES.N;
532     }
533 
534     /**
535      * @return whether the given window {@param targetWin} can intercept global drags.
536      */
targetInterceptsGlobalDrag(WindowState targetWin)537     public boolean targetInterceptsGlobalDrag(WindowState targetWin) {
538         return (targetWin.mAttrs.privateFlags & PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP) != 0;
539     }
540 
541     /* helper - send a ACTION_DRAG_STARTED event only if the window has not
542      * previously been notified, i.e. it became visible after the drag operation
543      * was begun.  This is a rare case.
544      */
sendDragStartedIfNeededLocked(WindowState newWin)545     void sendDragStartedIfNeededLocked(WindowState newWin) {
546         if (mDragInProgress) {
547             // If we have sent the drag-started, we needn't do so again
548             if (isWindowNotified(newWin)) {
549                 return;
550             }
551             if (DEBUG_DRAG) {
552                 Slog.d(TAG_WM, "need to send DRAG_STARTED to new window " + newWin);
553             }
554             sendDragStartedLocked(newWin, mCurrentX, mCurrentY,
555                     containsApplicationExtras(mDataDescription));
556         }
557     }
558 
isWindowNotified(WindowState newWin)559     boolean isWindowNotified(WindowState newWin) {
560         for (WindowState ws : mNotifiedWindows) {
561             if (ws == newWin) {
562                 return true;
563             }
564         }
565         return false;
566     }
567 
endDragLocked()568     void endDragLocked() {
569         if (mAnimator != null) {
570             return;
571         }
572         if (!mDragResult) {
573             if (!isAccessibilityDragDrop()) {
574                 mAnimator = createReturnAnimationLocked();
575                 return;  // Will call closeLocked() when the animation is done.
576             }
577         }
578         closeLocked();
579     }
580 
cancelDragLocked(boolean skipAnimation)581     void cancelDragLocked(boolean skipAnimation) {
582         if (mAnimator != null) {
583             return;
584         }
585         if (!mDragInProgress || skipAnimation || isAccessibilityDragDrop()) {
586             // mDragInProgress is false if an app invokes Session#cancelDragAndDrop before
587             // Session#performDrag. Reset the drag state without playing the cancel animation
588             // because H.DRAG_START_TIMEOUT may be sent to WindowManagerService, which will cause
589             // DragState#reset() while playing the cancel animation.
590             // skipAnimation is true when a caller requests to skip the drag cancel animation.
591             closeLocked();
592             return;
593         }
594         mAnimator = createCancelAnimationLocked();
595     }
596 
updateDragSurfaceLocked(boolean keepHandling, float x, float y)597     void updateDragSurfaceLocked(boolean keepHandling, float x, float y) {
598         if (mAnimator != null) {
599             return;
600         }
601         mCurrentX = x;
602         mCurrentY = y;
603 
604         if (!keepHandling) {
605             return;
606         }
607 
608         // Move the surface to the given touch
609         if (SHOW_LIGHT_TRANSACTIONS) {
610             Slog.i(TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked");
611         }
612         mTransaction.setPosition(mSurfaceControl, x - mThumbOffsetX, y - mThumbOffsetY).apply();
613         ProtoLog.i(WM_SHOW_TRANSACTIONS, "DRAG %s: pos=(%d,%d)", mSurfaceControl,
614                 (int) (x - mThumbOffsetX), (int) (y - mThumbOffsetY));
615     }
616 
617     /**
618      * Returns true if it has sent DRAG_STARTED broadcast out but has not been sent DRAG_END
619      * broadcast.
620      */
isInProgress()621     boolean isInProgress() {
622         return mDragInProgress;
623     }
624 
obtainDragEvent(int action, float x, float y, ClipData data, boolean includeDragSurface, IDragAndDropPermissions dragAndDropPermissions)625     private DragEvent obtainDragEvent(int action, float x, float y, ClipData data,
626             boolean includeDragSurface, IDragAndDropPermissions dragAndDropPermissions) {
627         return DragEvent.obtain(action, x, y, mThumbOffsetX, mThumbOffsetY,
628                 null  /* localState */, mDataDescription, data,
629                 includeDragSurface ? mSurfaceControl : null,
630                 dragAndDropPermissions, false /* result */);
631     }
632 
createReturnAnimationLocked()633     private ValueAnimator createReturnAnimationLocked() {
634         final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(
635                 PropertyValuesHolder.ofFloat(
636                         ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX,
637                         mOriginalX - mThumbOffsetX),
638                 PropertyValuesHolder.ofFloat(
639                         ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY,
640                         mOriginalY - mThumbOffsetY),
641                 PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, 1, 1),
642                 PropertyValuesHolder.ofFloat(
643                         ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, mOriginalAlpha / 2));
644 
645         final float translateX = mOriginalX - mCurrentX;
646         final float translateY = mOriginalY - mCurrentY;
647         // Adjust the duration to the travel distance.
648         final double travelDistance = Math.sqrt(translateX * translateX + translateY * translateY);
649         final double displayDiagonal =
650                 Math.sqrt(mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y);
651         final long duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal
652                 * (MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS));
653         final AnimationListener listener = new AnimationListener();
654         animator.setDuration(duration);
655         animator.setInterpolator(mCubicEaseOutInterpolator);
656         animator.addListener(listener);
657         animator.addUpdateListener(listener);
658 
659         mService.mAnimationHandler.post(() -> animator.start());
660         return animator;
661     }
662 
createCancelAnimationLocked()663     private ValueAnimator createCancelAnimationLocked() {
664         final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(
665                 PropertyValuesHolder.ofFloat(
666                         ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, mCurrentX),
667                 PropertyValuesHolder.ofFloat(
668                         ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, mCurrentY),
669                 PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, 1, 0),
670                 PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0));
671         final AnimationListener listener = new AnimationListener();
672         animator.setDuration(MIN_ANIMATION_DURATION_MS);
673         animator.setInterpolator(mCubicEaseOutInterpolator);
674         animator.addListener(listener);
675         animator.addUpdateListener(listener);
676 
677         mService.mAnimationHandler.post(() -> animator.start());
678         return animator;
679     }
680 
isFromSource(int source)681     private boolean isFromSource(int source) {
682         return (mTouchSource & source) == source;
683     }
684 
overridePointerIconLocked(int touchSource)685     void overridePointerIconLocked(int touchSource) {
686         mTouchSource = touchSource;
687         if (isFromSource(InputDevice.SOURCE_MOUSE)) {
688             InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_GRABBING);
689         }
690     }
691 
692     private class AnimationListener
693             implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
694         @Override
onAnimationUpdate(ValueAnimator animation)695         public void onAnimationUpdate(ValueAnimator animation) {
696             try (SurfaceControl.Transaction transaction =
697                          mService.mTransactionFactory.get()) {
698                 transaction.setPosition(
699                         mSurfaceControl,
700                         (float) animation.getAnimatedValue(ANIMATED_PROPERTY_X),
701                         (float) animation.getAnimatedValue(ANIMATED_PROPERTY_Y));
702                 transaction.setAlpha(
703                         mSurfaceControl,
704                         (float) animation.getAnimatedValue(ANIMATED_PROPERTY_ALPHA));
705                 transaction.setMatrix(
706                         mSurfaceControl,
707                         (float) animation.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0,
708                         0, (float) animation.getAnimatedValue(ANIMATED_PROPERTY_SCALE));
709                 transaction.apply();
710             }
711         }
712 
713         @Override
onAnimationStart(Animator animator)714         public void onAnimationStart(Animator animator) {}
715 
716         @Override
onAnimationCancel(Animator animator)717         public void onAnimationCancel(Animator animator) {}
718 
719         @Override
onAnimationRepeat(Animator animator)720         public void onAnimationRepeat(Animator animator) {}
721 
722         @Override
onAnimationEnd(Animator animator)723         public void onAnimationEnd(Animator animator) {
724             mAnimationCompleted = true;
725             // Updating mDragState requires the WM lock so continues it on the out of
726             // AnimationThread.
727             mDragDropController.sendHandlerMessage(MSG_ANIMATION_END, null);
728         }
729     }
730 
isAccessibilityDragDrop()731     boolean isAccessibilityDragDrop() {
732         return (mFlags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) != 0;
733     }
734 }
735