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

Commit ea43a07a authored by jainrachit's avatar jainrachit
Browse files

[2/n] Create AppCompatSafeRegionPolicy for letterboxing

- A safe region is an area on the WindowContainer in which an activity
  can open without have any occlusion.
- This policy controls how the letterboxing should be applied if a safe
  region is set and required.

Bug: 380132497
Flag: com.android.window.flags.safe_region_letterboxing
Test: atest AppCompatSafeRegionPolicyTests
Change-Id: I2637c80071b295393dc31318e47184c188bcaf20
parent 8f852c19
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -33,6 +33,8 @@ class AppCompatController {
    @NonNull
    private final AppCompatAspectRatioPolicy mAspectRatioPolicy;
    @NonNull
    private final AppCompatSafeRegionPolicy mSafeRegionPolicy;
    @NonNull
    private final AppCompatReachabilityPolicy mReachabilityPolicy;
    @NonNull
    private final DesktopAppCompatAspectRatioPolicy mDesktopAspectRatioPolicy;
@@ -62,6 +64,7 @@ class AppCompatController {
        mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord, mAppCompatOverrides);
        mAspectRatioPolicy = new AppCompatAspectRatioPolicy(activityRecord,
                mTransparentPolicy, mAppCompatOverrides);
        mSafeRegionPolicy = new AppCompatSafeRegionPolicy(activityRecord);
        mReachabilityPolicy = new AppCompatReachabilityPolicy(activityRecord,
                wmService.mAppCompatConfiguration);
        mLetterboxPolicy = new AppCompatLetterboxPolicy(activityRecord,
@@ -89,6 +92,11 @@ class AppCompatController {
        return mAspectRatioPolicy;
    }

    @NonNull
    AppCompatSafeRegionPolicy getSafeRegionPolicy() {
        return mSafeRegionPolicy;
    }

    @NonNull
    DesktopAppCompatAspectRatioPolicy getDesktopAspectRatioPolicy() {
        return mDesktopAspectRatioPolicy;
@@ -163,6 +171,6 @@ class AppCompatController {
        getTransparentPolicy().dump(pw, prefix);
        getLetterboxPolicy().dump(pw, prefix);
        getSizeCompatModePolicy().dump(pw, prefix);
        getSafeRegionPolicy().dump(pw, prefix);
    }

}
+101 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.wm;

import android.annotation.NonNull;
import android.content.res.Configuration;
import android.graphics.Rect;

import java.io.PrintWriter;

/**
 * Encapsulate app compat policy logic related to a safe region.
 */
class AppCompatSafeRegionPolicy {
    @NonNull
    private final ActivityRecord mActivityRecord;
    // Whether the Activity needs to be in the safe region bounds.
    private boolean mNeedsSafeRegionBounds = false;
    // Denotes the latest safe region bounds. Can be empty if the activity or the ancestors do
    // not have any safe region bounds.
    @NonNull
    private final Rect mLatestSafeRegionBounds = new Rect();

    AppCompatSafeRegionPolicy(@NonNull ActivityRecord activityRecord) {
        mActivityRecord = activityRecord;
    }

    /**
     * Computes the latest safe region bounds in
     * {@link ActivityRecord#resolveOverrideConfiguration(Configuration)} since the activity has not
     * been attached to the parent container when the ActivityRecord is instantiated.
     *
     * @return latest safe region bounds as set on an ancestor window container.
     */
    public Rect getLatestSafeRegionBounds() {
        // Get the latest safe region bounds since the bounds could have changed
        final Rect latestSafeRegionBounds = mActivityRecord.getSafeRegionBounds();
        if (latestSafeRegionBounds != null) {
            mLatestSafeRegionBounds.set(latestSafeRegionBounds);
        } else {
            mLatestSafeRegionBounds.setEmpty();
        }
        return latestSafeRegionBounds;
    }

    /**
     * Computes bounds when letterboxing is required only for the safe region bounds.
     */
    public void resolveSafeRegionBoundsConfiguration(@NonNull Configuration resolvedConfig,
            @NonNull Configuration newParentConfig) {
        if (mLatestSafeRegionBounds.isEmpty()) {
            return;
        }
        resolvedConfig.windowConfiguration.setBounds(mLatestSafeRegionBounds);
        mActivityRecord.computeConfigByResolveHint(resolvedConfig, newParentConfig);
    }

    /**
     * @return {@code true} if the activity is letterboxed only due to the safe region being set on
     * the current or ancestor window container.
     */
    boolean isLetterboxedForSafeRegionOnly() {
        return !mActivityRecord.areBoundsLetterboxed() && getNeedsSafeRegionBounds()
                && getLatestSafeRegionBounds() != null;
    }

    /**
     * Set {@code true} if this activity needs to be within the safe region bounds, else false.
     */
    public void setNeedsSafeRegionBounds(boolean needsSafeRegionBounds) {
        mNeedsSafeRegionBounds = needsSafeRegionBounds;
    }

    /**
     * @return {@code true} if this activity needs to be within the safe region bounds.
     */
    public boolean getNeedsSafeRegionBounds() {
        return mNeedsSafeRegionBounds;
    }

    void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
        if (mNeedsSafeRegionBounds) {
            pw.println(prefix + " mNeedsSafeRegionBounds=true");
        }
        pw.println(prefix + " isLetterboxedForSafeRegionOnly=" + isLetterboxedForSafeRegionOnly());
    }
}
+102 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.wm;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;

import static org.junit.Assert.assertEquals;

import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;

import androidx.annotation.NonNull;

import com.android.window.flags.Flags;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.function.Consumer;

/**
 * Test class for {@link AppCompatSafeRegionPolicy}.
 * Build/Install/Run:
 * atest WmTests:AppCompatSafeRegionPolicyTests
 */
@Presubmit
@RunWith(WindowTestRunner.class)
@EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
public class AppCompatSafeRegionPolicyTests extends WindowTestsBase {

    @Test
    public void testHasNeedsSafeRegion_returnTrue() {
        runTestScenario((robot) -> {
            robot.activity().createActivityWithComponent();
            robot.setNeedsSafeRegionBounds(/*needsSafeRegionBounds*/ true);

            robot.checkNeedsSafeRegionBounds(/* expected */ true);
        });
    }

    @Test
    public void testDoesNotHaveNeedsSafeRegion_returnFalse() {
        runTestScenario((robot) -> {
            robot.activity().createActivityWithComponent();
            robot.setNeedsSafeRegionBounds(/*needsSafeRegionBounds*/ false);

            robot.checkNeedsSafeRegionBounds(/* expected */ false);
        });
    }

    /**
     * Runs a test scenario providing a Robot.
     */
    void runTestScenario(@NonNull Consumer<AppCompatSafeRegionPolicyRobotTest> consumer) {
        final AppCompatSafeRegionPolicyRobotTest robot =
                new AppCompatSafeRegionPolicyRobotTest(mWm, mAtm, mSupervisor);
        consumer.accept(robot);
    }

    private static class AppCompatSafeRegionPolicyRobotTest extends AppCompatRobotBase {

        AppCompatSafeRegionPolicyRobotTest(@NonNull WindowManagerService wm,
                @NonNull ActivityTaskManagerService atm,
                @NonNull ActivityTaskSupervisor supervisor) {
            super(wm, atm, supervisor);
        }

        @Override
        void onPostActivityCreation(@NonNull ActivityRecord activity) {
            super.onPostActivityCreation(activity);
            spyOn(activity.mAppCompatController.getSafeRegionPolicy());
        }

        AppCompatSafeRegionPolicy getAppCompatSafeRegionPolicy() {
            return activity().top().mAppCompatController.getSafeRegionPolicy();
        }

        void setNeedsSafeRegionBounds(boolean needsSafeRegionBounds) {
            doReturn(needsSafeRegionBounds).when(
                    getAppCompatSafeRegionPolicy()).getNeedsSafeRegionBounds();
        }

        void checkNeedsSafeRegionBounds(boolean expected) {
            assertEquals(expected, getAppCompatSafeRegionPolicy().getNeedsSafeRegionBounds());
        }
    }
}