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

Commit 9d0689d9 authored by Massimo Carli's avatar Massimo Carli
Browse files

[13/n] Create Overrides and Policy for App Compat Camera

Create AppCompatCameraPolicy and the related Overrides classes
to encapsulate app compat camera logic.

Flag: EXEMPT refactor
Bug: 346253439
Test: atest WmTests:LetterboxUiControllerTest
Test: atest WmTests:DisplayRotationCompatPolicyTests
Test: atest WmTests:CameraCompatFreeformPolicyTests
Test: atest WmTests:AppCompatOrientationOverridesTest
Test: atest WmTests:ActivityRefresherTests

Change-Id: Iff31b0c76e184464e52424664c7b7f1ac9b190cc
parent 7150df0d
Loading
Loading
Loading
Loading
+11 −8
Original line number Diff line number Diff line
@@ -8179,8 +8179,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
    }
    void setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation) {
        if (mAppCompatController.getAppCompatOverrides()
                .getAppCompatOrientationOverrides()
        if (mAppCompatController.getAppCompatOrientationOverrides()
                .shouldIgnoreRequestedOrientation(requestedOrientation)) {
            return;
        }
@@ -8559,7 +8558,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        final int parentWindowingMode =
                newParentConfiguration.windowConfiguration.getWindowingMode();
        final boolean isInCameraCompatFreeform = parentWindowingMode == WINDOWING_MODE_FREEFORM
                && mLetterboxUiController.getFreeformCameraCompatMode()
                && mAppCompatController.getAppCompatCameraOverrides().getFreeformCameraCompatMode()
                        != CAMERA_COMPAT_FREEFORM_NONE;
        // Bubble activities should always fill their parent and should not be letterboxed.
@@ -9887,7 +9886,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
            return mLetterboxUiController.getUserMinAspectRatio();
        }
        if (!mLetterboxUiController.shouldOverrideMinAspectRatio()
                && !mLetterboxUiController.shouldOverrideMinAspectRatioForCamera()) {
                && !mAppCompatController.getAppCompatCameraOverrides()
                    .shouldOverrideMinAspectRatioForCamera()) {
            return info.getMinAspectRatio();
        }
        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
@@ -10814,15 +10814,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        proto.write(OVERRIDE_ORIENTATION, getOverrideOrientation());
        proto.write(SHOULD_SEND_COMPAT_FAKE_FOCUS, shouldSendCompatFakeFocus());
        proto.write(SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT,
                mLetterboxUiController.shouldForceRotateForCameraCompat());
                mAppCompatController.getAppCompatCameraOverrides()
                        .shouldForceRotateForCameraCompat());
        proto.write(SHOULD_REFRESH_ACTIVITY_FOR_CAMERA_COMPAT,
                mLetterboxUiController.shouldRefreshActivityForCameraCompat());
                mAppCompatController.getAppCompatCameraOverrides()
                        .shouldRefreshActivityForCameraCompat());
        proto.write(SHOULD_REFRESH_ACTIVITY_VIA_PAUSE_FOR_CAMERA_COMPAT,
                mLetterboxUiController.shouldRefreshActivityViaPauseForCameraCompat());
                mAppCompatController.getAppCompatCameraOverrides()
                        .shouldRefreshActivityViaPauseForCameraCompat());
        proto.write(SHOULD_OVERRIDE_MIN_ASPECT_RATIO,
                mLetterboxUiController.shouldOverrideMinAspectRatio());
        proto.write(SHOULD_IGNORE_ORIENTATION_REQUEST_LOOP,
                mAppCompatController.getAppCompatOverrides().getAppCompatOrientationOverrides()
                mAppCompatController.getAppCompatOrientationOverrides()
                        .shouldIgnoreOrientationRequestLoop());
        proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP,
                mLetterboxUiController.shouldOverrideForceResizeApp());
+9 −7
Original line number Diff line number Diff line
@@ -77,10 +77,10 @@ class ActivityRefresher {
        final boolean cycleThroughStop =
                mWmService.mLetterboxConfiguration
                        .isCameraCompatRefreshCycleThroughStopEnabled()
                        && !activity.mLetterboxUiController
                        && !activity.mAppCompatController.getAppCompatCameraOverrides()
                            .shouldRefreshActivityViaPauseForCameraCompat();

        activity.mLetterboxUiController.setIsRefreshRequested(true);
        activity.mAppCompatController.getAppCompatCameraOverrides().setIsRefreshRequested(true);
        ProtoLog.v(WM_DEBUG_STATES,
                "Refreshing activity for freeform camera compatibility treatment, "
                        + "activityRecord=%s", activity);
@@ -97,24 +97,26 @@ class ActivityRefresher {
                }
            }, REFRESH_CALLBACK_TIMEOUT_MS);
        } catch (RemoteException e) {
            activity.mLetterboxUiController.setIsRefreshRequested(false);
            activity.mAppCompatController.getAppCompatCameraOverrides()
                    .setIsRefreshRequested(false);
        }
    }

    boolean isActivityRefreshing(@NonNull ActivityRecord activity) {
        return activity.mLetterboxUiController.isRefreshRequested();
        return activity.mAppCompatController.getAppCompatCameraOverrides().isRefreshRequested();
    }

    void onActivityRefreshed(@NonNull ActivityRecord activity) {
        // TODO(b/333060789): can we tell that refresh did not happen by observing the activity
        //  state?
        activity.mLetterboxUiController.setIsRefreshRequested(false);
        activity.mAppCompatController.getAppCompatCameraOverrides().setIsRefreshRequested(false);
    }

    private boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
            @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
        return mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
                && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()
                && activity.mAppCompatController.getAppCompatOverrides()
                    .getAppCompatCameraOverrides().shouldRefreshActivityForCameraCompat()
                && ArrayUtils.find(mEvaluators.toArray(), evaluator ->
                ((Evaluator) evaluator)
                        .shouldRefreshActivity(activity, newConfig, lastReportedConfig)) != null;
+235 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
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_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
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_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;

import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;

import android.annotation.NonNull;
import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode;

import com.android.server.wm.utils.OptPropFactory;
import com.android.window.flags.Flags;

import java.util.function.BooleanSupplier;

/**
 * Encapsulates app compat configurations and overrides related to camera.
 */
class AppCompatCameraOverrides {

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

    @NonNull
    private final ActivityRecord mActivityRecord;
    @NonNull
    private final AppCompatCameraOverridesState mAppCompatCameraOverridesState;
    @NonNull
    private final LetterboxConfiguration mLetterboxConfiguration;
    @NonNull
    private final OptPropFactory.OptProp mAllowMinAspectRatioOverrideOptProp;
    @NonNull
    private final OptPropFactory.OptProp mCameraCompatAllowRefreshOptProp;
    @NonNull
    private final OptPropFactory.OptProp mCameraCompatEnableRefreshViaPauseOptProp;
    @NonNull
    private final OptPropFactory.OptProp mCameraCompatAllowForceRotationOptProp;

    AppCompatCameraOverrides(@NonNull ActivityRecord activityRecord,
            @NonNull LetterboxConfiguration letterboxConfiguration,
            @NonNull OptPropFactory optPropBuilder) {
        mActivityRecord = activityRecord;
        mLetterboxConfiguration = letterboxConfiguration;
        mAppCompatCameraOverridesState = new AppCompatCameraOverridesState();
        mAllowMinAspectRatioOverrideOptProp = optPropBuilder.create(
                PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
        final BooleanSupplier isCameraCompatTreatmentEnabled = AppCompatUtils.asLazy(
                mLetterboxConfiguration::isCameraCompatTreatmentEnabled);
        mCameraCompatAllowRefreshOptProp = optPropBuilder.create(
                PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH,
                isCameraCompatTreatmentEnabled);
        mCameraCompatEnableRefreshViaPauseOptProp = optPropBuilder.create(
                PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE,
                isCameraCompatTreatmentEnabled);
        mCameraCompatAllowForceRotationOptProp = optPropBuilder.create(
                PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION,
                isCameraCompatTreatmentEnabled);
    }

    /**
     * Whether we should apply the min aspect ratio per-app override only when an app is connected
     * to the camera.
     * When this override is applied the min aspect ratio given in the app's manifest will be
     * overridden to the largest enabled aspect ratio treatment unless the app's manifest value
     * is higher. The treatment will also apply if no value is provided in the manifest.
     *
     * <p>This method returns {@code true} when the following conditions are met:
     * <ul>
     *     <li>Opt-out component property isn't enabled
     *     <li>Per-app override is enabled
     * </ul>
     */
    boolean shouldOverrideMinAspectRatioForCamera() {
        return mActivityRecord.isCameraActive()
                && mAllowMinAspectRatioOverrideOptProp
                .shouldEnableWithOptInOverrideAndOptOutProperty(
                        isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA));
    }

    /**
     * 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 mCameraCompatAllowRefreshOptProp.shouldEnableWithOptOutOverrideAndProperty(
                isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH));
    }

    /**
     * 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 mCameraCompatEnableRefreshViaPauseOptProp.shouldEnableWithOverrideAndProperty(
                isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE));
    }

    /**
     * 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 mCameraCompatAllowForceRotationOptProp.shouldEnableWithOptOutOverrideAndProperty(
                isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION));
    }

    /**
     * Whether activity is eligible for camera compatibility free-form treatment.
     *
     * <p>The treatment is applied to a fixed-orientation camera activity in free-form windowing
     * mode. The treatment letterboxes or pillarboxes the activity to the expected orientation and
     * provides changes to the camera and display orientation signals to match those expected on a
     * portrait device in that orientation (for example, on a standard phone).
     *
     * <p>The treatment is enabled when the following conditions are met:
     * <ul>
     * <li>Property gating the camera compatibility free-form treatment is enabled.
     * <li>Activity isn't opted out by the device manufacturer with override.
     * </ul>
     */
    boolean shouldApplyFreeformTreatmentForCameraCompat() {
        return Flags.cameraCompatForFreeform() && !isCompatChangeEnabled(
                OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT);
    }

    /**
     * @return {@code true} if the configuration needs to be recomputed after a camera state update.
     */
    boolean shouldRecomputeConfigurationForCameraCompat() {
        return isOverrideOrientationOnlyForCameraEnabled()
                || isCameraCompatSplitScreenAspectRatioAllowed()
                || shouldOverrideMinAspectRatioForCamera();
    }

    boolean isOverrideOrientationOnlyForCameraEnabled() {
        return isCompatChangeEnabled(OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA);
    }

    /**
     * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}.
     */
    boolean isRefreshRequested() {
        return mAppCompatCameraOverridesState.mIsRefreshRequested;
    }

    /**
     * @param isRequested Whether activity "refresh" was requested but not finished
     *                    in {@link #activityResumedLocked}.
     */
    void setIsRefreshRequested(boolean isRequested) {
        mAppCompatCameraOverridesState.mIsRefreshRequested = isRequested;
    }

    /**
     * Whether we use split screen aspect ratio for the activity when camera compat treatment
     * is active because the corresponding config is enabled and activity supports resizing.
     */
    boolean isCameraCompatSplitScreenAspectRatioAllowed() {
        return mLetterboxConfiguration.isCameraCompatSplitScreenAspectRatioEnabled()
                && !mActivityRecord.shouldCreateCompatDisplayInsets();
    }

    @FreeformCameraCompatMode
    int getFreeformCameraCompatMode() {
        return mAppCompatCameraOverridesState.mFreeformCameraCompatMode;
    }

    void setFreeformCameraCompatMode(@FreeformCameraCompatMode int freeformCameraCompatMode) {
        mAppCompatCameraOverridesState.mFreeformCameraCompatMode = freeformCameraCompatMode;
    }

    private boolean isCompatChangeEnabled(long overrideChangeId) {
        return mActivityRecord.info.isChangeEnabled(overrideChangeId);
    }

    static class AppCompatCameraOverridesState {
        // Whether activity "refresh" was requested but not finished in
        // ActivityRecord#activityResumedLocked following the camera compat force rotation in
        // DisplayRotationCompatPolicy.
        private boolean mIsRefreshRequested;

        @FreeformCameraCompatMode
        private int mFreeformCameraCompatMode = CAMERA_COMPAT_FREEFORM_NONE;
    }
}
+49 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;

import android.annotation.NonNull;

/**
 * Encapsulate the app compat logic related to camera.
 */
class AppCompatCameraPolicy {

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

    @NonNull
    private final ActivityRecord mActivityRecord;

    @NonNull
    private final AppCompatCameraOverrides mAppCompatCameraOverrides;

    AppCompatCameraPolicy(@NonNull ActivityRecord activityRecord,
            @NonNull AppCompatCameraOverrides appCompatCameraOverrides) {
        mActivityRecord = activityRecord;
        mAppCompatCameraOverrides = appCompatCameraOverrides;
    }

    void recomputeConfigurationForCameraCompatIfNeeded() {
        if (mAppCompatCameraOverrides.shouldRecomputeConfigurationForCameraCompat()) {
            mActivityRecord.recomputeConfiguration();
        }
    }
}
+27 −2
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@
package com.android.server.wm;

import android.annotation.NonNull;
import android.content.pm.PackageManager;

import com.android.server.wm.utils.OptPropFactory;

/**
 * Allows the interaction with all the app compat policies and configurations
@@ -28,19 +31,26 @@ class AppCompatController {
    private final AppCompatOrientationPolicy mOrientationPolicy;
    @NonNull
    private final AppCompatOverrides mAppCompatOverrides;
    @NonNull
    private final AppCompatCameraPolicy mAppCompatCameraPolicy;

    AppCompatController(@NonNull WindowManagerService wmService,
                        @NonNull ActivityRecord activityRecord) {
        final PackageManager packageManager = wmService.mContext.getPackageManager();
        final OptPropFactory optPropBuilder = new OptPropFactory(packageManager,
                activityRecord.packageName);
        mTransparentPolicy = new TransparentPolicy(activityRecord,
                wmService.mLetterboxConfiguration);
        mAppCompatOverrides = new AppCompatOverrides(wmService, activityRecord,
                wmService.mLetterboxConfiguration);
        mAppCompatOverrides = new AppCompatOverrides(activityRecord,
                wmService.mLetterboxConfiguration, optPropBuilder);
        // TODO(b/341903757) Remove BooleanSuppliers after fixing dependency with aspectRatio.
        final LetterboxUiController tmpController = activityRecord.mLetterboxUiController;
        mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord,
                mAppCompatOverrides, tmpController::shouldApplyUserFullscreenOverride,
                tmpController::shouldApplyUserMinAspectRatioOverride,
                tmpController::isSystemOverrideToFullscreenEnabled);
        mAppCompatCameraPolicy = new AppCompatCameraPolicy(activityRecord,
                mAppCompatOverrides.getAppCompatCameraOverrides());
    }

    @NonNull
@@ -53,8 +63,23 @@ class AppCompatController {
        return mOrientationPolicy;
    }

    @NonNull
    AppCompatCameraPolicy getAppCompatCameraPolicy() {
        return mAppCompatCameraPolicy;
    }

    @NonNull
    AppCompatOverrides getAppCompatOverrides() {
        return mAppCompatOverrides;
    }

    @NonNull
    AppCompatOrientationOverrides getAppCompatOrientationOverrides() {
        return mAppCompatOverrides.getAppCompatOrientationOverrides();
    }

    @NonNull
    AppCompatCameraOverrides getAppCompatCameraOverrides() {
        return mAppCompatOverrides.getAppCompatCameraOverrides();
    }
}
Loading