1/ Add basic infra for global drag handling in the Shell
- Add private window flag INTERCEPT_GLOBAL_DRAG_AND_DROP (protected by
MANAGE_ACTIVITY_STACKS permission) to be considered as a part of the
normal flow for global drag and drops even if the window is not
visible. In addition, the window with the flag receives the clip
data on DRAG_STARTED and the drag surface on DROP. If the window
consumes the drop, then it will relinquish cleanup of the drag surface
from the system.
- Add MIMETYPE_APPLICATION_ACTIVITY for an app to report that they are
starting a global drag of an activity. The associated data must
include an intent with the pending intent and user to launch the
activity for.
- Add a test drag handler on the shell end to receive the drag, setup a
drag layout, and just launch the intent (for now).
Bug: 169894807
Test: atest DragDropControllerTest
Test: atest DragDropTest
Change-Id: I7f5cdca3cf515b693a8f1e507e90e22a670b5fa6
Merged-In: I7f5cdca3cf515b693a8f1e507e90e22a670b5fa6
Signed-off-by: Winson Chung <winsonc@google.com>
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 89cb8b8..4bc8a5e 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -475,4 +475,10 @@
* @return true if exists, false otherwise.
*/
public abstract boolean isPendingTopUid(int uid);
+
+ /**
+ * @return the intent for the given intent sender.
+ */
+ @Nullable
+ public abstract Intent getIntentForIntentSender(IIntentSender sender);
}
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index acc42dbc..861e437 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -399,23 +399,10 @@
*/
public static PendingIntent getActivity(Context context, int requestCode,
@NonNull Intent intent, @Flags int flags, @Nullable Bundle options) {
- String packageName = context.getPackageName();
- String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
- context.getContentResolver()) : null;
- checkFlags(flags, packageName);
- try {
- intent.migrateExtraStreamToClipData(context);
- intent.prepareToLeaveProcess(context);
- IIntentSender target =
- ActivityManager.getService().getIntentSenderWithFeature(
- ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
- context.getAttributionTag(), null, null, requestCode, new Intent[] { intent },
- resolvedType != null ? new String[] { resolvedType } : null,
- flags, options, context.getUserId());
- return target != null ? new PendingIntent(target) : null;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ // Some tests only mock Context.getUserId(), so fallback to the id Context.getUser() is null
+ final UserHandle user = context.getUser();
+ return getActivityAsUser(context, requestCode, intent, flags, options,
+ user != null ? user : UserHandle.of(context.getUserId()));
}
/**
@@ -543,24 +530,10 @@
*/
public static PendingIntent getActivities(Context context, int requestCode,
@NonNull Intent[] intents, @Flags int flags, @Nullable Bundle options) {
- String packageName = context.getPackageName();
- String[] resolvedTypes = new String[intents.length];
- for (int i=0; i<intents.length; i++) {
- intents[i].migrateExtraStreamToClipData(context);
- intents[i].prepareToLeaveProcess(context);
- resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver());
- }
- checkFlags(flags, packageName);
- try {
- IIntentSender target =
- ActivityManager.getService().getIntentSenderWithFeature(
- ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
- context.getAttributionTag(), null, null, requestCode, intents, resolvedTypes,
- flags, options, context.getUserId());
- return target != null ? new PendingIntent(target) : null;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ // Some tests only mock Context.getUserId(), so fallback to the id Context.getUser() is null
+ final UserHandle user = context.getUser();
+ return getActivitiesAsUser(context, requestCode, intents, flags, options,
+ user != null ? user : UserHandle.of(context.getUserId()));
}
/**
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index 2342165..78eb859 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -22,6 +22,7 @@
import static android.content.ContentResolver.SCHEME_FILE;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ActivityInfo;
import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.net.Uri;
@@ -201,6 +202,8 @@
final Intent mIntent;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Uri mUri;
+ // Additional activity info resolved by the system
+ ActivityInfo mActivityInfo;
/** @hide */
public Item(Item other) {
@@ -313,6 +316,22 @@
}
/**
+ * Retrieve the activity info contained in this Item.
+ * @hide
+ */
+ public ActivityInfo getActivityInfo() {
+ return mActivityInfo;
+ }
+
+ /**
+ * Updates the activity info for in this Item.
+ * @hide
+ */
+ public void setActivityInfo(ActivityInfo info) {
+ mActivityInfo = info;
+ }
+
+ /**
* Turn this item into text, regardless of the type of data it
* actually contains.
*
@@ -1129,18 +1148,9 @@
Item item = mItems.get(i);
TextUtils.writeToParcel(item.mText, dest, flags);
dest.writeString8(item.mHtmlText);
- if (item.mIntent != null) {
- dest.writeInt(1);
- item.mIntent.writeToParcel(dest, flags);
- } else {
- dest.writeInt(0);
- }
- if (item.mUri != null) {
- dest.writeInt(1);
- item.mUri.writeToParcel(dest, flags);
- } else {
- dest.writeInt(0);
- }
+ dest.writeTypedObject(item.mIntent, flags);
+ dest.writeTypedObject(item.mUri, flags);
+ dest.writeTypedObject(item.mActivityInfo, flags);
}
}
@@ -1151,14 +1161,17 @@
} else {
mIcon = null;
}
- mItems = new ArrayList<Item>();
+ mItems = new ArrayList<>();
final int N = in.readInt();
for (int i=0; i<N; i++) {
CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
String htmlText = in.readString8();
- Intent intent = in.readInt() != 0 ? Intent.CREATOR.createFromParcel(in) : null;
- Uri uri = in.readInt() != 0 ? Uri.CREATOR.createFromParcel(in) : null;
- mItems.add(new Item(text, htmlText, intent, uri));
+ Intent intent = in.readTypedObject(Intent.CREATOR);
+ Uri uri = in.readTypedObject(Uri.CREATOR);
+ ActivityInfo info = in.readTypedObject(ActivityInfo.CREATOR);
+ Item item = new Item(text, htmlText, intent, uri);
+ item.setActivityInfo(info);
+ mItems.add(item);
}
}
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index 6739138..dedfce6 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -64,6 +64,12 @@
public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
/**
+ * The MIME type for an activity.
+ * @hide
+ */
+ public static final String MIMETYPE_APPLICATION_ACTIVITY = "application/vnd.android.activity";
+
+ /**
* The MIME type for data whose type is otherwise unknown.
* <p>
* Per RFC 2046, the "application" media type is to be used for discrete
@@ -74,31 +80,22 @@
public static final String MIMETYPE_UNKNOWN = "application/octet-stream";
/**
- * The name of the extra used to define a component name when copying/dragging
- * an app icon from Launcher.
+ * The pending intent for the activity to launch.
* <p>
- * Type: String
- * </p>
- * <p>
- * Use {@link ComponentName#unflattenFromString(String)}
- * and {@link ComponentName#flattenToString()} to convert the extra value
- * to/from {@link ComponentName}.
+ * Type: PendingIntent
* </p>
* @hide
*/
- public static final String EXTRA_TARGET_COMPONENT_NAME =
- "android.content.extra.TARGET_COMPONENT_NAME";
+ public static final String EXTRA_PENDING_INTENT = "android.intent.extra.PENDING_INTENT";
/**
- * The name of the extra used to define a user serial number when copying/dragging
- * an app icon from Launcher.
+ * The activity options bundle to use when launching an activity.
* <p>
- * Type: long
+ * Type: Bundle
* </p>
* @hide
*/
- public static final String EXTRA_USER_SERIAL_NUMBER =
- "android.content.extra.USER_SERIAL_NUMBER";
+ public static final String EXTRA_ACTIVITY_OPTIONS = "android.intent.extra.ACTIVITY_OPTIONS";
final CharSequence mLabel;
diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java
index 35af0f2..9d8f7c3 100644
--- a/core/java/android/view/DragEvent.java
+++ b/core/java/android/view/DragEvent.java
@@ -140,6 +140,21 @@
boolean mDragResult;
boolean mEventHandlerWasCalled;
+ /**
+ * The drag surface containing the object being dragged. 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_DROP}.
+ */
+ private SurfaceControl mDragSurface;
+
+ /**
+ * The offsets from the touch that the surface is adjusted by as the surface is moved around the
+ * screen. Necessary for the target using the drag surface to animate it properly once it takes
+ * ownership of the drag surface after the drop.
+ */
+ private float mOffsetX;
+ private float mOffsetY;
+
private DragEvent mNext;
private RuntimeException mRecycledLocation;
private boolean mRecycled;
@@ -274,32 +289,37 @@
private DragEvent() {
}
- private void init(int action, float x, float y, ClipDescription description, ClipData data,
+ private void init(int action, float x, float y, float offsetX, float offsetY,
+ ClipDescription description, ClipData data, SurfaceControl dragSurface,
IDragAndDropPermissions dragAndDropPermissions, Object localState, boolean result) {
mAction = action;
mX = x;
mY = y;
+ mOffsetX = offsetX;
+ mOffsetY = offsetY;
mClipDescription = description;
mClipData = data;
- this.mDragAndDropPermissions = dragAndDropPermissions;
+ mDragSurface = dragSurface;
+ mDragAndDropPermissions = dragAndDropPermissions;
mLocalState = localState;
mDragResult = result;
}
static DragEvent obtain() {
- return DragEvent.obtain(0, 0f, 0f, null, null, null, null, false);
+ return DragEvent.obtain(0, 0f, 0f, 0f, 0f, null, null, null, null, null, false);
}
/** @hide */
- public static DragEvent obtain(int action, float x, float y, Object localState,
- ClipDescription description, ClipData data,
- IDragAndDropPermissions dragAndDropPermissions, boolean result) {
+ public static DragEvent obtain(int action, float x, float y, float offsetX, float offsetY,
+ 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, description, data, dragAndDropPermissions, localState,
- result);
+ ev.init(action, x, y, offsetX, offsetY, description, data, dragSurface,
+ dragAndDropPermissions, localState, result);
return ev;
}
ev = gRecyclerTop;
@@ -310,7 +330,8 @@
ev.mRecycled = false;
ev.mNext = null;
- ev.init(action, x, y, description, data, dragAndDropPermissions, localState, result);
+ ev.init(action, x, y, offsetX, offsetY, description, data, dragSurface,
+ dragAndDropPermissions, localState, result);
return ev;
}
@@ -318,9 +339,9 @@
/** @hide */
@UnsupportedAppUsage
public static DragEvent obtain(DragEvent source) {
- return obtain(source.mAction, source.mX, source.mY, source.mLocalState,
- source.mClipDescription, source.mClipData, source.mDragAndDropPermissions,
- source.mDragResult);
+ return obtain(source.mAction, source.mX, source.mY, source.mOffsetX, source.mOffsetY,
+ source.mLocalState, source.mClipDescription, source.mClipData, source.mDragSurface,
+ source.mDragAndDropPermissions, source.mDragResult);
}
/**
@@ -358,6 +379,16 @@
return mY;
}
+ /** @hide */
+ public float getOffsetX() {
+ return mOffsetX;
+ }
+
+ /** @hide */
+ public float getOffsetY() {
+ return mOffsetY;
+ }
+
/**
* Returns the {@link android.content.ClipData} object sent to the system as part of the call
* to
@@ -387,6 +418,11 @@
}
/** @hide */
+ public SurfaceControl getDragSurface() {
+ return mDragSurface;
+ }
+
+ /** @hide */
public IDragAndDropPermissions getDragAndDropPermissions() {
return mDragAndDropPermissions;
}
@@ -472,6 +508,34 @@
}
/**
+ * Returns a string that represents the symbolic name of the specified unmasked action
+ * such as "ACTION_DRAG_START", "ACTION_DRAG_END" or an equivalent numeric constant
+ * such as "35" if unknown.
+ *
+ * @param action The action.
+ * @return The symbolic name of the specified action.
+ * @see #getAction()
+ * @hide
+ */
+ public static String actionToString(int action) {
+ switch (action) {
+ case ACTION_DRAG_STARTED:
+ return "ACTION_DRAG_STARTED";
+ case ACTION_DRAG_LOCATION:
+ return "ACTION_DRAG_LOCATION";
+ case ACTION_DROP:
+ return "ACTION_DROP";
+ case ACTION_DRAG_ENDED:
+ return "ACTION_DRAG_ENDED";
+ case ACTION_DRAG_ENTERED:
+ return "ACTION_DRAG_ENTERED";
+ case ACTION_DRAG_EXITED:
+ return "ACTION_DRAG_EXITED";
+ }
+ return Integer.toString(action);
+ }
+
+ /**
* Returns a string containing a concise, human-readable representation of this DragEvent
* object.
* @return A string representation of the DragEvent object.
@@ -504,6 +568,8 @@
dest.writeInt(mAction);
dest.writeFloat(mX);
dest.writeFloat(mY);
+ dest.writeFloat(mOffsetX);
+ dest.writeFloat(mOffsetY);
dest.writeInt(mDragResult ? 1 : 0);
if (mClipData == null) {
dest.writeInt(0);
@@ -517,6 +583,12 @@
dest.writeInt(1);
mClipDescription.writeToParcel(dest, flags);
}
+ if (mDragSurface == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ mDragSurface.writeToParcel(dest, flags);
+ }
if (mDragAndDropPermissions == null) {
dest.writeInt(0);
} else {
@@ -535,6 +607,8 @@
event.mAction = in.readInt();
event.mX = in.readFloat();
event.mY = in.readFloat();
+ event.mOffsetX = in.readFloat();
+ event.mOffsetY = in.readFloat();
event.mDragResult = (in.readInt() != 0);
if (in.readInt() != 0) {
event.mClipData = ClipData.CREATOR.createFromParcel(in);
@@ -543,6 +617,9 @@
event.mClipDescription = ClipDescription.CREATOR.createFromParcel(in);
}
if (in.readInt() != 0) {
+ event.mDragSurface = SurfaceControl.CREATOR.createFromParcel(in);
+ }
+ if (in.readInt() != 0) {
event.mDragAndDropPermissions =
IDragAndDropPermissions.Stub.asInterface(in.readStrongBinder());;
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 9336872..d1d8c1a 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -69,6 +69,7 @@
import android.app.KeyguardManager;
import android.app.Presentation;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ClipData;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
@@ -2096,6 +2097,21 @@
public static final int PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME = 0x40000000;
/**
+ * Flag to indicate that we want to intercept and handle global drag and drop for all users.
+ * This flag allows a window to considered for drag events even if not visible, and will
+ * receive drags for all active users in the system.
+ *
+ * Additional data is provided to windows with this flag, including the {@link ClipData}
+ * including all items with the {@link DragEvent#ACTION_DRAG_STARTED} event, and the
+ * actual drag surface with the {@link DragEvent#ACTION_DROP} event. If the window consumes,
+ * the drop, then the cleanup of the drag surface (provided as a part of
+ * {@link DragEvent#ACTION_DROP}) will be relinquished to the window.
+ * @hide
+ */
+ @RequiresPermission(permission.MANAGE_ACTIVITY_STACKS)
+ public static final int PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP = 0x80000000;
+
+ /**
* An internal annotation for flags that can be specified to {@link #softInputMode}.
*
* @hide
@@ -2245,7 +2261,11 @@
@ViewDebug.FlagToString(
mask = PRIVATE_FLAG_TRUSTED_OVERLAY,
equals = PRIVATE_FLAG_TRUSTED_OVERLAY,
- name = "TRUSTED_OVERLAY")
+ name = "TRUSTED_OVERLAY"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP,
+ equals = PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP,
+ name = "INTERCEPT_GLOBAL_DRAG_AND_DROP")
})
@PrivateFlags
@TestApi
diff --git a/libs/WindowManager/Shell/res/layout/global_drop_target.xml b/libs/WindowManager/Shell/res/layout/global_drop_target.xml
new file mode 100644
index 0000000..0dd0b00
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/global_drop_target.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
index 3f6ca0f..f8db447 100644
--- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
+++ b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
@@ -25,18 +25,48 @@
"group": "WM_SHELL_TRANSITIONS",
"at": "com\/android\/wm\/shell\/Transitions.java"
},
+ "-1382704050": {
+ "message": "Display removed: %d",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_DRAG_AND_DROP",
+ "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
+ },
"-1340279385": {
"message": "Remove listener=%s",
"level": "VERBOSE",
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
},
+ "-1006733970": {
+ "message": "Display added: %d",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_DRAG_AND_DROP",
+ "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
+ },
+ "-1000962629": {
+ "message": "Animate bounds: from=%s to=%s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_DRAG_AND_DROP",
+ "at": "com\/android\/wm\/shell\/draganddrop\/DropOutlineDrawable.java"
+ },
"-880817403": {
"message": "Task vanished taskId=%d",
"level": "VERBOSE",
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
},
+ "-712674749": {
+ "message": "Clip description: %s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_DRAG_AND_DROP",
+ "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
+ },
+ "-710770147": {
+ "message": "Add target: %s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_DRAG_AND_DROP",
+ "at": "com\/android\/wm\/shell\/draganddrop\/DragLayout.java"
+ },
"-460572385": {
"message": "Task appeared taskId=%d",
"level": "VERBOSE",
@@ -55,6 +85,12 @@
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
},
+ "274140888": {
+ "message": "Animate alpha: from=%d to=%d",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_DRAG_AND_DROP",
+ "at": "com\/android\/wm\/shell\/draganddrop\/DropOutlineDrawable.java"
+ },
"481673835": {
"message": "addListenerForTaskId taskId=%s",
"level": "VERBOSE",
@@ -79,14 +115,41 @@
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
},
+ "1184615936": {
+ "message": "Set drop target window visibility: displayId=%d visibility=%d",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_DRAG_AND_DROP",
+ "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
+ },
+ "1481772149": {
+ "message": "Current target: %s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_DRAG_AND_DROP",
+ "at": "com\/android\/wm\/shell\/draganddrop\/DragLayout.java"
+ },
+ "1862198614": {
+ "message": "Drag event: action=%s x=%f y=%f xOffset=%f yOffset=%f",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_DRAG_AND_DROP",
+ "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
+ },
"1990759023": {
"message": "addListenerForType types=%s listener=%s",
"level": "VERBOSE",
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+ },
+ "2057038970": {
+ "message": "Display changed: %d",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_DRAG_AND_DROP",
+ "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
}
},
"groups": {
+ "WM_SHELL_DRAG_AND_DROP": {
+ "tag": "WindowManagerShell"
+ },
"WM_SHELL_TASK_ORG": {
"tag": "WindowManagerShell"
},
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 6a19083..cc3bf2a 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -22,4 +22,7 @@
<drawable name="forced_resizable_background">#59000000</drawable>
<color name="minimize_dock_shadow_start">#60000000</color>
<color name="minimize_dock_shadow_end">#00000000</color>
+
+ <!-- Background for the various drop targets when handling drag and drop. -->
+ <color name="drop_outline_background">#330000FF</color>
</resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index a9917a6..1d85e9f 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -69,4 +69,7 @@
<!-- One-Handed Mode -->
<!-- Threshold for dragging distance to enable one-handed mode -->
<dimen name="gestures_onehanded_drag_threshold">20dp</dimen>
+
+ <!-- The amount to inset the drop target regions from the edge of the display -->
+ <dimen name="drop_layout_display_margin">16dp</dimen>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java
new file mode 100644
index 0000000..4ba84223
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell;
+
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.onehanded.OneHanded;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.splitscreen.SplitScreen;
+
+import java.io.PrintWriter;
+import java.util.Optional;
+
+/**
+ * An entry point into the shell for dumping shell internal state.
+ */
+public class ShellDump {
+
+ private final Optional<SplitScreen> mSplitScreenOptional;
+ private final Optional<Pip> mPipOptional;
+ private final Optional<OneHanded> mOneHandedOptional;
+ private final ShellTaskOrganizer mShellTaskOrganizer;
+
+ public ShellDump(ShellTaskOrganizer shellTaskOrganizer,
+ Optional<SplitScreen> splitScreenOptional,
+ Optional<Pip> pipOptional,
+ Optional<OneHanded> oneHandedOptional) {
+ mShellTaskOrganizer = shellTaskOrganizer;
+ mSplitScreenOptional = splitScreenOptional;
+ mPipOptional = pipOptional;
+ mOneHandedOptional = oneHandedOptional;
+ }
+
+ public void dump(PrintWriter pw) {
+ mShellTaskOrganizer.dump(pw, "");
+ pw.println();
+ pw.println();
+ mPipOptional.ifPresent(pip -> pip.dump(pw));
+ mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw));
+ mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw));
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java
new file mode 100644
index 0000000..4269a90
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell;
+
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.splitscreen.SplitScreen;
+
+import java.util.Optional;
+
+/**
+ * An entry point into the shell for initializing shell internal state.
+ */
+public class ShellInit {
+
+ private final DisplayImeController mDisplayImeController;
+ private final DragAndDropController mDragAndDropController;
+ private final ShellTaskOrganizer mShellTaskOrganizer;
+ private final Optional<SplitScreen> mSplitScreenOptional;
+
+ public ShellInit(DisplayImeController displayImeController,
+ DragAndDropController dragAndDropController,
+ ShellTaskOrganizer shellTaskOrganizer,
+ Optional<SplitScreen> splitScreenOptional) {
+ mDisplayImeController = displayImeController;
+ mDragAndDropController = dragAndDropController;
+ mShellTaskOrganizer = shellTaskOrganizer;
+ mSplitScreenOptional = splitScreenOptional;
+ }
+
+ public void init() {
+ // Start listening for display changes
+ mDisplayImeController.startMonitorDisplays();
+ // Register the shell organizer
+ mShellTaskOrganizer.registerOrganizer();
+ // Bind the splitscreen impl to the drag drop controller
+ mDragAndDropController.setSplitScreenController(mSplitScreenOptional);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShell.java b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShell.java
deleted file mode 100644
index 273bd27..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShell.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell;
-
-/**
- * Interface for the shell.
- */
-public class WindowManagerShell {
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
index 416ada7..a3b720c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
@@ -17,12 +17,16 @@
package com.android.wm.shell.animation;
import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
/**
* Common interpolators used in wm shell library.
*/
public class Interpolators {
+
+ public static final Interpolator LINEAR = new LinearInterpolator();
+
/**
* Interpolator for alpha in animation.
*/
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
new file mode 100644
index 0000000..8f8b98b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.draganddrop;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
+import static android.view.DragEvent.ACTION_DRAG_ENDED;
+import static android.view.DragEvent.ACTION_DRAG_ENTERED;
+import static android.view.DragEvent.ACTION_DRAG_EXITED;
+import static android.view.DragEvent.ACTION_DRAG_LOCATION;
+import static android.view.DragEvent.ACTION_DRAG_STARTED;
+import static android.view.DragEvent.ACTION_DROP;
+import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.os.Binder;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.DragEvent;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.SplitScreen;
+
+import java.util.Optional;
+
+/**
+ * Handles the global drag and drop handling for the Shell.
+ */
+public class DragAndDropController implements DisplayController.OnDisplaysChangedListener,
+ View.OnDragListener {
+
+ private static final String TAG = DragAndDropController.class.getSimpleName();
+
+ private final DisplayController mDisplayController;
+ private SplitScreen mSplitScreen;
+
+ private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
+ private boolean mIsHandlingDrag;
+ private DragLayout mDragLayout;
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+ public DragAndDropController(DisplayController displayController) {
+ mDisplayController = displayController;
+ mDisplayController.addDisplayWindowListener(this);
+ }
+
+ public void setSplitScreenController(Optional<SplitScreen> splitscreen) {
+ mSplitScreen = splitscreen.orElse(null);
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Display added: %d", displayId);
+ final Context context = mDisplayController.getDisplayContext(displayId);
+ final WindowManager wm = context.getSystemService(WindowManager.class);
+
+ // TODO(b/169894807): Figure out the right layer for this, needs to be below the task bar
+ final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
+ TYPE_APPLICATION_OVERLAY,
+ FLAG_NOT_FOCUSABLE | FLAG_HARDWARE_ACCELERATED,
+ PixelFormat.TRANSLUCENT);
+ layoutParams.privateFlags |= SYSTEM_FLAG_SHOW_FOR_ALL_USERS
+ | PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP
+ | PRIVATE_FLAG_NO_MOVE_ANIMATION;
+ layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ layoutParams.setFitInsetsTypes(0);
+ layoutParams.setTitle("ShellDropTarget");
+
+ FrameLayout dropTarget = (FrameLayout) LayoutInflater.from(context).inflate(
+ R.layout.global_drop_target, null);
+ dropTarget.setOnDragListener(this);
+ dropTarget.setVisibility(View.INVISIBLE);
+ wm.addView(dropTarget, layoutParams);
+ mDisplayDropTargets.put(displayId, new PerDisplay(displayId, context, wm, dropTarget));
+ }
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Display changed: %d", displayId);
+ final PerDisplay pd = mDisplayDropTargets.get(displayId);
+ pd.dropTarget.requestApplyInsets();
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Display removed: %d", displayId);
+ final PerDisplay pd = mDisplayDropTargets.get(displayId);
+ pd.wm.removeViewImmediate(pd.dropTarget);
+ mDisplayDropTargets.remove(displayId);
+ }
+
+ @Override
+ public boolean onDrag(View target, DragEvent event) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Drag event: action=%s x=%f y=%f xOffset=%f yOffset=%f",
+ DragEvent.actionToString(event.getAction()), event.getX(), event.getY(),
+ event.getOffsetX(), event.getOffsetY());
+ final int displayId = target.getDisplay().getDisplayId();
+ final PerDisplay pd = mDisplayDropTargets.get(displayId);
+
+ if (event.getAction() == ACTION_DRAG_STARTED) {
+ final ClipDescription description = event.getClipDescription();
+ final boolean hasValidClipData = description.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY);
+ mIsHandlingDrag = hasValidClipData;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Clip description: %s",
+ getMimeTypes(description));
+ }
+
+ if (!mIsHandlingDrag) {
+ return false;
+ }
+
+ switch (event.getAction()) {
+ case ACTION_DRAG_STARTED:
+ mDragLayout = new DragLayout(pd.context,
+ mDisplayController.getDisplayLayout(displayId), mSplitScreen);
+ pd.dropTarget.addView(mDragLayout,
+ new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ setDropTargetWindowVisibility(pd, View.VISIBLE);
+ break;
+ case ACTION_DRAG_ENTERED:
+ mDragLayout.show(event);
+ break;
+ case ACTION_DRAG_LOCATION:
+ mDragLayout.update(event);
+ break;
+ case ACTION_DROP: {
+ final SurfaceControl dragSurface = event.getDragSurface();
+ final View dragLayout = mDragLayout;
+ final ClipData data = event.getClipData();
+ return mDragLayout.drop(event, dragSurface, (dropTargetBounds) -> {
+ if (dropTargetBounds != null) {
+ // TODO(b/169894807): Properly handle the drop, for now just launch it
+ if (data.getItemCount() > 0) {
+ Intent intent = data.getItemAt(0).getIntent();
+ PendingIntent pi = intent.getParcelableExtra(
+ ClipDescription.EXTRA_PENDING_INTENT);
+ try {
+ pi.send();
+ } catch (PendingIntent.CanceledException e) {
+ Slog.e(TAG, "Failed to launch activity", e);
+ }
+ }
+ }
+
+ setDropTargetWindowVisibility(pd, View.INVISIBLE);
+ pd.dropTarget.removeView(dragLayout);
+
+ // Clean up the drag surface
+ mTransaction.reparent(dragSurface, null);
+ mTransaction.apply();
+ });
+ }
+ case ACTION_DRAG_EXITED: {
+ // Either one of DROP or EXITED will happen, and when EXITED we won't consume
+ // the drag surface
+ mDragLayout.hide(event, null);
+ break;
+ }
+ case ACTION_DRAG_ENDED:
+ // TODO(b/169894807): Ensure sure it's not possible to get ENDED without DROP
+ // or EXITED
+ if (!mDragLayout.hasDropped()) {
+ final View dragLayout = mDragLayout;
+ mDragLayout.hide(event, () -> {
+ setDropTargetWindowVisibility(pd, View.INVISIBLE);
+ pd.dropTarget.removeView(dragLayout);
+ });
+ }
+ mDragLayout = null;
+ break;
+ }
+ return true;
+ }
+
+ private void setDropTargetWindowVisibility(PerDisplay pd, int visibility) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Set drop target window visibility: displayId=%d visibility=%d",
+ pd.displayId, visibility);
+ pd.dropTarget.setVisibility(visibility);
+ if (visibility == View.VISIBLE) {
+ pd.dropTarget.requestApplyInsets();
+ }
+ }
+
+ private String getMimeTypes(ClipDescription description) {
+ String mimeTypes = "";
+ for (int i = 0; i < description.getMimeTypeCount(); i++) {
+ if (i > 0) {
+ mimeTypes += ", ";
+ }
+ mimeTypes += description.getMimeType(i);
+ }
+ return mimeTypes;
+ }
+
+ private static class PerDisplay {
+ final int displayId;
+ final Context context;
+ final WindowManager wm;
+ final FrameLayout dropTarget;
+
+ PerDisplay(int dispId, Context c, WindowManager w, FrameLayout l) {
+ displayId = dispId;
+ context = c;
+ wm = w;
+ dropTarget = l;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
new file mode 100644
index 0000000..b70036b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.draganddrop;
+
+import static com.android.wm.shell.animation.Interpolators.FAST_OUT_LINEAR_IN;
+import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.wm.shell.animation.Interpolators.LINEAR;
+import static com.android.wm.shell.animation.Interpolators.LINEAR_OUT_SLOW_IN;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.DragEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.SplitScreen;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Coordinates the visible drop targets for the current drag.
+ */
+public class DragLayout extends View {
+
+ private final DisplayLayout mDisplayLayout;
+ private final SplitScreen mSplitScreen;
+
+ private final ArrayList<Target> mTargets = new ArrayList<>();
+ private Target mCurrentTarget = null;
+ private DropOutlineDrawable mDropOutline;
+ private int mDisplayMargin;
+ private Insets mInsets = Insets.NONE;
+ private boolean mHasDropped;
+
+ public DragLayout(Context context, DisplayLayout displayLayout, SplitScreen splitscreen) {
+ super(context);
+ mDisplayLayout = displayLayout;
+ mSplitScreen = splitscreen;
+ mDisplayMargin = context.getResources().getDimensionPixelSize(
+ R.dimen.drop_layout_display_margin);
+ mDropOutline = new DropOutlineDrawable(context);
+ setBackground(mDropOutline);
+ setWillNotDraw(false);
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ mInsets = insets.getInsets(Type.systemBars() | Type.displayCutout());
+ calculateDropTargets();
+ return super.onApplyWindowInsets(insets);
+ }
+
+ @Override
+ protected boolean verifyDrawable(@NonNull Drawable who) {
+ return who == mDropOutline || super.verifyDrawable(who);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mCurrentTarget != null) {
+ mDropOutline.draw(canvas);
+ }
+ }
+
+ public boolean hasDropTarget() {
+ return mCurrentTarget != null;
+ }
+
+ public boolean hasDropped() {
+ return mHasDropped;
+ }
+
+ public void show(DragEvent event) {
+ calculateDropTargets();
+ mHasDropped = false;
+ }
+
+ private void calculateDropTargets() {
+ // Calculate all the regions based on split and landscape portrait
+ // TODO: Filter based on clip data
+ final float SIDE_MARGIN_PCT = 0.3f;
+ final int w = mDisplayLayout.width();
+ final int h = mDisplayLayout.height();
+ final int iw = w - mInsets.left - mInsets.right;
+ final int ih = h - mInsets.top - mInsets.bottom;
+ final int l = mInsets.left;
+ final int t = mInsets.top;
+ final int r = mInsets.right;
+ final int b = mInsets.bottom;
+ mTargets.clear();
+
+ // Left split
+ addTarget(new Target(
+ new Rect(0, 0,
+ (int) (w * SIDE_MARGIN_PCT), h),
+ new Rect(l + mDisplayMargin, t + mDisplayMargin,
+ l + iw / 2 - mDisplayMargin, t + ih - mDisplayMargin),
+ new Rect(0, 0, w / 2, h)));
+
+ // Fullscreen
+ addTarget(new Target(
+ new Rect((int) (w * SIDE_MARGIN_PCT), 0,
+ w - (int) (w * SIDE_MARGIN_PCT), h),
+ new Rect(l + mDisplayMargin, t + mDisplayMargin,
+ l + iw - mDisplayMargin, t + ih - mDisplayMargin),
+ new Rect(0, 0, w, h)));
+
+ // Right split
+ addTarget(new Target(
+ new Rect(w - (int) (w * SIDE_MARGIN_PCT), 0,
+ w, h),
+ new Rect(l + iw / 2 + mDisplayMargin, t + mDisplayMargin,
+ l + iw - mDisplayMargin, t + ih - mDisplayMargin),
+ new Rect(w / 2, 0, w, h)));
+ }
+
+ private void addTarget(Target t) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Add target: %s", t);
+ mTargets.add(t);
+ }
+
+ public void update(DragEvent event) {
+ // Find containing region, if the same as mCurrentRegion, then skip, otherwise, animate the
+ // visibility of the current region
+ Target target = null;
+ for (int i = mTargets.size() - 1; i >= 0; i--) {
+ Target t = mTargets.get(i);
+ if (t.hitRegion.contains((int) event.getX(), (int) event.getY())) {
+ target = t;
+ break;
+ }
+ }
+ if (target != null && mCurrentTarget != target) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Current target: %s", target);
+ Interpolator boundsInterpolator = FAST_OUT_SLOW_IN;
+ if (mCurrentTarget == null) {
+ mDropOutline.startVisibilityAnimation(true, LINEAR);
+ Rect initialBounds = new Rect(target.drawRegion);
+ initialBounds.inset(mDisplayMargin, mDisplayMargin);
+ mDropOutline.setRegionBounds(initialBounds);
+ boundsInterpolator = LINEAR_OUT_SLOW_IN;
+ }
+ mDropOutline.startBoundsAnimation(target.drawRegion, boundsInterpolator);
+ mCurrentTarget = target;
+ }
+ }
+
+ public void hide(DragEvent event, Runnable hideCompleteCallback) {
+ ObjectAnimator alphaAnimator = mDropOutline.startVisibilityAnimation(false, LINEAR);
+ ObjectAnimator boundsAnimator = null;
+ if (mCurrentTarget != null) {
+ Rect finalBounds = new Rect(mCurrentTarget.drawRegion);
+ finalBounds.inset(mDisplayMargin, mDisplayMargin);
+ boundsAnimator = mDropOutline.startBoundsAnimation(finalBounds, FAST_OUT_LINEAR_IN);
+ }
+
+ if (hideCompleteCallback != null) {
+ ObjectAnimator lastAnim = boundsAnimator != null
+ ? boundsAnimator
+ : alphaAnimator;
+ lastAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ hideCompleteCallback.run();
+ }
+ });
+ }
+
+ mCurrentTarget = null;
+ }
+
+ public boolean drop(DragEvent event, SurfaceControl dragSurface,
+ Consumer<Rect> dropCompleteCallback) {
+ mHasDropped = true;
+ // TODO(b/169894807): Coordinate with dragSurface
+ final Rect dropRegion = mCurrentTarget != null
+ ? mCurrentTarget.dropTargetBounds
+ : null;
+
+ ObjectAnimator alphaAnimator = mDropOutline.startVisibilityAnimation(false, LINEAR);
+ ObjectAnimator boundsAnimator = null;
+ if (dropRegion != null) {
+ Rect finalBounds = new Rect(mCurrentTarget.drawRegion);
+ finalBounds.inset(mDisplayMargin, mDisplayMargin);
+ mDropOutline.startBoundsAnimation(finalBounds, FAST_OUT_LINEAR_IN);
+ }
+
+ if (dropCompleteCallback != null) {
+ ObjectAnimator lastAnim = boundsAnimator != null
+ ? boundsAnimator
+ : alphaAnimator;
+ lastAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ dropCompleteCallback.accept(dropRegion);
+ }
+ });
+ }
+ return dropRegion != null;
+ }
+
+ private static class Target {
+ final Rect hitRegion;
+ final Rect drawRegion;
+ final Rect dropTargetBounds;
+
+ public Target(Rect hit, Rect draw, Rect drop) {
+ hitRegion = hit;
+ drawRegion = draw;
+ dropTargetBounds = drop;
+ }
+
+ @Override
+ public String toString() {
+ return "Target {hit=" + hitRegion + " drop=" + dropTargetBounds + "}";
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java
new file mode 100644
index 0000000..08edc2f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.draganddrop;
+
+import android.animation.ObjectAnimator;
+import android.animation.RectEvaluator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.IntProperty;
+import android.util.Property;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.graphics.ColorUtils;
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.R;
+
+/**
+ * Drawable to draw the region of the
+ */
+public class DropOutlineDrawable extends Drawable {
+
+ private static final int BOUNDS_DURATION = 200;
+ private static final int ALPHA_DURATION = 135;
+
+ private final IntProperty<DropOutlineDrawable> ALPHA =
+ new IntProperty<DropOutlineDrawable>("alpha") {
+ @Override
+ public void setValue(DropOutlineDrawable d, int alpha) {
+ d.setAlpha(alpha);
+ }
+
+ @Override
+ public Integer get(DropOutlineDrawable d) {
+ return d.getAlpha();
+ }
+ };
+
+ private final Property<DropOutlineDrawable, Rect> BOUNDS =
+ new Property<DropOutlineDrawable, Rect>(Rect.class, "bounds") {
+ @Override
+ public void set(DropOutlineDrawable d, Rect bounds) {
+ d.setRegionBounds(bounds);
+ }
+
+ @Override
+ public Rect get(DropOutlineDrawable d) {
+ return d.getRegionBounds();
+ }
+ };
+
+ private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
+ private ObjectAnimator mBoundsAnimator;
+ private ObjectAnimator mAlphaAnimator;
+
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final Rect mBounds = new Rect();
+ private final float mCornerRadius;
+ private final int mMaxAlpha;
+ private int mColor;
+
+ public DropOutlineDrawable(Context context) {
+ super();
+ // TODO(b/169894807): Use corner specific radii and maybe lower radius for non-edge corners
+ mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context.getResources());
+ mColor = context.getColor(R.color.drop_outline_background);
+ mMaxAlpha = Color.alpha(mColor);
+ // Initialize as hidden
+ ALPHA.set(this, 0);
+ }
+
+ @Override
+ public void setColorFilter(@Nullable ColorFilter colorFilter) {
+ // Do nothing
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mColor = ColorUtils.setAlphaComponent(mColor, alpha);
+ mPaint.setColor(mColor);
+ invalidateSelf();
+ }
+
+ @Override
+ public int getAlpha() {
+ return Color.alpha(mColor);
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ invalidateSelf();
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas) {
+ canvas.drawRoundRect(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom,
+ mCornerRadius, mCornerRadius, mPaint);
+ }
+
+ public void setRegionBounds(Rect bounds) {
+ mBounds.set(bounds);
+ invalidateSelf();
+ }
+
+ public Rect getRegionBounds() {
+ return mBounds;
+ }
+
+ ObjectAnimator startBoundsAnimation(Rect toBounds, Interpolator interpolator) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Animate bounds: from=%s to=%s",
+ mBounds, toBounds);
+ if (mBoundsAnimator != null) {
+ mBoundsAnimator.cancel();
+ }
+ mBoundsAnimator = ObjectAnimator.ofObject(this, BOUNDS, mRectEvaluator,
+ mBounds, toBounds);
+ mBoundsAnimator.setDuration(BOUNDS_DURATION);
+ mBoundsAnimator.setInterpolator(interpolator);
+ mBoundsAnimator.start();
+ return mBoundsAnimator;
+ }
+
+ ObjectAnimator startVisibilityAnimation(boolean visible, Interpolator interpolator) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Animate alpha: from=%d to=%d",
+ Color.alpha(mColor), visible ? mMaxAlpha : 0);
+ if (mAlphaAnimator != null) {
+ mAlphaAnimator.cancel();
+ }
+ mAlphaAnimator = ObjectAnimator.ofInt(this, ALPHA, Color.alpha(mColor),
+ visible ? mMaxAlpha : 0);
+ mAlphaAnimator.setDuration(ALPHA_DURATION);
+ mAlphaAnimator.setInterpolator(interpolator);
+ mAlphaAnimator.start();
+ return mAlphaAnimator;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index f3dadfc..4f4e7da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -30,6 +30,8 @@
Consts.TAG_WM_SHELL),
WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM_SHELL),
+ WM_SHELL_DRAG_AND_DROP(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_SHELL),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index bdd0b55..187c31f 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -103,11 +103,13 @@
// components that shouldn't be run in the test environment
builder = builder.setPip(mWMComponent.getPip())
.setSplitScreen(mWMComponent.getSplitScreen())
- .setOneHanded(mWMComponent.getOneHanded());
+ .setOneHanded(mWMComponent.getOneHanded())
+ .setShellDump(mWMComponent.getShellDump());
} else {
builder = builder.setPip(Optional.ofNullable(null))
.setSplitScreen(Optional.ofNullable(null))
- .setOneHanded(Optional.ofNullable(null));
+ .setOneHanded(Optional.ofNullable(null))
+ .setShellDump(Optional.ofNullable(null));
}
mSysUIComponent = builder
.setInputConsumerController(mWMComponent.getInputConsumerController())
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index b098579..d73633e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -25,6 +25,7 @@
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.InjectionInflationController;
+import com.android.wm.shell.ShellDump;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
@@ -68,6 +69,9 @@
@BindsInstance
Builder setShellTaskOrganizer(ShellTaskOrganizer s);
+ @BindsInstance
+ Builder setShellDump(Optional<ShellDump> shellDump);
+
SysUIComponent build();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index e3bd1b2..6635286 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -18,8 +18,9 @@
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.wmshell.WMShellModule;
+import com.android.wm.shell.ShellDump;
+import com.android.wm.shell.ShellInit;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.splitscreen.SplitScreen;
@@ -47,23 +48,26 @@
* Initializes all the WMShell components before starting any of the SystemUI components.
*/
default void init() {
- // This is to prevent circular init problem by separating registration step out of its
- // constructor. And make sure the initialization of DisplayImeController won't depend on
- // specific feature anymore.
- getDisplayImeController().startMonitorDisplays();
- getShellTaskOrganizer().registerOrganizer();
+ getShellInit().init();
}
- // Required components to be initialized at start up
+ // Gets the Shell init instance
@WMSingleton
- ShellTaskOrganizer getShellTaskOrganizer();
+ ShellInit getShellInit();
+ // Gets the Shell dump instance
@WMSingleton
- DisplayImeController getDisplayImeController();
+ Optional<ShellDump> getShellDump();
+ // TODO(b/162923491): Refactor this out so Pip doesn't need to inject this
@WMSingleton
InputConsumerController getInputConsumerController();
+ // TODO(b/162923491): To be removed once Bubbles migrates over to the Shell
+
+ @WMSingleton
+ ShellTaskOrganizer getShellTaskOrganizer();
+
// TODO(b/162923491): We currently pass the instances through to SysUI, but that may change
// depending on the threading mechanism we go with
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 89ea9e2..4330659 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -64,7 +64,7 @@
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
-import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellDump;
import com.android.wm.shell.nano.WmShellTraceProto;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEvents;
@@ -106,13 +106,12 @@
private final NavigationModeController mNavigationModeController;
private final ScreenLifecycle mScreenLifecycle;
private final SysUiState mSysUiState;
- // TODO: This is only here because we need to dump state. Remove and replace with a dumper
- // interface.
- private final ShellTaskOrganizer mShellTaskOrganizer;
private final Optional<Pip> mPipOptional;
private final Optional<SplitScreen> mSplitScreenOptional;
private final Optional<OneHanded> mOneHandedOptional;
private final ProtoTracer mProtoTracer;
+ private final Optional<ShellDump> mShellDump;
+
private boolean mIsSysUiStateValid;
private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback;
private KeyguardUpdateMonitorCallback mPipKeyguardCallback;
@@ -130,8 +129,8 @@
Optional<Pip> pipOptional,
Optional<SplitScreen> splitScreenOptional,
Optional<OneHanded> oneHandedOptional,
- ShellTaskOrganizer shellTaskOrganizer,
- ProtoTracer protoTracer) {
+ ProtoTracer protoTracer,
+ Optional<ShellDump> shellDump) {
super(context);
mCommandQueue = commandQueue;
mConfigurationController = configurationController;
@@ -144,9 +143,9 @@
mPipOptional = pipOptional;
mSplitScreenOptional = splitScreenOptional;
mOneHandedOptional = oneHandedOptional;
- mShellTaskOrganizer = shellTaskOrganizer;
mProtoTracer = protoTracer;
mProtoTracer.add(this);
+ mShellDump = shellDump;
}
@Override
@@ -410,12 +409,7 @@
return;
}
// Dump WMShell stuff here if no commands were handled
- mShellTaskOrganizer.dump(pw, "");
- pw.println();
- pw.println();
- mPipOptional.ifPresent(pip -> pip.dump(pw));
- mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw));
- mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw));
+ mShellDump.ifPresent((shellDump) -> shellDump.dump(pw));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index b333f8b..ead2fc1 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -27,15 +27,20 @@
import com.android.systemui.dagger.WMSingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shared.system.InputConsumerController;
+import com.android.wm.shell.ShellDump;
+import com.android.wm.shell.ShellInit;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.AnimationThread;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PipAppOpsListener;
@@ -55,6 +60,33 @@
*/
@Module
public abstract class WMShellBaseModule {
+
+ @WMSingleton
+ @Provides
+ static ShellInit provideShellInit(DisplayImeController displayImeController,
+ DragAndDropController dragAndDropController,
+ ShellTaskOrganizer shellTaskOrganizer,
+ Optional<SplitScreen> splitScreenOptional) {
+ return new ShellInit(displayImeController,
+ dragAndDropController,
+ shellTaskOrganizer,
+ splitScreenOptional);
+ }
+
+ /**
+ * Note, this is only optional because we currently pass this to the SysUI component scope and
+ * for non-primary users, we may inject a null-optional for that dependency.
+ */
+ @WMSingleton
+ @Provides
+ static Optional<ShellDump> provideShellDump(ShellTaskOrganizer shellTaskOrganizer,
+ Optional<SplitScreen> splitScreenOptional,
+ Optional<Pip> pipOptional,
+ Optional<OneHanded> oneHandedOptional) {
+ return Optional.of(new ShellDump(shellTaskOrganizer, splitScreenOptional, pipOptional,
+ oneHandedOptional));
+ }
+
@WMSingleton
@Provides
static TransactionPool provideTransactionPool() {
@@ -70,6 +102,12 @@
@WMSingleton
@Provides
+ static DragAndDropController provideDragAndDropController(DisplayController displayController) {
+ return new DragAndDropController(displayController);
+ }
+
+ @WMSingleton
+ @Provides
static InputConsumerController provideInputConsumerController() {
return InputConsumerController.getPipInputConsumer();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index dd7f263..d0bf281 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -27,7 +27,6 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.model.SysUiState;
@@ -38,8 +37,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tracing.ProtoTracer;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.ShellDump;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedGestureHandler;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -54,7 +52,6 @@
import org.mockito.MockitoAnnotations;
import java.util.Optional;
-import java.util.concurrent.ExecutionException;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -74,9 +71,8 @@
@Mock PipTouchHandler mPipTouchHandler;
@Mock SplitScreen mSplitScreen;
@Mock OneHanded mOneHanded;
- @Mock ShellTaskOrganizer mTaskOrganizer;
@Mock ProtoTracer mProtoTracer;
- @Mock PackageManager mMockPackageManager;
+ @Mock ShellDump mShellDump;
@Before
public void setUp() {
@@ -86,7 +82,8 @@
mWMShell = new WMShell(mContext, mCommandQueue, mConfigurationController,
mInputConsumerController, mKeyguardUpdateMonitor, mTaskStackChangeListeners,
mNavigationModeController, mScreenLifecycle, mSysUiState, Optional.of(mPip),
- Optional.of(mSplitScreen), Optional.of(mOneHanded), mTaskOrganizer, mProtoTracer);
+ Optional.of(mSplitScreen), Optional.of(mOneHanded), mProtoTracer,
+ Optional.of(mShellDump));
when(mPip.getPipTouchHandler()).thenReturn(mPipTouchHandler);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 63f7d44..32d95f5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16590,6 +16590,11 @@
public boolean isPendingTopUid(int uid) {
return mPendingStartActivityUids.isPendingTopUid(uid);
}
+
+ @Override
+ public Intent getIntentForIntentSender(IIntentSender sender) {
+ return ActivityManagerService.this.getIntentForIntentSender(sender);
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 51f7d01..db51929 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1659,11 +1659,8 @@
try {
// Collect information about the target of the Intent.
- ActivityInfo aInfo = mStackSupervisor.resolveActivity(intent, resolvedType,
- 0 /* startFlags */, null /* profilerInfo */, userId,
- ActivityStarter.computeResolveFilterUid(callingUid, callingUid,
- UserHandle.USER_NULL));
- aInfo = mAmInternal.getActivityInfoForUser(aInfo, userId);
+ final ActivityInfo aInfo = resolveActivityInfoForIntent(intent, resolvedType, userId,
+ callingUid);
synchronized (mGlobalLock) {
return mStackSupervisor.canPlaceEntityOnDisplay(displayId, callingPid, callingUid,
@@ -1674,6 +1671,15 @@
}
}
+ ActivityInfo resolveActivityInfoForIntent(Intent intent, String resolvedType,
+ int userId, int callingUid) {
+ ActivityInfo aInfo = mStackSupervisor.resolveActivity(intent, resolvedType,
+ 0 /* startFlags */, null /* profilerInfo */, userId,
+ ActivityStarter.computeResolveFilterUid(callingUid, callingUid,
+ UserHandle.USER_NULL));
+ return mAmInternal.getActivityInfoForUser(aInfo, userId);
+ }
+
/**
* This is the internal entry point for handling Activity.finish().
*
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index b0ddb61..baa26e6 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -66,6 +66,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
@@ -989,6 +990,11 @@
android.Manifest.permission.INTERNAL_SYSTEM_WINDOW, callingPid, callingUid,
"DisplayPolicy");
}
+ if ((attrs.privateFlags & PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP) != 0) {
+ mContext.enforcePermission(
+ android.Manifest.permission.MANAGE_ACTIVITY_STACKS, callingPid, callingUid,
+ "DisplayPolicy");
+ }
switch (attrs.type) {
case TYPE_STATUS_BAR:
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index ec62ed4..3eac1be 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -31,10 +31,8 @@
import android.view.Display;
import android.view.IWindow;
import android.view.SurfaceControl;
-import android.view.SurfaceSession;
import android.view.View;
-import com.android.internal.util.Preconditions;
import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
import java.util.Objects;
@@ -70,26 +68,30 @@
@NonNull private AtomicReference<IDragDropCallback> mCallback = new AtomicReference<>(
new IDragDropCallback() {});
+ DragDropController(WindowManagerService service, Looper looper) {
+ mService = service;
+ mHandler = new DragHandler(service, looper);
+ }
+
boolean dragDropActiveLocked() {
return mDragState != null && !mDragState.isClosing();
}
+ boolean dragSurfaceRelinquished() {
+ return mDragState != null && mDragState.mRelinquishDragSurface;
+ }
+
void registerCallback(IDragDropCallback callback) {
Objects.requireNonNull(callback);
mCallback.set(callback);
}
- DragDropController(WindowManagerService service, Looper looper) {
- mService = service;
- mHandler = new DragHandler(service, looper);
- }
-
void sendDragStartedIfNeededLocked(WindowState window) {
mDragState.sendDragStartedIfNeededLocked(window);
}
- IBinder performDrag(SurfaceSession session, int callerPid, int callerUid, IWindow window,
- int flags, SurfaceControl surface, int touchSource, float touchX, float touchY,
+ IBinder performDrag(int callerPid, int callerUid, IWindow window, int flags,
+ SurfaceControl surface, int touchSource, float touchX, float touchY,
float thumbCenterX, float thumbCenterY, ClipData data) {
if (DEBUG_DRAG) {
Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=" +
@@ -157,6 +159,7 @@
return null;
}
+ final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
mDragState.mData = data;
mDragState.broadcastDragStartedLocked(touchX, touchY);
mDragState.overridePointerIconLocked(touchSource);
@@ -165,7 +168,6 @@
mDragState.mThumbOffsetY = thumbCenterY;
// Make the surface visible at the proper location
- final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag");
final SurfaceControl.Transaction transaction = mDragState.mTransaction;
@@ -229,6 +231,8 @@
}
mDragState.mDragResult = consumed;
+ mDragState.mRelinquishDragSurface = consumed
+ && mDragState.targetInterceptsGlobalDrag(callingWin);
mDragState.endDragLocked();
}
} finally {
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index b80ed6b..72ecf6b 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -16,7 +16,10 @@
package com.android.server.wm;
+import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.IInputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
@@ -101,6 +104,7 @@
ClipDescription mDataDescription;
int mTouchSource;
boolean mDragResult;
+ boolean mRelinquishDragSurface;
float mOriginalAlpha;
float mOriginalX, mOriginalY;
float mCurrentX, mCurrentY;
@@ -141,7 +145,7 @@
mSurfaceControl = surface;
mFlags = flags;
mLocalWin = localWin;
- mNotifiedWindows = new ArrayList<WindowState>();
+ mNotifiedWindows = new ArrayList<>();
mTransaction = service.mTransactionFactory.get();
}
@@ -211,7 +215,8 @@
y = mCurrentY;
}
DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
- x, y, null, null, null, null, mDragResult);
+ x, y, mThumbOffsetX, mThumbOffsetY, null, null, null, null, null,
+ mDragResult);
try {
ws.mClient.dispatchDragEvent(evt);
} catch (RemoteException e) {
@@ -239,7 +244,9 @@
mInputSurface = null;
}
if (mSurfaceControl != null) {
- mTransaction.reparent(mSurfaceControl, null).apply();
+ if (!mRelinquishDragSurface) {
+ mTransaction.reparent(mSurfaceControl, null).apply();
+ }
mSurfaceControl = null;
}
if (mAnimator != null && !mAnimationCompleted) {
@@ -365,7 +372,7 @@
}
mDisplayContent.forAllWindows(w -> {
- sendDragStartedLocked(w, touchX, touchY, mDataDescription);
+ sendDragStartedLocked(w, touchX, touchY, mDataDescription, mData);
}, false /* traverseTopToBottom */ );
}
@@ -378,10 +385,12 @@
* process, so it's safe for the caller to call recycle() on the event afterwards.
*/
private void sendDragStartedLocked(WindowState newWin, float touchX, float touchY,
- ClipDescription desc) {
- if (mDragInProgress && isValidDropTarget(newWin)) {
+ ClipDescription desc, ClipData data) {
+ final boolean interceptsGlobalDrag = targetInterceptsGlobalDrag(newWin);
+ if (mDragInProgress && isValidDropTarget(newWin, interceptsGlobalDrag)) {
DragEvent event = obtainDragEvent(newWin, DragEvent.ACTION_DRAG_STARTED,
- touchX, touchY, null, desc, null, null, false);
+ touchX, touchY, mThumbOffsetX, mThumbOffsetY, null, desc,
+ interceptsGlobalDrag ? data : null, null, null, false);
try {
newWin.mClient.dispatchDragEvent(event);
// track each window that we've notified that the drag is starting
@@ -397,11 +406,11 @@
}
}
- private boolean isValidDropTarget(WindowState targetWin) {
+ private boolean isValidDropTarget(WindowState targetWin, boolean interceptsGlobalDrag) {
if (targetWin == null) {
return false;
}
- if (!targetWin.isPotentialDragTarget()) {
+ if (!targetWin.isPotentialDragTarget(interceptsGlobalDrag)) {
return false;
}
if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0 || !targetWindowSupportsGlobalDrag(targetWin)) {
@@ -411,8 +420,9 @@
}
}
- return mCrossProfileCopyAllowed ||
- mSourceUserId == UserHandle.getUserId(targetWin.getOwningUid());
+ return interceptsGlobalDrag
+ || mCrossProfileCopyAllowed
+ || mSourceUserId == UserHandle.getUserId(targetWin.getOwningUid());
}
private boolean targetWindowSupportsGlobalDrag(WindowState targetWin) {
@@ -422,6 +432,13 @@
|| targetWin.mActivityRecord.mTargetSdk >= Build.VERSION_CODES.N;
}
+ /**
+ * @return whether the given window {@param targetWin} can intercept global drags.
+ */
+ public boolean targetInterceptsGlobalDrag(WindowState targetWin) {
+ return (targetWin.mAttrs.privateFlags & PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP) != 0;
+ }
+
/* helper - send a ACTION_DRAG_STARTED event only if the window has not
* previously been notified, i.e. it became visible after the drag operation
* was begun. This is a rare case.
@@ -435,7 +452,7 @@
if (DEBUG_DRAG) {
Slog.d(TAG_WM, "need to send DRAG_STARTED to new window " + newWin);
}
- sendDragStartedLocked(newWin, mCurrentX, mCurrentY, mDataDescription);
+ sendDragStartedLocked(newWin, mCurrentX, mCurrentY, mDataDescription, mData);
}
}
@@ -512,7 +529,7 @@
}
// force DRAG_EXITED_EVENT if appropriate
DragEvent evt = obtainDragEvent(mTargetWindow, DragEvent.ACTION_DRAG_EXITED,
- 0, 0, null, null, null, null, false);
+ 0, 0, 0, 0, null, null, null, null, null, false);
mTargetWindow.mClient.dispatchDragEvent(evt);
if (myPid != mTargetWindow.mSession.mPid) {
evt.recycle();
@@ -523,7 +540,7 @@
Slog.d(TAG_WM, "sending DRAG_LOCATION to " + touchedWin);
}
DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DRAG_LOCATION,
- x, y, null, null, null, null, false);
+ x, y, mThumbOffsetX, mThumbOffsetY, null, null, null, null, null, false);
touchedWin.mClient.dispatchDragEvent(evt);
if (myPid != touchedWin.mSession.mPid) {
evt.recycle();
@@ -581,7 +598,9 @@
final int myPid = Process.myPid();
final IBinder token = touchedWin.mClient.asBinder();
final DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y,
- null, null, mData, dragAndDropPermissions, false);
+ mThumbOffsetX, mThumbOffsetY, null, null, mData,
+ targetInterceptsGlobalDrag(touchedWin) ? mSurfaceControl : null,
+ dragAndDropPermissions, false);
try {
touchedWin.mClient.dispatchDragEvent(evt);
@@ -606,15 +625,14 @@
return mDragInProgress;
}
- private static DragEvent obtainDragEvent(WindowState win, int action,
- float x, float y, Object localState,
- ClipDescription description, ClipData data,
- IDragAndDropPermissions dragAndDropPermissions,
- boolean result) {
+ private static DragEvent obtainDragEvent(WindowState win, int action, float x, float y,
+ float offsetX, float offsetY, Object localState, ClipDescription description,
+ ClipData data, SurfaceControl dragSurface,
+ IDragAndDropPermissions dragAndDropPermissions, boolean result) {
final float winX = win.translateToWindowX(x);
final float winY = win.translateToWindowY(y);
- return DragEvent.obtain(action, winX, winY, localState, description, data,
- dragAndDropPermissions, result);
+ return DragEvent.obtain(action, winX, winY, offsetX, offsetY, localState, description, data,
+ dragSurface, dragAndDropPermissions, result);
}
private ValueAnimator createReturnAnimationLocked() {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 70dbf68..72edb86 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -19,6 +19,8 @@
import static android.Manifest.permission.DEVICE_POWER;
import static android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -30,7 +32,12 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.Nullable;
+import android.app.ActivityManagerInternal;
+import android.app.PendingIntent;
import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
@@ -58,8 +65,10 @@
import android.view.WindowManager;
import android.window.ClientWindowFrames;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.logging.MetricsLoggerWrapper;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.LocalServices;
import com.android.server.wm.WindowManagerService.H;
import java.io.PrintWriter;
@@ -272,15 +281,72 @@
@Override
public IBinder performDrag(IWindow window, int flags, SurfaceControl surface, int touchSource,
float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) {
+ // Validate and resolve ClipDescription data before clearing the calling identity
+ validateAndResolveDragMimeTypeExtras(data, Binder.getCallingUid());
final long ident = Binder.clearCallingIdentity();
try {
- return mDragDropController.performDrag(mSurfaceSession, mPid, mUid, window,
- flags, surface, touchSource, touchX, touchY, thumbCenterX, thumbCenterY, data);
+ return mDragDropController.performDrag(mPid, mUid, window, flags, surface, touchSource,
+ touchX, touchY, thumbCenterX, thumbCenterY, data);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
+ /**
+ * Validates the given drag data.
+ */
+ @VisibleForTesting
+ public void validateAndResolveDragMimeTypeExtras(ClipData data, int callingUid) {
+ final ClipDescription desc = data != null ? data.getDescription() : null;
+ if (desc == null) {
+ return;
+ }
+ // Ensure that only one of the app mime types are set
+ final boolean hasActivity = desc.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY);
+ int appMimeTypeCount = (hasActivity ? 1 : 0);
+ if (appMimeTypeCount == 0) {
+ return;
+ } else if (appMimeTypeCount > 1) {
+ throw new IllegalArgumentException("Can not specify more than one of activity, "
+ + "or task mime types");
+ }
+ // Ensure that data is provided and that they are intents
+ if (data.getItemCount() == 0) {
+ throw new IllegalArgumentException("Unexpected number of items (none)");
+ }
+ for (int i = 0; i < data.getItemCount(); i++) {
+ if (data.getItemAt(i).getIntent() == null) {
+ throw new IllegalArgumentException("Unexpected item, expected an intent");
+ }
+ }
+
+ if (hasActivity) {
+ long origId = Binder.clearCallingIdentity();
+ try {
+ // Resolve the activity info for each intent
+ for (int i = 0; i < data.getItemCount(); i++) {
+ final ClipData.Item item = data.getItemAt(i);
+ final Intent intent = item.getIntent();
+ final PendingIntent pi = intent.getParcelableExtra(
+ ClipDescription.EXTRA_PENDING_INTENT);
+ final UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
+ if (pi == null || user == null) {
+ throw new IllegalArgumentException("Clip data must include the pending "
+ + "intent to launch and its associated user to launch for.");
+ }
+ final Intent launchIntent = mService.mAmInternal.getIntentForIntentSender(
+ pi.getIntentSender().getTarget());
+ final ActivityInfo info = mService.mAtmService.resolveActivityInfoForIntent(
+ launchIntent, null /* resolvedType */, user.getIdentifier(),
+ callingUid);
+ item.setActivityInfo(info);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+
@Override
public void reportDropResult(IWindow window, boolean consumed) {
final long ident = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a4b4fcb..3f15ff8 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1745,8 +1745,8 @@
* a combination of the above "visible now" with the check that the
* Input Manager uses when discarding windows from input consideration.
*/
- boolean isPotentialDragTarget() {
- return isVisibleNow() && !mRemoved
+ boolean isPotentialDragTarget(boolean targetInterceptsGlobalDrag) {
+ return (targetInterceptsGlobalDrag || isVisibleNow()) && !mRemoved
&& mInputChannel != null && mInputWindowHandle != null;
}
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 4536997..64a05bb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -18,27 +18,43 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
+import static android.view.DragEvent.ACTION_DRAG_STARTED;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+import android.app.PendingIntent;
import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Intent;
import android.graphics.PixelFormat;
+import android.os.Binder;
import android.os.IBinder;
import android.os.Looper;
+import android.os.Parcelable;
import android.os.UserHandle;
import android.os.UserManagerInternal;
import android.platform.test.annotations.Presubmit;
+import android.view.DragEvent;
+import android.view.IWindowSessionCallback;
import android.view.InputChannel;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
+import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -51,6 +67,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -71,6 +88,7 @@
static class TestDragDropController extends DragDropController {
private Runnable mCloseCallback;
+ boolean mDeferDragStateClosed;
TestDragDropController(WindowManagerService service, Looper looper) {
super(service, looper);
@@ -83,6 +101,9 @@
@Override
void onDragStateClosedLocked(DragState dragState) {
+ if (mDeferDragStateClosed) {
+ return;
+ }
super.onDragStateClosedLocked(dragState);
if (mCloseCallback != null) {
mCloseCallback.run();
@@ -101,8 +122,9 @@
final Task task = createTaskInStack(stack, ownerId);
task.addChild(activity, 0);
+ // Use a new TestIWindow so we don't collect events for other windows
final WindowState window = createWindow(
- null, TYPE_BASE_APPLICATION, activity, name, ownerId, false);
+ null, TYPE_BASE_APPLICATION, activity, name, ownerId, false, new TestIWindow());
window.mInputChannel = new InputChannel();
window.mHasSurface = true;
return window;
@@ -146,12 +168,12 @@
@Test
public void testDragFlow() {
- dragFlow(0, ClipData.newPlainText("label", "Test"), 0, 0);
+ doDragAndDrop(0, ClipData.newPlainText("label", "Test"), 0, 0);
}
@Test
public void testPerformDrag_NullDataWithGrantUri() {
- dragFlow(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0);
+ doDragAndDrop(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0);
}
@Test
@@ -160,10 +182,110 @@
createDropTargetWindow("Other user's window", 1 * UserHandle.PER_USER_RANGE);
doReturn(otherUsersWindow).when(mDisplayContent).getTouchableWinAtPointLocked(10, 10);
- dragFlow(0, null, 10, 10);
+ doDragAndDrop(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 10, 10);
+ mToken = otherUsersWindow.mClient.asBinder();
}
- private void dragFlow(int flag, ClipData data, float dropX, float dropY) {
+ @Test
+ public void testPrivateInterceptGlobalDragDropFlagChecksPermission() {
+ spyOn(mWm.mContext);
+
+ DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+ WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+ attrs.privateFlags |= PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
+ policy.validateAddingWindowLw(attrs, Binder.getCallingPid(), Binder.getCallingUid());
+
+ verify(mWm.mContext).enforcePermission(
+ eq(android.Manifest.permission.MANAGE_ACTIVITY_STACKS), anyInt(), anyInt(), any());
+ }
+
+ @Test
+ public void testPrivateInterceptGlobalDragDropFlagBehaviour() {
+ 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_GLOBAL_URI_READ,
+ ClipData.newPlainText("label", "text"), () -> {
+ // Verify the start-drag event is sent for invisible windows
+ final DragEvent dragEvent = dragEvents.get(0);
+ assertTrue(dragEvent.getAction() == ACTION_DRAG_STARTED);
+
+ // Verify after consuming that the drag surface is relinquished
+ try {
+ mTarget.mDeferDragStateClosed = true;
+
+ // Verify the drop event includes the drag surface
+ mTarget.handleMotionEvent(false, 0, 0);
+ final DragEvent dropEvent = dragEvents.get(dragEvents.size() - 1);
+ assertTrue(dropEvent.getDragSurface() != null);
+
+ mTarget.reportDropResult(iwindow, true);
+ } finally {
+ mTarget.mDeferDragStateClosed = false;
+ }
+ assertTrue(mTarget.dragSurfaceRelinquished());
+ });
+ }
+
+ @Test
+ public void testValidateAppActivityArguments() {
+ final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
+ @Override
+ public void onAnimatorScaleChanged(float scale) {}
+ });
+ try {
+ session.validateAndResolveDragMimeTypeExtras(
+ createClipDataForActivity(null, null), 0);
+ fail("Expected failure without pending intent and user");
+ } catch (IllegalArgumentException e) {
+ // Expected failure
+ }
+ try {
+ session.validateAndResolveDragMimeTypeExtras(
+ createClipDataForActivity(mock(PendingIntent.class), null), 0);
+ fail("Expected failure without user");
+ } catch (IllegalArgumentException e) {
+ // Expected failure
+ }
+ try {
+ session.validateAndResolveDragMimeTypeExtras(
+ createClipDataForActivity(null, mock(UserHandle.class)), 0);
+ fail("Expected failure without pending intent");
+ } catch (IllegalArgumentException e) {
+ // Expected failure
+ }
+ }
+
+ private ClipData createClipDataForActivity(PendingIntent pi, UserHandle user) {
+ final Intent data = new Intent();
+ if (pi != null) {
+ data.putExtra(ClipDescription.EXTRA_PENDING_INTENT, (Parcelable) pi);
+ }
+ if (user != null) {
+ data.putExtra(Intent.EXTRA_USER, user);
+ }
+ final ClipData clipData = new ClipData(
+ new ClipDescription("drag", new String[] {
+ MIMETYPE_APPLICATION_ACTIVITY}),
+ new ClipData.Item(data));
+ return clipData;
+ }
+
+ private void doDragAndDrop(int flags, ClipData data, float dropX, float dropY) {
+ startDrag(flags, data, () -> {
+ mTarget.handleMotionEvent(false, dropX, dropY);
+ mToken = mWindow.mClient.asBinder();
+ });
+ }
+
+ private void startDrag(int flag, ClipData data, Runnable r) {
final SurfaceSession appSession = new SurfaceSession();
try {
final SurfaceControl surface = new SurfaceControl.Builder(appSession)
@@ -174,13 +296,10 @@
assertTrue(mWm.mInputManager.transferTouchFocus(new InputChannel(),
new InputChannel()));
- mToken = mTarget.performDrag(
- new SurfaceSession(), 0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0,
- data);
+ mToken = mTarget.performDrag(0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0, data);
assertNotNull(mToken);
- mTarget.handleMotionEvent(false, dropX, dropY);
- mToken = mWindow.mClient.asBinder();
+ r.run();
} finally {
appSession.kill();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index db1c12f..b78d6bb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -30,7 +30,12 @@
import com.android.internal.os.IResultReceiver;
+import java.util.ArrayList;
+
public class TestIWindow extends IWindow.Stub {
+
+ private ArrayList<DragEvent> mDragEvents;
+
@Override
public void executeCommand(String command, String parameters,
ParcelFileDescriptor descriptor) throws RemoteException {
@@ -85,8 +90,16 @@
public void dispatchWallpaperCommand(String action, int x, int y, int z, Bundle extras,
boolean sync) throws RemoteException {
}
+
+ public void setDragEventJournal(ArrayList<DragEvent> journal) {
+ mDragEvents = journal;
+ }
+
@Override
public void dispatchDragEvent(DragEvent event) throws RemoteException {
+ if (mDragEvents != null) {
+ mDragEvents.add(DragEvent.obtain(event));
+ }
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 6237be0..1eb7cbe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -316,6 +316,15 @@
return createWindow(null, type, activity, name);
}
+ // TODO: Move these calls to a builder?
+ WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name,
+ IWindow iwindow) {
+ final WindowToken token = createWindowToken(
+ dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type);
+ return createWindow(parent, type, token, name, 0 /* ownerId */,
+ false /* ownerCanAddInternalSystemWindow */, iwindow);
+ }
+
WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name) {
final WindowToken token = createWindowToken(
dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type);
@@ -350,8 +359,14 @@
WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
int ownerId, boolean ownerCanAddInternalSystemWindow) {
+ return createWindow(parent, type, token, name, ownerId, ownerCanAddInternalSystemWindow,
+ mIWindow);
+ }
+
+ WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
+ int ownerId, boolean ownerCanAddInternalSystemWindow, IWindow iwindow) {
return createWindow(parent, type, token, name, ownerId, UserHandle.getUserId(ownerId),
- ownerCanAddInternalSystemWindow, mWm, mMockSession, mIWindow,
+ ownerCanAddInternalSystemWindow, mWm, mMockSession, iwindow,
mSystemServicesTestRule.getPowerManagerWrapper());
}