Loading services/core/java/com/android/server/wm/ActivityRecord.java +23 −17 Original line number Diff line number Diff line Loading @@ -802,6 +802,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final LetterboxUiController mLetterboxUiController; /** * The policy for transparent activities */ final TransparentPolicy mTransparentPolicy; /** * The scale to fit at least one side of the activity to its parent. If the activity uses * 1920x1080, and the actually size on the screen is 960x540, then the scale is 0.5. Loading Loading @@ -1698,7 +1703,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (isState(RESUMED)) { newParent.setResumedActivity(this, "onParentChanged"); } mLetterboxUiController.updateInheritedLetterbox(); mTransparentPolicy.start(); } if (rootTask != null && rootTask.topRunningActivity() == this) { Loading Loading @@ -2136,6 +2141,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Don't move below setOrientation(info.screenOrientation) since it triggers // getOverrideOrientation that requires having mLetterboxUiController // initialised. mTransparentPolicy = new TransparentPolicy(this, mWmService.mLetterboxConfiguration); mLetterboxUiController = new LetterboxUiController(mWmService, this); mCameraCompatControlEnabled = mWmService.mContext.getResources() .getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled); Loading Loading @@ -8080,13 +8086,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Configuration.Orientation int getRequestedConfigurationOrientation(boolean forDisplay, @ActivityInfo.ScreenOrientation int requestedOrientation) { if (mLetterboxUiController.hasInheritedOrientation()) { if (mTransparentPolicy.hasInheritedOrientation()) { final RootDisplayArea root = getRootDisplayArea(); if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) { return reverseConfigurationOrientation( mLetterboxUiController.getInheritedOrientation()); mTransparentPolicy.getInheritedOrientation()); } else { return mLetterboxUiController.getInheritedOrientation(); return mTransparentPolicy.getInheritedOrientation(); } } if (task != null && requestedOrientation == SCREEN_ORIENTATION_BEHIND) { Loading Loading @@ -8302,8 +8308,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Nullable CompatDisplayInsets getCompatDisplayInsets() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedCompatDisplayInsets(); if (mTransparentPolicy.isRunning()) { return mTransparentPolicy.getInheritedCompatDisplayInsets(); } return mCompatDisplayInsets; } Loading Loading @@ -8466,7 +8472,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } mSizeCompatBounds = null; mCompatDisplayInsets = null; mLetterboxUiController.clearInheritedCompatDisplayInsets(); mTransparentPolicy.clearInheritedCompatDisplayInsets(); } @VisibleForTesting Loading Loading @@ -8784,8 +8790,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; } // TODO(b/256564921): Investigate if we need new metrics for translucent activities if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedAppCompatState(); if (mTransparentPolicy.isRunning()) { return mTransparentPolicy.getInheritedAppCompatState(); } if (mInSizeCompatModeForBounds) { return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE; Loading Loading @@ -8938,7 +8944,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // We check if the current activity is transparent. In that case we need to // recomputeConfiguration of the first opaque activity beneath, to allow a // proper computation of the new bounds. if (!mLetterboxUiController.applyOnOpaqueActivityBelow( if (!mTransparentPolicy.applyOnOpaqueActivityBelow( ActivityRecord::recomputeConfiguration)) { onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration()); } Loading Loading @@ -9411,7 +9417,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A void updateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) { // Only allow to scale down. mSizeCompatScale = mLetterboxUiController.findOpaqueNotFinishingActivityBelow() mSizeCompatScale = mTransparentPolicy.findOpaqueNotFinishingActivityBelow() .map(activityRecord -> activityRecord.mSizeCompatScale) .orElseGet(() -> { final int contentW = resolvedAppBounds.width(); Loading @@ -9424,7 +9430,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { if (mTransparentPolicy.isRunning()) { // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity // is letterboxed. return false; Loading Loading @@ -9487,7 +9493,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A public Rect getBounds() { // TODO(b/268458693): Refactor configuration inheritance in case of translucent activities final Rect superBounds = super.getBounds(); return mLetterboxUiController.findOpaqueNotFinishingActivityBelow() return mTransparentPolicy.findOpaqueNotFinishingActivityBelow() .map(ActivityRecord::getBounds) .orElseGet(() -> { if (mSizeCompatBounds != null) { Loading Loading @@ -9851,8 +9857,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * Returns the min aspect ratio of this activity. */ float getMinAspectRatio() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedMinAspectRatio(); if (mTransparentPolicy.isRunning()) { return mTransparentPolicy.getInheritedMinAspectRatio(); } if (info.applicationInfo == null) { return info.getMinAspectRatio(); Loading Loading @@ -9902,8 +9908,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } float getMaxAspectRatio() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedMaxAspectRatio(); if (mTransparentPolicy.isRunning()) { return mTransparentPolicy.getInheritedMaxAspectRatio(); } return info.getMaxAspectRatio(); } Loading services/core/java/com/android/server/wm/LetterboxUiController.java +11 −108 Original line number Diff line number Diff line Loading @@ -129,10 +129,7 @@ import com.android.server.wm.utils.OptPropFactory.OptProp; import com.android.window.flags.Flags; import java.io.PrintWriter; import java.util.Optional; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Predicate; /** Controls behaviour of the letterbox UI for {@link mActivityRecord}. */ // TODO(b/185262487): Improve test coverage of this class. Parts of it are tested in Loading Loading @@ -228,10 +225,6 @@ final class LetterboxUiController { @NonNull private final OptProp mFakeFocusOptProp; // TODO(b/336807329) Eventually eemove this dependency when refactoring Reachability. @NonNull private final TransparentPolicy mTransparentPolicy; private boolean mIsRelaunchingAfterRequestedOrientationChanged; private boolean mLastShouldShowLetterboxUi; Loading @@ -245,24 +238,6 @@ final class LetterboxUiController { // to use it after since controller is only used in ActivityRecord. mActivityRecord = activityRecord; mTransparentPolicy = new TransparentPolicy(activityRecord, mLetterboxConfiguration, new Predicate<ActivityRecord>() { @Override public boolean test(ActivityRecord opaqueActivity) { if (opaqueActivity == null || opaqueActivity.isEmbedded()) { // We skip letterboxing if the translucent activity doesn't have any // opaque activities beneath or the activity below is embedded which // never has letterbox. mActivityRecord.recomputeConfiguration(); return true; } if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent() || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) { return true; } return false; } }); PackageManager packageManager = wmService.mContext.getPackageManager(); final OptPropFactory optPropBuilder = new OptPropFactory(packageManager, Loading Loading @@ -340,7 +315,7 @@ final class LetterboxUiController { mLetterbox.destroy(); mLetterbox = null; } mTransparentPolicy.stop(); mActivityRecord.mTransparentPolicy.stop(); } void onMovedToDisplay(int displayId) { Loading @@ -349,11 +324,6 @@ final class LetterboxUiController { } } @NonNull TransparentPolicy getTransparentPolicy() { return mTransparentPolicy; } /** * Whether should ignore app requested orientation in response to an app * calling {@link android.app.Activity#setRequestedOrientation}. Loading Loading @@ -854,7 +824,7 @@ final class LetterboxUiController { // For this reason we use ActivityRecord#getBounds() that the translucent activity // inherits from the first opaque activity beneath and also takes care of the scaling // in case of activities in size compat mode. final Rect innerFrame = hasInheritedLetterboxBehavior() final Rect innerFrame = mActivityRecord.mTransparentPolicy.isRunning() ? mActivityRecord.getBounds() : w.getFrame(); mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint); if (mDoubleTapEvent) { Loading Loading @@ -1320,10 +1290,9 @@ final class LetterboxUiController { } // Use screen resolved bounds which uses resolved bounds or size compat bounds // as activity bounds can sometimes be empty final Rect opaqueActivityBounds = hasInheritedLetterboxBehavior() ? getTransparentPolicy().getTransparentPolicyState() .mFirstOpaqueActivity.getScreenResolvedBounds() : mActivityRecord.getScreenResolvedBounds(); final Rect opaqueActivityBounds = mActivityRecord.mTransparentPolicy .getFirstOpaqueActivity().map(ActivityRecord::getScreenResolvedBounds) .orElse(mActivityRecord.getScreenResolvedBounds()); return mLetterboxConfiguration.getIsHorizontalReachabilityEnabled() && parentConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN Loading Loading @@ -1358,11 +1327,10 @@ final class LetterboxUiController { return false; } // Use screen resolved bounds which uses resolved bounds or size compat bounds // as activity bounds can sometimes be empty final Rect opaqueActivityBounds = hasInheritedLetterboxBehavior() ? getTransparentPolicy().getTransparentPolicyState() .mFirstOpaqueActivity.getScreenResolvedBounds() : mActivityRecord.getScreenResolvedBounds(); // as activity bounds can sometimes be empty. final Rect opaqueActivityBounds = mActivityRecord.mTransparentPolicy .getFirstOpaqueActivity().map(ActivityRecord::getScreenResolvedBounds) .orElse(mActivityRecord.getScreenResolvedBounds()); return mLetterboxConfiguration.getIsVerticalReachabilityEnabled() && parentConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN Loading Loading @@ -1469,7 +1437,8 @@ final class LetterboxUiController { // corners because we assume the specific layout would. This is the case when the layout // of the translucent activity uses only a part of all the bounds because of the use of // LayoutParams.WRAP_CONTENT. if (hasInheritedLetterboxBehavior() && (cropBounds.width() != mainWindow.mRequestedWidth if (mActivityRecord.mTransparentPolicy.isRunning() && (cropBounds.width() != mainWindow.mRequestedWidth || cropBounds.height() != mainWindow.mRequestedHeight)) { return null; } Loading Loading @@ -1773,72 +1742,6 @@ final class LetterboxUiController { ); } /** * Handles translucent activities letterboxing inheriting constraints from the * first opaque activity beneath. */ void updateInheritedLetterbox() { mTransparentPolicy.start(); } /** * @return {@code true} if the current activity is translucent with an opaque activity * beneath. In this case it will inherit bounds, orientation and aspect ratios from * the first opaque activity beneath. */ boolean hasInheritedLetterboxBehavior() { return mTransparentPolicy.hasInheritedLetterboxBehavior(); } /** * @return {@code true} if the current activity is translucent with an opaque activity * beneath and needs to inherit its orientation. */ boolean hasInheritedOrientation() { return mTransparentPolicy.hasInheritedOrientation(); } float getInheritedMinAspectRatio() { return mTransparentPolicy.getInheritedMinAspectRatio(); } float getInheritedMaxAspectRatio() { return mTransparentPolicy.getInheritedMaxAspectRatio(); } int getInheritedAppCompatState() { return mTransparentPolicy.getInheritedAppCompatState(); } @Configuration.Orientation int getInheritedOrientation() { return mTransparentPolicy.getInheritedOrientation(); } ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { return mTransparentPolicy.getInheritedCompatDisplayInsets(); } void clearInheritedCompatDisplayInsets() { mTransparentPolicy.clearInheritedCompatDisplayInsets(); } /** * In case of translucent activities, it consumes the {@link ActivityRecord} of the first opaque * activity beneath using the given consumer and returns {@code true}. */ boolean applyOnOpaqueActivityBelow(@NonNull Consumer<ActivityRecord> consumer) { return mTransparentPolicy.applyOnOpaqueActivityBelow(consumer); } /** * @return The first not finishing opaque activity beneath the current translucent activity * if it exists and the strategy is enabled. */ Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() { return mTransparentPolicy.findOpaqueNotFinishingActivityBelow(); } @NonNull private static BooleanSupplier asLazy(@NonNull BooleanSupplier supplier) { return new BooleanSupplier() { Loading services/core/java/com/android/server/wm/TransparentPolicy.java +57 −50 Original line number Diff line number Diff line Loading @@ -40,6 +40,11 @@ import java.util.function.Predicate; /** * Encapsulate logic about translucent activities. * <p/> * An activity is defined as translucent if {@link ActivityRecord#fillsParent()} returns * {@code false}. When the policy is running for a letterboxed activity, a transparent activity * will inherit constraints about bounds, aspect ratios and orientation from the first not finishing * activity below. */ class TransparentPolicy { Loading @@ -50,30 +55,28 @@ class TransparentPolicy { private static final Predicate<ActivityRecord> FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE = ActivityRecord::occludesParent; // The predicate to check to skip the policy @NonNull private final Predicate<ActivityRecord> mSkipLetterboxPredicate; // The ActivityRecord this policy relates to. @NonNull private final ActivityRecord mActivityRecord; // If transparent activity policy is enabled. @NonNull private final BooleanSupplier mIsTranslucentLetterboxingEnabledSupplier; // The list of observers for the destroy event of candidate opaque activities // when dealing with translucent activities. @NonNull private final List<TransparentPolicy> mDestroyListeners = new ArrayList<>(); // THe current state for the possible transparent activity // The current state for the possible transparent activity @NonNull private final TransparentPolicyState mTransparentPolicyState; TransparentPolicy(@NonNull ActivityRecord activityRecord, @NonNull LetterboxConfiguration letterboxConfiguration, @NonNull Predicate<ActivityRecord> skipLetterboxPredicate) { @NonNull LetterboxConfiguration letterboxConfiguration) { mActivityRecord = activityRecord; mIsTranslucentLetterboxingEnabledSupplier = () -> letterboxConfiguration .isTranslucentLetterboxingEnabled(); mSkipLetterboxPredicate = skipLetterboxPredicate; mIsTranslucentLetterboxingEnabledSupplier = letterboxConfiguration::isTranslucentLetterboxingEnabled; mTransparentPolicyState = new TransparentPolicyState(activityRecord); } Loading @@ -98,7 +101,7 @@ class TransparentPolicy { true /* traverseTopToBottom */); // We check if we need for some reason to skip the policy gievn the specific first // opaque activity if (mSkipLetterboxPredicate.test(firstOpaqueActivity)) { if (shouldSkipTransparentPolicy(firstOpaqueActivity)) { return; } mTransparentPolicyState.start(firstOpaqueActivity); Loading @@ -112,7 +115,12 @@ class TransparentPolicy { mTransparentPolicyState.reset(); } boolean hasInheritedLetterboxBehavior() { /** * @return {@code true} if the current activity is translucent with an opaque activity * beneath and the related policy is running. In this case it will inherit bounds, orientation * and aspect ratios from the first opaque activity beneath. */ boolean isRunning() { return mTransparentPolicyState.isRunning(); } Loading @@ -121,35 +129,33 @@ class TransparentPolicy { * beneath and needs to inherit its orientation. */ boolean hasInheritedOrientation() { // To force a different orientation, the transparent one needs to have an explicit one // otherwise the existing one is fine and the actual orientation will depend on the // bounds. // To avoid wrong behaviour, we're not forcing orientation for activities with not // fixed orientation (e.g. permission dialogs). return hasInheritedLetterboxBehavior() // To avoid wrong behaviour (e.g. permission dialogs not centered or with wrong size), // transparent activities inherit orientation from the first opaque activity below only if // they explicitly define an orientation different from SCREEN_ORIENTATION_UNSPECIFIED. return isRunning() && mActivityRecord.getOverrideOrientation() != SCREEN_ORIENTATION_UNSPECIFIED; } float getInheritedMinAspectRatio() { return mTransparentPolicyState.getInheritedMinAspectRatio(); return mTransparentPolicyState.mInheritedMinAspectRatio; } float getInheritedMaxAspectRatio() { return mTransparentPolicyState.getInheritedMaxAspectRatio(); return mTransparentPolicyState.mInheritedMaxAspectRatio; } int getInheritedAppCompatState() { return mTransparentPolicyState.getInheritedAppCompatState(); return mTransparentPolicyState.mInheritedAppCompatState; } @Configuration.Orientation int getInheritedOrientation() { return mTransparentPolicyState.getInheritedOrientation(); return mTransparentPolicyState.mInheritedOrientation; } ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { return mTransparentPolicyState.getInheritedCompatDisplayInsets(); return mTransparentPolicyState.mInheritedCompatDisplayInsets; } void clearInheritedCompatDisplayInsets() { Loading @@ -168,6 +174,12 @@ class TransparentPolicy { return mTransparentPolicyState.applyOnOpaqueActivityBelow(consumer); } @NonNull Optional<ActivityRecord> getFirstOpaqueActivity() { return isRunning() ? Optional.of(mTransparentPolicyState.mFirstOpaqueActivity) : Optional.empty(); } /** * @return The first not finishing opaque activity beneath the current translucent activity * if it exists and the strategy is enabled. Loading @@ -176,6 +188,22 @@ class TransparentPolicy { return mTransparentPolicyState.findOpaqueNotFinishingActivityBelow(); } // We evaluate the case when the policy should not be applied. private boolean shouldSkipTransparentPolicy(@Nullable ActivityRecord opaqueActivity) { if (opaqueActivity == null || opaqueActivity.isEmbedded()) { // We skip letterboxing if the translucent activity doesn't have any // opaque activities beneath or the activity below is embedded which // never has letterbox. mActivityRecord.recomputeConfiguration(); return true; } if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent() || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) { return true; } return false; } /** Resets the screen size related fields so they can be resolved by requested bounds later. */ private static void resetTranslucentOverrideConfig(Configuration config) { // The values for the following properties will be defined during the configuration Loading Loading @@ -212,10 +240,11 @@ class TransparentPolicy { private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; // The CompatDisplayInsets of the opaque activity beneath the translucent one. @Nullable private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets; @Nullable ActivityRecord mFirstOpaqueActivity; private ActivityRecord mFirstOpaqueActivity; /* * WindowContainerListener responsible to make translucent activities inherit Loading @@ -231,9 +260,8 @@ class TransparentPolicy { private void start(@NonNull ActivityRecord firstOpaqueActivity) { mFirstOpaqueActivity = firstOpaqueActivity; mFirstOpaqueActivity.mLetterboxUiController.getTransparentPolicy() .mDestroyListeners.add(mActivityRecord.mLetterboxUiController .getTransparentPolicy()); mFirstOpaqueActivity.mTransparentPolicy .mDestroyListeners.add(mActivityRecord.mTransparentPolicy); inheritFromOpaque(firstOpaqueActivity); final WindowContainer<?> parent = mActivityRecord.getParent(); mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation( Loading Loading @@ -284,9 +312,8 @@ class TransparentPolicy { mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; mInheritedCompatDisplayInsets = null; if (mFirstOpaqueActivity != null) { mFirstOpaqueActivity.mLetterboxUiController.getTransparentPolicy() .mDestroyListeners.remove(mActivityRecord.mLetterboxUiController .getTransparentPolicy()); mFirstOpaqueActivity.mTransparentPolicy .mDestroyListeners.remove(mActivityRecord.mTransparentPolicy); } mFirstOpaqueActivity = null; } Loading @@ -295,26 +322,6 @@ class TransparentPolicy { return mLetterboxConfigListener != null; } private int getInheritedOrientation() { return mInheritedOrientation; } private float getInheritedMinAspectRatio() { return mInheritedMinAspectRatio; } private float getInheritedMaxAspectRatio() { return mInheritedMaxAspectRatio; } private int getInheritedAppCompatState() { return mInheritedAppCompatState; } private ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { return mInheritedCompatDisplayInsets; } private void clearInheritedCompatDisplayInsets() { mInheritedCompatDisplayInsets = null; } Loading services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +4 −3 Original line number Diff line number Diff line Loading @@ -77,6 +77,7 @@ import static org.mockito.ArgumentMatchers.anyString; 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.annotation.Nullable; import android.compat.testing.PlatformCompatChangeRule; Loading Loading @@ -110,7 +111,7 @@ import org.junit.rules.TestRule; import org.junit.runner.RunWith; /** * Test class for {@link LetterboxUiControllerTest}. * Test class for {@link LetterboxUiController}. * * Build/Install/Run: * atest WmTests:LetterboxUiControllerTest Loading Loading @@ -521,8 +522,8 @@ public class LetterboxUiControllerTest extends WindowTestsBase { final Rect opaqueBounds = new Rect(0, 0, 500, 300); doReturn(opaqueBounds).when(mActivity).getBounds(); // Activity is translucent spyOn(mActivity.mLetterboxUiController); doReturn(true).when(mActivity.mLetterboxUiController).hasInheritedLetterboxBehavior(); spyOn(mActivity.mTransparentPolicy); when(mActivity.mTransparentPolicy.isRunning()).thenReturn(true); // Makes requested sizes different mainWindow.mRequestedWidth = opaqueBounds.width() - 1; Loading services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +19 −21 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
services/core/java/com/android/server/wm/ActivityRecord.java +23 −17 Original line number Diff line number Diff line Loading @@ -802,6 +802,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final LetterboxUiController mLetterboxUiController; /** * The policy for transparent activities */ final TransparentPolicy mTransparentPolicy; /** * The scale to fit at least one side of the activity to its parent. If the activity uses * 1920x1080, and the actually size on the screen is 960x540, then the scale is 0.5. Loading Loading @@ -1698,7 +1703,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (isState(RESUMED)) { newParent.setResumedActivity(this, "onParentChanged"); } mLetterboxUiController.updateInheritedLetterbox(); mTransparentPolicy.start(); } if (rootTask != null && rootTask.topRunningActivity() == this) { Loading Loading @@ -2136,6 +2141,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Don't move below setOrientation(info.screenOrientation) since it triggers // getOverrideOrientation that requires having mLetterboxUiController // initialised. mTransparentPolicy = new TransparentPolicy(this, mWmService.mLetterboxConfiguration); mLetterboxUiController = new LetterboxUiController(mWmService, this); mCameraCompatControlEnabled = mWmService.mContext.getResources() .getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled); Loading Loading @@ -8080,13 +8086,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Configuration.Orientation int getRequestedConfigurationOrientation(boolean forDisplay, @ActivityInfo.ScreenOrientation int requestedOrientation) { if (mLetterboxUiController.hasInheritedOrientation()) { if (mTransparentPolicy.hasInheritedOrientation()) { final RootDisplayArea root = getRootDisplayArea(); if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) { return reverseConfigurationOrientation( mLetterboxUiController.getInheritedOrientation()); mTransparentPolicy.getInheritedOrientation()); } else { return mLetterboxUiController.getInheritedOrientation(); return mTransparentPolicy.getInheritedOrientation(); } } if (task != null && requestedOrientation == SCREEN_ORIENTATION_BEHIND) { Loading Loading @@ -8302,8 +8308,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Nullable CompatDisplayInsets getCompatDisplayInsets() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedCompatDisplayInsets(); if (mTransparentPolicy.isRunning()) { return mTransparentPolicy.getInheritedCompatDisplayInsets(); } return mCompatDisplayInsets; } Loading Loading @@ -8466,7 +8472,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } mSizeCompatBounds = null; mCompatDisplayInsets = null; mLetterboxUiController.clearInheritedCompatDisplayInsets(); mTransparentPolicy.clearInheritedCompatDisplayInsets(); } @VisibleForTesting Loading Loading @@ -8784,8 +8790,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; } // TODO(b/256564921): Investigate if we need new metrics for translucent activities if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedAppCompatState(); if (mTransparentPolicy.isRunning()) { return mTransparentPolicy.getInheritedAppCompatState(); } if (mInSizeCompatModeForBounds) { return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE; Loading Loading @@ -8938,7 +8944,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // We check if the current activity is transparent. In that case we need to // recomputeConfiguration of the first opaque activity beneath, to allow a // proper computation of the new bounds. if (!mLetterboxUiController.applyOnOpaqueActivityBelow( if (!mTransparentPolicy.applyOnOpaqueActivityBelow( ActivityRecord::recomputeConfiguration)) { onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration()); } Loading Loading @@ -9411,7 +9417,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A void updateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) { // Only allow to scale down. mSizeCompatScale = mLetterboxUiController.findOpaqueNotFinishingActivityBelow() mSizeCompatScale = mTransparentPolicy.findOpaqueNotFinishingActivityBelow() .map(activityRecord -> activityRecord.mSizeCompatScale) .orElseGet(() -> { final int contentW = resolvedAppBounds.width(); Loading @@ -9424,7 +9430,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { if (mTransparentPolicy.isRunning()) { // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity // is letterboxed. return false; Loading Loading @@ -9487,7 +9493,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A public Rect getBounds() { // TODO(b/268458693): Refactor configuration inheritance in case of translucent activities final Rect superBounds = super.getBounds(); return mLetterboxUiController.findOpaqueNotFinishingActivityBelow() return mTransparentPolicy.findOpaqueNotFinishingActivityBelow() .map(ActivityRecord::getBounds) .orElseGet(() -> { if (mSizeCompatBounds != null) { Loading Loading @@ -9851,8 +9857,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * Returns the min aspect ratio of this activity. */ float getMinAspectRatio() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedMinAspectRatio(); if (mTransparentPolicy.isRunning()) { return mTransparentPolicy.getInheritedMinAspectRatio(); } if (info.applicationInfo == null) { return info.getMinAspectRatio(); Loading Loading @@ -9902,8 +9908,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } float getMaxAspectRatio() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedMaxAspectRatio(); if (mTransparentPolicy.isRunning()) { return mTransparentPolicy.getInheritedMaxAspectRatio(); } return info.getMaxAspectRatio(); } Loading
services/core/java/com/android/server/wm/LetterboxUiController.java +11 −108 Original line number Diff line number Diff line Loading @@ -129,10 +129,7 @@ import com.android.server.wm.utils.OptPropFactory.OptProp; import com.android.window.flags.Flags; import java.io.PrintWriter; import java.util.Optional; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Predicate; /** Controls behaviour of the letterbox UI for {@link mActivityRecord}. */ // TODO(b/185262487): Improve test coverage of this class. Parts of it are tested in Loading Loading @@ -228,10 +225,6 @@ final class LetterboxUiController { @NonNull private final OptProp mFakeFocusOptProp; // TODO(b/336807329) Eventually eemove this dependency when refactoring Reachability. @NonNull private final TransparentPolicy mTransparentPolicy; private boolean mIsRelaunchingAfterRequestedOrientationChanged; private boolean mLastShouldShowLetterboxUi; Loading @@ -245,24 +238,6 @@ final class LetterboxUiController { // to use it after since controller is only used in ActivityRecord. mActivityRecord = activityRecord; mTransparentPolicy = new TransparentPolicy(activityRecord, mLetterboxConfiguration, new Predicate<ActivityRecord>() { @Override public boolean test(ActivityRecord opaqueActivity) { if (opaqueActivity == null || opaqueActivity.isEmbedded()) { // We skip letterboxing if the translucent activity doesn't have any // opaque activities beneath or the activity below is embedded which // never has letterbox. mActivityRecord.recomputeConfiguration(); return true; } if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent() || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) { return true; } return false; } }); PackageManager packageManager = wmService.mContext.getPackageManager(); final OptPropFactory optPropBuilder = new OptPropFactory(packageManager, Loading Loading @@ -340,7 +315,7 @@ final class LetterboxUiController { mLetterbox.destroy(); mLetterbox = null; } mTransparentPolicy.stop(); mActivityRecord.mTransparentPolicy.stop(); } void onMovedToDisplay(int displayId) { Loading @@ -349,11 +324,6 @@ final class LetterboxUiController { } } @NonNull TransparentPolicy getTransparentPolicy() { return mTransparentPolicy; } /** * Whether should ignore app requested orientation in response to an app * calling {@link android.app.Activity#setRequestedOrientation}. Loading Loading @@ -854,7 +824,7 @@ final class LetterboxUiController { // For this reason we use ActivityRecord#getBounds() that the translucent activity // inherits from the first opaque activity beneath and also takes care of the scaling // in case of activities in size compat mode. final Rect innerFrame = hasInheritedLetterboxBehavior() final Rect innerFrame = mActivityRecord.mTransparentPolicy.isRunning() ? mActivityRecord.getBounds() : w.getFrame(); mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint); if (mDoubleTapEvent) { Loading Loading @@ -1320,10 +1290,9 @@ final class LetterboxUiController { } // Use screen resolved bounds which uses resolved bounds or size compat bounds // as activity bounds can sometimes be empty final Rect opaqueActivityBounds = hasInheritedLetterboxBehavior() ? getTransparentPolicy().getTransparentPolicyState() .mFirstOpaqueActivity.getScreenResolvedBounds() : mActivityRecord.getScreenResolvedBounds(); final Rect opaqueActivityBounds = mActivityRecord.mTransparentPolicy .getFirstOpaqueActivity().map(ActivityRecord::getScreenResolvedBounds) .orElse(mActivityRecord.getScreenResolvedBounds()); return mLetterboxConfiguration.getIsHorizontalReachabilityEnabled() && parentConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN Loading Loading @@ -1358,11 +1327,10 @@ final class LetterboxUiController { return false; } // Use screen resolved bounds which uses resolved bounds or size compat bounds // as activity bounds can sometimes be empty final Rect opaqueActivityBounds = hasInheritedLetterboxBehavior() ? getTransparentPolicy().getTransparentPolicyState() .mFirstOpaqueActivity.getScreenResolvedBounds() : mActivityRecord.getScreenResolvedBounds(); // as activity bounds can sometimes be empty. final Rect opaqueActivityBounds = mActivityRecord.mTransparentPolicy .getFirstOpaqueActivity().map(ActivityRecord::getScreenResolvedBounds) .orElse(mActivityRecord.getScreenResolvedBounds()); return mLetterboxConfiguration.getIsVerticalReachabilityEnabled() && parentConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN Loading Loading @@ -1469,7 +1437,8 @@ final class LetterboxUiController { // corners because we assume the specific layout would. This is the case when the layout // of the translucent activity uses only a part of all the bounds because of the use of // LayoutParams.WRAP_CONTENT. if (hasInheritedLetterboxBehavior() && (cropBounds.width() != mainWindow.mRequestedWidth if (mActivityRecord.mTransparentPolicy.isRunning() && (cropBounds.width() != mainWindow.mRequestedWidth || cropBounds.height() != mainWindow.mRequestedHeight)) { return null; } Loading Loading @@ -1773,72 +1742,6 @@ final class LetterboxUiController { ); } /** * Handles translucent activities letterboxing inheriting constraints from the * first opaque activity beneath. */ void updateInheritedLetterbox() { mTransparentPolicy.start(); } /** * @return {@code true} if the current activity is translucent with an opaque activity * beneath. In this case it will inherit bounds, orientation and aspect ratios from * the first opaque activity beneath. */ boolean hasInheritedLetterboxBehavior() { return mTransparentPolicy.hasInheritedLetterboxBehavior(); } /** * @return {@code true} if the current activity is translucent with an opaque activity * beneath and needs to inherit its orientation. */ boolean hasInheritedOrientation() { return mTransparentPolicy.hasInheritedOrientation(); } float getInheritedMinAspectRatio() { return mTransparentPolicy.getInheritedMinAspectRatio(); } float getInheritedMaxAspectRatio() { return mTransparentPolicy.getInheritedMaxAspectRatio(); } int getInheritedAppCompatState() { return mTransparentPolicy.getInheritedAppCompatState(); } @Configuration.Orientation int getInheritedOrientation() { return mTransparentPolicy.getInheritedOrientation(); } ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { return mTransparentPolicy.getInheritedCompatDisplayInsets(); } void clearInheritedCompatDisplayInsets() { mTransparentPolicy.clearInheritedCompatDisplayInsets(); } /** * In case of translucent activities, it consumes the {@link ActivityRecord} of the first opaque * activity beneath using the given consumer and returns {@code true}. */ boolean applyOnOpaqueActivityBelow(@NonNull Consumer<ActivityRecord> consumer) { return mTransparentPolicy.applyOnOpaqueActivityBelow(consumer); } /** * @return The first not finishing opaque activity beneath the current translucent activity * if it exists and the strategy is enabled. */ Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() { return mTransparentPolicy.findOpaqueNotFinishingActivityBelow(); } @NonNull private static BooleanSupplier asLazy(@NonNull BooleanSupplier supplier) { return new BooleanSupplier() { Loading
services/core/java/com/android/server/wm/TransparentPolicy.java +57 −50 Original line number Diff line number Diff line Loading @@ -40,6 +40,11 @@ import java.util.function.Predicate; /** * Encapsulate logic about translucent activities. * <p/> * An activity is defined as translucent if {@link ActivityRecord#fillsParent()} returns * {@code false}. When the policy is running for a letterboxed activity, a transparent activity * will inherit constraints about bounds, aspect ratios and orientation from the first not finishing * activity below. */ class TransparentPolicy { Loading @@ -50,30 +55,28 @@ class TransparentPolicy { private static final Predicate<ActivityRecord> FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE = ActivityRecord::occludesParent; // The predicate to check to skip the policy @NonNull private final Predicate<ActivityRecord> mSkipLetterboxPredicate; // The ActivityRecord this policy relates to. @NonNull private final ActivityRecord mActivityRecord; // If transparent activity policy is enabled. @NonNull private final BooleanSupplier mIsTranslucentLetterboxingEnabledSupplier; // The list of observers for the destroy event of candidate opaque activities // when dealing with translucent activities. @NonNull private final List<TransparentPolicy> mDestroyListeners = new ArrayList<>(); // THe current state for the possible transparent activity // The current state for the possible transparent activity @NonNull private final TransparentPolicyState mTransparentPolicyState; TransparentPolicy(@NonNull ActivityRecord activityRecord, @NonNull LetterboxConfiguration letterboxConfiguration, @NonNull Predicate<ActivityRecord> skipLetterboxPredicate) { @NonNull LetterboxConfiguration letterboxConfiguration) { mActivityRecord = activityRecord; mIsTranslucentLetterboxingEnabledSupplier = () -> letterboxConfiguration .isTranslucentLetterboxingEnabled(); mSkipLetterboxPredicate = skipLetterboxPredicate; mIsTranslucentLetterboxingEnabledSupplier = letterboxConfiguration::isTranslucentLetterboxingEnabled; mTransparentPolicyState = new TransparentPolicyState(activityRecord); } Loading @@ -98,7 +101,7 @@ class TransparentPolicy { true /* traverseTopToBottom */); // We check if we need for some reason to skip the policy gievn the specific first // opaque activity if (mSkipLetterboxPredicate.test(firstOpaqueActivity)) { if (shouldSkipTransparentPolicy(firstOpaqueActivity)) { return; } mTransparentPolicyState.start(firstOpaqueActivity); Loading @@ -112,7 +115,12 @@ class TransparentPolicy { mTransparentPolicyState.reset(); } boolean hasInheritedLetterboxBehavior() { /** * @return {@code true} if the current activity is translucent with an opaque activity * beneath and the related policy is running. In this case it will inherit bounds, orientation * and aspect ratios from the first opaque activity beneath. */ boolean isRunning() { return mTransparentPolicyState.isRunning(); } Loading @@ -121,35 +129,33 @@ class TransparentPolicy { * beneath and needs to inherit its orientation. */ boolean hasInheritedOrientation() { // To force a different orientation, the transparent one needs to have an explicit one // otherwise the existing one is fine and the actual orientation will depend on the // bounds. // To avoid wrong behaviour, we're not forcing orientation for activities with not // fixed orientation (e.g. permission dialogs). return hasInheritedLetterboxBehavior() // To avoid wrong behaviour (e.g. permission dialogs not centered or with wrong size), // transparent activities inherit orientation from the first opaque activity below only if // they explicitly define an orientation different from SCREEN_ORIENTATION_UNSPECIFIED. return isRunning() && mActivityRecord.getOverrideOrientation() != SCREEN_ORIENTATION_UNSPECIFIED; } float getInheritedMinAspectRatio() { return mTransparentPolicyState.getInheritedMinAspectRatio(); return mTransparentPolicyState.mInheritedMinAspectRatio; } float getInheritedMaxAspectRatio() { return mTransparentPolicyState.getInheritedMaxAspectRatio(); return mTransparentPolicyState.mInheritedMaxAspectRatio; } int getInheritedAppCompatState() { return mTransparentPolicyState.getInheritedAppCompatState(); return mTransparentPolicyState.mInheritedAppCompatState; } @Configuration.Orientation int getInheritedOrientation() { return mTransparentPolicyState.getInheritedOrientation(); return mTransparentPolicyState.mInheritedOrientation; } ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { return mTransparentPolicyState.getInheritedCompatDisplayInsets(); return mTransparentPolicyState.mInheritedCompatDisplayInsets; } void clearInheritedCompatDisplayInsets() { Loading @@ -168,6 +174,12 @@ class TransparentPolicy { return mTransparentPolicyState.applyOnOpaqueActivityBelow(consumer); } @NonNull Optional<ActivityRecord> getFirstOpaqueActivity() { return isRunning() ? Optional.of(mTransparentPolicyState.mFirstOpaqueActivity) : Optional.empty(); } /** * @return The first not finishing opaque activity beneath the current translucent activity * if it exists and the strategy is enabled. Loading @@ -176,6 +188,22 @@ class TransparentPolicy { return mTransparentPolicyState.findOpaqueNotFinishingActivityBelow(); } // We evaluate the case when the policy should not be applied. private boolean shouldSkipTransparentPolicy(@Nullable ActivityRecord opaqueActivity) { if (opaqueActivity == null || opaqueActivity.isEmbedded()) { // We skip letterboxing if the translucent activity doesn't have any // opaque activities beneath or the activity below is embedded which // never has letterbox. mActivityRecord.recomputeConfiguration(); return true; } if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent() || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) { return true; } return false; } /** Resets the screen size related fields so they can be resolved by requested bounds later. */ private static void resetTranslucentOverrideConfig(Configuration config) { // The values for the following properties will be defined during the configuration Loading Loading @@ -212,10 +240,11 @@ class TransparentPolicy { private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; // The CompatDisplayInsets of the opaque activity beneath the translucent one. @Nullable private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets; @Nullable ActivityRecord mFirstOpaqueActivity; private ActivityRecord mFirstOpaqueActivity; /* * WindowContainerListener responsible to make translucent activities inherit Loading @@ -231,9 +260,8 @@ class TransparentPolicy { private void start(@NonNull ActivityRecord firstOpaqueActivity) { mFirstOpaqueActivity = firstOpaqueActivity; mFirstOpaqueActivity.mLetterboxUiController.getTransparentPolicy() .mDestroyListeners.add(mActivityRecord.mLetterboxUiController .getTransparentPolicy()); mFirstOpaqueActivity.mTransparentPolicy .mDestroyListeners.add(mActivityRecord.mTransparentPolicy); inheritFromOpaque(firstOpaqueActivity); final WindowContainer<?> parent = mActivityRecord.getParent(); mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation( Loading Loading @@ -284,9 +312,8 @@ class TransparentPolicy { mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; mInheritedCompatDisplayInsets = null; if (mFirstOpaqueActivity != null) { mFirstOpaqueActivity.mLetterboxUiController.getTransparentPolicy() .mDestroyListeners.remove(mActivityRecord.mLetterboxUiController .getTransparentPolicy()); mFirstOpaqueActivity.mTransparentPolicy .mDestroyListeners.remove(mActivityRecord.mTransparentPolicy); } mFirstOpaqueActivity = null; } Loading @@ -295,26 +322,6 @@ class TransparentPolicy { return mLetterboxConfigListener != null; } private int getInheritedOrientation() { return mInheritedOrientation; } private float getInheritedMinAspectRatio() { return mInheritedMinAspectRatio; } private float getInheritedMaxAspectRatio() { return mInheritedMaxAspectRatio; } private int getInheritedAppCompatState() { return mInheritedAppCompatState; } private ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { return mInheritedCompatDisplayInsets; } private void clearInheritedCompatDisplayInsets() { mInheritedCompatDisplayInsets = null; } Loading
services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +4 −3 Original line number Diff line number Diff line Loading @@ -77,6 +77,7 @@ import static org.mockito.ArgumentMatchers.anyString; 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.annotation.Nullable; import android.compat.testing.PlatformCompatChangeRule; Loading Loading @@ -110,7 +111,7 @@ import org.junit.rules.TestRule; import org.junit.runner.RunWith; /** * Test class for {@link LetterboxUiControllerTest}. * Test class for {@link LetterboxUiController}. * * Build/Install/Run: * atest WmTests:LetterboxUiControllerTest Loading Loading @@ -521,8 +522,8 @@ public class LetterboxUiControllerTest extends WindowTestsBase { final Rect opaqueBounds = new Rect(0, 0, 500, 300); doReturn(opaqueBounds).when(mActivity).getBounds(); // Activity is translucent spyOn(mActivity.mLetterboxUiController); doReturn(true).when(mActivity.mLetterboxUiController).hasInheritedLetterboxBehavior(); spyOn(mActivity.mTransparentPolicy); when(mActivity.mTransparentPolicy.isRunning()).thenReturn(true); // Makes requested sizes different mainWindow.mRequestedWidth = opaqueBounds.width() - 1; Loading
services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +19 −21 File changed.Preview size limit exceeded, changes collapsed. Show changes