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());
     }