Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 80fbf9b0 authored by Winson Chung's avatar Winson Chung Committed by Android (Google) Code Review
Browse files

Merge "Add hidden flag for SysUI to control the return drag animation"

parents 36957f4a 22e0d342
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -5058,6 +5058,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     */
    public static final int DRAG_FLAG_ACCESSIBILITY_ACTION = 1 << 10;
    /**
     * Flag indicating that the caller desires to take ownership of the drag surface for handling
     * the animation associated with an unhandled drag.  It is mainly useful if the view starting
     * a global drag changes visibility during the gesture and the default animation of animating
     * the surface back to the origin is not sufficient.
     *
     * The calling app must hold the {@link android.Manifest.permission#START_TASKS_FROM_RECENTS}
     * permission and will receive the drag surface as a part of
     * {@link action.view.DragEvent#ACTION_DRAG_ENDED} only if the drag event's
     * {@link action.view.DragEvent#getDragResult()} is {@code false}.  The caller is responsible
     * for removing the surface after its animation.
     *
     * This flag has no effect if the system decides that a cancel-drag animation does not need to
     * occur.
     * @hide
     */
    public static final int DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION = 1 << 11;
    /**
     * Vertical scroll factor cached by {@link #getVerticalScrollFactor}.
     */
+4 −4
Original line number Diff line number Diff line
@@ -81,8 +81,8 @@ class DragDropController {
        return mDragState != null && !mDragState.isClosing();
    }

    boolean dragSurfaceRelinquished() {
        return mDragState != null && mDragState.mRelinquishDragSurface;
    boolean dragSurfaceRelinquishedToDropTarget() {
        return mDragState != null && mDragState.mRelinquishDragSurfaceToDropTarget;
    }

    void registerCallback(IDragDropCallback callback) {
@@ -245,7 +245,7 @@ class DragDropController {
                }

                mDragState.mDragResult = consumed;
                mDragState.mRelinquishDragSurface = consumed
                mDragState.mRelinquishDragSurfaceToDropTarget = consumed
                        && mDragState.targetInterceptsGlobalDrag(callingWin);
                mDragState.endDragLocked();
            }
@@ -419,7 +419,7 @@ class DragDropController {
                    synchronized (mService.mGlobalLock) {
                        if (mDragState == null) {
                            Slog.wtf(TAG_WM, "mDragState unexpectedly became null while " +
                                    "plyaing animation");
                                    "playing animation");
                            return;
                        }
                        mDragState.closeLocked();
+14 −4
Original line number Diff line number Diff line
@@ -106,7 +106,7 @@ class DragState {
    ClipDescription mDataDescription;
    int mTouchSource;
    boolean mDragResult;
    boolean mRelinquishDragSurface;
    boolean mRelinquishDragSurfaceToDropTarget;
    float mOriginalAlpha;
    float mOriginalX, mOriginalY;
    float mCurrentX, mCurrentY;
@@ -214,13 +214,19 @@ class DragState {
            for (WindowState ws : mNotifiedWindows) {
                float x = 0;
                float y = 0;
                SurfaceControl dragSurface = null;
                if (!mDragResult && (ws.mSession.mPid == mPid)) {
                    // Report unconsumed drop location back to the app that started the drag.
                    x = mCurrentX;
                    y = mCurrentY;
                    if (relinquishDragSurfaceToDragSource()) {
                        // If requested (and allowed), report the drag surface back to the app
                        // starting the drag to handle the return animation
                        dragSurface = mSurfaceControl;
                    }
                }
                DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
                        x, y, mThumbOffsetX, mThumbOffsetY, null, null, null, null, null,
                        x, y, mThumbOffsetX, mThumbOffsetY, null, null, null, dragSurface, null,
                        mDragResult);
                try {
                    ws.mClient.dispatchDragEvent(event);
@@ -249,7 +255,7 @@ class DragState {
            mInputSurface = null;
        }
        if (mSurfaceControl != null) {
            if (!mRelinquishDragSurface) {
            if (!mRelinquishDragSurfaceToDropTarget && !relinquishDragSurfaceToDragSource()) {
                mTransaction.reparent(mSurfaceControl, null).apply();
            } else {
                mDragDropController.sendTimeoutMessage(MSG_REMOVE_DRAG_SURFACE_TIMEOUT,
@@ -570,7 +576,7 @@ class DragState {
            return;
        }
        if (!mDragResult) {
            if (!isAccessibilityDragDrop()) {
            if (!isAccessibilityDragDrop() && !relinquishDragSurfaceToDragSource()) {
                mAnimator = createReturnAnimationLocked();
                return;  // Will call closeLocked() when the animation is done.
            }
@@ -731,4 +737,8 @@ class DragState {
    boolean isAccessibilityDragDrop() {
        return (mFlags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) != 0;
    }

    private boolean relinquishDragSurfaceToDragSource() {
        return (mFlags & View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION) != 0;
    }
}
+21 −2
Original line number Diff line number Diff line
@@ -71,6 +71,7 @@ import android.view.InsetsState;
import android.view.InsetsVisibilities;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
import android.view.WindowManager;
import android.window.ClientWindowFrames;
import android.window.IOnBackInvokedCallback;
@@ -293,9 +294,11 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
    @Override
    public IBinder performDrag(IWindow window, int flags, SurfaceControl surface, int touchSource,
            float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) {
        final int callingUid = Binder.getCallingUid();
        final int callingPid = Binder.getCallingPid();
        // Validate and resolve ClipDescription data before clearing the calling identity
        validateAndResolveDragMimeTypeExtras(data, Binder.getCallingUid(), Binder.getCallingPid(),
                mPackageName);
        validateAndResolveDragMimeTypeExtras(data, callingUid, callingPid, mPackageName);
        validateDragFlags(flags, callingUid);
        final long ident = Binder.clearCallingIdentity();
        try {
            return mDragDropController.performDrag(mPid, mUid, window, flags, surface, touchSource,
@@ -316,6 +319,22 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
        }
    }

    /**
     * Validates the given drag flags.
     */
    @VisibleForTesting
    void validateDragFlags(int flags, int callingUid) {
        if (callingUid == Process.SYSTEM_UID) {
            throw new IllegalStateException("Need to validate before calling identify is cleared");
        }

        if ((flags & View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION) != 0) {
            if (!mCanStartTasksFromRecents) {
                throw new SecurityException("Requires START_TASKS_FROM_RECENTS permission");
            }
        }
    }

    /**
     * Validates the given drag data.
     */
+98 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.view.DragEvent.ACTION_DRAG_ENDED;
import static android.view.DragEvent.ACTION_DRAG_STARTED;
import static android.view.DragEvent.ACTION_DROP;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
@@ -145,7 +146,9 @@ public class DragDropControllerTests extends WindowTestsBase {
        final WindowState window = createWindow(
                null, TYPE_BASE_APPLICATION, activity, name, ownerId, false, new TestIWindow());
        window.mInputChannel = new InputChannel();
        window.mInputChannelToken = window.mInputChannel.getToken();
        window.mHasSurface = true;
        mWm.mWindowMap.put(window.mClient.asBinder(), window);
        mWm.mInputToWindowMap.put(window.mInputChannelToken, window);
        return window;
    }
@@ -259,7 +262,7 @@ public class DragDropControllerTests extends WindowTestsBase {
                    } finally {
                        mTarget.mDeferDragStateClosed = false;
                    }
                    assertTrue(mTarget.dragSurfaceRelinquished());
                    assertTrue(mTarget.dragSurfaceRelinquishedToDropTarget());
                });
    }

@@ -457,6 +460,100 @@ public class DragDropControllerTests extends WindowTestsBase {
        }
    }

    @Test
    public void testValidateFlags() {
        final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
            @Override
            public void onAnimatorScaleChanged(float scale) {}
        });
        try {
            session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION,
                    TEST_UID);
            fail("Expected failure without permission");
        } catch (SecurityException e) {
            // Expected failure
        }
    }

    @Test
    public void testValidateFlagsWithPermission() {
        doReturn(PERMISSION_GRANTED).when(mWm.mContext)
                .checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS));
        final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
            @Override
            public void onAnimatorScaleChanged(float scale) {}
        });
        try {
            session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION,
                    TEST_UID);
            // Expected pass
        } catch (SecurityException e) {
            fail("Expected no failure with permission");
        }
    }

    @Test
    public void testRequestSurfaceForReturnAnimationFlag_dropSuccessful() {
        WindowState otherWindow = createDropTargetWindow("App drag test window", 0);
        TestIWindow otherIWindow = (TestIWindow) otherWindow.mClient;

        // Necessary for now since DragState.sendDragStartedLocked() will recycle drag events
        // immediately after dispatching, which is a problem when using mockito arguments captor
        // because it returns and modifies the same drag event
        TestIWindow iwindow = (TestIWindow) mWindow.mClient;
        final ArrayList<DragEvent> dragEvents = new ArrayList<>();
        iwindow.setDragEventJournal(dragEvents);

        startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ
                        | View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION,
                ClipData.newPlainText("label", "text"), () -> {
                    assertTrue(dragEvents.get(0).getAction() == ACTION_DRAG_STARTED);

                    // Verify after consuming that the drag surface is relinquished
                    mTarget.reportDropWindow(otherWindow.mInputChannelToken, 0, 0);
                    mTarget.handleMotionEvent(false, 0, 0);
                    mToken = otherWindow.mClient.asBinder();
                    mTarget.reportDropResult(otherIWindow, true);

                    // Verify the DRAG_ENDED event does NOT include the drag surface
                    final DragEvent dropEvent = dragEvents.get(dragEvents.size() - 1);
                    assertTrue(dragEvents.get(dragEvents.size() - 1).getAction()
                            == ACTION_DRAG_ENDED);
                    assertTrue(dropEvent.getDragSurface() == null);
                });
    }

    @Test
    public void testRequestSurfaceForReturnAnimationFlag_dropUnsuccessful() {
        WindowState otherWindow = createDropTargetWindow("App drag test window", 0);
        TestIWindow otherIWindow = (TestIWindow) otherWindow.mClient;

        // Necessary for now since DragState.sendDragStartedLocked() will recycle drag events
        // immediately after dispatching, which is a problem when using mockito arguments captor
        // because it returns and modifies the same drag event
        TestIWindow iwindow = (TestIWindow) mWindow.mClient;
        final ArrayList<DragEvent> dragEvents = new ArrayList<>();
        iwindow.setDragEventJournal(dragEvents);

        startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ
                        | View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION,
                ClipData.newPlainText("label", "text"), () -> {
                    assertTrue(dragEvents.get(0).getAction() == ACTION_DRAG_STARTED);

                    // Verify after consuming that the drag surface is relinquished
                    mTarget.reportDropWindow(otherWindow.mInputChannelToken, 0, 0);
                    mTarget.handleMotionEvent(false, 0, 0);
                    mToken = otherWindow.mClient.asBinder();
                    mTarget.reportDropResult(otherIWindow, false);

                    // Verify the DRAG_ENDED event includes the drag surface
                    final DragEvent dropEvent = dragEvents.get(dragEvents.size() - 1);
                    assertTrue(dragEvents.get(dragEvents.size() - 1).getAction()
                            == ACTION_DRAG_ENDED);
                    assertTrue(dropEvent.getDragSurface() != null);
                });
    }

    private void doDragAndDrop(int flags, ClipData data, float dropX, float dropY) {
        startDrag(flags, data, () -> {
            mTarget.reportDropWindow(mWindow.mInputChannelToken, dropX, dropY);