diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 47e90ceaca79fec2ee5ebc4f265e338e1e565b57..36c7930c50e8644dbcabddce38e0e27837d4a409 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1162,7 +1162,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
         mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);
         mDisplaySwitchTransitionLauncher = new PhysicalDisplaySwitchTransitionLauncher(this,
                 mTransitionController);
-        mRemoteDisplayChangeController = new RemoteDisplayChangeController(mWmService, mDisplayId);
+        mRemoteDisplayChangeController = new RemoteDisplayChangeController(this);
 
         final InputChannel inputChannel = mWmService.mInputManager.monitorInput(
                 "PointerEventDispatcher" + mDisplayId, mDisplayId);
diff --git a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
index 43baebc7255aae0e67ec0f8b69f7e5b33bd50ed1..bad0c01c8f66560f06e4bcc419522344e546c6ac 100644
--- a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
+++ b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
@@ -26,6 +26,7 @@ import android.view.IDisplayChangeWindowCallback;
 import android.window.DisplayAreaInfo;
 import android.window.WindowContainerTransaction;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 
 import java.util.ArrayList;
@@ -44,16 +45,16 @@ public class RemoteDisplayChangeController {
     private static final int REMOTE_DISPLAY_CHANGE_TIMEOUT_MS = 800;
 
     private final WindowManagerService mService;
-    private final int mDisplayId;
+    private final DisplayContent mDisplayContent;
 
     private final Runnable mTimeoutRunnable = this::onContinueTimedOut;
 
     // all remote changes that haven't finished yet.
     private final List<ContinueRemoteDisplayChangeCallback> mCallbacks = new ArrayList<>();
 
-    public RemoteDisplayChangeController(WindowManagerService service, int displayId) {
-        mService = service;
-        mDisplayId = displayId;
+    RemoteDisplayChangeController(@NonNull DisplayContent displayContent) {
+        mService = displayContent.mWmService;
+        mDisplayContent = displayContent;
     }
 
     /**
@@ -99,8 +100,8 @@ public class RemoteDisplayChangeController {
         try {
             mService.mH.removeCallbacks(mTimeoutRunnable);
             mService.mH.postDelayed(mTimeoutRunnable, REMOTE_DISPLAY_CHANGE_TIMEOUT_MS);
-            mService.mDisplayChangeController.onDisplayChange(mDisplayId, fromRotation, toRotation,
-                    newDisplayAreaInfo, remoteCallback);
+            mService.mDisplayChangeController.onDisplayChange(mDisplayContent.mDisplayId,
+                    fromRotation, toRotation, newDisplayAreaInfo, remoteCallback);
             return true;
         } catch (RemoteException e) {
             Slog.e(TAG, "Exception while dispatching remote display-change", e);
@@ -117,10 +118,23 @@ public class RemoteDisplayChangeController {
                 mCallbacks.get(i).onContinueRemoteDisplayChange(null /* transaction */);
             }
             mCallbacks.clear();
+            onCompleted();
         }
     }
 
-    private void continueDisplayChange(@NonNull ContinueRemoteDisplayChangeCallback callback,
+    /** Called when all remote callbacks are done. */
+    private void onCompleted() {
+        // Because DisplayContent#sendNewConfiguration() will be skipped if there are pending remote
+        // changes, check again when all remote callbacks are done. E.g. callback X is done but
+        // there is a pending callback Y so its invocation is skipped, and when the callback Y is
+        // done, it doesn't call sendNewConfiguration().
+        if (mDisplayContent.mWaitingForConfig) {
+            mDisplayContent.sendNewConfiguration();
+        }
+    }
+
+    @VisibleForTesting
+    void continueDisplayChange(@NonNull ContinueRemoteDisplayChangeCallback callback,
             @Nullable WindowContainerTransaction transaction) {
         synchronized (mService.mGlobalLock) {
             int idx = mCallbacks.indexOf(callback);
@@ -133,11 +147,16 @@ public class RemoteDisplayChangeController {
                 // ordering by continuing everything up until this one with empty transactions.
                 mCallbacks.get(i).onContinueRemoteDisplayChange(null /* transaction */);
             }
+            // The "toIndex" is exclusive, so it needs +1 to clear the current calling callback.
             mCallbacks.subList(0, idx + 1).clear();
-            if (mCallbacks.isEmpty()) {
+            final boolean completed = mCallbacks.isEmpty();
+            if (completed) {
                 mService.mH.removeCallbacks(mTimeoutRunnable);
             }
             callback.onContinueRemoteDisplayChange(transaction);
+            if (completed) {
+                onCompleted();
+            }
         }
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 9b22efdae7da8fb5d4c7781359ca0b7c2879ebc7..89fc65a54ab39ae9b373c83ec4eb85b39c357674 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -144,6 +144,7 @@ import android.window.DisplayAreaInfo;
 import android.window.IDisplayAreaOrganizer;
 import android.window.ScreenCapture;
 import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
 
 import androidx.test.filters.SmallTest;
 
@@ -2012,6 +2013,53 @@ public class DisplayContentTests extends WindowTestsBase {
         assertFalse(mWm.mDisplayFrozen);
     }
 
+    @Test
+    public void testRemoteDisplayChange() {
+        mWm.mDisplayChangeController = mock(IDisplayChangeWindowController.class);
+        final Boolean[] isWaitingForRemote = new Boolean[2];
+        final var callbacks = new RemoteDisplayChangeController.ContinueRemoteDisplayChangeCallback[
+                isWaitingForRemote.length];
+        for (int i = 0; i < isWaitingForRemote.length; i++) {
+            final int index = i;
+            var callback = new RemoteDisplayChangeController.ContinueRemoteDisplayChangeCallback() {
+                @Override
+                public void onContinueRemoteDisplayChange(WindowContainerTransaction transaction) {
+                    isWaitingForRemote[index] =
+                            mDisplayContent.mRemoteDisplayChangeController
+                                    .isWaitingForRemoteDisplayChange();
+                }
+            };
+            mDisplayContent.mRemoteDisplayChangeController.performRemoteDisplayChange(
+                    ROTATION_0, ROTATION_0, null /* newDisplayAreaInfo */, callback);
+            callbacks[i] = callback;
+        }
+
+        // The last callback is completed, all callbacks should be notified.
+        mDisplayContent.mRemoteDisplayChangeController.continueDisplayChange(callbacks[1],
+                null /* transaction */);
+        // When notifying 0, the callback 1 still exists.
+        assertTrue(isWaitingForRemote[0]);
+        assertFalse(isWaitingForRemote[1]);
+
+        // The first callback is completed, other callbacks after it should remain.
+        for (int i = 0; i < isWaitingForRemote.length; i++) {
+            isWaitingForRemote[i] = null;
+            mDisplayContent.mRemoteDisplayChangeController.performRemoteDisplayChange(
+                    ROTATION_0, ROTATION_0, null /* newDisplayAreaInfo */, callbacks[i]);
+        }
+        mDisplayContent.mRemoteDisplayChangeController.continueDisplayChange(callbacks[0],
+                null /* transaction */);
+        assertTrue(isWaitingForRemote[0]);
+        assertNull(isWaitingForRemote[1]);
+
+        // Complete the last callback. It should be able to consume pending config change.
+        mDisplayContent.mWaitingForConfig = true;
+        mDisplayContent.mRemoteDisplayChangeController.continueDisplayChange(callbacks[1],
+                null /* transaction */);
+        assertFalse(isWaitingForRemote[1]);
+        assertFalse(mDisplayContent.mWaitingForConfig);
+    }
+
     @Test
     public void testShellTransitRotation() {
         DisplayContent dc = createNewDisplay();