Merge "Add hidden flag for SysUI to control the return drag animation"
This commit is contained in:
@@ -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}.
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user