Adapt tests for display cutout

With display cutout some rotations will not cause config changes
in certain scenarios because of different window insets.
This CL changes the fixed orientation test to acknowledge the cutout
and disables other config change tests where the outcome is hard to
predict.

Bug: 109759160
Test: ActivityManagerAppConfigurationTests#testFixedOrientationWhenRotating
Test: ActivityManagerConfigChangeTests
Change-Id: Ifa3f6bf4d9b6ed237f3ceba942aff3eb877a0c70
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/BroadcastReceiverActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/BroadcastReceiverActivity.java
index d9f7589..a148b32 100644
--- a/tests/framework/base/activitymanager/app/src/android/server/am/BroadcastReceiverActivity.java
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/BroadcastReceiverActivity.java
@@ -22,7 +22,9 @@
 import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_DISMISS_KEYGUARD_METHOD;
 import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_FINISH_BROADCAST;
 import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_MOVE_BROADCAST_TO_BACK;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 
 import android.app.Activity;
 import android.app.KeyguardManager;
@@ -32,6 +34,9 @@
 import android.content.IntentFilter;
 import android.os.Bundle;
 import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
 
 /**
  * Activity that registers broadcast receiver .
@@ -48,6 +53,20 @@
         IntentFilter broadcastFilter = new IntentFilter(ACTION_TRIGGER_BROADCAST);
 
         registerReceiver(mBroadcastReceiver, broadcastFilter);
+
+        // Determine if a display cutout is present
+        final View view = new View(this);
+        getWindow().requestFeature(Window.FEATURE_NO_TITLE);
+        getWindow().getAttributes().layoutInDisplayCutoutMode =
+                LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+        view.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+        view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+        view.setOnApplyWindowInsetsListener((v, insets) -> {
+            Log.i(getClass().getSimpleName(), "cutout=" + (insets.getDisplayCutout() != null));
+            return insets;
+        });
+        setContentView(view);
     }
 
     @Override
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
index 1d2bd3e..1ea220a 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
@@ -67,11 +67,6 @@
  */
 public class ActivityManagerActivityVisibilityTests extends ActivityManagerTestBase {
 
-    // TODO(b/70247058): Use {@link Context#sendBroadcast(Intent).
-    // Shell command to finish {@link #BROADCAST_RECEIVER_ACTIVITY}.
-    private static final String FINISH_ACTIVITY_BROADCAST = "am broadcast -a "
-            + ACTION_TRIGGER_BROADCAST + " --ez " + EXTRA_FINISH_BROADCAST + " true";
-
     @Rule
     public final DisableScreenDozeRule mDisableScreenDozeRule = new DisableScreenDozeRule();
 
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAppConfigurationTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAppConfigurationTests.java
index 0ce9dcf..0cef20f 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAppConfigurationTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAppConfigurationTests.java
@@ -28,7 +28,6 @@
 import static android.server.am.Components.BROADCAST_RECEIVER_ACTIVITY;
 import static android.server.am.Components.BroadcastReceiverActivity.ACTION_TRIGGER_BROADCAST;
 import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_BROADCAST_ORIENTATION;
-import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_FINISH_BROADCAST;
 import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_MOVE_BROADCAST_TO_BACK;
 import static android.server.am.Components.DIALOG_WHEN_LARGE_ACTIVITY;
 import static android.server.am.Components.LANDSCAPE_ORIENTATION_ACTIVITY;
@@ -54,6 +53,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import android.content.ComponentName;
@@ -73,9 +73,6 @@
 public class ActivityManagerAppConfigurationTests extends ActivityManagerTestBase {
 
     // TODO(b/70247058): Use {@link Context#sendBroadcast(Intent).
-    // Shell command to finish {@link #BROADCAST_RECEIVER_ACTIVITY}.
-    private static final String FINISH_ACTIVITY_BROADCAST = "am broadcast -a "
-            + ACTION_TRIGGER_BROADCAST + " --ez " + EXTRA_FINISH_BROADCAST + " true";
     // Shell command to move {@link #BROADCAST_RECEIVER_ACTIVITY} task to back.
     private static final String MOVE_TASK_TO_BACK_BROADCAST = "am broadcast -a "
             + ACTION_TRIGGER_BROADCAST + " --ez " + EXTRA_MOVE_BROADCAST_TO_BACK + " true";
@@ -643,6 +640,9 @@
     @Test
     public void testFixedOrientationWhenRotating() throws Exception {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
+        // TODO(b/110533226): Fix test on devices with display cutout
+        assumeFalse("Skipping test: display cutout present, can't predict exact lifecycle",
+                hasDisplayCutout());
 
         // Start portrait-fixed activity
         LogSeparator logSeparator = separateLogs();
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerConfigChangeTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerConfigChangeTests.java
index 30ed944..f041ebd 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerConfigChangeTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerConfigChangeTests.java
@@ -21,12 +21,20 @@
 import static android.server.am.Components.FONT_SCALE_ACTIVITY;
 import static android.server.am.Components.FONT_SCALE_NO_RELAUNCH_ACTIVITY;
 import static android.server.am.Components.NO_RELAUNCH_ACTIVITY;
+import static android.server.am.Components.RESIZEABLE_ACTIVITY;
 import static android.server.am.Components.TEST_ACTIVITY;
 import static android.server.am.StateLogger.log;
 import static android.server.am.StateLogger.logE;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
 
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.ComponentName;
 import android.platform.test.annotations.Presubmit;
@@ -49,24 +57,39 @@
 
     @Test
     public void testRotation90Relaunch() throws Exception{
+        assumeTrue("Skipping test: no rotation support", supportsRotation());
+
         // Should relaunch on every rotation and receive no onConfigurationChanged()
         testRotation(TEST_ACTIVITY, 1, 1, 0);
     }
 
     @Test
     public void testRotation90NoRelaunch() throws Exception {
+        assumeTrue("Skipping test: no rotation support", supportsRotation());
+
         // Should receive onConfigurationChanged() on every rotation and no relaunch
         testRotation(NO_RELAUNCH_ACTIVITY, 1, 0, 1);
     }
 
+    // TODO(b/110382028): Fix relaunch testing and use an activity that doesn't handle config change
     @Test
     public void testRotation180Relaunch() throws Exception {
-        // Should receive nothing
+        assumeTrue("Skipping test: no rotation support", supportsRotation());
+        // TODO(b/110533226): Fix test on devices with display cutout
+        assumeFalse("Skipping test: display cutout present, can't predict exact lifecycle",
+                hasDisplayCutout());
+
+        // Should receive a relaunch
         testRotation(TEST_ACTIVITY, 2, 0, 0);
     }
 
     @Test
     public void testRotation180NoRelaunch() throws Exception {
+        assumeTrue("Skipping test: no rotation support", supportsRotation());
+        // TODO(b/110533226): Fix test on devices with display cutout
+        assumeFalse("Skipping test: display cutout present, can't predict exact lifecycle",
+                hasDisplayCutout());
+
         // Should receive nothing
         testRotation(NO_RELAUNCH_ACTIVITY, 2, 0, 0);
     }
@@ -87,6 +110,54 @@
         testChangeFontScale(FONT_SCALE_NO_RELAUNCH_ACTIVITY, false /* relaunch */);
     }
 
+    /**
+     * Test activity configuration changes for devices with cutout(s). Landscape and
+     * reverse-landscape rotations should result in same screen space available for apps.
+     */
+    @Test
+    public void testConfigChangeWhenRotatingWithCutout() throws Exception {
+        assumeTrue("Skipping test: no rotation support", supportsRotation());
+        assumeTrue("Skipping test: no display cutout", hasDisplayCutout());
+
+        // Start an activity that handles config changes
+        launchActivity(RESIZEABLE_ACTIVITY);
+        mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
+        final int displayId = mAmWmState.getAmState().getDisplayByActivity(RESIZEABLE_ACTIVITY);
+
+        // 0 - 180 rotation or 270 - 90 rotation should have same screen space
+        boolean configSame0_180 = false, configSame270_90 = false;
+        final int[] cutoutRotations = { ROTATION_180, ROTATION_90, ROTATION_270 };
+
+        // Rotate the activity and check that the orientation doesn't change at least once
+        try (final RotationSession rotationSession = new RotationSession()) {
+            rotationSession.set(ROTATION_0);
+
+            final ReportedSizes[] sizes = new ReportedSizes[cutoutRotations.length];
+            for (int i = 0; i < cutoutRotations.length; i++) {
+                final int rotation = cutoutRotations[i];
+                final LogSeparator logSeparator = separateLogs();
+                rotationSession.set(rotation);
+                final int newDeviceRotation = getDeviceRotation(displayId);
+                if (rotation != newDeviceRotation) {
+                    log("This device doesn't support locked user "
+                            + "rotation mode. Not continuing the rotation checks.");
+                    continue;
+                }
+
+                // Record configuration changes on rotations between opposite orientations
+                sizes[i] = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY, logSeparator);
+                if (i == 0) {
+                    configSame0_180 = sizes[i] == null;
+                } else if (i == 2) {
+                    configSame270_90 = sizes[i] == null;
+                }
+            }
+
+            assertThat("A device with cutout should have same available screen space in "
+                    + "landscape and reverse-landscape", configSame0_180 || configSame270_90);
+        }
+    }
+
     private void testRotation(ComponentName activityName, int rotationStep, int numRelaunch,
             int numConfigChange) throws Exception {
         launchActivity(activityName);
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
index d853788..90658e5 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
@@ -83,9 +83,6 @@
 public class ActivityManagerMultiDisplayTests extends ActivityManagerDisplayTestBase {
 
     // TODO(b/70247058): Use {@link Context#sendBroadcast(Intent).
-    // Shell command to finish {@link #BROADCAST_RECEIVER_ACTIVITY}.
-    private static final String FINISH_ACTIVITY_BROADCAST = "am broadcast -a "
-            + ACTION_TRIGGER_BROADCAST + " --ez " + EXTRA_FINISH_BROADCAST + " true";
     // Shell command to launch activity via {@link #BROADCAST_RECEIVER_ACTIVITY}.
     private static final String LAUNCH_ACTIVITY_BROADCAST = "am broadcast -a "
             + ACTION_TRIGGER_BROADCAST + " --ez " + KEY_LAUNCH_ACTIVITY + " true --ez "
diff --git a/tests/framework/base/activitymanager/src/android/server/am/KeyguardTests.java b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTests.java
index e79c13b..a53993b 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/KeyguardTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTests.java
@@ -65,9 +65,6 @@
     // Shell command to dismiss keyguard via {@link #BROADCAST_RECEIVER_ACTIVITY} method.
     private static final String DISMISS_KEYGUARD_METHOD_BROADCAST = "am broadcast -a "
             + ACTION_TRIGGER_BROADCAST + " --ez " + EXTRA_DISMISS_KEYGUARD_METHOD + " true";
-    // Shell command to finish {@link #BROADCAST_RECEIVER_ACTIVITY}.
-    private static final String FINISH_ACTIVITY_BROADCAST = "am broadcast -a "
-            + ACTION_TRIGGER_BROADCAST + " --ez " + EXTRA_FINISH_BROADCAST + " true";
 
     @Before
     @Override
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
index fcca246..8ea430c 100644
--- a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
@@ -48,6 +48,9 @@
 import static android.server.am.ActivityManagerState.STATE_RESUMED;
 import static android.server.am.ComponentNameUtils.getActivityName;
 import static android.server.am.ComponentNameUtils.getLogTag;
+import static android.server.am.Components.BROADCAST_RECEIVER_ACTIVITY;
+import static android.server.am.Components.BroadcastReceiverActivity.ACTION_TRIGGER_BROADCAST;
+import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_FINISH_BROADCAST;
 import static android.server.am.Components.LAUNCHING_ACTIVITY;
 import static android.server.am.Components.TEST_ACTIVITY;
 import static android.server.am.StateLogger.log;
@@ -64,6 +67,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -144,6 +148,11 @@
 
     private static final String LOCK_CREDENTIAL = "1234";
 
+    // TODO(b/70247058): Use {@link Context#sendBroadcast(Intent).
+    // Shell command to finish {@link #BROADCAST_RECEIVER_ACTIVITY}.
+    static final String FINISH_ACTIVITY_BROADCAST = "am broadcast -a "
+            + ACTION_TRIGGER_BROADCAST + " --ez " + EXTRA_FINISH_BROADCAST + " true";
+
     private static final int UI_MODE_TYPE_MASK = 0x0f;
     private static final int UI_MODE_TYPE_VR_HEADSET = 0x07;
 
@@ -1100,6 +1109,8 @@
             "(.+): config size=\\((\\d+),(\\d+)\\) displaySize=\\((\\d+),(\\d+)\\)"
             + " metricsSize=\\((\\d+),(\\d+)\\) smallestScreenWidth=(\\d+) densityDpi=(\\d+)"
             + " orientation=(\\d+)");
+    private static final Pattern sDisplayCutoutPattern = Pattern.compile(
+            "(.+): cutout=(true|false)");
     private static final Pattern sDisplayStatePattern =
             Pattern.compile("Display Power: state=(.+)");
     private static final Pattern sCurrentUiModePattern = Pattern.compile("mCurUiMode=0x(\\d+)");
@@ -1181,6 +1192,59 @@
         return null;
     }
 
+    /** Check if a device has display cutout. */
+    boolean hasDisplayCutout() {
+        // Launch an activity to report cutout state
+        final LogSeparator logSeparator = separateLogs();
+        launchActivity(BROADCAST_RECEIVER_ACTIVITY);
+
+        // Read the logs to check if cutout is present
+        final Boolean displayCutoutPresent =
+                getCutoutStateForActivity(BROADCAST_RECEIVER_ACTIVITY, logSeparator);
+        assertNotNull("The activity should report cutout state", displayCutoutPresent);
+
+        // Finish activity
+        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+        mAmWmState.waitForWithAmState(
+                (state) -> !state.containsActivity(BROADCAST_RECEIVER_ACTIVITY),
+                "Waiting for activity to be removed");
+
+        return displayCutoutPresent;
+    }
+
+    /**
+     * Wait for activity to report cutout state in logs and return it. Will return {@code null}
+     * after timeout.
+     */
+    @Nullable
+    private Boolean getCutoutStateForActivity(ComponentName activityName,
+            LogSeparator logSeparator) {
+        final String logTag = getLogTag(activityName);
+        for (int retry = 1; retry <= 5; retry++ ) {
+            final Boolean result = readLastReportedCutoutState(logSeparator, logTag);
+            if (result != null) {
+                return result;
+            }
+            logAlways("***Waiting for cutout state to be reported... retry=" + retry);
+            SystemClock.sleep(1000);
+        }
+        logE("***Waiting for activity cutout state failed: activityName=" + logTag);
+        return null;
+    }
+
+    /** Read display cutout state from device logs. */
+    private Boolean readLastReportedCutoutState(LogSeparator logSeparator, String logTag) {
+        final String[] lines = getDeviceLogsForComponents(logSeparator, logTag);
+        for (int i = lines.length - 1; i >= 0; i--) {
+            final String line = lines[i].trim();
+            final Matcher matcher = sDisplayCutoutPattern.matcher(line);
+            if (matcher.matches()) {
+                return "true".equals(matcher.group(2));
+            }
+        }
+        return null;
+    }
+
     /** Waits for at least one onMultiWindowModeChanged event. */
     ActivityLifecycleCounts waitForOnMultiWindowModeChanged(ComponentName activityName,
             LogSeparator logSeparator) {