diff options
9 files changed, 112 insertions, 31 deletions
diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java index 1898407c83db..b65e3ebc3871 100644 --- a/core/java/android/view/DragEvent.java +++ b/core/java/android/view/DragEvent.java @@ -156,6 +156,13 @@ public class DragEvent implements Parcelable { private float mOffsetX; private float mOffsetY; + /** + * The View#DRAG_FLAG_* flags used to start the current drag, only provided if the target window + * has the {@link WindowManager.LayoutParams#PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP} flag + * and is only sent with {@link #ACTION_DRAG_STARTED} and {@link #ACTION_DROP}. + */ + private int mFlags; + private DragEvent mNext; private RuntimeException mRecycledLocation; private boolean mRecycled; @@ -290,7 +297,7 @@ public class DragEvent implements Parcelable { private DragEvent() { } - private void init(int action, float x, float y, float offsetX, float offsetY, + private void init(int action, float x, float y, float offsetX, float offsetY, int flags, ClipDescription description, ClipData data, SurfaceControl dragSurface, IDragAndDropPermissions dragAndDropPermissions, Object localState, boolean result) { mAction = action; @@ -298,6 +305,7 @@ public class DragEvent implements Parcelable { mY = y; mOffsetX = offsetX; mOffsetY = offsetY; + mFlags = flags; mClipDescription = description; mClipData = data; mDragSurface = dragSurface; @@ -307,19 +315,19 @@ public class DragEvent implements Parcelable { } static DragEvent obtain() { - return DragEvent.obtain(0, 0f, 0f, 0f, 0f, null, null, null, null, null, false); + return DragEvent.obtain(0, 0f, 0f, 0f, 0f, 0, null, null, null, null, null, false); } /** @hide */ public static DragEvent obtain(int action, float x, float y, float offsetX, float offsetY, - Object localState, ClipDescription description, ClipData data, + int flags, Object localState, ClipDescription description, ClipData data, SurfaceControl dragSurface, IDragAndDropPermissions dragAndDropPermissions, boolean result) { final DragEvent ev; synchronized (gRecyclerLock) { if (gRecyclerTop == null) { ev = new DragEvent(); - ev.init(action, x, y, offsetX, offsetY, description, data, dragSurface, + ev.init(action, x, y, offsetX, offsetY, flags, description, data, dragSurface, dragAndDropPermissions, localState, result); return ev; } @@ -331,7 +339,7 @@ public class DragEvent implements Parcelable { ev.mRecycled = false; ev.mNext = null; - ev.init(action, x, y, offsetX, offsetY, description, data, dragSurface, + ev.init(action, x, y, offsetX, offsetY, flags, description, data, dragSurface, dragAndDropPermissions, localState, result); return ev; @@ -341,8 +349,8 @@ public class DragEvent implements Parcelable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static DragEvent obtain(DragEvent source) { return obtain(source.mAction, source.mX, source.mY, source.mOffsetX, source.mOffsetY, - source.mLocalState, source.mClipDescription, source.mClipData, source.mDragSurface, - source.mDragAndDropPermissions, source.mDragResult); + source.mFlags, source.mLocalState, source.mClipDescription, source.mClipData, + source.mDragSurface, source.mDragAndDropPermissions, source.mDragResult); } /** @@ -424,6 +432,11 @@ public class DragEvent implements Parcelable { } /** @hide */ + public int getDragFlags() { + return mFlags; + } + + /** @hide */ public IDragAndDropPermissions getDragAndDropPermissions() { return mDragAndDropPermissions; } @@ -571,6 +584,7 @@ public class DragEvent implements Parcelable { dest.writeFloat(mY); dest.writeFloat(mOffsetX); dest.writeFloat(mOffsetY); + dest.writeInt(mFlags); dest.writeInt(mDragResult ? 1 : 0); if (mClipData == null) { dest.writeInt(0); @@ -610,6 +624,7 @@ public class DragEvent implements Parcelable { event.mY = in.readFloat(); event.mOffsetX = in.readFloat(); event.mOffsetY = in.readFloat(); + event.mFlags = in.readInt(); event.mDragResult = (in.readInt() != 0); if (in.readInt() != 0) { event.mClipData = ClipData.CREATOR.createFromParcel(in); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index db55a9d03c9f..c9161ab6f404 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -10263,7 +10263,7 @@ public final class ViewRootImpl implements ViewParent, // force DRAG_EXITED_EVENT if appropriate DragEvent event = DragEvent.obtain( isExiting ? DragEvent.ACTION_DRAG_EXITED : DragEvent.ACTION_DRAG_LOCATION, - x, y, 0 /* offsetX */, 0 /* offsetY */, null/* localState */, + x, y, 0 /* offsetX */, 0 /* offsetY */, 0 /* flags */, null/* localState */, null/* description */, null /* data */, null /* dragSurface */, null /* dragAndDropPermissions */, false /* result */); dispatchDragEvent(event); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java index 165feec58455..7e70d6a3debe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java @@ -316,7 +316,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll } // TODO(b/290391688): Also update the session data with task stack changes pd.dragSession = new DragSession(ActivityTaskManager.getInstance(), - mDisplayController.getDisplayLayout(displayId), event.getClipData()); + mDisplayController.getDisplayLayout(displayId), event.getClipData(), + event.getDragFlags()); pd.dragSession.update(); pd.activeDragCount++; pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java index 8f1bc59af1ef..0addd432aff0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java @@ -40,6 +40,7 @@ import java.util.List; public class DragSession { private final ActivityTaskManager mActivityTaskManager; private final ClipData mInitialDragData; + private final int mInitialDragFlags; final DisplayLayout displayLayout; // The activity info associated with the activity in the appData or the launchableIntent @@ -62,9 +63,10 @@ public class DragSession { boolean dragItemSupportsSplitscreen; DragSession(ActivityTaskManager activityTaskManager, - DisplayLayout dispLayout, ClipData data) { + DisplayLayout dispLayout, ClipData data, int dragFlags) { mActivityTaskManager = activityTaskManager; mInitialDragData = data; + mInitialDragFlags = dragFlags; displayLayout = dispLayout; } @@ -94,6 +96,6 @@ public class DragSession { dragItemSupportsSplitscreen = activityInfo == null || ActivityInfo.isResizeableMode(activityInfo.resizeMode); appData = mInitialDragData.getItemAt(0).getIntent(); - launchableIntent = DragUtils.getLaunchIntent(mInitialDragData); + launchableIntent = DragUtils.getLaunchIntent(mInitialDragData, mInitialDragFlags); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java index 24f8e186bf76..e215870f1894 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java @@ -24,6 +24,7 @@ import android.app.PendingIntent; import android.content.ClipData; import android.content.ClipDescription; import android.view.DragEvent; +import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -67,14 +68,18 @@ public class DragUtils { */ @Nullable public static PendingIntent getLaunchIntent(@NonNull DragEvent dragEvent) { - return getLaunchIntent(dragEvent.getClipData()); + return getLaunchIntent(dragEvent.getClipData(), dragEvent.getDragFlags()); } /** * Returns a launchable intent in the given `ClipData` or `null` if there is none. */ @Nullable - public static PendingIntent getLaunchIntent(@NonNull ClipData data) { + public static PendingIntent getLaunchIntent(@NonNull ClipData data, int dragFlags) { + if ((dragFlags & View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) == 0) { + // Disallow launching the intent if the app does not want to delegate it to the system + return null; + } for (int i = 0; i < data.getItemCount(); i++) { final ClipData.Item item = data.getItemAt(i); if (item.getIntentSender() != null) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java index 5dd9d8a859d6..6e72e8df8d62 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java @@ -66,6 +66,7 @@ import android.graphics.Insets; import android.os.RemoteException; import android.view.DisplayInfo; import android.view.DragEvent; +import android.view.View; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -115,6 +116,7 @@ public class DragAndDropPolicyTest extends ShellTestCase { private DragAndDropPolicy mPolicy; private ClipData mActivityClipData; + private PendingIntent mLaunchableIntentPendingIntent; private ClipData mLaunchableIntentClipData; private ClipData mNonResizeableActivityClipData; private ClipData mTaskClipData; @@ -151,7 +153,10 @@ public class DragAndDropPolicyTest extends ShellTestCase { mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mSplitScreenStarter)); mActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY); - mLaunchableIntentClipData = createIntentClipData(); + mLaunchableIntentPendingIntent = mock(PendingIntent.class); + when(mLaunchableIntentPendingIntent.getCreatorUserHandle()) + .thenReturn(android.os.Process.myUserHandle()); + mLaunchableIntentClipData = createIntentClipData(mLaunchableIntentPendingIntent); mNonResizeableActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY); setClipDataResizeable(mNonResizeableActivityClipData, false); mTaskClipData = createAppClipData(MIMETYPE_APPLICATION_TASK); @@ -202,16 +207,13 @@ public class DragAndDropPolicyTest extends ShellTestCase { /** * Creates an intent-based clip data that is by default resizeable. */ - private ClipData createIntentClipData() { + private ClipData createIntentClipData(PendingIntent intent) { ClipDescription clipDescription = new ClipDescription("Intent", new String[] { MIMETYPE_TEXT_INTENT }); - PendingIntent intent = mock(PendingIntent.class); - when(intent.getCreatorUserHandle()).thenReturn(android.os.Process.myUserHandle()); ClipData.Item item = new ClipData.Item.Builder() .setIntentSender(intent.getIntentSender()) .build(); ClipData data = new ClipData(clipDescription, item); - when(DragUtils.getLaunchIntent((ClipData) any())).thenReturn(intent); return data; } @@ -259,16 +261,22 @@ public class DragAndDropPolicyTest extends ShellTestCase { @Test public void testDragIntentOverFullscreenHome_expectOnlyFullscreenTarget() { + when(DragUtils.getLaunchIntent((ClipData) any(), anyInt())).thenReturn( + mLaunchableIntentPendingIntent); dragOverFullscreenHome_expectOnlyFullscreenTarget(mLaunchableIntentClipData); } @Test public void testDragIntentOverFullscreenApp_expectSplitScreenTargets() { + when(DragUtils.getLaunchIntent((ClipData) any(), anyInt())).thenReturn( + mLaunchableIntentPendingIntent); dragOverFullscreenApp_expectSplitScreenTargets(mLaunchableIntentClipData); } @Test public void testDragIntentOverFullscreenAppPhone_expectVerticalSplitScreenTargets() { + when(DragUtils.getLaunchIntent((ClipData) any(), anyInt())).thenReturn( + mLaunchableIntentPendingIntent); dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(mLaunchableIntentClipData); } @@ -276,7 +284,7 @@ public class DragAndDropPolicyTest extends ShellTestCase { doReturn(true).when(mSplitScreenStarter).isLeftRightSplit(); setRunningTask(mHomeTask); DragSession dragSession = new DragSession(mActivityTaskManager, - mLandscapeDisplayLayout, data); + mLandscapeDisplayLayout, data, 0 /* dragFlags */); dragSession.update(); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( @@ -291,7 +299,7 @@ public class DragAndDropPolicyTest extends ShellTestCase { doReturn(true).when(mSplitScreenStarter).isLeftRightSplit(); setRunningTask(mFullscreenAppTask); DragSession dragSession = new DragSession(mActivityTaskManager, - mLandscapeDisplayLayout, data); + mLandscapeDisplayLayout, data, 0 /* dragFlags */); dragSession.update(); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( @@ -311,7 +319,7 @@ public class DragAndDropPolicyTest extends ShellTestCase { doReturn(false).when(mSplitScreenStarter).isLeftRightSplit(); setRunningTask(mFullscreenAppTask); DragSession dragSession = new DragSession(mActivityTaskManager, - mPortraitDisplayLayout, data); + mPortraitDisplayLayout, data, 0 /* dragFlags */); dragSession.update(); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( @@ -331,7 +339,7 @@ public class DragAndDropPolicyTest extends ShellTestCase { public void testTargetHitRects() { setRunningTask(mFullscreenAppTask); DragSession dragSession = new DragSession(mActivityTaskManager, - mLandscapeDisplayLayout, mActivityClipData); + mLandscapeDisplayLayout, mActivityClipData, 0 /* dragFlags */); dragSession.update(); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = mPolicy.getTargets(mInsets); @@ -345,6 +353,11 @@ public class DragAndDropPolicyTest extends ShellTestCase { } } + @Test + public void testDisallowLaunchIntentWithoutDelegationFlag() { + assertTrue(DragUtils.getLaunchIntent(mLaunchableIntentClipData, 0) == null); + } + private Target filterTargetByType(ArrayList<Target> targets, int type) { for (Target t : targets) { if (type == t.type) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt index e731b06c0947..d410151b4602 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt @@ -74,7 +74,7 @@ class UnhandledDragControllerTest : ShellTestCase() { @Test fun onUnhandledDrop_noListener_expectNotifyUnhandled() { // Simulate an unhandled drop - val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null, + val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, 0, null, null, null, null, null, false) val wmCallback = mock<IUnhandledDragCallback>() mController.onUnhandledDrop(dropEvent, wmCallback) @@ -98,7 +98,7 @@ class UnhandledDragControllerTest : ShellTestCase() { // Simulate an unhandled drop val dragSurface = mock<SurfaceControl>() - val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null, + val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, 0, null, null, null, dragSurface, null, false) val wmCallback = mock<IUnhandledDragCallback>() mController.onUnhandledDrop(dropEvent, wmCallback) diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index 5ed343a4d028..bf547e09a1da 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -243,8 +243,8 @@ class DragState { dragSurface = mSurfaceControl; } } - DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, - x, y, mThumbOffsetX, mThumbOffsetY, null, null, null, dragSurface, null, + DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, x, y, + mThumbOffsetX, mThumbOffsetY, mFlags, null, null, null, dragSurface, null, mDragResult); try { if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DRAG_ENDED to " + ws); @@ -308,7 +308,7 @@ class DragState { * as a part of the dispatched event. */ private DragEvent createDropEvent(float x, float y, @Nullable WindowState touchedWin, - boolean includeDragSurface) { + boolean includePrivateInfo) { if (touchedWin != null) { final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid()); final DragAndDropPermissionsHandler dragAndDropPermissions; @@ -329,11 +329,16 @@ class DragState { mData.fixUris(mSourceUserId); } } + final boolean targetInterceptsGlobalDrag = targetInterceptsGlobalDrag(touchedWin); return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mData, - targetInterceptsGlobalDrag(touchedWin), dragAndDropPermissions); + /* includeDragSurface= */ targetInterceptsGlobalDrag, + /* includeDragFlags= */ targetInterceptsGlobalDrag, + dragAndDropPermissions); } else { return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mData, - includeDragSurface /* includeDragSurface */, null /* dragAndDropPermissions */); + /* includeDragSurface= */ includePrivateInfo, + /* includeDragFlags= */ includePrivateInfo, + null /* dragAndDropPermissions */); } } @@ -535,7 +540,7 @@ class DragState { ClipData data = interceptsGlobalDrag ? mData.copyForTransferWithActivityInfo() : null; DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED, newWin.translateToWindowX(touchX), newWin.translateToWindowY(touchY), - data, false /* includeDragSurface */, + data, false /* includeDragSurface */, true /* includeDragFlags */, null /* dragAndDropPermission */); try { newWin.mClient.dispatchDragEvent(event); @@ -706,8 +711,10 @@ class DragState { } private DragEvent obtainDragEvent(int action, float x, float y, ClipData data, - boolean includeDragSurface, IDragAndDropPermissions dragAndDropPermissions) { + boolean includeDragSurface, boolean includeDragFlags, + IDragAndDropPermissions dragAndDropPermissions) { return DragEvent.obtain(action, x, y, mThumbOffsetX, mThumbOffsetY, + includeDragFlags ? mFlags : 0, null /* localState */, mDataDescription, data, includeDragSurface ? mSurfaceControl : null, dragAndDropPermissions, false /* result */); diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java index a0461a6ea9a4..7faf2aacc0bc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java @@ -324,6 +324,44 @@ public class DragDropControllerTests extends WindowTestsBase { }); } + @Test + public void testPrivateInterceptGlobalDragDropGetsDragFlags() { + mWindow.mAttrs.privateFlags |= PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP; + mWindow.setViewVisibility(View.GONE); + + // 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_START_INTENT_SENDER_ON_UNHANDLED_DRAG, + ClipData.newPlainText("label", "text"), () -> { + // Verify the start-drag event has the drag flags + final DragEvent dragEvent = dragEvents.get(0); + assertTrue(dragEvent.getAction() == ACTION_DRAG_STARTED); + assertTrue(dragEvent.getDragFlags() == + (View.DRAG_FLAG_GLOBAL + | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG)); + + try { + mTarget.mDeferDragStateClosed = true; + mTarget.reportDropWindow(mWindow.mInputChannelToken, 0, 0); + // // Verify the drop event does not have the drag flags + mTarget.handleMotionEvent(false, 0, 0); + final DragEvent dropEvent = dragEvents.get(dragEvents.size() - 1); + assertTrue(dropEvent.getDragFlags() == + (View.DRAG_FLAG_GLOBAL + | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG)); + + mTarget.reportDropResult(iwindow, true); + } finally { + mTarget.mDeferDragStateClosed = false; + } + }); + } + private DragEvent last(ArrayList<DragEvent> list) { return list.get(list.size() - 1); } |