/*
 * 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.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_DISABLE_SIMULATE_REQUESTED_ORIENTATION;
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.internal.perfetto.protos.Windowmanagerservice.ActivityRecordProto.SHOULD_ALLOW_SIMULATE_REQUESTED_ORIENTATION_FOR_CAMERA_COMPAT;
import static android.internal.perfetto.protos.Windowmanagerservice.ActivityRecordProto.SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT;
import static android.internal.perfetto.protos.Windowmanagerservice.ActivityRecordProto.SHOULD_REFRESH_ACTIVITY_FOR_CAMERA_COMPAT;
import static android.internal.perfetto.protos.Windowmanagerservice.ActivityRecordProto.SHOULD_REFRESH_ACTIVITY_VIA_PAUSE_FOR_CAMERA_COMPAT;
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_SIMULATE_REQUESTED_ORIENTATION;
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 static com.android.server.wm.AppCompatUtils.isChangeEnabled;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.util.proto.ProtoOutputStream;
import android.window.DesktopModeFlags;

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

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
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;


    /** There is no active or requested refresh for the activity. */
    static final int NONE = 0;

    /** A request was made for this activity to be refreshed, but it hasn't started yet. */
    static final int REQUESTED = 1;

    /** The activity is currently refreshing. */
    static final int IN_PROGRESS = 2;

    @IntDef(value = {
            NONE,
            REQUESTED,
            IN_PROGRESS,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ActivityRefreshState {}

    @NonNull
    private final ActivityRecord mActivityRecord;
    @NonNull
    private final AppCompatCameraOverridesState mAppCompatCameraOverridesState;
    @NonNull
    private final AppCompatConfiguration mAppCompatConfiguration;
    @NonNull
    private final OptPropFactory.OptProp mAllowMinAspectRatioOverrideOptProp;
    @NonNull
    private final OptPropFactory.OptProp mCameraCompatAllowRefreshOptProp;
    @NonNull
    private final OptPropFactory.OptProp mCameraCompatEnableRefreshViaPauseOptProp;
    @NonNull
    private final OptPropFactory.OptProp mCameraCompatAllowForceRotationOptProp;
    @NonNull
    private final OptPropFactory.OptProp mCameraCompatAllowOrientationTreatmentOptProp;

    AppCompatCameraOverrides(@NonNull ActivityRecord activityRecord,
            @NonNull AppCompatConfiguration appCompatConfiguration,
            @NonNull OptPropFactory optPropBuilder) {
        mActivityRecord = activityRecord;
        mAppCompatConfiguration = appCompatConfiguration;
        mAppCompatCameraOverridesState = new AppCompatCameraOverridesState();
        mAllowMinAspectRatioOverrideOptProp = optPropBuilder.create(
                PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
        final BooleanSupplier isCameraCompatForceRotateTreatmentEnabled = AppCompatUtils.asLazy(
                mAppCompatConfiguration::isCameraCompatForceRotateTreatmentEnabled);
        final BooleanSupplier isCameraCompatSimulateRequestedOrientationTreatmentEnabled =
                AppCompatUtils.asLazy(mAppCompatConfiguration
                        ::isCameraCompatSimulateRequestedOrientationTreatmentEnabled);
        final BooleanSupplier isAnyCameraCompatTreatmentEnabled = AppCompatUtils.asLazy(
                mAppCompatConfiguration::isAnyCameraCompatTreatmentEnabled);

        mCameraCompatAllowRefreshOptProp = optPropBuilder.create(
                PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH,
                isAnyCameraCompatTreatmentEnabled);
        mCameraCompatEnableRefreshViaPauseOptProp = optPropBuilder.create(
                PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE,
                isAnyCameraCompatTreatmentEnabled);
        mCameraCompatAllowForceRotationOptProp = optPropBuilder.create(
                PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION,
                isCameraCompatForceRotateTreatmentEnabled);
        mCameraCompatAllowOrientationTreatmentOptProp = optPropBuilder.create(
                PROPERTY_CAMERA_COMPAT_ALLOW_SIMULATE_REQUESTED_ORIENTATION,
                isCameraCompatSimulateRequestedOrientationTreatmentEnabled);
    }

    /**
     * Whether applying the min aspect ratio per-app override only when an app is connected
     * to the camera is allowed.
     *
     * <p>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 isOverrideMinAspectRatioForCameraEnabled() {
        return mAllowMinAspectRatioOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty(
                        isChangeEnabled(mActivityRecord,
                                OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA));
    }

    /**
     * Whether activity is eligible for activity "refresh" after camera compat force rotation
     * treatment. See {@link AppCompatCameraDisplayRotationPolicy} 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(
                isChangeEnabled(mActivityRecord, 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 AppCompatCameraDisplayRotationPolicy} 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(
                isChangeEnabled(mActivityRecord, OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE));
    }

    /**
     * Whether activity is eligible for camera compat force rotation treatment. See {@link
     * AppCompatCameraDisplayRotationPolicy} 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(
                isChangeEnabled(mActivityRecord, 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>Feature flag gating the camera compatibility free-form treatment is enabled.
     * <li>Activity is opted-in using per-app override, or the treatment is enabled for all apps.
     * </ul>
     */
    boolean shouldApplyCameraCompatSimReqOrientationTreatment() {
        return DesktopModeFlags.ENABLE_CAMERA_COMPAT_SIMULATE_REQUESTED_ORIENTATION.isTrue()
                && (shouldEnableCameraCompatSimulateRequestedOrientationTreatmentForApp()
                || shouldForceEnableCameraCompatSimulateRequestedOrientationTreatment());
    }

    private boolean shouldEnableCameraCompatSimulateRequestedOrientationTreatmentForApp() {
        return mAppCompatConfiguration
                .isCameraCompatSimulateRequestedOrientationTreatmentEnabled()
                && mCameraCompatAllowOrientationTreatmentOptProp
                        .shouldEnableWithOptOutOverrideAndProperty(isChangeEnabled(mActivityRecord,
                                OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION));
    }

    /**
     * Returns whether camera compat treatment should be enabled for all apps targeted for treatment
     * (usually fixed-orientation apps).
     *
     * <p>This can be enabled via adb only.
     */
    private boolean shouldForceEnableCameraCompatSimulateRequestedOrientationTreatment() {
        return mActivityRecord.mWmService.mAppCompatConfiguration
                .isCameraCompatSimReqOrientationTreatmentForceEnabled();
    }

    boolean isOverrideOrientationOnlyForCameraEnabled() {
        return isChangeEnabled(mActivityRecord, OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA);
    }

    /**
     * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}.
     */
    @ActivityRefreshState
    int getActivityRefreshState() {
        return mAppCompatCameraOverridesState.mActivityRefreshState;
    }

    /**
     * @param refreshState Whether activity "refresh" is requested, pending (in progress), or not
     *                    needed, in {@link #activityResumedLocked}.
     */
    void setActivityRefreshState(@ActivityRefreshState int refreshState) {
        mAppCompatCameraOverridesState.mActivityRefreshState = refreshState;
    }

    /**
     * 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 mAppCompatConfiguration.isCameraCompatSplitScreenAspectRatioEnabled()
                && !mActivityRecord.shouldCreateAppCompatDisplayInsets();
    }

    public void dumpDebug(@NonNull ProtoOutputStream proto) {
        proto.write(SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT, shouldForceRotateForCameraCompat());
        proto.write(SHOULD_REFRESH_ACTIVITY_FOR_CAMERA_COMPAT,
                shouldRefreshActivityForCameraCompat());
        proto.write(SHOULD_REFRESH_ACTIVITY_VIA_PAUSE_FOR_CAMERA_COMPAT,
                shouldRefreshActivityViaPauseForCameraCompat());
        proto.write(SHOULD_ALLOW_SIMULATE_REQUESTED_ORIENTATION_FOR_CAMERA_COMPAT,
                shouldApplyCameraCompatSimReqOrientationTreatment());
    }

    static class AppCompatCameraOverridesState {
        // Whether activity "refresh" was requested, in progress, or not needed, following the
        // camera compat treatment applied by AppCompatCameraSimReqOrientationPolicy
        // (AppCompatCameraDisplayRotationPolicy is being sunset so it is left unchanged).
        @ActivityRefreshState
        private int mActivityRefreshState = NONE;

        // Whether activity "refresh" was requested but not finished in
        // ActivityRecord#activityResumedLocked following the camera compat treatment applied by
        // AppCompatCameraDisplayRotationPolicy or AppCompatCameraSimReqOrientationPolicy.
        private boolean mIsRefreshRequested;
    }
}
