Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 70657ee8 authored by Massimo Carli's avatar Massimo Carli Committed by Android (Google) Code Review
Browse files

Merge "[1/n] Create TransparentPolicy and move related code" into main

parents 14f056b2 0b0860b4
Loading
Loading
Loading
Loading
+24 −166
Original line number Diff line number Diff line
@@ -40,7 +40,6 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
import static android.content.pm.ActivityInfo.isFixedOrientation;
import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
@@ -54,10 +53,6 @@ import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -80,7 +75,6 @@ import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANG
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM;
import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT;
@@ -135,12 +129,9 @@ import com.android.server.wm.utils.OptPropFactory.OptProp;
import com.android.window.flags.Flags;

import java.io.PrintWriter;
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;

/** Controls behaviour of the letterbox UI for {@link mActivityRecord}. */
// TODO(b/185262487): Improve test coverage of this class. Parts of it are tested in
@@ -150,13 +141,8 @@ import java.util.function.Predicate;
// TODO(b/263021211): Consider renaming to more generic CompatUIController.
final class LetterboxUiController {

    private static final Predicate<ActivityRecord> FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE =
            ActivityRecord::occludesParent;

    private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM;

    private static final float UNDEFINED_ASPECT_RATIO = 0f;

    // Minimum value of mSetOrientationRequestCounter before qualifying as orientation request loop
    @VisibleForTesting
    static final int MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP = 2;
@@ -188,10 +174,6 @@ final class LetterboxUiController {
    // Corresponds to OVERRIDE_RESPECT_REQUESTED_ORIENTATION
    private final boolean mIsOverrideRespectRequestedOrientationEnabled;

    // The list of observers for the destroy event of candidate opaque activities
    // when dealing with translucent activities.
    private final List<LetterboxUiController> mDestroyListeners = new ArrayList<>();

    @NonNull
    private final OptProp mAllowOrientationOverrideOptProp;
    @NonNull
@@ -206,34 +188,11 @@ final class LetterboxUiController {
    @NonNull
    private final OptProp mAllowUserAspectRatioFullscreenOverrideOptProp;

    /*
     * 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;

    private boolean mShowWallpaperForLetterboxBackground;

    // 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;

    // Updated when ActivityRecord#setRequestedOrientation is called
    private long mTimeMsLastSetOrientationRequest = 0;

    @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;

    // Counter for ActivityRecord#setRequestedOrientation
    private int mSetOrientationRequestCounter = 0;

@@ -242,9 +201,6 @@ final class LetterboxUiController {
    @PackageManager.UserMinAspectRatio
    private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET;

    // The CompatDisplayInsets of the opaque activity beneath the translucent one.
    private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets;

    @Nullable
    private Letterbox mLetterbox;

@@ -271,6 +227,10 @@ 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;
@@ -284,6 +244,7 @@ final class LetterboxUiController {
        // to use it after since controller is only used in ActivityRecord.
        mActivityRecord = activityRecord;

        mTransparentPolicy = new TransparentPolicy(activityRecord, mLetterboxConfiguration);
        PackageManager packageManager = wmService.mContext.getPackageManager();

        final OptPropFactory optPropBuilder = new OptPropFactory(packageManager,
@@ -361,14 +322,7 @@ final class LetterboxUiController {
            mLetterbox.destroy();
            mLetterbox = null;
        }
        for (int i = mDestroyListeners.size() - 1; i >= 0; i--) {
            mDestroyListeners.get(i).updateInheritedLetterbox();
        }
        mDestroyListeners.clear();
        if (mLetterboxConfigListener != null) {
            mLetterboxConfigListener.onRemoved();
            mLetterboxConfigListener = null;
        }
        mTransparentPolicy.destroy();
    }

    void onMovedToDisplay(int displayId) {
@@ -377,6 +331,11 @@ 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}.
@@ -1344,7 +1303,7 @@ 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()
                ? mFirstOpaqueActivityBeneath.getScreenResolvedBounds()
                ? getTransparentPolicy().mFirstOpaqueActivityBeneath.getScreenResolvedBounds()
                : mActivityRecord.getScreenResolvedBounds();
        return mLetterboxConfiguration.getIsHorizontalReachabilityEnabled()
                && parentConfiguration.windowConfiguration.getWindowingMode()
@@ -1382,7 +1341,7 @@ 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()
                ? mFirstOpaqueActivityBeneath.getScreenResolvedBounds()
                ? getTransparentPolicy().mFirstOpaqueActivityBeneath.getScreenResolvedBounds()
                : mActivityRecord.getScreenResolvedBounds();
        return mLetterboxConfiguration.getIsVerticalReachabilityEnabled()
                && parentConfiguration.windowConfiguration.getWindowingMode()
@@ -1797,56 +1756,9 @@ final class LetterboxUiController {
    /**
     * Handles translucent activities letterboxing inheriting constraints from the
     * first opaque activity beneath.
     * @param parent The parent container.
     */
    void updateInheritedLetterbox() {
        final WindowContainer<?> parent = mActivityRecord.getParent();
        if (parent == null) {
            return;
        }
        if (!mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) {
            return;
        }
        if (mLetterboxConfigListener != null) {
            mLetterboxConfigListener.onRemoved();
            clearInheritedConfig();
        }
        // 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(
                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();
            return;
        }
        if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent()
                || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) {
            return;
        }
        mFirstOpaqueActivityBeneath.mLetterboxUiController.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;
                });
        mTransparentPolicy.updateInheritedLetterbox();
    }

    /**
@@ -1855,7 +1767,7 @@ final class LetterboxUiController {
     * the first opaque activity beneath.
     */
    boolean hasInheritedLetterboxBehavior() {
        return mLetterboxConfigListener != null;
        return mTransparentPolicy.hasInheritedLetterboxBehavior();
    }

    /**
@@ -1863,39 +1775,32 @@ final class LetterboxUiController {
     * 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()
                && mActivityRecord.getOverrideOrientation()
                        != SCREEN_ORIENTATION_UNSPECIFIED;
        return mTransparentPolicy.hasInheritedOrientation();
    }

    float getInheritedMinAspectRatio() {
        return mInheritedMinAspectRatio;
        return mTransparentPolicy.getInheritedMinAspectRatio();
    }

    float getInheritedMaxAspectRatio() {
        return mInheritedMaxAspectRatio;
        return mTransparentPolicy.getInheritedMaxAspectRatio();
    }

    int getInheritedAppCompatState() {
        return mInheritedAppCompatState;
        return mTransparentPolicy.getInheritedAppCompatState();
    }

    @Configuration.Orientation
    int getInheritedOrientation() {
        return mInheritedOrientation;
        return mTransparentPolicy.getInheritedOrientation();
    }

    ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() {
        return mInheritedCompatDisplayInsets;
        return mTransparentPolicy.getInheritedCompatDisplayInsets();
    }

    void clearInheritedCompatDisplayInsets() {
        mInheritedCompatDisplayInsets = null;
        mTransparentPolicy.clearInheritedCompatDisplayInsets();
    }

    /**
@@ -1903,11 +1808,7 @@ final class LetterboxUiController {
     * 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 mTransparentPolicy.applyOnOpaqueActivityBelow(consumer);
    }

    /**
@@ -1915,50 +1816,7 @@ final class LetterboxUiController {
     * if it exists and the strategy is enabled.
     */
    Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() {
        if (!hasInheritedLetterboxBehavior() || mActivityRecord.getTask() == null) {
            return Optional.empty();
        }
        return Optional.ofNullable(mFirstOpaqueActivityBeneath);
    }

    /** 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
        // resolution in {@link ActivityRecord#resolveOverrideConfiguration} using the
        // properties inherited from the first not finishing opaque activity beneath.
        config.orientation = ORIENTATION_UNDEFINED;
        config.screenWidthDp = config.compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED;
        config.screenHeightDp = config.compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED;
        config.smallestScreenWidthDp = config.compatSmallestScreenWidthDp =
                SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
    }

    private void inheritConfiguration(ActivityRecord firstOpaque) {
        // 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();
        }
        if (mActivityRecord.getMaxAspectRatio() != UNDEFINED_ASPECT_RATIO) {
            mInheritedMaxAspectRatio = firstOpaque.getMaxAspectRatio();
        }
        mInheritedOrientation = firstOpaque.getRequestedConfigurationOrientation();
        mInheritedAppCompatState = firstOpaque.getAppCompatState();
        mInheritedCompatDisplayInsets = firstOpaque.getCompatDisplayInsets();
    }

    private void clearInheritedConfig() {
        if (mFirstOpaqueActivityBeneath != null) {
            mFirstOpaqueActivityBeneath.mLetterboxUiController.mDestroyListeners.remove(this);
        }
        mFirstOpaqueActivityBeneath = null;
        mLetterboxConfigListener = null;
        mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO;
        mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO;
        mInheritedOrientation = ORIENTATION_UNDEFINED;
        mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
        mInheritedCompatDisplayInsets = null;
        return mTransparentPolicy.findOpaqueNotFinishingActivityBelow();
    }

    @NonNull
+276 −0

File added.

Preview size limit exceeded, changes collapsed.

+13 −9
Original line number Diff line number Diff line
@@ -388,15 +388,16 @@ 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());

        // We destroy the first opaque activity
        opaqueActivity.setState(DESTROYED, "testing");
        opaqueActivity.removeImmediately();

        // Check that updateInheritedLetterbox() is invoked again
        verify(translucentActivity.mLetterboxUiController).updateInheritedLetterbox();
        verify(translucentActivity.mLetterboxUiController.getTransparentPolicy())
                .updateInheritedLetterbox();
    }

    // TODO(b/333663877): Enable test after fix
@@ -465,17 +466,20 @@ public class SizeCompatTests extends WindowTestsBase {
        mTask.addChild(translucentActivity);
        // Transparent strategy applied
        assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior());
        assertNotNull(translucentActivity.mLetterboxUiController.mFirstOpaqueActivityBeneath);
        assertNotNull(translucentActivity.mLetterboxUiController
                .getTransparentPolicy().mFirstOpaqueActivityBeneath);

        spyOn(translucentActivity.mLetterboxUiController);
        clearInvocations(translucentActivity.mLetterboxUiController);
        spyOn(translucentActivity.mLetterboxUiController.getTransparentPolicy());
        clearInvocations(translucentActivity.mLetterboxUiController.getTransparentPolicy());

        // We destroy the first opaque activity
        mActivity.removeImmediately();

        // Check that updateInheritedLetterbox() is invoked again
        verify(translucentActivity.mLetterboxUiController).updateInheritedLetterbox();
        assertNull(translucentActivity.mLetterboxUiController.mFirstOpaqueActivityBeneath);
        // Check that updateInheritedLetterbox() is invoked again on the TransparentPolicy
        verify(translucentActivity.mLetterboxUiController.getTransparentPolicy())
                .updateInheritedLetterbox();
        assertNull(translucentActivity.mLetterboxUiController
                .getTransparentPolicy().mFirstOpaqueActivityBeneath);
    }

    @Test