diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index c81ead9bd0d12eb2f738ec1b72e9c90936aa5500..7adac5b283c529e70a8f86d1859a2f1a1e145fff 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -16,8 +16,6 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; @@ -62,7 +60,6 @@ import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANG import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; -import static com.android.server.wm.ActivityRecord.State.DESTROYED; import static com.android.server.wm.ActivityRecord.State.PAUSED; import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS; import static com.android.server.wm.ActivityRecord.State.RESUMED; @@ -77,7 +74,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -198,26 +194,6 @@ public class SizeCompatTests extends WindowTestsBase { setUpApp(builder.build()); } - @Test - public void testCleanLetterboxConfigListenerWhenTranslucentIsDestroyed() { - mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); - setUpDisplaySizeWithApp(2000, 1000); - prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - // Translucent Activity - final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) - .setActivityTheme(android.R.style.Theme_Translucent) - .setLaunchedFromUid(mActivity.getUid()) - .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) - .build(); - mTask.addChild(translucentActivity); - - translucentActivity.setState(DESTROYED, "testing"); - translucentActivity.removeImmediately(); - - assertFalse(translucentActivity.mTransparentPolicy.isRunning()); - } - @Test public void testHorizontalReachabilityEnabledForTranslucentActivities() { testReachabilityEnabledForTranslucentActivity(/* dw */ 2500, /* dh */1000, @@ -363,42 +339,6 @@ public class SizeCompatTests extends WindowTestsBase { } } - @Test - public void testApplyStrategyAgainWhenOpaqueIsDestroyed() { - mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); - setUpDisplaySizeWithApp(2000, 1000); - prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - // Launch another opaque activity - final ActivityRecord opaqueActivity = new ActivityBuilder(mAtm) - .setLaunchedFromUid(mActivity.getUid()) - .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) - .build(); - mTask.addChild(opaqueActivity); - // Transparent activity strategy not applied - assertFalse(opaqueActivity.mTransparentPolicy.isRunning()); - - // Launch translucent Activity - final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) - .setActivityTheme(android.R.style.Theme_Translucent) - .setLaunchedFromUid(mActivity.getUid()) - .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) - .build(); - mTask.addChild(translucentActivity); - // Transparent strategy applied - assertTrue(translucentActivity.mTransparentPolicy.isRunning()); - - spyOn(translucentActivity.mTransparentPolicy); - clearInvocations(translucentActivity.mTransparentPolicy); - - // We destroy the first opaque activity - opaqueActivity.setState(DESTROYED, "testing"); - opaqueActivity.removeImmediately(); - - // Check that updateInheritedLetterbox() is invoked again - verify(translucentActivity.mTransparentPolicy).start(); - } - // TODO(b/333663877): Enable test after fix @Test @RequiresFlagsDisabled({Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION}) @@ -449,282 +389,6 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(cutoutHeight, innerBoundsOf.apply(mActivity).top); } - @Test - public void testResetOpaqueReferenceWhenOpaqueIsDestroyed() { - mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); - setUpDisplaySizeWithApp(2000, 1000); - prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - - // Launch translucent Activity - final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) - .setActivityTheme(android.R.style.Theme_Translucent) - .setLaunchedFromUid(mActivity.getUid()) - .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) - .build(); - mTask.addChild(translucentActivity); - // Transparent strategy applied - assertTrue(translucentActivity.mTransparentPolicy.isRunning()); - - spyOn(translucentActivity.mTransparentPolicy); - clearInvocations(translucentActivity.mTransparentPolicy); - - // We destroy the first opaque activity - mActivity.removeImmediately(); - - // Check that updateInheritedLetterbox() is invoked again on the TransparentPolicy - verify(translucentActivity.mTransparentPolicy).start(); - assertFalse(translucentActivity.mTransparentPolicy.isRunning()); - } - - @Test - public void testNotApplyStrategyAgainWhenOpaqueIsNotDestroyed() { - mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); - setUpDisplaySizeWithApp(2000, 1000); - prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - // Launch another opaque activity - final ActivityRecord opaqueActivity = new ActivityBuilder(mAtm) - .setLaunchedFromUid(mActivity.getUid()) - .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) - .build(); - mTask.addChild(opaqueActivity); - // Transparent activity strategy not applied - assertFalse(opaqueActivity.mTransparentPolicy.isRunning()); - - // Launch translucent Activity - final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) - .setActivityTheme(android.R.style.Theme_Translucent) - .setLaunchedFromUid(mActivity.getUid()) - .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) - .build(); - mTask.addChild(translucentActivity); - // Transparent strategy applied - assertTrue(translucentActivity.mTransparentPolicy.isRunning()); - - spyOn(translucentActivity.mTransparentPolicy); - clearInvocations(translucentActivity.mTransparentPolicy); - - // Check that updateInheritedLetterbox() is invoked again - verify(translucentActivity.mTransparentPolicy, never()).start(); - } - - @Test - public void testApplyStrategyToTranslucentActivities() { - mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); - setUpDisplaySizeWithApp(2000, 1000); - prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); - mActivity.info.setMinAspectRatio(1.2f); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - // Translucent Activity - final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) - .setActivityTheme(android.R.style.Theme_Translucent) - .setLaunchedFromUid(mActivity.getUid()) - .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) - .setMinAspectRatio(1.1f) - .setMaxAspectRatio(3f) - .build(); - mTask.addChild(translucentActivity); - // We check bounds - final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds(); - final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds(); - assertEquals(opaqueBounds, translucentRequestedBounds); - // We check orientation - final int translucentOrientation = - translucentActivity.getRequestedConfigurationOrientation(); - assertEquals(ORIENTATION_PORTRAIT, translucentOrientation); - // We check aspect ratios - assertEquals(1.2f, translucentActivity.getMinAspectRatio(), 0.00001f); - assertEquals(1.5f, translucentActivity.getMaxAspectRatio(), 0.00001f); - } - - @Test - public void testApplyStrategyToTranslucentActivitiesRetainsWindowConfigurationProperties() { - mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); - setUpDisplaySizeWithApp(2000, 1000); - prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - // Translucent Activity - final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) - .setActivityTheme(android.R.style.Theme_Translucent) - .setLaunchedFromUid(mActivity.getUid()) - .build(); - final Configuration requestedConfig = - translucentActivity.getRequestedOverrideConfiguration(); - final WindowConfiguration translucentWinConf = requestedConfig.windowConfiguration; - translucentWinConf.setActivityType(ACTIVITY_TYPE_STANDARD); - translucentWinConf.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - translucentWinConf.setAlwaysOnTop(true); - translucentActivity.onRequestedOverrideConfigurationChanged(requestedConfig); - - mTask.addChild(translucentActivity); - - // The original override of WindowConfiguration should keep. - assertEquals(ACTIVITY_TYPE_STANDARD, translucentActivity.getActivityType()); - assertEquals(WINDOWING_MODE_MULTI_WINDOW, translucentWinConf.getWindowingMode()); - assertTrue(translucentWinConf.isAlwaysOnTop()); - // Unless display is going to be rotated, it should always inherit from parent. - assertEquals(ROTATION_UNDEFINED, translucentWinConf.getDisplayRotation()); - } - - @Test - public void testApplyStrategyToMultipleTranslucentActivities() { - mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); - setUpDisplaySizeWithApp(2000, 1000); - prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); - mActivity.info.setMinAspectRatio(1.2f); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - // Translucent Activity - final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) - .setActivityTheme(android.R.style.Theme_Translucent) - .setLaunchedFromUid(mActivity.getUid()) - .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) - .setMinAspectRatio(1.1f) - .setMaxAspectRatio(3f) - .build(); - mTask.addChild(translucentActivity); - // We check bounds - final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds(); - final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds(); - assertEquals(opaqueBounds, translucentRequestedBounds); - // Launch another translucent activity - final ActivityRecord translucentActivity2 = new ActivityBuilder(mAtm) - .setActivityTheme(android.R.style.Theme_Translucent) - .setLaunchedFromUid(mActivity.getUid()) - .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) - .build(); - mTask.addChild(translucentActivity2); - // We check bounds - final Rect translucent2RequestedBounds = translucentActivity2.getRequestedOverrideBounds(); - assertEquals(opaqueBounds, translucent2RequestedBounds); - } - - @Test - public void testNotApplyStrategyToTranslucentActivitiesOverEmbeddedActivities() { - mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); - setUpDisplaySizeWithApp(2000, 1000); - mActivity.info.screenOrientation = SCREEN_ORIENTATION_PORTRAIT; - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - // Mock the activity as embedded without additional TaskFragment layer in the task for - // simplicity. - doReturn(true).when(mActivity).isEmbedded(); - // Translucent Activity - final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) - .setActivityTheme(android.R.style.Theme_Translucent).build(); - doReturn(false).when(translucentActivity).matchParentBounds(); - mTask.addChild(translucentActivity); - // Check the strategy has not being applied - assertFalse(translucentActivity.mTransparentPolicy.isRunning()); - } - - @Test - public void testTranslucentActivitiesDontGoInSizeCompatMode() { - mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); - setUpDisplaySizeWithApp(2800, 1400); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - prepareUnresizable(mActivity, -1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); - // Rotate to put activity in size compat mode. - rotateDisplay(mActivity.mDisplayContent, ROTATION_90); - assertTrue(mActivity.inSizeCompatMode()); - // Rotate back - rotateDisplay(mActivity.mDisplayContent, ROTATION_0); - assertFalse(mActivity.inSizeCompatMode()); - // We launch a transparent activity - final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) - .setActivityTheme(android.R.style.Theme_Translucent) - .setLaunchedFromUid(mActivity.getUid()) - .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) - .build(); - mTask.addChild(translucentActivity); - // It should not be in SCM - assertFalse(translucentActivity.inSizeCompatMode()); - // We rotate again - rotateDisplay(translucentActivity.mDisplayContent, ROTATION_90); - assertFalse(translucentActivity.inSizeCompatMode()); - } - - @Test - public void testCheckOpaqueIsLetterboxedWhenStrategyIsApplied() { - mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); - setUpDisplaySizeWithApp(2000, 1000); - prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - // Translucent Activity - final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) - .setActivityTheme(android.R.style.Theme_Translucent) - .setLaunchedFromUid(mActivity.getUid()) - .build(); - assertFalse(translucentActivity.fillsParent()); - assertTrue(mActivity.fillsParent()); - mActivity.finishing = true; - assertFalse(mActivity.occludesParent()); - mTask.addChild(translucentActivity); - // The translucent activity won't inherit letterbox behavior from a finishing activity. - assertFalse(translucentActivity.mTransparentPolicy.isRunning()); - } - - @Test - public void testTranslucentActivitiesWhenUnfolding() { - mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); - setUpDisplaySizeWithApp(2800, 1400); - mActivity.mDisplayContent.setIgnoreOrientationRequest( - true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier( - 1.0f /*letterboxVerticalPositionMultiplier*/); - prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); - // We launch a transparent activity - final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) - .setActivityTheme(android.R.style.Theme_Translucent) - .setLaunchedFromUid(mActivity.getUid()) - .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) - .build(); - mTask.addChild(translucentActivity); - assertEquals(translucentActivity.getBounds(), mActivity.getBounds()); - - mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - spyOn(mActivity); - - // Halffold - setFoldablePosture(translucentActivity, true /* isHalfFolded */, - false /* isTabletop */); - verify(mActivity).recomputeConfiguration(); - assertEquals(translucentActivity.getBounds(), mActivity.getBounds()); - clearInvocations(mActivity); - - // Unfold - setFoldablePosture(translucentActivity, false /* isHalfFolded */, - false /* isTabletop */); - verify(mActivity).recomputeConfiguration(); - assertEquals(translucentActivity.getBounds(), mActivity.getBounds()); - } - - @Test - public void testTranslucentActivity_clearSizeCompatMode_inheritedCompatDisplayInsetsCleared() { - mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); - setUpDisplaySizeWithApp(2800, 1400); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - prepareUnresizable(mActivity, -1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); - // Rotate to put activity in size compat mode. - rotateDisplay(mActivity.mDisplayContent, ROTATION_90); - assertTrue(mActivity.inSizeCompatMode()); - - // We launch a transparent activity - final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) - .setActivityTheme(android.R.style.Theme_Translucent) - .setLaunchedFromUid(mActivity.getUid()) - .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) - .build(); - mTask.addChild(translucentActivity); - - // The transparent activity inherits the compat display insets of the opaque activity - // beneath it - assertNotNull(translucentActivity.getCompatDisplayInsets()); - - // Clearing SCM should also clear the inherited compat display insets - translucentActivity.clearSizeCompatMode(); - assertNull(translucentActivity.getCompatDisplayInsets()); - } - @Test public void testRestartProcessIfVisible() { setUpDisplaySizeWithApp(1000, 2500); diff --git a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1d6e307779222c2540a2b3dc8ac48cfae846e798 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java @@ -0,0 +1,637 @@ +/* + * Copyright (C) 2024 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.server.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ROTATION_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; +import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_90; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.WindowConfiguration; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Test class for {@link TransparentPolicy}. + * + * Build/Install/Run: + * atest WmTests:TransparentPolicyTest + */ +@Presubmit +@RunWith(WindowTestRunner.class) +public class TransparentPolicyTest extends WindowTestsBase { + + @Test + public void testNotStartingWhenDisabled() { + runTestScenario((robot) -> { + robot.launchTransparentActivityInTask(); + + robot.checkTopActivityPolicyStateIsNotRunning(); + }, /* policyEnabled */ false); + } + + @Test + public void testNotStartingWithoutTask() { + runTestScenario((robot) -> { + robot.launchTransparentActivity(); + + robot.checkTopActivityPolicyStartNotInvoked(); + robot.checkTopActivityPolicyStateIsNotRunning(); + }); + } + + @Test + public void testPolicyRunningWhenTransparentIsUsed() { + runTestScenario((robot) -> { + robot.launchTransparentActivityInTask(); + + robot.checkTopActivityPolicyStartNotInvoked(); + robot.checkTopActivityPolicyStateIsRunning(); + }); + } + + @Test + public void testCleanLetterboxConfigListenerWhenTranslucentIsDestroyed() { + runTestScenario((robot) -> { + robot.launchTransparentActivityInTask(); + robot.checkTopActivityPolicyStartNotInvoked(); + robot.checkTopActivityPolicyStateIsRunning(); + + robot.clearInteractions(); + robot.destroyTopActivity(); + + robot.checkTopActivityPolicyStopInvoked(); + robot.checkTopActivityPolicyStateIsNotRunning(); + }); + } + + @Test + public void testApplyStrategyAgainWhenOpaqueIsDestroyed() { + runTestScenario((robot) -> { + robot.launchOpaqueActivityInTask(); + robot.checkTopActivityPolicyStateIsNotRunning(); + + robot.launchTransparentActivityInTask(); + robot.checkTopActivityPolicyStateIsRunning(); + + robot.destroyActivity(/* fromTop */ 1); + robot.checkTopActivityPolicyStartInvoked(); + }); + } + + @Test + public void testResetOpaqueReferenceWhenOpaqueIsDestroyed() { + runTestScenario((robot) -> { + robot.launchTransparentActivityInTask(); + + robot.clearInteractions(); + robot.destroyActivity(/* fromTop */ 1); + + robot.checkTopActivityPolicyStartInvoked(); + robot.checkTopActivityPolicyStateIsNotRunning(); + }); + } + + @Test + public void testNotApplyStrategyAgainWhenOpaqueIsNotDestroyed() { + runTestScenario((robot) -> { + robot.launchOpaqueActivityInTask(); + robot.checkTopActivityPolicyStateIsNotRunning(); + + robot.launchTransparentActivityInTask(); + robot.checkTopActivityPolicyStateIsRunning(); + + robot.clearInteractions(); + robot.checkTopActivityPolicyStopNotInvoked(); + }); + } + + @Test + public void testApplyStrategyToTranslucentActivities() { + runTestScenario((robot) -> { + robot.configureTopActivity(/* minAspect */ 1.2f, /* maxAspect */ 1.5f, + SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable */ true); + robot.configureTopActivityIgnoreOrientationRequest(true); + robot.launchActivity(/* minAspect */ 1.1f, /* maxAspect */ 3f, + SCREEN_ORIENTATION_LANDSCAPE, /* transparent */true, /* addToTask */true); + robot.checkTopActivityPolicyStateIsRunning(); + robot.checkTopActivityHasInheritedBoundsFrom(/* fromTop */ 1); + robot.checkTopOrientation(SCREEN_ORIENTATION_PORTRAIT); + robot.checkTopAspectRatios(/* minAspectRatio */ 1.2f, /* maxAspectRatio */ 1.5f); + }); + } + + @Test + public void testApplyStrategyToTransparentActivitiesRetainsWindowConfigurationProperties() { + runTestScenario((robot) -> { + robot.launchTransparentActivity(); + + robot.forceChangeInTopActivityConfiguration(); + robot.attachTopActivityToTask(); + + robot.checkTopActivityConfigurationConfiguration(); + }); + } + + @Test + public void testApplyStrategyToMultipleTranslucentActivities() { + runTestScenario((robot) -> { + robot.launchTransparentActivityInTask(); + robot.checkTopActivityPolicyStateIsRunning(); + robot.checkTopActivityHasInheritedBoundsFrom(/* fromTop */ 1); + + robot.launchTransparentActivityInTask(); + robot.checkTopActivityPolicyStateIsRunning(); + robot.checkTopActivityHasInheritedBoundsFrom(/* fromTop */ 2); + }); + } + + @Test + public void testNotApplyStrategyToTranslucentActivitiesOverEmbeddedActivities() { + runTestScenario((robot) -> { + robot.configureTopActivityAsEmbedded(); + robot.launchTransparentActivityInTask(); + + robot.checkTopActivityPolicyStartNotInvoked(); + robot.checkTopActivityPolicyStateIsNotRunning(); + }); + } + + @Test + public void testTranslucentActivitiesDontGoInSizeCompatMode() { + runTestScenario((robot) -> { + robot.configureTopActivityIgnoreOrientationRequest(true); + robot.configureUnresizableTopActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.rotateDisplayForTopActivity(ROTATION_90); + robot.checkTopActivitySizeCompatMode(/* inScm */ true); + robot.rotateDisplayForTopActivity(ROTATION_0); + robot.checkTopActivitySizeCompatMode(/* inScm */ false); + + robot.launchTransparentActivityInTask(); + robot.checkTopActivitySizeCompatMode(/* inScm */ false); + robot.rotateDisplayForTopActivity(ROTATION_90); + robot.checkTopActivitySizeCompatMode(/* inScm */ false); + }, /* displayWidth */ 2800, /* displayHeight */ 1400); + } + + @Test + public void testCheckOpaqueIsLetterboxedWhenStrategyIsApplied() { + runTestScenario((robot) -> { + robot.configureUnresizableTopActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.configureTopActivityIgnoreOrientationRequest(true); + robot.launchTransparentActivity(); + + robot.assertFalseOnTopActivity(ActivityRecord::fillsParent); + robot.assertTrueOnActivity(/* fromTop */ 1, ActivityRecord::fillsParent); + robot.applyTo(/* fromTop */ 1, (activity) -> { + activity.finishing = true; + }); + robot.assertFalseOnActivity(/* fromTop */ 1, ActivityRecord::occludesParent); + robot.attachTopActivityToTask(); + + robot.checkTopActivityPolicyStateIsNotRunning(); + }); + } + + @Test + public void testTranslucentActivitiesWhenUnfolding() { + runTestScenario((robot) -> { + robot.applyToTop((activity) -> { + activity.mWmService.mLetterboxConfiguration + .setLetterboxHorizontalPositionMultiplier(1.0f); + }); + robot.configureUnresizableTopActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.configureTopActivityIgnoreOrientationRequest(true); + robot.launchTransparentActivityInTask(); + robot.checkTopActivityHasInheritedBoundsFrom(/* fromTop */ 1); + + robot.configureTaskWindowingMode(WINDOWING_MODE_FULLSCREEN); + + robot.configureTopActivityFoldablePosture(/* isHalfFolded */ true, + /* isTabletop */ false); + robot.checkTopActivityRecomputedConfiguration(); + robot.checkTopActivityHasInheritedBoundsFrom(/* fromTop */ 1); + robot.clearInteractions(); + + robot.configureTopActivityFoldablePosture(/* isHalfFolded */ false, + /* isTabletop */ false); + robot.checkTopActivityRecomputedConfiguration(); + robot.checkTopActivityHasInheritedBoundsFrom(/* fromTop */ 1); + }, /* displayWidth */ 2800, /* displayHeight */ 1400); + } + + + @Test + public void testTranslucentActivity_clearSizeCompatMode_inheritedCompatDisplayInsetsCleared() { + runTestScenario((robot) -> { + robot.configureTopActivityIgnoreOrientationRequest(true); + robot.configureUnresizableTopActivity(SCREEN_ORIENTATION_PORTRAIT); + // Rotate to put activity in size compat mode. + robot.rotateDisplayForTopActivity(ROTATION_90); + robot.checkTopActivitySizeCompatMode(/* inScm */ true); + + robot.launchTransparentActivityInTask(); + robot.assertNotNullOnTopActivity(ActivityRecord::getCompatDisplayInsets); + robot.applyToTop(ActivityRecord::clearSizeCompatMode); + robot.assertNullOnTopActivity(ActivityRecord::getCompatDisplayInsets); + }); + } + + private void runTestScenario(Consumer<TransparentPolicyRobotTest> consumer, + boolean policyEnabled, int displayWidth, int displayHeight) { + spyOn(mWm.mLetterboxConfiguration); + when(mWm.mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) + .thenReturn(policyEnabled); + final TestDisplayContent.Builder builder = new TestDisplayContent.Builder(mAtm, + displayWidth, displayHeight); + final Task task = new TaskBuilder(mSupervisor).setDisplay(builder.build()) + .setCreateActivity(true).build(); + final ActivityRecord opaqueActivity = task.getTopNonFinishingActivity(); + final TransparentPolicyRobotTest robot = new TransparentPolicyRobotTest(mAtm, task, + opaqueActivity); + consumer.accept(robot); + } + + private void runTestScenario(Consumer<TransparentPolicyRobotTest> consumer, + int displayWidth, int displayHeight) { + runTestScenario(consumer, /* policyEnabled */ true, displayWidth, displayHeight); + } + + private void runTestScenario(Consumer<TransparentPolicyRobotTest> consumer, + boolean policyEnabled) { + runTestScenario(consumer, policyEnabled, /* displayWidth */ 2000, + /* displayHeight */ 1000); + } + + private void runTestScenario(Consumer<TransparentPolicyRobotTest> consumer) { + runTestScenario(consumer, /* policyEnabled */ true); + } + + /** + * Robot pattern implementation for TransparentPolicy + * TODO(b/344587983): Extract Robot to be reused in different test classes. + */ + private static class TransparentPolicyRobotTest { + + private final ActivityTaskManagerService mAtm; + + private final Task mTask; + + private final ActivityStackTest mActivityStack; + + private WindowConfiguration mTopActivityWindowConfiguration; + + private TransparentPolicyRobotTest(ActivityTaskManagerService atm, Task task, + ActivityRecord opaqueActivity) { + mAtm = atm; + mTask = task; + mActivityStack = new ActivityStackTest(); + mActivityStack.pushActivity(opaqueActivity); + spyOn(opaqueActivity.mTransparentPolicy); + } + + void configureTopActivityAsEmbedded() { + final ActivityRecord topActivity = mActivityStack.top(); + spyOn(topActivity); + doReturn(true).when(topActivity).isEmbedded(); + } + + private void launchActivity(float minAspectRatio, float maxAspectRatio, + @Configuration.Orientation int orientation, boolean transparent, + boolean addToTask) { + final ActivityBuilder activityBuilder = new ActivityBuilder(mAtm) + .setScreenOrientation(orientation) + .setLaunchedFromUid(mActivityStack.base().getUid()); + if (transparent) { + activityBuilder.setActivityTheme(android.R.style.Theme_Translucent); + } + if (minAspectRatio >= 0) { + activityBuilder.setMinAspectRatio(minAspectRatio); + } + if (maxAspectRatio >= 0) { + activityBuilder.setMaxAspectRatio(maxAspectRatio); + } + final ActivityRecord newActivity = activityBuilder.build(); + if (addToTask) { + mTask.addChild(newActivity); + } + spyOn(newActivity.mTransparentPolicy); + mActivityStack.pushActivity(newActivity); + } + + void attachTopActivityToTask() { + mTask.addChild(mActivityStack.top()); + } + + void launchTransparentActivity() { + launchActivity(/*minAspectRatio */ -1, /* maxAspectRatio */ -1, + SCREEN_ORIENTATION_PORTRAIT, /* transparent */ true, + /* addToTask */ false); + } + + void launchTransparentActivityInTask() { + launchActivity(/*minAspectRatio */ -1, /* maxAspectRatio */ -1, + SCREEN_ORIENTATION_PORTRAIT, /* transparent */ true, + /* addToTask */true); + } + + void launchOpaqueActivityInTask() { + launchActivity(/*minAspectRatio */ -1, /* maxAspectRatio */ -1, + SCREEN_ORIENTATION_PORTRAIT, /* transparent */ false, + /* addToTask */true); + } + + void destroyTopActivity() { + mActivityStack.top().removeImmediately(); + } + + void destroyActivity(int fromTop) { + mActivityStack.applyTo(/* fromTop */ fromTop, ActivityRecord::removeImmediately); + } + + void forceChangeInTopActivityConfiguration() { + mActivityStack.applyToTop((activity) -> { + final Configuration requestedConfig = activity.getRequestedOverrideConfiguration(); + mTopActivityWindowConfiguration = requestedConfig.windowConfiguration; + mTopActivityWindowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD); + mTopActivityWindowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + mTopActivityWindowConfiguration.setAlwaysOnTop(true); + activity.onRequestedOverrideConfigurationChanged(requestedConfig); + }); + } + + void checkTopActivityPolicyStateIsRunning() { + assertTrue(mActivityStack.top().mTransparentPolicy.isRunning()); + } + + void checkTopActivityPolicyStateIsNotRunning() { + assertFalse(mActivityStack.top().mTransparentPolicy.isRunning()); + } + + void checkTopActivityPolicyStopInvoked() { + verify(mActivityStack.top().mTransparentPolicy).stop(); + } + + void checkTopActivityPolicyStopNotInvoked() { + mActivityStack.applyToTop((activity) -> { + verify(activity.mTransparentPolicy, never()).stop(); + }); + } + + void checkTopActivityPolicyStartInvoked() { + mActivityStack.applyToTop((activity) -> { + verify(activity.mTransparentPolicy).start(); + }); + } + + void checkTopActivityPolicyStartNotInvoked() { + verify(mActivityStack.top().mTransparentPolicy, never()).start(); + } + + void assertTrueOnActivity(int fromTop, Predicate<ActivityRecord> predicate) { + mActivityStack.applyTo(fromTop, (activity) -> { + Assert.assertTrue(predicate.test(activity)); + }); + } + + void assertFalseOnTopActivity(Predicate<ActivityRecord> predicate) { + Assert.assertFalse(predicate.test(mActivityStack.top())); + } + + void assertFalseOnActivity(int fromTop, Predicate<ActivityRecord> predicate) { + mActivityStack.applyTo(fromTop, (activity) -> { + Assert.assertFalse(predicate.test(activity)); + }); + } + + void assertNotNullOnTopActivity(Function<ActivityRecord, Object> getter) { + Assert.assertNotNull(getter.apply(mActivityStack.top())); + } + + void assertNullOnTopActivity(Function<ActivityRecord, Object> getter) { + Assert.assertNull(getter.apply(mActivityStack.top())); + } + + void checkTopActivityConfigurationConfiguration() { + mActivityStack.applyToTop((activity) -> { + // The original override of WindowConfiguration should keep. + assertEquals(ACTIVITY_TYPE_STANDARD, activity.getActivityType()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, + mTopActivityWindowConfiguration.getWindowingMode()); + assertTrue(mTopActivityWindowConfiguration.isAlwaysOnTop()); + // Unless display is going to be rotated, it should always inherit from parent. + assertEquals(ROTATION_UNDEFINED, + mTopActivityWindowConfiguration.getDisplayRotation()); + }); + } + + void checkTopActivityHasInheritedBoundsFrom(int fromTop) { + final ActivityRecord topActivity = mActivityStack.top(); + final ActivityRecord otherActivity = mActivityStack.getFromTop(/* fromTop */ fromTop); + final Rect opaqueBounds = otherActivity.getConfiguration().windowConfiguration + .getBounds(); + final Rect translucentRequestedBounds = topActivity.getRequestedOverrideBounds(); + Assert.assertEquals(opaqueBounds, translucentRequestedBounds); + } + + void checkTopActivityRecomputedConfiguration() { + verify(mActivityStack.top()).recomputeConfiguration(); + } + + void checkTopOrientation(int orientation) { + Assert.assertEquals(orientation, mActivityStack.top() + .getRequestedConfigurationOrientation()); + } + + void configureTaskWindowingMode(int windowingMode) { + mTask.setWindowingMode(windowingMode); + } + + void checkTopAspectRatios(float minAspectRatio, float maxAspectRatio) { + final ActivityRecord topActivity = mActivityStack.top(); + Assert.assertEquals(minAspectRatio, topActivity.getMinAspectRatio(), 0.0001); + Assert.assertEquals(maxAspectRatio, topActivity.getMaxAspectRatio(), 0.0001); + } + + void checkTopActivitySizeCompatMode(boolean inScm) { + Assert.assertEquals(inScm, mActivityStack.top().inSizeCompatMode()); + } + + void clearInteractions() { + mActivityStack.applyToAll((activity) -> { + clearInvocations(activity); + clearInvocations(activity.mTransparentPolicy); + }); + } + + void configureTopActivity(float minAspect, float maxAspect, int screenOrientation, + boolean isUnresizable) { + prepareLimitedBounds(mActivityStack.top(), minAspect, maxAspect, screenOrientation, + isUnresizable); + } + + void configureTopActivityIgnoreOrientationRequest(boolean ignoreOrientationRequest) { + mActivityStack.top().mDisplayContent + .setIgnoreOrientationRequest(ignoreOrientationRequest); + } + + void configureUnresizableTopActivity(int screenOrientation) { + configureTopActivity(-1, -1, screenOrientation, true); + } + + void applyToTop(Consumer<ActivityRecord> consumer) { + consumer.accept(mActivityStack.top()); + } + + void applyTo(int fromTop, Consumer<ActivityRecord> consumer) { + mActivityStack.applyTo(fromTop, consumer); + } + + void rotateDisplayForTopActivity(int rotation) { + rotateDisplay(mActivityStack.top().mDisplayContent, rotation); + } + + /** + * Setups activity with restriction on its bounds, such as maxAspect, minAspect, + * fixed orientation, and/or whether it is resizable. + */ + void prepareLimitedBounds(ActivityRecord activity, float minAspect, float maxAspect, + int screenOrientation, boolean isUnresizable) { + activity.info.resizeMode = isUnresizable + ? RESIZE_MODE_UNRESIZEABLE + : RESIZE_MODE_RESIZEABLE; + final Task task = activity.getTask(); + if (task != null) { + // Update the Task resize value as activity will follow the task. + task.mResizeMode = activity.info.resizeMode; + task.getRootActivity().info.resizeMode = activity.info.resizeMode; + } + activity.setVisibleRequested(true); + if (maxAspect >= 0) { + activity.info.setMaxAspectRatio(maxAspect); + } + if (minAspect >= 0) { + activity.info.setMinAspectRatio(minAspect); + } + if (screenOrientation != SCREEN_ORIENTATION_UNSPECIFIED) { + activity.info.screenOrientation = screenOrientation; + activity.setRequestedOrientation(screenOrientation); + } + // Make sure to use the provided configuration to construct the size compat fields. + activity.clearSizeCompatMode(); + activity.ensureActivityConfiguration(); + // Make sure the display configuration reflects the change of activity. + if (activity.mDisplayContent.updateOrientation()) { + activity.mDisplayContent.sendNewConfiguration(); + } + } + + void configureTopActivityFoldablePosture(boolean isHalfFolded, boolean isTabletop) { + mActivityStack.applyToTop((activity) -> { + final DisplayRotation r = activity.mDisplayContent.getDisplayRotation(); + doReturn(isHalfFolded).when(r).isDisplaySeparatingHinge(); + doReturn(false).when(r) + .isDeviceInPosture(any(DeviceStateController.DeviceState.class), + anyBoolean()); + if (isHalfFolded) { + doReturn(true).when(r) + .isDeviceInPosture(DeviceStateController.DeviceState.HALF_FOLDED, + isTabletop); + } + activity.recomputeConfiguration(); + }); + } + + private static void rotateDisplay(DisplayContent display, int rotation) { + final Configuration c = new Configuration(); + display.getDisplayRotation().setRotation(rotation); + display.computeScreenConfiguration(c); + display.onRequestedOverrideConfigurationChanged(c); + } + + /** + * Contains all the ActivityRecord launched in the test. This is different from what's in + * the Task because activities are added here even if not added to tasks. + */ + private static class ActivityStackTest { + private final List<ActivityRecord> mActivities = new ArrayList<>(); + + void pushActivity(ActivityRecord activityRecord) { + mActivities.add(activityRecord); + } + + void applyToTop(Consumer<ActivityRecord> consumer) { + consumer.accept(top()); + } + + ActivityRecord getFromTop(int fromTop) { + return mActivities.get(mActivities.size() - fromTop - 1); + } + + ActivityRecord base() { + return mActivities.get(0); + } + + ActivityRecord top() { + return mActivities.get(mActivities.size() - 1); + } + + // Allows access to the activity at position beforeLast from the top. + // If fromTop = 0 the activity used is the top one. + void applyTo(int fromTop, Consumer<ActivityRecord> consumer) { + consumer.accept(getFromTop(fromTop)); + } + + void applyToAll(Consumer<ActivityRecord> consumer) { + for (int i = mActivities.size() - 1; i >= 0; i--) { + consumer.accept(mActivities.get(i)); + } + } + } + } +}