Loading services/core/java/com/android/server/wm/LetterboxUiController.java +25 −5 Original line number Diff line number Diff line Loading @@ -132,6 +132,7 @@ 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 @@ -244,7 +245,24 @@ final class LetterboxUiController { // to use it after since controller is only used in ActivityRecord. mActivityRecord = activityRecord; mTransparentPolicy = new TransparentPolicy(activityRecord, mLetterboxConfiguration); 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 @@ -322,7 +340,7 @@ final class LetterboxUiController { mLetterbox.destroy(); mLetterbox = null; } mTransparentPolicy.destroy(); mTransparentPolicy.stop(); } void onMovedToDisplay(int displayId) { Loading Loading @@ -1303,7 +1321,8 @@ 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().mFirstOpaqueActivityBeneath.getScreenResolvedBounds() ? getTransparentPolicy().getTransparentPolicyState() .mFirstOpaqueActivity.getScreenResolvedBounds() : mActivityRecord.getScreenResolvedBounds(); return mLetterboxConfiguration.getIsHorizontalReachabilityEnabled() && parentConfiguration.windowConfiguration.getWindowingMode() Loading Loading @@ -1341,7 +1360,8 @@ 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().mFirstOpaqueActivityBeneath.getScreenResolvedBounds() ? getTransparentPolicy().getTransparentPolicyState() .mFirstOpaqueActivity.getScreenResolvedBounds() : mActivityRecord.getScreenResolvedBounds(); return mLetterboxConfiguration.getIsVerticalReachabilityEnabled() && parentConfiguration.windowConfiguration.getWindowingMode() Loading Loading @@ -1758,7 +1778,7 @@ final class LetterboxUiController { * first opaque activity beneath. */ void updateInheritedLetterbox() { mTransparentPolicy.updateInheritedLetterbox(); mTransparentPolicy.start(); } /** Loading services/core/java/com/android/server/wm/TransparentPolicy.java +189 −119 Original line number Diff line number Diff line Loading @@ -31,11 +31,10 @@ import android.annotation.Nullable; import android.content.res.Configuration; import android.graphics.Rect; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Predicate; Loading @@ -46,126 +45,75 @@ class TransparentPolicy { private static final String TAG = TAG_WITH_CLASS_NAME ? "TransparentPolicy" : TAG_ATM; // Aspect ratio value to consider as undefined. private static final float UNDEFINED_ASPECT_RATIO = 0f; // The predicate used to find the first opaque not finishing activity below the potential // transparent activity. 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. private final ActivityRecord mActivityRecord; // The LetterboxConfiguration private final LetterboxConfiguration mLetterboxConfiguration; // If transparent activity policy is enabled. private final BooleanSupplier mIsTranslucentLetterboxingEnabledSupplier; // The list of observers for the destroy event of candidate opaque activities // when dealing with translucent activities. private final List<TransparentPolicy> mDestroyListeners = new ArrayList<>(); // In case of transparent activities we might need to access the aspectRatio of the // first opaque activity beneath. private float mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; private float mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; @Configuration.Orientation private int mInheritedOrientation = ORIENTATION_UNDEFINED; // The app compat state for the opaque activity if any private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; // The CompatDisplayInsets of the opaque activity beneath the translucent one. private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets; /* * WindowContainerListener responsible to make translucent activities inherit * constraints from the first opaque activity beneath them. It's null for not * translucent activities. */ @Nullable private WindowContainerListener mLetterboxConfigListener; @Nullable @VisibleForTesting ActivityRecord mFirstOpaqueActivityBeneath; // THe current state for the possible transparent activity private final TransparentPolicyState mTransparentPolicyState; TransparentPolicy(@NonNull ActivityRecord activityRecord, @NonNull LetterboxConfiguration letterboxConfiguration) { @NonNull LetterboxConfiguration letterboxConfiguration, @NonNull Predicate<ActivityRecord> skipLetterboxPredicate) { mActivityRecord = activityRecord; mLetterboxConfiguration = letterboxConfiguration; mIsTranslucentLetterboxingEnabledSupplier = () -> letterboxConfiguration .isTranslucentLetterboxingEnabled(); mSkipLetterboxPredicate = skipLetterboxPredicate; mTransparentPolicyState = new TransparentPolicyState(activityRecord); } /** * Handles translucent activities letterboxing inheriting constraints from the * first opaque activity beneath. */ void updateInheritedLetterbox() { final WindowContainer<?> parent = mActivityRecord.getParent(); if (parent == null) { void start() { if (!mIsTranslucentLetterboxingEnabledSupplier.getAsBoolean()) { return; } if (!mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) { final WindowContainer<?> parent = mActivityRecord.getParent(); if (parent == null) { return; } if (mLetterboxConfigListener != null) { mLetterboxConfigListener.onRemoved(); clearInheritedConfig(); } mTransparentPolicyState.reset(); // In case mActivityRecord.hasCompatDisplayInsetsWithoutOverride() we don't apply the // opaque activity constraints because we're expecting the activity is already letterboxed. mFirstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity( final ActivityRecord firstOpaqueActivity = mActivityRecord.getTask().getActivity( FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */, mActivityRecord /* boundary */, false /* includeBoundary */, true /* traverseTopToBottom */); if (mFirstOpaqueActivityBeneath == null || mFirstOpaqueActivityBeneath.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(); // We check if we need for some reason to skip the policy gievn the specific first // opaque activity if (mSkipLetterboxPredicate.test(firstOpaqueActivity)) { return; } if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent() || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) { return; mTransparentPolicyState.start(firstOpaqueActivity); } mFirstOpaqueActivityBeneath.mLetterboxUiController.getTransparentPolicy() .mDestroyListeners.add(this); inheritConfiguration(mFirstOpaqueActivityBeneath); mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation( mActivityRecord, mFirstOpaqueActivityBeneath, (opaqueConfig, transparentOverrideConfig) -> { resetTranslucentOverrideConfig(transparentOverrideConfig); final Rect parentBounds = parent.getWindowConfiguration().getBounds(); final Rect bounds = transparentOverrideConfig.windowConfiguration.getBounds(); final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds(); // We cannot use letterboxBounds directly here because the position relies on // letterboxing. Using letterboxBounds directly, would produce a double offset. bounds.set(parentBounds.left, parentBounds.top, parentBounds.left + letterboxBounds.width(), parentBounds.top + letterboxBounds.height()); // We need to initialize appBounds to avoid NPE. The actual value will // be set ahead when resolving the Configuration for the activity. transparentOverrideConfig.windowConfiguration.setAppBounds(new Rect()); inheritConfiguration(mFirstOpaqueActivityBeneath); return transparentOverrideConfig; }); } void destroy() { void stop() { for (int i = mDestroyListeners.size() - 1; i >= 0; i--) { mDestroyListeners.get(i).updateInheritedLetterbox(); mDestroyListeners.get(i).start(); } mDestroyListeners.clear(); if (mLetterboxConfigListener != null) { mLetterboxConfigListener.onRemoved(); mLetterboxConfigListener = null; } mTransparentPolicyState.reset(); } boolean hasInheritedLetterboxBehavior() { return mLetterboxConfigListener != null; return mTransparentPolicyState.isRunning(); } /** Loading @@ -184,28 +132,32 @@ class TransparentPolicy { } float getInheritedMinAspectRatio() { return mInheritedMinAspectRatio; return mTransparentPolicyState.getInheritedMinAspectRatio(); } float getInheritedMaxAspectRatio() { return mInheritedMaxAspectRatio; return mTransparentPolicyState.getInheritedMaxAspectRatio(); } int getInheritedAppCompatState() { return mInheritedAppCompatState; return mTransparentPolicyState.getInheritedAppCompatState(); } @Configuration.Orientation int getInheritedOrientation() { return mInheritedOrientation; return mTransparentPolicyState.getInheritedOrientation(); } ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { return mInheritedCompatDisplayInsets; return mTransparentPolicyState.getInheritedCompatDisplayInsets(); } void clearInheritedCompatDisplayInsets() { mInheritedCompatDisplayInsets = null; mTransparentPolicyState.clearInheritedCompatDisplayInsets(); } TransparentPolicyState getTransparentPolicyState() { return mTransparentPolicyState; } /** Loading @@ -213,11 +165,7 @@ class TransparentPolicy { * activity beneath using the given consumer and returns {@code true}. */ boolean applyOnOpaqueActivityBelow(@NonNull Consumer<ActivityRecord> consumer) { return findOpaqueNotFinishingActivityBelow() .map(activityRecord -> { consumer.accept(activityRecord); return true; }).orElse(false); return mTransparentPolicyState.applyOnOpaqueActivityBelow(consumer); } /** Loading @@ -225,10 +173,7 @@ class TransparentPolicy { * if it exists and the strategy is enabled. */ Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() { if (!hasInheritedLetterboxBehavior() || mActivityRecord.getTask() == null) { return Optional.empty(); } return Optional.ofNullable(mFirstOpaqueActivityBeneath); return mTransparentPolicyState.findOpaqueNotFinishingActivityBelow(); } /** Resets the screen size related fields so they can be resolved by requested bounds later. */ Loading @@ -244,33 +189,158 @@ class TransparentPolicy { } private void inheritConfiguration(ActivityRecord firstOpaque) { mTransparentPolicyState.inheritFromOpaque(firstOpaque); } /** * Encapsulate the state for the current translucent activity when the transparent policy * has started. */ static class TransparentPolicyState { // Aspect ratio value to consider as undefined. private static final float UNDEFINED_ASPECT_RATIO = 0f; @NonNull private final ActivityRecord mActivityRecord; @Configuration.Orientation private int mInheritedOrientation = ORIENTATION_UNDEFINED; private float mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; private float mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; // The app compat state for the opaque activity if any private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; // The CompatDisplayInsets of the opaque activity beneath the translucent one. private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets; @Nullable ActivityRecord mFirstOpaqueActivity; /* * WindowContainerListener responsible to make translucent activities inherit * constraints from the first opaque activity beneath them. It's null for not * translucent activities. */ @Nullable private WindowContainerListener mLetterboxConfigListener; TransparentPolicyState(@NonNull ActivityRecord activityRecord) { mActivityRecord = activityRecord; } private void start(@NonNull ActivityRecord firstOpaqueActivity) { mFirstOpaqueActivity = firstOpaqueActivity; mFirstOpaqueActivity.mLetterboxUiController.getTransparentPolicy() .mDestroyListeners.add(mActivityRecord.mLetterboxUiController .getTransparentPolicy()); inheritFromOpaque(firstOpaqueActivity); final WindowContainer<?> parent = mActivityRecord.getParent(); mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation( mActivityRecord, mFirstOpaqueActivity, (opaqueConfig, transparentOverrideConfig) -> { resetTranslucentOverrideConfig(transparentOverrideConfig); final Rect parentBounds = parent.getWindowConfiguration().getBounds(); final Rect bounds = transparentOverrideConfig .windowConfiguration.getBounds(); final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds(); // We cannot use letterboxBounds directly here because the position relies // on letterboxing. Using letterboxBounds directly, would produce a // double offset. bounds.set(parentBounds.left, parentBounds.top, parentBounds.left + letterboxBounds.width(), parentBounds.top + letterboxBounds.height()); // We need to initialize appBounds to avoid NPE. The actual value will // be set ahead when resolving the Configuration for the activity. transparentOverrideConfig.windowConfiguration.setAppBounds(new Rect()); inheritFromOpaque(mFirstOpaqueActivity); return transparentOverrideConfig; }); } private void inheritFromOpaque(@NonNull ActivityRecord opaqueActivity) { // To avoid wrong behaviour, we're not forcing a specific aspect ratio to activities // which are not already providing one (e.g. permission dialogs) and presumably also // not resizable. if (mActivityRecord.getMinAspectRatio() != UNDEFINED_ASPECT_RATIO) { mInheritedMinAspectRatio = firstOpaque.getMinAspectRatio(); mInheritedMinAspectRatio = opaqueActivity.getMinAspectRatio(); } if (mActivityRecord.getMaxAspectRatio() != UNDEFINED_ASPECT_RATIO) { mInheritedMaxAspectRatio = firstOpaque.getMaxAspectRatio(); mInheritedMaxAspectRatio = opaqueActivity.getMaxAspectRatio(); } mInheritedOrientation = firstOpaque.getRequestedConfigurationOrientation(); mInheritedAppCompatState = firstOpaque.getAppCompatState(); mInheritedCompatDisplayInsets = firstOpaque.getCompatDisplayInsets(); mInheritedOrientation = opaqueActivity.getRequestedConfigurationOrientation(); mInheritedAppCompatState = opaqueActivity.getAppCompatState(); mInheritedCompatDisplayInsets = opaqueActivity.getCompatDisplayInsets(); } private void clearInheritedConfig() { if (mFirstOpaqueActivityBeneath != null) { mFirstOpaqueActivityBeneath.mLetterboxUiController.getTransparentPolicy() .mDestroyListeners.remove(this); private void reset() { if (mLetterboxConfigListener != null) { mLetterboxConfigListener.onRemoved(); } mFirstOpaqueActivityBeneath = null; mLetterboxConfigListener = null; mInheritedOrientation = ORIENTATION_UNDEFINED; mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; mInheritedOrientation = ORIENTATION_UNDEFINED; mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; mInheritedCompatDisplayInsets = null; if (mFirstOpaqueActivity != null) { mFirstOpaqueActivity.mLetterboxUiController.getTransparentPolicy() .mDestroyListeners.remove(mActivityRecord.mLetterboxUiController .getTransparentPolicy()); } mFirstOpaqueActivity = null; } private boolean isRunning() { 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; } /** * @return The first not finishing opaque activity beneath the current translucent activity * if it exists and the strategy is enabled. */ private Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() { if (!isRunning() || mActivityRecord.getTask() == null) { return Optional.empty(); } return Optional.ofNullable(mFirstOpaqueActivity); } /** * In case of translucent activities, it consumes the {@link ActivityRecord} of the first * opaque activity beneath using the given consumer and returns {@code true}. */ private boolean applyOnOpaqueActivityBelow(@NonNull Consumer<ActivityRecord> consumer) { return findOpaqueNotFinishingActivityBelow() .map(activityRecord -> { consumer.accept(activityRecord); return true; }).orElse(false); } } } services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +9 −12 Original line number Diff line number Diff line Loading @@ -396,8 +396,7 @@ public class SizeCompatTests extends WindowTestsBase { opaqueActivity.removeImmediately(); // Check that updateInheritedLetterbox() is invoked again verify(translucentActivity.mLetterboxUiController.getTransparentPolicy()) .updateInheritedLetterbox(); verify(translucentActivity.mLetterboxUiController.getTransparentPolicy()).start(); } // TODO(b/333663877): Enable test after fix Loading Loading @@ -466,8 +465,6 @@ public class SizeCompatTests extends WindowTestsBase { mTask.addChild(translucentActivity); // Transparent strategy applied assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); assertNotNull(translucentActivity.mLetterboxUiController .getTransparentPolicy().mFirstOpaqueActivityBeneath); spyOn(translucentActivity.mLetterboxUiController.getTransparentPolicy()); clearInvocations(translucentActivity.mLetterboxUiController.getTransparentPolicy()); Loading @@ -475,11 +472,10 @@ public class SizeCompatTests extends WindowTestsBase { // We destroy the first opaque activity mActivity.removeImmediately(); // Check that updateInheritedLetterbox() is invoked again on the TransparentPolicy verify(translucentActivity.mLetterboxUiController.getTransparentPolicy()) .updateInheritedLetterbox(); assertNull(translucentActivity.mLetterboxUiController .getTransparentPolicy().mFirstOpaqueActivityBeneath); // Check that start() is invoked again on the TransparentPolicy verify(translucentActivity.mLetterboxUiController.getTransparentPolicy()).start(); assertFalse(translucentActivity.mLetterboxUiController .getTransparentPolicy().hasInheritedLetterboxBehavior()); } @Test Loading Loading @@ -507,11 +503,12 @@ public class SizeCompatTests extends WindowTestsBase { // Transparent strategy applied assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); spyOn(translucentActivity.mLetterboxUiController); clearInvocations(translucentActivity.mLetterboxUiController); spyOn(translucentActivity.mLetterboxUiController.getTransparentPolicy()); clearInvocations(translucentActivity.mLetterboxUiController.getTransparentPolicy()); // Check that updateInheritedLetterbox() is invoked again verify(translucentActivity.mLetterboxUiController, never()).updateInheritedLetterbox(); verify(translucentActivity.mLetterboxUiController.getTransparentPolicy(), never()) .start(); } @Test Loading Loading
services/core/java/com/android/server/wm/LetterboxUiController.java +25 −5 Original line number Diff line number Diff line Loading @@ -132,6 +132,7 @@ 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 @@ -244,7 +245,24 @@ final class LetterboxUiController { // to use it after since controller is only used in ActivityRecord. mActivityRecord = activityRecord; mTransparentPolicy = new TransparentPolicy(activityRecord, mLetterboxConfiguration); 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 @@ -322,7 +340,7 @@ final class LetterboxUiController { mLetterbox.destroy(); mLetterbox = null; } mTransparentPolicy.destroy(); mTransparentPolicy.stop(); } void onMovedToDisplay(int displayId) { Loading Loading @@ -1303,7 +1321,8 @@ 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().mFirstOpaqueActivityBeneath.getScreenResolvedBounds() ? getTransparentPolicy().getTransparentPolicyState() .mFirstOpaqueActivity.getScreenResolvedBounds() : mActivityRecord.getScreenResolvedBounds(); return mLetterboxConfiguration.getIsHorizontalReachabilityEnabled() && parentConfiguration.windowConfiguration.getWindowingMode() Loading Loading @@ -1341,7 +1360,8 @@ 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().mFirstOpaqueActivityBeneath.getScreenResolvedBounds() ? getTransparentPolicy().getTransparentPolicyState() .mFirstOpaqueActivity.getScreenResolvedBounds() : mActivityRecord.getScreenResolvedBounds(); return mLetterboxConfiguration.getIsVerticalReachabilityEnabled() && parentConfiguration.windowConfiguration.getWindowingMode() Loading Loading @@ -1758,7 +1778,7 @@ final class LetterboxUiController { * first opaque activity beneath. */ void updateInheritedLetterbox() { mTransparentPolicy.updateInheritedLetterbox(); mTransparentPolicy.start(); } /** Loading
services/core/java/com/android/server/wm/TransparentPolicy.java +189 −119 Original line number Diff line number Diff line Loading @@ -31,11 +31,10 @@ import android.annotation.Nullable; import android.content.res.Configuration; import android.graphics.Rect; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Predicate; Loading @@ -46,126 +45,75 @@ class TransparentPolicy { private static final String TAG = TAG_WITH_CLASS_NAME ? "TransparentPolicy" : TAG_ATM; // Aspect ratio value to consider as undefined. private static final float UNDEFINED_ASPECT_RATIO = 0f; // The predicate used to find the first opaque not finishing activity below the potential // transparent activity. 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. private final ActivityRecord mActivityRecord; // The LetterboxConfiguration private final LetterboxConfiguration mLetterboxConfiguration; // If transparent activity policy is enabled. private final BooleanSupplier mIsTranslucentLetterboxingEnabledSupplier; // The list of observers for the destroy event of candidate opaque activities // when dealing with translucent activities. private final List<TransparentPolicy> mDestroyListeners = new ArrayList<>(); // In case of transparent activities we might need to access the aspectRatio of the // first opaque activity beneath. private float mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; private float mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; @Configuration.Orientation private int mInheritedOrientation = ORIENTATION_UNDEFINED; // The app compat state for the opaque activity if any private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; // The CompatDisplayInsets of the opaque activity beneath the translucent one. private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets; /* * WindowContainerListener responsible to make translucent activities inherit * constraints from the first opaque activity beneath them. It's null for not * translucent activities. */ @Nullable private WindowContainerListener mLetterboxConfigListener; @Nullable @VisibleForTesting ActivityRecord mFirstOpaqueActivityBeneath; // THe current state for the possible transparent activity private final TransparentPolicyState mTransparentPolicyState; TransparentPolicy(@NonNull ActivityRecord activityRecord, @NonNull LetterboxConfiguration letterboxConfiguration) { @NonNull LetterboxConfiguration letterboxConfiguration, @NonNull Predicate<ActivityRecord> skipLetterboxPredicate) { mActivityRecord = activityRecord; mLetterboxConfiguration = letterboxConfiguration; mIsTranslucentLetterboxingEnabledSupplier = () -> letterboxConfiguration .isTranslucentLetterboxingEnabled(); mSkipLetterboxPredicate = skipLetterboxPredicate; mTransparentPolicyState = new TransparentPolicyState(activityRecord); } /** * Handles translucent activities letterboxing inheriting constraints from the * first opaque activity beneath. */ void updateInheritedLetterbox() { final WindowContainer<?> parent = mActivityRecord.getParent(); if (parent == null) { void start() { if (!mIsTranslucentLetterboxingEnabledSupplier.getAsBoolean()) { return; } if (!mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) { final WindowContainer<?> parent = mActivityRecord.getParent(); if (parent == null) { return; } if (mLetterboxConfigListener != null) { mLetterboxConfigListener.onRemoved(); clearInheritedConfig(); } mTransparentPolicyState.reset(); // In case mActivityRecord.hasCompatDisplayInsetsWithoutOverride() we don't apply the // opaque activity constraints because we're expecting the activity is already letterboxed. mFirstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity( final ActivityRecord firstOpaqueActivity = mActivityRecord.getTask().getActivity( FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */, mActivityRecord /* boundary */, false /* includeBoundary */, true /* traverseTopToBottom */); if (mFirstOpaqueActivityBeneath == null || mFirstOpaqueActivityBeneath.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(); // We check if we need for some reason to skip the policy gievn the specific first // opaque activity if (mSkipLetterboxPredicate.test(firstOpaqueActivity)) { return; } if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent() || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) { return; mTransparentPolicyState.start(firstOpaqueActivity); } mFirstOpaqueActivityBeneath.mLetterboxUiController.getTransparentPolicy() .mDestroyListeners.add(this); inheritConfiguration(mFirstOpaqueActivityBeneath); mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation( mActivityRecord, mFirstOpaqueActivityBeneath, (opaqueConfig, transparentOverrideConfig) -> { resetTranslucentOverrideConfig(transparentOverrideConfig); final Rect parentBounds = parent.getWindowConfiguration().getBounds(); final Rect bounds = transparentOverrideConfig.windowConfiguration.getBounds(); final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds(); // We cannot use letterboxBounds directly here because the position relies on // letterboxing. Using letterboxBounds directly, would produce a double offset. bounds.set(parentBounds.left, parentBounds.top, parentBounds.left + letterboxBounds.width(), parentBounds.top + letterboxBounds.height()); // We need to initialize appBounds to avoid NPE. The actual value will // be set ahead when resolving the Configuration for the activity. transparentOverrideConfig.windowConfiguration.setAppBounds(new Rect()); inheritConfiguration(mFirstOpaqueActivityBeneath); return transparentOverrideConfig; }); } void destroy() { void stop() { for (int i = mDestroyListeners.size() - 1; i >= 0; i--) { mDestroyListeners.get(i).updateInheritedLetterbox(); mDestroyListeners.get(i).start(); } mDestroyListeners.clear(); if (mLetterboxConfigListener != null) { mLetterboxConfigListener.onRemoved(); mLetterboxConfigListener = null; } mTransparentPolicyState.reset(); } boolean hasInheritedLetterboxBehavior() { return mLetterboxConfigListener != null; return mTransparentPolicyState.isRunning(); } /** Loading @@ -184,28 +132,32 @@ class TransparentPolicy { } float getInheritedMinAspectRatio() { return mInheritedMinAspectRatio; return mTransparentPolicyState.getInheritedMinAspectRatio(); } float getInheritedMaxAspectRatio() { return mInheritedMaxAspectRatio; return mTransparentPolicyState.getInheritedMaxAspectRatio(); } int getInheritedAppCompatState() { return mInheritedAppCompatState; return mTransparentPolicyState.getInheritedAppCompatState(); } @Configuration.Orientation int getInheritedOrientation() { return mInheritedOrientation; return mTransparentPolicyState.getInheritedOrientation(); } ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { return mInheritedCompatDisplayInsets; return mTransparentPolicyState.getInheritedCompatDisplayInsets(); } void clearInheritedCompatDisplayInsets() { mInheritedCompatDisplayInsets = null; mTransparentPolicyState.clearInheritedCompatDisplayInsets(); } TransparentPolicyState getTransparentPolicyState() { return mTransparentPolicyState; } /** Loading @@ -213,11 +165,7 @@ class TransparentPolicy { * activity beneath using the given consumer and returns {@code true}. */ boolean applyOnOpaqueActivityBelow(@NonNull Consumer<ActivityRecord> consumer) { return findOpaqueNotFinishingActivityBelow() .map(activityRecord -> { consumer.accept(activityRecord); return true; }).orElse(false); return mTransparentPolicyState.applyOnOpaqueActivityBelow(consumer); } /** Loading @@ -225,10 +173,7 @@ class TransparentPolicy { * if it exists and the strategy is enabled. */ Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() { if (!hasInheritedLetterboxBehavior() || mActivityRecord.getTask() == null) { return Optional.empty(); } return Optional.ofNullable(mFirstOpaqueActivityBeneath); return mTransparentPolicyState.findOpaqueNotFinishingActivityBelow(); } /** Resets the screen size related fields so they can be resolved by requested bounds later. */ Loading @@ -244,33 +189,158 @@ class TransparentPolicy { } private void inheritConfiguration(ActivityRecord firstOpaque) { mTransparentPolicyState.inheritFromOpaque(firstOpaque); } /** * Encapsulate the state for the current translucent activity when the transparent policy * has started. */ static class TransparentPolicyState { // Aspect ratio value to consider as undefined. private static final float UNDEFINED_ASPECT_RATIO = 0f; @NonNull private final ActivityRecord mActivityRecord; @Configuration.Orientation private int mInheritedOrientation = ORIENTATION_UNDEFINED; private float mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; private float mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; // The app compat state for the opaque activity if any private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; // The CompatDisplayInsets of the opaque activity beneath the translucent one. private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets; @Nullable ActivityRecord mFirstOpaqueActivity; /* * WindowContainerListener responsible to make translucent activities inherit * constraints from the first opaque activity beneath them. It's null for not * translucent activities. */ @Nullable private WindowContainerListener mLetterboxConfigListener; TransparentPolicyState(@NonNull ActivityRecord activityRecord) { mActivityRecord = activityRecord; } private void start(@NonNull ActivityRecord firstOpaqueActivity) { mFirstOpaqueActivity = firstOpaqueActivity; mFirstOpaqueActivity.mLetterboxUiController.getTransparentPolicy() .mDestroyListeners.add(mActivityRecord.mLetterboxUiController .getTransparentPolicy()); inheritFromOpaque(firstOpaqueActivity); final WindowContainer<?> parent = mActivityRecord.getParent(); mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation( mActivityRecord, mFirstOpaqueActivity, (opaqueConfig, transparentOverrideConfig) -> { resetTranslucentOverrideConfig(transparentOverrideConfig); final Rect parentBounds = parent.getWindowConfiguration().getBounds(); final Rect bounds = transparentOverrideConfig .windowConfiguration.getBounds(); final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds(); // We cannot use letterboxBounds directly here because the position relies // on letterboxing. Using letterboxBounds directly, would produce a // double offset. bounds.set(parentBounds.left, parentBounds.top, parentBounds.left + letterboxBounds.width(), parentBounds.top + letterboxBounds.height()); // We need to initialize appBounds to avoid NPE. The actual value will // be set ahead when resolving the Configuration for the activity. transparentOverrideConfig.windowConfiguration.setAppBounds(new Rect()); inheritFromOpaque(mFirstOpaqueActivity); return transparentOverrideConfig; }); } private void inheritFromOpaque(@NonNull ActivityRecord opaqueActivity) { // To avoid wrong behaviour, we're not forcing a specific aspect ratio to activities // which are not already providing one (e.g. permission dialogs) and presumably also // not resizable. if (mActivityRecord.getMinAspectRatio() != UNDEFINED_ASPECT_RATIO) { mInheritedMinAspectRatio = firstOpaque.getMinAspectRatio(); mInheritedMinAspectRatio = opaqueActivity.getMinAspectRatio(); } if (mActivityRecord.getMaxAspectRatio() != UNDEFINED_ASPECT_RATIO) { mInheritedMaxAspectRatio = firstOpaque.getMaxAspectRatio(); mInheritedMaxAspectRatio = opaqueActivity.getMaxAspectRatio(); } mInheritedOrientation = firstOpaque.getRequestedConfigurationOrientation(); mInheritedAppCompatState = firstOpaque.getAppCompatState(); mInheritedCompatDisplayInsets = firstOpaque.getCompatDisplayInsets(); mInheritedOrientation = opaqueActivity.getRequestedConfigurationOrientation(); mInheritedAppCompatState = opaqueActivity.getAppCompatState(); mInheritedCompatDisplayInsets = opaqueActivity.getCompatDisplayInsets(); } private void clearInheritedConfig() { if (mFirstOpaqueActivityBeneath != null) { mFirstOpaqueActivityBeneath.mLetterboxUiController.getTransparentPolicy() .mDestroyListeners.remove(this); private void reset() { if (mLetterboxConfigListener != null) { mLetterboxConfigListener.onRemoved(); } mFirstOpaqueActivityBeneath = null; mLetterboxConfigListener = null; mInheritedOrientation = ORIENTATION_UNDEFINED; mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; mInheritedOrientation = ORIENTATION_UNDEFINED; mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; mInheritedCompatDisplayInsets = null; if (mFirstOpaqueActivity != null) { mFirstOpaqueActivity.mLetterboxUiController.getTransparentPolicy() .mDestroyListeners.remove(mActivityRecord.mLetterboxUiController .getTransparentPolicy()); } mFirstOpaqueActivity = null; } private boolean isRunning() { 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; } /** * @return The first not finishing opaque activity beneath the current translucent activity * if it exists and the strategy is enabled. */ private Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() { if (!isRunning() || mActivityRecord.getTask() == null) { return Optional.empty(); } return Optional.ofNullable(mFirstOpaqueActivity); } /** * In case of translucent activities, it consumes the {@link ActivityRecord} of the first * opaque activity beneath using the given consumer and returns {@code true}. */ private boolean applyOnOpaqueActivityBelow(@NonNull Consumer<ActivityRecord> consumer) { return findOpaqueNotFinishingActivityBelow() .map(activityRecord -> { consumer.accept(activityRecord); return true; }).orElse(false); } } }
services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +9 −12 Original line number Diff line number Diff line Loading @@ -396,8 +396,7 @@ public class SizeCompatTests extends WindowTestsBase { opaqueActivity.removeImmediately(); // Check that updateInheritedLetterbox() is invoked again verify(translucentActivity.mLetterboxUiController.getTransparentPolicy()) .updateInheritedLetterbox(); verify(translucentActivity.mLetterboxUiController.getTransparentPolicy()).start(); } // TODO(b/333663877): Enable test after fix Loading Loading @@ -466,8 +465,6 @@ public class SizeCompatTests extends WindowTestsBase { mTask.addChild(translucentActivity); // Transparent strategy applied assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); assertNotNull(translucentActivity.mLetterboxUiController .getTransparentPolicy().mFirstOpaqueActivityBeneath); spyOn(translucentActivity.mLetterboxUiController.getTransparentPolicy()); clearInvocations(translucentActivity.mLetterboxUiController.getTransparentPolicy()); Loading @@ -475,11 +472,10 @@ public class SizeCompatTests extends WindowTestsBase { // We destroy the first opaque activity mActivity.removeImmediately(); // Check that updateInheritedLetterbox() is invoked again on the TransparentPolicy verify(translucentActivity.mLetterboxUiController.getTransparentPolicy()) .updateInheritedLetterbox(); assertNull(translucentActivity.mLetterboxUiController .getTransparentPolicy().mFirstOpaqueActivityBeneath); // Check that start() is invoked again on the TransparentPolicy verify(translucentActivity.mLetterboxUiController.getTransparentPolicy()).start(); assertFalse(translucentActivity.mLetterboxUiController .getTransparentPolicy().hasInheritedLetterboxBehavior()); } @Test Loading Loading @@ -507,11 +503,12 @@ public class SizeCompatTests extends WindowTestsBase { // Transparent strategy applied assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); spyOn(translucentActivity.mLetterboxUiController); clearInvocations(translucentActivity.mLetterboxUiController); spyOn(translucentActivity.mLetterboxUiController.getTransparentPolicy()); clearInvocations(translucentActivity.mLetterboxUiController.getTransparentPolicy()); // Check that updateInheritedLetterbox() is invoked again verify(translucentActivity.mLetterboxUiController, never()).updateInheritedLetterbox(); verify(translucentActivity.mLetterboxUiController.getTransparentPolicy(), never()) .start(); } @Test Loading