diff --git a/core/java/android/window/IGlobalDragListener.aidl b/core/java/android/window/IGlobalDragListener.aidl
index 81388559f8dc6870362d437e94e4c8f1539effa4..8f2ca02b3c09f9e875ecceb1b85fb7f59eab3882 100644
--- a/core/java/android/window/IGlobalDragListener.aidl
+++ b/core/java/android/window/IGlobalDragListener.aidl
@@ -16,6 +16,7 @@
 
 package android.window;
 
+import android.app.ActivityManager;
 import android.view.DragEvent;
 import android.window.IUnhandledDragCallback;
 
@@ -24,6 +25,12 @@ import android.window.IUnhandledDragCallback;
  * {@hide}
  */
 oneway interface IGlobalDragListener {
+    /**
+     * Called when a cross-window drag is handled by another window.
+     * @param taskInfo the task containing the window that consumed the drop
+     */
+    void onCrossWindowDrop(in ActivityManager.RunningTaskInfo taskInfo);
+
     /**
      * Called when the user finishes the drag gesture but no windows have reported handling the
      * drop.  The DragEvent is populated with the drag surface for the listener to animate.  The
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index fe65fdd30e48349ec194eb6a8aa9a7cb7443cfc7..d8d0d876b4f2dac86309a0dd1f084da32bd3e765 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -643,19 +643,6 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
         }
     }
 
-    /** Helper to set int metadata on the Surface corresponding to the task id. */
-    public void setSurfaceMetadata(int taskId, int key, int value) {
-        synchronized (mLock) {
-            final TaskAppearedInfo info = mTasks.get(taskId);
-            if (info == null || info.getLeash() == null) {
-                return;
-            }
-            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-            t.setMetadata(info.getLeash(), key, value);
-            t.apply();
-        }
-    }
-
     private boolean updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash,
             TaskListener oldListener, TaskListener newListener) {
         if (oldListener == newListener) return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 34359a523cd8974802ec84a0f734dad2507507af..aded1153d778647d0000ade6c3e54dea173764ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -577,9 +577,12 @@ public abstract class WMShellModule {
             DisplayController displayController,
             UiEventLogger uiEventLogger,
             IconProvider iconProvider,
+            GlobalDragListener globalDragListener,
+            Transitions transitions,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return new DragAndDropController(context, shellInit, shellController,
-                shellCommandHandler, displayController, uiEventLogger, iconProvider, mainExecutor);
+        return new DragAndDropController(context, shellInit, shellController, shellCommandHandler,
+                displayController, uiEventLogger, iconProvider, globalDragListener, transitions,
+                mainExecutor);
     }
 
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 269c3699ac0a083c28c32edc4b7e78376d17e70d..0fea3e3e219c0ba87f3e5aa385897ba64f544811 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -35,6 +35,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
 
+import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.content.ClipDescription;
 import android.content.ComponentCallbacks2;
@@ -51,6 +52,7 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
+import android.window.WindowContainerTransaction;
 
 import androidx.annotation.BinderThread;
 import androidx.annotation.NonNull;
@@ -71,6 +73,7 @@ import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -79,6 +82,7 @@ import java.util.ArrayList;
  * Handles the global drag and drop handling for the Shell.
  */
 public class DragAndDropController implements RemoteCallable<DragAndDropController>,
+        GlobalDragListener.GlobalDragListenerCallback,
         DisplayController.OnDisplaysChangedListener,
         View.OnDragListener, ComponentCallbacks2 {
 
@@ -90,6 +94,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
     private final DisplayController mDisplayController;
     private final DragAndDropEventLogger mLogger;
     private final IconProvider mIconProvider;
+    private final GlobalDragListener mGlobalDragListener;
+    private final Transitions mTransitions;
     private SplitScreenController mSplitScreen;
     private ShellExecutor mMainExecutor;
     private ArrayList<DragAndDropListener> mListeners = new ArrayList<>();
@@ -112,6 +118,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
             DisplayController displayController,
             UiEventLogger uiEventLogger,
             IconProvider iconProvider,
+            GlobalDragListener globalDragListener,
+            Transitions transitions,
             ShellExecutor mainExecutor) {
         mContext = context;
         mShellController = shellController;
@@ -119,6 +127,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
         mDisplayController = displayController;
         mLogger = new DragAndDropEventLogger(uiEventLogger);
         mIconProvider = iconProvider;
+        mGlobalDragListener = globalDragListener;
+        mTransitions = transitions;
         mMainExecutor = mainExecutor;
         shellInit.addInitCallback(this::onInit, this);
     }
@@ -136,6 +146,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
         mShellController.addExternalInterface(KEY_EXTRA_SHELL_DRAG_AND_DROP,
                 this::createExternalInterface, this);
         mShellCommandHandler.addDumpCallback(this::dump, this);
+        mGlobalDragListener.setListener(this);
     }
 
     private ExternalInterfaceBinder createExternalInterface() {
@@ -322,6 +333,14 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
         return true;
     }
 
+    @Override
+    public void onCrossWindowDrop(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
+        // Bring the task forward when an item is dropped on it
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.reorder(taskInfo.token, true /* onTop */);
+        mTransitions.startTransition(WindowManager.TRANSIT_TO_FRONT, wct, null);
+    }
+
     /**
      * Handles dropping on the drop target.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt
index eb352fcf1a8ff52a426e3ef863cc5e0df0639fcc..7f7cfb964e7db1628efd1c42367617173059d006 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt
@@ -15,6 +15,7 @@
  */
 package com.android.wm.shell.draganddrop
 
+import android.app.ActivityManager
 import android.os.RemoteException
 import android.util.Log
 import android.view.DragEvent
@@ -31,13 +32,19 @@ import java.util.function.Consumer
  * Manages the listener and callbacks for unhandled global drags.
  */
 class GlobalDragListener(
-    val wmService: IWindowManager,
-    mainExecutor: ShellExecutor
+    private val wmService: IWindowManager,
+    private val mainExecutor: ShellExecutor
 ) {
     private var callback: GlobalDragListenerCallback? = null
 
     private val globalDragListener: IGlobalDragListener =
         object : IGlobalDragListener.Stub() {
+            override fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {
+                mainExecutor.execute() {
+                    this@GlobalDragListener.onCrossWindowDrop(taskInfo)
+                }
+            }
+
             override fun onUnhandledDrop(event: DragEvent, callback: IUnhandledDragCallback) {
                 mainExecutor.execute() {
                     this@GlobalDragListener.onUnhandledDrop(event, callback)
@@ -49,6 +56,11 @@ class GlobalDragListener(
      * Callbacks for global drag events.
      */
     interface GlobalDragListenerCallback {
+        /**
+         * Called when a global drag is successfully handled by another window.
+         */
+        fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {}
+
         /**
          * Called when a global drag is unhandled (ie. dropped outside of all visible windows, or
          * dropped on a window that does not want to handle it).
@@ -79,12 +91,20 @@ class GlobalDragListener(
         }
     }
 
+    @VisibleForTesting
+    fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+            "onCrossWindowDrop: %s", taskInfo)
+        callback?.onCrossWindowDrop(taskInfo)
+    }
+
     @VisibleForTesting
     fun onUnhandledDrop(dragEvent: DragEvent, wmCallback: IUnhandledDragCallback) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
             "onUnhandledDrop: %s", dragEvent)
         if (callback == null) {
             wmCallback.notifyUnhandledDropComplete(false)
+            return
         }
 
         callback?.onUnhandledDrop(dragEvent) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 5e79681e060b4ea836bf7a4197936a1ff0fe4676..8a0e7977178c52496f90e0a2f89d363ee54c4a33 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -1170,7 +1170,11 @@ public class Transitions implements RemoteCallable<Transitions>,
         mPendingTransitions.add(0, active);
     }
 
-    /** Start a new transition directly. */
+    /**
+     * Start a new transition directly.
+     * @param handler if null, the transition will be dispatched to the registered set of transition
+     *                handlers to be handled
+     */
     public IBinder startTransition(@WindowManager.TransitionType int type,
             @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Directly starting a new transition "
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index 54f36f61859d5ea74d7b230b5b04c2ea49561f45..a64ebd301c002e7b4284aa36321daedf182dc73c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -45,12 +45,14 @@ import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -84,7 +86,9 @@ public class DragAndDropControllerTest extends ShellTestCase {
     @Mock
     private ShellExecutor mMainExecutor;
     @Mock
-    private WindowManager mWindowManager;
+    private Transitions mTransitions;
+    @Mock
+    private GlobalDragListener mGlobalDragListener;
 
     private DragAndDropController mController;
 
@@ -93,7 +97,7 @@ public class DragAndDropControllerTest extends ShellTestCase {
         MockitoAnnotations.initMocks(this);
         mController = new DragAndDropController(mContext, mShellInit, mShellController,
                 mShellCommandHandler, mDisplayController, mUiEventLogger, mIconProvider,
-                mMainExecutor);
+                mGlobalDragListener, mTransitions, mMainExecutor);
         mController.onInit();
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt
index 4c025984e095e5616e0922e5731db35a10049803..e731b06c0947d0280976f642877d43733fb49a04 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt
@@ -15,6 +15,7 @@
  */
 package com.android.wm.shell.draganddrop
 
+import android.app.ActivityManager.RunningTaskInfo
 import android.os.RemoteException
 import android.view.DragEvent
 import android.view.DragEvent.ACTION_DROP
@@ -32,12 +33,10 @@ import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers
-import org.mockito.Mock
 import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
 
 /**
  * Tests for the unhandled drag controller.
@@ -45,18 +44,14 @@ import org.mockito.MockitoAnnotations
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class UnhandledDragControllerTest : ShellTestCase() {
-    @Mock
-    private lateinit var mIWindowManager: IWindowManager
-
-    @Mock
-    private lateinit var mMainExecutor: ShellExecutor
+    private val mIWindowManager = mock<IWindowManager>()
+    private val mMainExecutor = mock<ShellExecutor>()
 
     private lateinit var mController: GlobalDragListener
 
     @Before
     @Throws(RemoteException::class)
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
         mController = GlobalDragListener(mIWindowManager, mMainExecutor)
     }
 
@@ -81,7 +76,7 @@ class UnhandledDragControllerTest : ShellTestCase() {
         // Simulate an unhandled drop
         val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null,
             null, null, false)
-        val wmCallback = mock(IUnhandledDragCallback::class.java)
+        val wmCallback = mock<IUnhandledDragCallback>()
         mController.onUnhandledDrop(dropEvent, wmCallback)
 
         verify(wmCallback).notifyUnhandledDropComplete(ArgumentMatchers.eq(false))
@@ -102,14 +97,31 @@ class UnhandledDragControllerTest : ShellTestCase() {
         })
 
         // Simulate an unhandled drop
-        val dragSurface = mock(SurfaceControl::class.java)
+        val dragSurface = mock<SurfaceControl>()
         val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null,
             dragSurface, null, false)
-        val wmCallback = mock(IUnhandledDragCallback::class.java)
+        val wmCallback = mock<IUnhandledDragCallback>()
         mController.onUnhandledDrop(dropEvent, wmCallback)
 
         verify(wmCallback).notifyUnhandledDropComplete(ArgumentMatchers.eq(true))
         verify(dragSurface).release()
         assertEquals(lastDragEvent.get(0), dropEvent)
     }
+
+    @Test
+    fun onCrossWindowDrop() {
+        val lastTaskInfo = arrayOfNulls<RunningTaskInfo>(1)
+
+        // Set a listener to listen for unhandled drops
+        mController.setListener(object : GlobalDragListenerCallback {
+            override fun onCrossWindowDrop(taskInfo: RunningTaskInfo) {
+                lastTaskInfo[0] = taskInfo
+            }
+        })
+
+        // Simulate a cross-window drop
+        val taskInfo = mock<RunningTaskInfo>()
+        mController.onCrossWindowDrop(taskInfo)
+        assertEquals(lastTaskInfo.get(0), taskInfo)
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index a83c25feb236397735dd4d2db4aa1fb12f98d7ee..b68e67e291e76889ce1d06501c8e682da7a13511 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -25,6 +25,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACT
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.NonNull;
+import android.app.ActivityManager;
 import android.content.ClipData;
 import android.content.Context;
 import android.hardware.input.InputManagerGlobal;
@@ -344,7 +345,20 @@ class DragDropController {
 
                 final boolean relinquishDragSurfaceToDropTarget =
                         consumed && mDragState.targetInterceptsGlobalDrag(callingWin);
+                final boolean isCrossWindowDrag = !mDragState.mLocalWin.equals(token);
                 mDragState.endDragLocked(consumed, relinquishDragSurfaceToDropTarget);
+
+                final Task droppedWindowTask = callingWin.getTask();
+                if (com.android.window.flags.Flags.delegateUnhandledDrags()
+                        && mGlobalDragListener != null && droppedWindowTask != null && consumed
+                        && isCrossWindowDrag) {
+                    try {
+                        mGlobalDragListener.onCrossWindowDrop(droppedWindowTask.getTaskInfo());
+                    } catch (RemoteException e) {
+                        Slog.e(TAG_WM, "Failed to call global drag listener for cross-window "
+                                + "drop", e);
+                    }
+                }
             }
         } finally {
             mCallback.get().postReportDropResult();
@@ -383,7 +397,7 @@ class DragDropController {
             });
             return true;
         } catch (RemoteException e) {
-            Slog.e(TAG_WM, "Failed to call unhandled drag listener", e);
+            Slog.e(TAG_WM, "Failed to call global drag listener for unhandled drop", e);
             return false;
         }
     }