Continue layout if needed

Sometimes the operations in the deferLayout~continueLayout don't
change significant states related to layout.

This reduces 1~3 times performSurfacePlacement when switching between
activities. Also reduce lots of invocations when resizing task/stack.
Bounds change isn't a layout reason from activity aspect because there
are already many invocations from relayoutWindow, finishDrawingWindow
and animate that will request traversal.

Test: go/wm-smoke
Test: Enable debug log in WindowSurfacePlacer to observe the
      invocation of performSurfacePlacement from continueLayout.
Bug: 140407614

Change-Id: I347f1fe1db676dcf320163bed0df693775b5f022
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index f3e7384..e488cc9 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -740,7 +740,7 @@
     }
 
     private void onSplitScreenModeDismissed() {
-        mRootActivityContainer.mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             // Adjust the windowing mode of any stack in secondary split-screen to fullscreen.
             for (int i = mStacks.size() - 1; i >= 0; --i) {
@@ -764,12 +764,12 @@
                 mHomeStack.moveToFront("onSplitScreenModeDismissed");
                 topFullscreenStack.moveToFront("onSplitScreenModeDismissed");
             }
-            mRootActivityContainer.mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
     private void onSplitScreenModeActivated() {
-        mRootActivityContainer.mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             // Adjust the windowing mode of any affected by split-screen to split-screen secondary.
             for (int i = mStacks.size() - 1; i >= 0; --i) {
@@ -784,7 +784,7 @@
                         false /* creating */);
             }
         } finally {
-            mRootActivityContainer.mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
@@ -1002,12 +1002,9 @@
         Configuration values = new Configuration();
         mDisplayContent.computeScreenConfiguration(values);
 
-        if (mService.mWindowManager != null) {
-            final Message msg = PooledLambda.obtainMessage(
-                    ActivityManagerInternal::updateOomLevelsForDisplay, mService.mAmInternal,
-                    mDisplayId);
-            mService.mH.sendMessage(msg);
-        }
+        mService.mH.sendMessage(PooledLambda.obtainMessage(
+                ActivityManagerInternal::updateOomLevelsForDisplay, mService.mAmInternal,
+                mDisplayId));
 
         Settings.System.clearConfiguration(values);
         updateDisplayOverrideConfigurationLocked(values, null /* starting */,
@@ -1026,9 +1023,7 @@
         int changes = 0;
         boolean kept = true;
 
-        if (mService.mWindowManager != null) {
-            mService.mWindowManager.deferSurfaceLayout();
-        }
+        mService.deferWindowLayout();
         try {
             if (values != null) {
                 if (mDisplayId == DEFAULT_DISPLAY) {
@@ -1045,9 +1040,7 @@
 
             kept = mService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
         } finally {
-            if (mService.mWindowManager != null) {
-                mService.mWindowManager.continueSurfaceLayout();
-            }
+            mService.continueWindowLayout();
         }
 
         if (result != null) {
@@ -1096,6 +1089,8 @@
             mService.mWindowManager.setNewDisplayOverrideConfiguration(
                     overrideConfiguration, mDisplayContent);
         }
+        mService.addWindowLayoutReasons(
+                ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 61bf2ce..7a66731 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1703,7 +1703,7 @@
                 // is not visible if it only contains finishing activities.
                 && mRootActivityContainer.isTopDisplayFocusedStack(stack);
 
-        mAtmService.mWindowManager.deferSurfaceLayout();
+        mAtmService.deferWindowLayout();
         try {
             makeFinishingLocked();
             final TaskRecord task = getTaskRecord();
@@ -1809,7 +1809,7 @@
 
             return FINISH_RESULT_REQUESTED;
         } finally {
-            mAtmService.mWindowManager.continueSurfaceLayout();
+            mAtmService.continueWindowLayout();
         }
     }
 
@@ -2547,6 +2547,8 @@
             return;
         }
         mAppWindowToken.setVisibility(visible, mDeferHidingClient);
+        mAtmService.addWindowLayoutReasons(
+                ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED);
         mStackSupervisor.getActivityMetricsLogger().notifyVisibilityChanged(this);
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 29e87b7..8bb37bb 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -757,7 +757,6 @@
             return;
         }
 
-        final WindowManagerService wm = mService.mWindowManager;
         final ActivityRecord topActivity = getTopActivity();
 
         // For now, assume that the Stack's windowing mode is what will actually be used
@@ -779,7 +778,7 @@
                     topTask.taskId, FORCED_RESIZEABLE_REASON_SPLIT_SCREEN, packageName);
         }
 
-        wm.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             if (!animate && topActivity != null) {
                 mStackSupervisor.mNoAnimActivities.add(topActivity);
@@ -850,7 +849,7 @@
                 // If task moved to docked stack - show recents if needed.
                 mService.mWindowManager.showRecentApps();
             }
-            wm.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
 
         if (!deferEnsuringVisibility) {
@@ -1750,11 +1749,11 @@
             if (mPausingActivity == r) {
                 if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to PAUSED: " + r
                         + (timeout ? " (due to timeout)" : " (pause complete)"));
-                mService.mWindowManager.deferSurfaceLayout();
+                mService.deferWindowLayout();
                 try {
                     completePauseLocked(true /* resumeNext */, null /* resumingActivity */);
                 } finally {
-                    mService.mWindowManager.continueSurfaceLayout();
+                    mService.continueWindowLayout();
                 }
                 return;
             } else {
@@ -4360,7 +4359,7 @@
         }
 
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "stack.resize_" + mStackId);
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             // Update override configurations of all tasks in the stack.
             final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : bounds;
@@ -4384,7 +4383,7 @@
                         topRunningActivityLocked(), preserveWindows);
             }
         } finally {
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
         }
     }
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 7a3f022..1aa1d48 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -1495,7 +1495,7 @@
     private void moveTasksToFullscreenStackInSurfaceTransaction(ActivityStack fromStack,
             int toDisplayId, boolean onTop) {
 
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             final int windowingMode = fromStack.getWindowingMode();
             final boolean inPinnedWindowingMode = windowingMode == WINDOWING_MODE_PINNED;
@@ -1561,7 +1561,7 @@
             mRootActivityContainer.resumeFocusedStacksTopActivities();
         } finally {
             mAllowDockedStackResize = true;
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
@@ -1630,7 +1630,7 @@
         }
 
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeDockedStack");
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             // Don't allow re-entry while resizing. E.g. due to docked stack detaching.
             mAllowDockedStackResize = false;
@@ -1694,7 +1694,7 @@
             }
         } finally {
             mAllowDockedStackResize = true;
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
         }
     }
@@ -1718,9 +1718,8 @@
         }
 
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizePinnedStack");
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
-            ActivityRecord r = stack.topRunningActivityLocked();
             Rect insetBounds = null;
             if (tempPinnedTaskBounds != null && stack.isAnimatingBoundsToFullscreen()) {
                 // Use 0,0 as the position for the inset rect because we are headed for fullscreen.
@@ -1739,7 +1738,7 @@
             stack.resize(pinnedBounds, tempPinnedTaskBounds, insetBounds, !PRESERVE_WINDOWS,
                     !DEFER_RESUME);
         } finally {
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
         }
     }
@@ -2731,7 +2730,7 @@
                     + taskId + " can't be launch in the home/recents stack.");
         }
 
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 mWindowManager.setDockedStackCreateStateLocked(
@@ -2822,7 +2821,7 @@
                     mWindowManager.checkSplitScreenMinimizedChanged(false /* animate */);
                 }
             }
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 641b00a..55db1a0 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1400,7 +1400,7 @@
         int result = START_CANCELED;
         final ActivityStack startedActivityStack;
         try {
-            mService.mWindowManager.deferSurfaceLayout();
+            mService.deferWindowLayout();
             result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
                     startFlags, doResume, options, inTask, outActivity, restrictedBgActivity);
         } finally {
@@ -1436,7 +1436,7 @@
                     startedActivityStack.remove();
                 }
             }
-            mService.mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
 
         postStartActivityProcessing(r, result, startedActivityStack);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ed7e12c..2f7acba 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -124,6 +124,7 @@
 import static com.android.server.wm.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -598,6 +599,19 @@
      */
     int mTopProcessState = ActivityManager.PROCESS_STATE_TOP;
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            LAYOUT_REASON_CONFIG_CHANGED,
+            LAYOUT_REASON_VISIBILITY_CHANGED,
+    })
+    @interface LayoutReason {}
+    static final int LAYOUT_REASON_CONFIG_CHANGED = 0x1;
+    static final int LAYOUT_REASON_VISIBILITY_CHANGED = 0x2;
+
+    /** The reasons to perform surface placement. */
+    @LayoutReason
+    private int mLayoutReasons;
+
     // Whether we should show our dialogs (ANR, crash, etc) or just perform their default action
     // automatically. Important for devices without direct input devices.
     private boolean mShowDialogs = true;
@@ -4388,17 +4402,19 @@
         mAmInternal.enforceCallingPermission(CHANGE_CONFIGURATION, "updateConfiguration()");
 
         synchronized (mGlobalLock) {
-            if (values == null && mWindowManager != null) {
+            if (mWindowManager == null) {
+                Slog.w(TAG, "Skip updateConfiguration because mWindowManager isn't set");
+                return false;
+            }
+
+            if (values == null) {
                 // sentinel: fetch the current configuration from the window manager
                 values = mWindowManager.computeNewConfiguration(DEFAULT_DISPLAY);
             }
 
-            if (mWindowManager != null) {
-                final Message msg = PooledLambda.obtainMessage(
-                        ActivityManagerInternal::updateOomLevelsForDisplay, mAmInternal,
-                        DEFAULT_DISPLAY);
-                mH.sendMessage(msg);
-            }
+            mH.sendMessage(PooledLambda.obtainMessage(
+                    ActivityManagerInternal::updateOomLevelsForDisplay, mAmInternal,
+                    DEFAULT_DISPLAY));
 
             final long origId = Binder.clearCallingIdentity();
             try {
@@ -5099,9 +5115,7 @@
         int changes = 0;
         boolean kept = true;
 
-        if (mWindowManager != null) {
-            mWindowManager.deferSurfaceLayout();
-        }
+        deferWindowLayout();
         try {
             if (values != null) {
                 changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId,
@@ -5110,9 +5124,7 @@
 
             kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
         } finally {
-            if (mWindowManager != null) {
-                mWindowManager.continueSurfaceLayout();
-            }
+            continueWindowLayout();
         }
 
         if (result != null) {
@@ -5240,6 +5252,34 @@
         return changes;
     }
 
+    /** @see WindowSurfacePlacer#deferLayout */
+    void deferWindowLayout() {
+        if (!mWindowManager.mWindowPlacerLocked.isLayoutDeferred()) {
+            // Reset the reasons at the first entrance because we only care about the changes in the
+            // deferred scope.
+            mLayoutReasons = 0;
+        }
+
+        mWindowManager.mWindowPlacerLocked.deferLayout();
+    }
+
+    /** @see WindowSurfacePlacer#continueLayout */
+    void continueWindowLayout() {
+        mWindowManager.mWindowPlacerLocked.continueLayout(mLayoutReasons != 0);
+        if (DEBUG_ALL && !mWindowManager.mWindowPlacerLocked.isLayoutDeferred()) {
+            Slog.i(TAG, "continueWindowLayout reason=" + mLayoutReasons);
+        }
+    }
+
+    /**
+     * If a reason is added between {@link #deferWindowLayout} and {@link #continueWindowLayout},
+     * it will make sure {@link WindowSurfacePlacer#performSurfacePlacement} is called when the last
+     * defer count is gone.
+     */
+    void addWindowLayoutReasons(@LayoutReason int reasons) {
+        mLayoutReasons |= reasons;
+    }
+
     private void updateEventDispatchingLocked(boolean booted) {
         mWindowManager.setEventDispatching(booted && !mShuttingDown);
     }
@@ -6667,7 +6707,7 @@
                 }
 
                 if (!restarting && hasVisibleActivities) {
-                    mWindowManager.deferSurfaceLayout();
+                    deferWindowLayout();
                     try {
                         if (!mRootActivityContainer.resumeFocusedStacksTopActivities()) {
                             // If there was nothing to resume, and we are not already restarting
@@ -6678,7 +6718,7 @@
                                     !PRESERVE_WINDOWS);
                         }
                     } finally {
-                        mWindowManager.continueSurfaceLayout();
+                        continueWindowLayout();
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 422b6e5..d528ef6 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -182,7 +182,7 @@
             return;
         }
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "keyguardGoingAway");
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             setKeyguardGoingAway(true);
             EventLog.writeEvent(EventLogTags.AM_SET_KEYGUARD_SHOWN,
@@ -204,7 +204,7 @@
             mWindowManager.executeAppTransition();
         } finally {
             Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "keyguardGoingAway: surfaceLayout");
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
 
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
@@ -330,7 +330,7 @@
 
         mWindowManager.onKeyguardOccludedChanged(isDisplayOccluded(DEFAULT_DISPLAY));
         if (isKeyguardLocked()) {
-            mWindowManager.deferSurfaceLayout();
+            mService.deferWindowLayout();
             try {
                 mRootActivityContainer.getDefaultDisplay().mDisplayContent
                         .prepareAppTransition(resolveOccludeTransit(),
@@ -340,7 +340,7 @@
                 mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
                 mWindowManager.executeAppTransition();
             } finally {
-                mWindowManager.continueSurfaceLayout();
+                mService.continueWindowLayout();
             }
         }
         dismissDockedStackIfNeeded();
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index 59df09b..6de48d1 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -133,7 +133,7 @@
             return false;
         }
 
-        mService.mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
 
         try {
             if (mTmpParams.hasPreferredDisplay()
@@ -161,7 +161,7 @@
             task.setLastNonFullscreenBounds(mTmpParams.mBounds);
             return false;
         } finally {
-            mService.mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 1a8944a..5cabbd9 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -196,7 +196,7 @@
             mCaller.setRunningRecentsAnimation(true);
         }
 
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             if (hasExistingActivity) {
                 // Move the recents activity into place for the animation if it is not top most
@@ -260,7 +260,7 @@
             Slog.e(TAG, "Failed to start recents activity", e);
             throw e;
         } finally {
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
         }
     }
@@ -297,7 +297,7 @@
             mWindowManager.inSurfaceTransaction(() -> {
                 Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
                         "RecentsAnimation#onAnimationFinished_inSurfaceTransaction");
-                mWindowManager.deferSurfaceLayout();
+                mService.deferWindowLayout();
                 try {
                     mWindowManager.cleanupRecentsAnimation(reorderMode);
 
@@ -387,7 +387,7 @@
                     Slog.e(TAG, "Failed to clean up recents activity", e);
                     throw e;
                 } finally {
-                    mWindowManager.continueSurfaceLayout();
+                    mService.continueWindowLayout();
                     Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
                 }
             });
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index eeea185..d9e30a2 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -945,7 +945,7 @@
 
     void moveActivityToPinnedStack(ActivityRecord r, Rect sourceHintBounds, float aspectRatio,
             String reason) {
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
 
         final ActivityDisplay display = r.getActivityStack().getDisplay();
         ActivityStack stack = display.getPinnedStack();
@@ -994,7 +994,7 @@
             // to the pinned stack
             r.supportsEnterPipOnTaskSwitch = false;
         } finally {
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
 
         // Notify the pinned stack controller to prepare the PiP animation, expect callback
@@ -2060,7 +2060,7 @@
      * @param userId user handle for the locked managed profile.
      */
     void lockAllProfileTasks(@UserIdInt int userId) {
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
                 final ActivityDisplay display = mActivityDisplays.get(displayNdx);
@@ -2081,7 +2081,7 @@
                 }
             }
         } finally {
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index d3f3981..4b3691c8 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -545,7 +545,7 @@
     }
 
     boolean resize(Rect bounds, int resizeMode, boolean preserveWindow, boolean deferResume) {
-        mService.mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
 
         try {
             if (!isResizeable()) {
@@ -619,7 +619,7 @@
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             return kept;
         } finally {
-            mService.mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
@@ -725,7 +725,7 @@
             windowManager.setWillReplaceWindow(topActivity.appToken, animate);
         }
 
-        windowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         boolean kept = true;
         try {
             final ActivityRecord r = topRunningActivityLocked();
@@ -809,7 +809,7 @@
                         !mightReplaceWindow, deferResume);
             }
         } finally {
-            windowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
 
         if (mightReplaceWindow) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8d3a107..a7f6688 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2775,21 +2775,6 @@
     }
 
     /**
-     * Starts deferring layout passes. Useful when doing multiple changes but to optimize
-     * performance, only one layout pass should be done. This can be called multiple times, and
-     * layouting will be resumed once the last caller has called
-     * {@link #continueSurfaceLayout}.
-     */
-    void deferSurfaceLayout() {
-        mWindowPlacerLocked.deferLayout();
-    }
-
-    /** Resumes layout passes after deferring them. See {@link #deferSurfaceLayout()} */
-    void continueSurfaceLayout() {
-        mWindowPlacerLocked.continueLayout();
-    }
-
-    /**
      * Notifies activity manager that some Keyguard flags have changed and that it needs to
      * reevaluate the visibilities of the activities.
      * @param callback Runnable to be called when activity manager is done reevaluating visibilities
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index cc791787..56f6d4b 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -25,7 +25,6 @@
 import android.os.Debug;
 import android.os.Trace;
 import android.util.Slog;
-import android.util.SparseIntArray;
 
 import java.io.PrintWriter;
 
@@ -50,8 +49,8 @@
 
     private boolean mTraversalScheduled;
     private int mDeferDepth = 0;
-
-    private final SparseIntArray mTempTransitionReasons = new SparseIntArray();
+    /** The number of layout requests when deferring. */
+    private int mDeferredRequests;
 
     private final Runnable mPerformSurfacePlacement;
 
@@ -65,19 +64,38 @@
     }
 
     /**
-     * See {@link WindowManagerService#deferSurfaceLayout()}
+     * Starts deferring layout passes. Useful when doing multiple changes but to optimize
+     * performance, only one layout pass should be done. This can be called multiple times, and
+     * layouting will be resumed once the last caller has called {@link #continueLayout}.
      */
     void deferLayout() {
         mDeferDepth++;
     }
 
     /**
-     * See {@link WindowManagerService#continueSurfaceLayout()}
+     * Resumes layout passes after deferring them. If there is a deferred direct invocation of
+     * {@link #performSurfacePlacement} ({@link #mDeferredRequests} > 0), when the defer is
+     * done, it will continue to perform layout.
+     *
+     * @param hasChanges Something has changed. That means whether to call
+     *                   {@link #performSurfacePlacement} when {@link #mDeferDepth} becomes zero.
+     * @see #deferLayout
      */
-    void continueLayout() {
+    void continueLayout(boolean hasChanges) {
         mDeferDepth--;
-        if (mDeferDepth <= 0) {
+        if (mDeferDepth > 0) {
+            return;
+        }
+
+        if (hasChanges || mDeferredRequests > 0) {
+            if (DEBUG) {
+                Slog.i(TAG, "continueLayout hasChanges=" + hasChanges
+                        + " deferredRequests=" + mDeferredRequests + " " + Debug.getCallers(2, 3));
+            }
             performSurfacePlacement();
+            mDeferredRequests = 0;
+        } else if (DEBUG) {
+            Slog.i(TAG, "Cancel continueLayout " + Debug.getCallers(2, 3));
         }
     }
 
@@ -97,6 +115,7 @@
 
     final void performSurfacePlacement(boolean force) {
         if (mDeferDepth > 0 && !force) {
+            mDeferredRequests++;
             return;
         }
         int loopCount = 6;
@@ -195,10 +214,19 @@
     }
 
     void requestTraversal() {
-        if (!mTraversalScheduled) {
-            mTraversalScheduled = true;
-            mService.mAnimationHandler.post(mPerformSurfacePlacement);
+        if (mTraversalScheduled) {
+            return;
         }
+
+        // Set as scheduled even the request will be deferred because mDeferredRequests is also
+        // increased, then the end of deferring will perform the request.
+        mTraversalScheduled = true;
+        if (mDeferDepth > 0) {
+            mDeferredRequests++;
+            if (DEBUG) Slog.i(TAG, "Defer requestTraversal " + Debug.getCallers(3));
+            return;
+        }
+        mService.mAnimationHandler.post(mPerformSurfacePlacement);
     }
 
     public void dump(PrintWriter pw, String prefix) {