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

Commit 6162cc7f authored by Mariia Sandrikova's avatar Mariia Sandrikova Committed by Android (Google) Code Review
Browse files

Merge "[4/n] Camera Compat: per-app controls for force rotation and refresh" into tm-qpr-dev

parents 705dfd7c 6e24a213
Loading
Loading
Loading
Loading
+35 −0
Original line number Diff line number Diff line
@@ -1057,6 +1057,41 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
    @TestApi
    public static final long ALWAYS_SANDBOX_DISPLAY_APIS = 185004937L; // buganizer id

    /**
     * This change id excludes the packages it is applied to from the camera compat force rotation
     * treatment. See com.android.server.wm.DisplayRotationCompatPolicy for context.
     * @hide
     */
    @ChangeId
    @Overridable
    @Disabled
    public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION =
            263959004L; // buganizer id

    /**
     * This change id excludes the packages it is applied to from activity refresh after camera
     * compat force rotation treatment. See com.android.server.wm.DisplayRotationCompatPolicy for
     * context.
     * @hide
     */
    @ChangeId
    @Overridable
    @Disabled
    public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH = 264304459L; // buganizer id

    /**
     * This change id makes the packages it is applied to do activity refresh after camera compat
     * force rotation treatment using "resumed -> paused -> resumed" cycle rather than "resumed ->
     * ... -> stopped -> ... -> resumed" cycle. See
     * com.android.server.wm.DisplayRotationCompatPolicy for context.
     * @hide
     */
    @ChangeId
    @Overridable
    @Disabled
    public static final long OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
            264301586L; // buganizer id

    /**
     * This change id is the gatekeeper for all treatments that force a given min aspect ratio.
     * Enabling this change will allow the following min aspect ratio treatments to be applied:
+137 −0
Original line number Diff line number Diff line
@@ -852,6 +852,143 @@ public interface WindowManager extends ViewManager {
    String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION =
            "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";

    /**
     * Activity level {@link android.content.pm.PackageManager.Property PackageManager
     * .Property} for an app to inform the system that the activity should be excluded from the
     * camera compatibility force rotation treatment.
     *
     * <p>The camera compatibility treatment aligns orientations of portrait app window and natural
     * orientation of the device and set opposite to natural orientation for a landscape app
     * window. Mismatch between them can lead to camera issues like sideways or stretched
     * viewfinder since this is one of the strongest assumptions that apps make when they implement
     * camera previews. Since app and natural display orientations aren't guaranteed to match, the
     * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
     * camera and is removed once camera is closed.
     *
     * <p>The camera compatibility can be enabled by device manufacturers on the displays that have
     * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
     * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
     * for more details).
     *
     * <p>With this property set to {@code true} or unset, the system may apply the force rotation
     * treatment to fixed orientation activities. Device manufacturers can exclude packages from the
     * treatment using their discretion to improve display compatibility.
     *
     * <p>With this property set to {@code false}, the system will not apply the force rotation
     * treatment.
     *
     * <p><b>Syntax:</b>
     * <pre>
     * &lt;activity&gt;
     *   &lt;property
     *     android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION"
     *     android:value="true|false"/&gt;
     * &lt;/activity&gt;
     * </pre>
     *
     * @hide
     */
    // TODO(b/263984287): Make this public API.
    String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION =
            "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";

    /**
     * Activity level {@link android.content.pm.PackageManager.Property PackageManager
     * .Property} for an app to inform the system that the activity should be excluded
     * from the activity "refresh" after the camera compatibility force rotation treatment.
     *
     * <p>The camera compatibility treatment aligns orientations of portrait app window and natural
     * orientation of the device and set opposite to natural orientation for a landscape app
     * window. Mismatch between them can lead to camera issues like sideways or stretched
     * viewfinder since this is one of the strongest assumptions that apps make when they implement
     * camera previews. Since app and natural display orientations aren't guaranteed to match, the
     * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
     * camera and is removed once camera is closed.
     *
     * <p>Force rotation is followed by the "Refresh" of the activity by going through "resumed ->
     * ... -> stopped -> ... -> resumed" cycle (by default) or "resumed -> paused -> resumed" cycle
     * (if overridden, see {@link #PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE} for context).
     * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
     * camera preview and can lead to sideways or stretching issues persisting even after force
     * rotation.
     *
     * <p>The camera compatibility can be enabled by device manufacturers on the displays that have
     * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
     * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
     * for more details).
     *
     * <p>With this property set to {@code true} or unset, the system may "refresh" activity after
     * the force rotation treatment. Device manufacturers can exclude packages from the "refresh"
     * using their discretion to improve display compatibility.
     *
     * <p>With this property set to {@code false}, the system will not "refresh" activity after the
     * force rotation treatment.
     *
     * <p><b>Syntax:</b>
     * <pre>
     * &lt;activity&gt;
     *   &lt;property
     *     android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH"
     *     android:value="true|false"/&gt;
     * &lt;/activity&gt;
     * </pre>
     *
     * @hide
     */
    // TODO(b/263984287): Make this public API.
    String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH =
            "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";

    /**
     * Activity level {@link android.content.pm.PackageManager.Property PackageManager
     * .Property} for an app to inform the system that the activity should be or shouldn't be
     * "refreshed" after the camera compatibility force rotation treatment using "paused ->
     * resumed" cycle rather than "stopped -> resumed".
     *
     * <p>The camera compatibility treatment aligns orientations of portrait app window and natural
     * orientation of the device and set opposite to natural orientation for a landscape app
     * window. Mismatch between them can lead to camera issues like sideways or stretched
     * viewfinder since this is one of the strongest assumptions that apps make when they implement
     * camera previews. Since app and natural display orientations aren't guaranteed to match, the
     * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
     * camera and is removed once camera is closed.
     *
     * <p>Force rotation is followed by the "Refresh" of the activity by going through "resumed ->
     * ... -> stopped -> ... -> resumed" cycle (by default) or "resumed -> paused -> resumed" cycle
     * (if overridden by device manufacturers or using this property). This allows to clear cached
     * values in apps (e.g., display or camera rotation) that influence camera preview and can lead
     * to sideways or stretching issues persisting even after force rotation.
     *
     * <p>The camera compatibility can be enabled by device manufacturers on the displays that have
     * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
     * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
     * for more details).
     *
     * <p>Device manufacturers can override packages to "refresh" via "resumed -> paused -> resumed"
     * cycle using their discretion to improve display compatibility.
     *
     * <p>With this property set to {@code true}, the system will "refresh" activity after the
     * force rotation treatment using "resumed -> paused -> resumed" cycle.
     *
     * <p>With this property set to {@code false}, the system will not "refresh" activity after the
     * force rotation treatment using "resumed -> paused -> resumed" cycle even if the device
     * manufacturer adds the corresponding override.
     *
     * <p><b>Syntax:</b>
     * <pre>
     * &lt;activity&gt;
     *   &lt;property
     *     android:name="android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE"
     *     android:value="true|false"/&gt;
     * &lt;/activity&gt;
     * </pre>
     *
     * @hide
     */
    // TODO(b/263984287): Make this public API.
    String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
            "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";

    /**
     * @hide
     */
+9 −4
Original line number Diff line number Diff line
@@ -203,8 +203,11 @@ final class DisplayRotationCompatPolicy {
                || !shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
            return;
        }
        boolean cycleThroughStop = mWmService.mLetterboxConfiguration
                .isCameraCompatRefreshCycleThroughStopEnabled();
        boolean cycleThroughStop =
                mWmService.mLetterboxConfiguration
                        .isCameraCompatRefreshCycleThroughStopEnabled()
                && !activity.mLetterboxUiController
                        .shouldRefreshActivityViaPauseForCameraCompat();
        try {
            activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true);
            ProtoLog.v(WM_DEBUG_STATES,
@@ -255,7 +258,8 @@ final class DisplayRotationCompatPolicy {
            Configuration lastReportedConfig) {
        return newConfig.windowConfiguration.getDisplayRotation()
                        != lastReportedConfig.windowConfiguration.getDisplayRotation()
                && isTreatmentEnabledForActivity(activity);
                && isTreatmentEnabledForActivity(activity)
                && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat();
    }

    /**
@@ -294,7 +298,8 @@ final class DisplayRotationCompatPolicy {
                // handle dynamic changes so we shouldn't force rotate them.
                && activity.getRequestedOrientation() != SCREEN_ORIENTATION_NOSENSOR
                && activity.getRequestedOrientation() != SCREEN_ORIENTATION_LOCKED
                && mCameraIdPackageBiMap.containsPackageName(activity.packageName);
                && mCameraIdPackageBiMap.containsPackageName(activity.packageName)
                && activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
    }

    private synchronized void notifyCameraOpened(
+148 −9
Original line number Diff line number Diff line
@@ -17,12 +17,18 @@
package com.android.server.wm;

import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.ActivityInfo.screenOrientationToString;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;

import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
@@ -132,6 +138,15 @@ final class LetterboxUiController {
    @Nullable
    private Letterbox mLetterbox;

    @Nullable
    private final Boolean mBooleanPropertyCameraCompatAllowForceRotation;

    @Nullable
    private final Boolean mBooleanPropertyCameraCompatAllowRefresh;

    @Nullable
    private final Boolean mBooleanPropertyCameraCompatEnableRefreshViaPause;

    // Whether activity "refresh" was requested but not finished in
    // ActivityRecord#activityResumedLocked following the camera compat force rotation in
    // DisplayRotationCompatPolicy.
@@ -154,8 +169,33 @@ final class LetterboxUiController {
                readComponentProperty(packageManager, mActivityRecord.packageName,
                        mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled,
                        PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION);
        mBooleanPropertyCameraCompatAllowForceRotation =
                readComponentProperty(packageManager, mActivityRecord.packageName,
                        () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
                                /* checkDeviceConfig */ true),
                        PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION);
        mBooleanPropertyCameraCompatAllowRefresh =
                readComponentProperty(packageManager, mActivityRecord.packageName,
                        () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
                                /* checkDeviceConfig */ true),
                        PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH);
        mBooleanPropertyCameraCompatEnableRefreshViaPause =
                readComponentProperty(packageManager, mActivityRecord.packageName,
                        () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
                                /* checkDeviceConfig */ true),
                        PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE);
    }

    /**
     * Reads a {@link Boolean} component property fot a given {@code packageName} and a {@code
     * propertyName}. Returns {@code null} if {@code gatingCondition} is {@code false} or if the
     * property isn't specified for the package.
     *
     * <p>Return value is {@link Boolean} rather than {@code boolean} so we can know when the
     * property is unset. Particularly, when this returns {@code null}, {@link
     * #shouldEnableWithOverrideAndProperty} will check the value of override for the final
     * decision.
     */
    @Nullable
    private static Boolean readComponentProperty(PackageManager packageManager, String packageName,
            BooleanSupplier gatingCondition, String propertyName) {
@@ -210,15 +250,11 @@ final class LetterboxUiController {
     * </ul>
     */
    boolean shouldIgnoreRequestedOrientation(@ScreenOrientation int requestedOrientation) {
        if (!mLetterboxConfiguration.isPolicyForIgnoringRequestedOrientationEnabled()) {
            return false;
        }
        if (Boolean.FALSE.equals(mBooleanPropertyIgnoreRequestedOrientation)) {
            return false;
        }
        if (!Boolean.TRUE.equals(mBooleanPropertyIgnoreRequestedOrientation)
                && !mActivityRecord.info.isChangeEnabled(
                        OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION)) {
        if (!shouldEnableWithOverrideAndProperty(
                /* gatingCondition */ mLetterboxConfiguration
                        ::isPolicyForIgnoringRequestedOrientationEnabled,
                OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION,
                mBooleanPropertyIgnoreRequestedOrientation)) {
            return false;
        }
        if (mIsRelauchingAfterRequestedOrientationChanged) {
@@ -262,6 +298,109 @@ final class LetterboxUiController {
        mIsRefreshAfterRotationRequested = isRequested;
    }

    /**
     * Whether activity is eligible for activity "refresh" after camera compat force rotation
     * treatment. See {@link DisplayRotationCompatPolicy} for context.
     *
     * <p>This treatment is enabled when the following conditions are met:
     * <ul>
     *     <li>Flag gating the camera compat treatment is enabled.
     *     <li>Activity isn't opted out by the device manufacturer with override or by the app
     *     developers with the component property.
     * </ul>
     */
    boolean shouldRefreshActivityForCameraCompat() {
        return shouldEnableWithOptOutOverrideAndProperty(
                /* gatingCondition */ () -> mLetterboxConfiguration
                        .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
                OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH,
                mBooleanPropertyCameraCompatAllowRefresh);
    }

    /**
     * Whether activity should be "refreshed" after the camera compat force rotation treatment
     * using the "resumed -> paused -> resumed" cycle rather than the "resumed -> ... -> stopped
     * -> ... -> resumed" cycle. See {@link DisplayRotationCompatPolicy} for context.
     *
     * <p>This treatment is enabled when the following conditions are met:
     * <ul>
     *     <li>Flag gating the camera compat treatment is enabled.
     *     <li>Activity "refresh" via "resumed -> paused -> resumed" cycle isn't disabled with the
     *     component property by the app developers.
     *     <li>Activity "refresh" via "resumed -> paused -> resumed" cycle is enabled by the device
     *     manufacturer with override / by the app developers with the component property.
     * </ul>
     */
    boolean shouldRefreshActivityViaPauseForCameraCompat() {
        return shouldEnableWithOverrideAndProperty(
                /* gatingCondition */ () -> mLetterboxConfiguration
                        .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
                OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE,
                mBooleanPropertyCameraCompatEnableRefreshViaPause);
    }

    /**
     * Whether activity is eligible for camera compat force rotation treatment. See {@link
     * DisplayRotationCompatPolicy} for context.
     *
     * <p>This treatment is enabled when the following conditions are met:
     * <ul>
     *     <li>Flag gating the camera compat treatment is enabled.
     *     <li>Activity isn't opted out by the device manufacturer with override or by the app
     *     developers with the component property.
     * </ul>
     */
    boolean shouldForceRotateForCameraCompat() {
        return shouldEnableWithOptOutOverrideAndProperty(
                /* gatingCondition */ () -> mLetterboxConfiguration
                        .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
                OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION,
                mBooleanPropertyCameraCompatAllowForceRotation);
    }

    /**
     * Returns {@code true} when the following conditions are met:
     * <ul>
     *     <li>{@code gatingCondition} isn't {@code false}
     *     <li>OEM didn't opt out with a {@code overrideChangeId} override
     *     <li>App developers didn't opt out with a component {@code property}
     * </ul>
     *
     * <p>This is used for the treatments that are enabled based with the heuristic but can be
     * disabled on per-app basis by OEMs or app developers.
     */
    private boolean shouldEnableWithOptOutOverrideAndProperty(BooleanSupplier gatingCondition,
            long overrideChangeId, Boolean property) {
        if (!gatingCondition.getAsBoolean()) {
            return false;
        }
        return !Boolean.FALSE.equals(property)
                && !mActivityRecord.info.isChangeEnabled(overrideChangeId);
    }

    /**
     * Returns {@code true} when the following conditions are met:
     * <ul>
     *     <li>{@code gatingCondition} isn't {@code false}
     *     <li>App developers didn't opt out with a component {@code property}
     *     <li>App developers opted in with a component {@code property} or an OEM opted in with a
     *     component {@code property}
     * </ul>
     *
     * <p>This is used for the treatments that are enabled only on per-app basis.
     */
    private boolean shouldEnableWithOverrideAndProperty(BooleanSupplier gatingCondition,
            long overrideChangeId, Boolean property) {
        if (!gatingCondition.getAsBoolean()) {
            return false;
        }
        if (Boolean.FALSE.equals(property)) {
            return false;
        }
        return Boolean.TRUE.equals(property)
                || mActivityRecord.info.isChangeEnabled(overrideChangeId);
    }

    boolean hasWallpaperBackgroundForLetterbox() {
        return mShowWallpaperForLetterboxBackground;
    }
+40 −1
Original line number Diff line number Diff line
@@ -154,6 +154,18 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
        assertNoForceRotationOrRefresh();
    }

    @Test
    public void testTreatmentDisabledPerApp_noForceRotationOrRefresh()
            throws Exception {
        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
        when(mActivity.mLetterboxUiController.shouldForceRotateForCameraCompat())
                .thenReturn(false);

        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);

        assertNoForceRotationOrRefresh();
    }

    @Test
    public void testMultiWindowMode_returnUnspecified_noForceRotationOrRefresh() throws Exception {
        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -327,7 +339,21 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
    }

    @Test
    public void testOnActivityConfigurationChanging_refreshDisabled_noRefresh() throws Exception {
    public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh()
            throws Exception {
        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
        when(mActivity.mLetterboxUiController.shouldRefreshActivityForCameraCompat())
                .thenReturn(false);

        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
        callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);

        assertActivityRefreshRequested(/* refreshRequested */ false);
    }

    @Test
    public void testOnActivityConfigurationChanging_refreshDisabledPerApp_noRefresh()
            throws Exception {
        when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()).thenReturn(false);

        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -362,6 +388,19 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
        assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
    }

    @Test
    public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
            throws Exception {
        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
        when(mActivity.mLetterboxUiController.shouldRefreshActivityViaPauseForCameraCompat())
                .thenReturn(true);

        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
        callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);

        assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
    }

    private void configureActivity(@ScreenOrientation int activityOrientation) {
        configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT);
    }
Loading