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

Commit 379c5bf6 authored by Massimo Carli's avatar Massimo Carli
Browse files

[15/n] Improved AppCompat camera classes dependencies

Encapsulates AppCompat Rotation policy in AppCompatCameraPolcy and
improved dependencies between Overrides and Policy components.

Flag: EXEMPT refactor
Fix: 349331008
Test: atest WmTests:ActivityRecordTests
Test: atest Wmtests:AppCompatCameraPolicyTest
Test: atest WmTests:AppCompatCameraOverridesTest

Change-Id: I05b5e2f57f4830a95685ed6f047fecee92e699cc
parent 944433b0
Loading
Loading
Loading
Loading
+4 −17
Original line number Diff line number Diff line
@@ -6527,9 +6527,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
            // and the token could be null.
            return;
        }
        if (r.mDisplayContent.mActivityRefresher != null) {
            r.mDisplayContent.mActivityRefresher.onActivityRefreshed(r);
        }
        r.mDisplayContent.mAppCompatCameraPolicy.onActivityRefreshed(r);
    }
    static void splashScreenAttachedLocked(IBinder token) {
@@ -8186,7 +8184,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
    }
    void setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation) {
        if (mAppCompatController.getAppCompatOrientationOverrides()
        if (mAppCompatController.getOrientationPolicy()
                .shouldIgnoreRequestedOrientation(requestedOrientation)) {
            return;
        }
@@ -10022,16 +10020,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        return updateReportedConfigurationAndSend();
    }
    /**
     * @return {@code true} if the Camera is active for the current activity
     */
    boolean isCameraActive() {
        return mDisplayContent != null
                && mDisplayContent.getDisplayRotationCompatPolicy() != null
                && mDisplayContent.getDisplayRotationCompatPolicy()
                    .isCameraActive(this, /* mustBeFullscreen */ true);
    }
    boolean updateReportedConfigurationAndSend() {
        if (isConfigurationDispatchPaused()) {
            Slog.wtf(TAG, "trying to update reported(client) config while dispatch is paused");
@@ -10179,11 +10167,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
    private void notifyActivityRefresherAboutConfigurationChange(
            Configuration newConfig, Configuration lastReportedConfig) {
        if (mDisplayContent.mActivityRefresher == null
                || !shouldBeResumed(/* activeActivity */ null)) {
        if (!shouldBeResumed(/* activeActivity */ null)) {
            return;
        }
        mDisplayContent.mActivityRefresher.onActivityConfigurationChanging(
        mDisplayContent.mAppCompatCameraPolicy.onActivityConfigurationChanging(
                this, newConfig, lastReportedConfig);
    }
+10 −2
Original line number Diff line number Diff line
@@ -97,8 +97,7 @@ class AppCompatCameraOverrides {
     * </ul>
     */
    boolean shouldOverrideMinAspectRatioForCamera() {
        return mActivityRecord.isCameraActive()
                && mAllowMinAspectRatioOverrideOptProp
        return isCameraActive() && mAllowMinAspectRatioOverrideOptProp
                .shouldEnableWithOptInOverrideAndOptOutProperty(
                        isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA));
    }
@@ -173,6 +172,15 @@ class AppCompatCameraOverrides {
                OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT);
    }

    /**
     * @return {@code true} if the Camera is active for the current activity
     */
    boolean isCameraActive() {
        return mActivityRecord.mDisplayContent != null
                && mActivityRecord.mDisplayContent.mAppCompatCameraPolicy
                    .isCameraActive(mActivityRecord, /* mustBeFullscreen */ true);
    }

    /**
     * @return {@code true} if the configuration needs to be recomputed after a camera state update.
     */
+140 −16
Original line number Diff line number Diff line
@@ -16,34 +16,158 @@

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 static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.res.Configuration;
import android.widget.Toast;

import com.android.window.flags.Flags;

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

    private static final String TAG = TAG_WITH_CLASS_NAME
            ? "AppCompatCameraPolicy" : TAG_ATM;
    @Nullable
    private final CameraStateMonitor mCameraStateMonitor;
    @Nullable
    private final ActivityRefresher mActivityRefresher;
    @Nullable
    final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
    @Nullable
    final CameraCompatFreeformPolicy mCameraCompatFreeformPolicy;

    @NonNull
    private final ActivityRecord mActivityRecord;
    AppCompatCameraPolicy(@NonNull WindowManagerService wmService,
            @NonNull DisplayContent displayContent) {
        // Not checking DeviceConfig value here to allow enabling via DeviceConfig
        // without the need to restart the device.
        final boolean needsDisplayRotationCompatPolicy =
                wmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabledAtBuildTime();
        final boolean needsCameraCompatFreeformPolicy = Flags.cameraCompatForFreeform()
                && DesktopModeLaunchParamsModifier.canEnterDesktopMode(wmService.mContext);
        if (needsDisplayRotationCompatPolicy || needsCameraCompatFreeformPolicy) {
            mCameraStateMonitor = new CameraStateMonitor(displayContent, wmService.mH);
            mActivityRefresher = new ActivityRefresher(wmService, wmService.mH);
            mDisplayRotationCompatPolicy =
                    needsDisplayRotationCompatPolicy ? new DisplayRotationCompatPolicy(
                            displayContent, mCameraStateMonitor, mActivityRefresher) : null;
            mCameraCompatFreeformPolicy =
                    needsCameraCompatFreeformPolicy ? new CameraCompatFreeformPolicy(displayContent,
                            mCameraStateMonitor, mActivityRefresher) : null;
        } else {
            mDisplayRotationCompatPolicy = null;
            mCameraCompatFreeformPolicy = null;
            mCameraStateMonitor = null;
            mActivityRefresher = null;
        }
    }

    @NonNull
    private final AppCompatCameraOverrides mAppCompatCameraOverrides;
    void onActivityRefreshed(@NonNull ActivityRecord activity) {
        if (mActivityRefresher != null) {
            mActivityRefresher.onActivityRefreshed(activity);
        }
    }

    AppCompatCameraPolicy(@NonNull ActivityRecord activityRecord,
            @NonNull AppCompatCameraOverrides appCompatCameraOverrides) {
        mActivityRecord = activityRecord;
        mAppCompatCameraOverrides = appCompatCameraOverrides;
    /**
     * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
     * 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.
     */
    void onActivityConfigurationChanging(@NonNull ActivityRecord activity,
            @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
        if (mActivityRefresher != null) {
            mActivityRefresher.onActivityConfigurationChanging(activity, newConfig,
                    lastReportedConfig);
        }
    }

    void recomputeConfigurationForCameraCompatIfNeeded() {
        if (mAppCompatCameraOverrides.shouldRecomputeConfigurationForCameraCompat()) {
            mActivityRecord.recomputeConfiguration();
    /**
     * Notifies that animation in {@link ScreenRotationAnimation} has finished.
     *
     * <p>This class uses this signal as a trigger for notifying the user about forced rotation
     * reason with the {@link Toast}.
     */
    void onScreenRotationAnimationFinished() {
        if (mDisplayRotationCompatPolicy != null) {
            mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
        }
    }

    boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
        if (mDisplayRotationCompatPolicy != null) {
            return mDisplayRotationCompatPolicy.isActivityEligibleForOrientationOverride(activity);
        }
        return false;
    }

    /**
     * Whether camera compat treatment is applicable for the given activity.
     *
     * <p>Conditions that need to be met:
     * <ul>
     *     <li>Camera is active for the package.
     *     <li>The activity is in fullscreen
     *     <li>The activity has fixed orientation but not "locked" or "nosensor" one.
     * </ul>
     */
    boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
        if (mDisplayRotationCompatPolicy != null) {
            return mDisplayRotationCompatPolicy.isTreatmentEnabledForActivity(activity);
        }
        return false;
    }

    void start() {
        if (mCameraCompatFreeformPolicy != null) {
            mCameraCompatFreeformPolicy.start();
        }
        if (mCameraStateMonitor != null) {
            mCameraStateMonitor.startListeningToCameraState();
        }
    }

    void dispose() {
        if (mDisplayRotationCompatPolicy != null) {
            mDisplayRotationCompatPolicy.dispose();
        }
        if (mCameraCompatFreeformPolicy != null) {
            mCameraCompatFreeformPolicy.dispose();
        }
        if (mCameraStateMonitor != null) {
            mCameraStateMonitor.dispose();
        }
    }

    boolean hasDisplayRotationCompatPolicy() {
        return mDisplayRotationCompatPolicy != null;
    }

    boolean hasCameraCompatFreeformPolicy() {
        return mCameraCompatFreeformPolicy != null;
    }

    @ScreenOrientation
    int getOrientation() {
        return mDisplayRotationCompatPolicy != null
                ? mDisplayRotationCompatPolicy.getOrientation()
                : SCREEN_ORIENTATION_UNSPECIFIED;
    }

    boolean isCameraActive(@NonNull ActivityRecord activity, boolean mustBeFullscreen) {
        return mDisplayRotationCompatPolicy != null
                && mDisplayRotationCompatPolicy.isCameraActive(activity, mustBeFullscreen);
    }

    @Nullable
    String getSummaryForDisplayRotationHistoryRecord() {
        if (mDisplayRotationCompatPolicy != null) {
            return mDisplayRotationCompatPolicy.getSummaryForDisplayRotationHistoryRecord();
        }
        return null;
    }

}
+12 −9
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
package com.android.server.wm;

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

import com.android.server.wm.utils.OptPropFactory;
@@ -25,17 +26,18 @@ import com.android.server.wm.utils.OptPropFactory;
 */
class AppCompatController {

    @NonNull
    private final ActivityRecord mActivityRecord;
    @NonNull
    private final TransparentPolicy mTransparentPolicy;
    @NonNull
    private final AppCompatOrientationPolicy mOrientationPolicy;
    @NonNull
    private final AppCompatOverrides mAppCompatOverrides;
    @NonNull
    private final AppCompatCameraPolicy mAppCompatCameraPolicy;

    AppCompatController(@NonNull WindowManagerService wmService,
                        @NonNull ActivityRecord activityRecord) {
        mActivityRecord = activityRecord;
        final PackageManager packageManager = wmService.mContext.getPackageManager();
        final OptPropFactory optPropBuilder = new OptPropFactory(packageManager,
                activityRecord.packageName);
@@ -49,8 +51,6 @@ class AppCompatController {
                mAppCompatOverrides, tmpController::shouldApplyUserFullscreenOverride,
                tmpController::shouldApplyUserMinAspectRatioOverride,
                tmpController::isSystemOverrideToFullscreenEnabled);
        mAppCompatCameraPolicy = new AppCompatCameraPolicy(activityRecord,
                mAppCompatOverrides.getAppCompatCameraOverrides());
    }

    @NonNull
@@ -63,11 +63,6 @@ class AppCompatController {
        return mOrientationPolicy;
    }

    @NonNull
    AppCompatCameraPolicy getAppCompatCameraPolicy() {
        return mAppCompatCameraPolicy;
    }

    @NonNull
    AppCompatOverrides getAppCompatOverrides() {
        return mAppCompatOverrides;
@@ -82,4 +77,12 @@ class AppCompatController {
    AppCompatCameraOverrides getAppCompatCameraOverrides() {
        return mAppCompatOverrides.getAppCompatCameraOverrides();
    }

    @Nullable
    AppCompatCameraPolicy getAppCompatCameraPolicy() {
        if (mActivityRecord.mDisplayContent != null) {
            return mActivityRecord.mDisplayContent.mAppCompatCameraPolicy;
        }
        return null;
    }
}
+8 −71
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQU
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT;
import static android.content.pm.ActivityInfo.screenOrientationToString;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;

@@ -31,8 +30,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS
import static com.android.server.wm.AppCompatUtils.asLazy;

import android.annotation.NonNull;
import android.content.pm.ActivityInfo;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.utils.OptPropFactory;
@@ -51,6 +48,8 @@ class AppCompatOrientationOverrides {

    @NonNull
    private final ActivityRecord mActivityRecord;
    @NonNull
    private final AppCompatCameraOverrides mAppCompatCameraOverrides;

    @NonNull
    private final OptPropFactory.OptProp mIgnoreRequestedOrientationOptProp;
@@ -62,8 +61,10 @@ class AppCompatOrientationOverrides {

    AppCompatOrientationOverrides(@NonNull ActivityRecord activityRecord,
            @NonNull LetterboxConfiguration letterboxConfiguration,
            @NonNull OptPropFactory optPropBuilder) {
            @NonNull OptPropFactory optPropBuilder,
            @NonNull AppCompatCameraOverrides appCompatCameraOverrides) {
        mActivityRecord = activityRecord;
        mAppCompatCameraOverrides = appCompatCameraOverrides;
        mOrientationOverridesState = new OrientationOverridesState(mActivityRecord,
                System::currentTimeMillis);
        final BooleanSupplier isPolicyForIgnoringRequestedOrientationEnabled = asLazy(
@@ -76,59 +77,9 @@ class AppCompatOrientationOverrides {
                isPolicyForIgnoringRequestedOrientationEnabled);
    }

    /**
     * Whether should ignore app requested orientation in response to an app
     * calling {@link android.app.Activity#setRequestedOrientation}.
     *
     * <p>This is needed to avoid getting into {@link android.app.Activity#setRequestedOrientation}
     * loop when {@link DisplayContent#getIgnoreOrientationRequest} is enabled or device has
     * landscape natural orientation which app developers don't expect. For example, the loop can
     * look like this:
     * <ol>
     *     <li>App sets default orientation to "unspecified" at runtime
     *     <li>App requests to "portrait" after checking some condition (e.g. display rotation).
     *     <li>(2) leads to fullscreen -> letterboxed bounds change and activity relaunch because
     *     app can't handle the corresponding config changes.
     *     <li>Loop goes back to (1)
     * </ol>
     *
     * <p>This treatment is enabled when the following conditions are met:
     * <ul>
     *     <li>Flag gating the treatment is enabled
     *     <li>Opt-out component property isn't enabled
     *     <li>Opt-in component property or per-app override are enabled
     *     <li>Activity is relaunched after {@link android.app.Activity#setRequestedOrientation}
     *     call from an app or camera compat force rotation treatment is active for the activity.
     *     <li>Orientation request loop detected and is not letterboxed for fixed orientation
     * </ul>
     */
    boolean shouldIgnoreRequestedOrientation(
            @ActivityInfo.ScreenOrientation int requestedOrientation) {
        if (mIgnoreRequestedOrientationOptProp.shouldEnableWithOverrideAndProperty(
                isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION))) {
            if (mOrientationOverridesState.mIsRelaunchingAfterRequestedOrientationChanged) {
                Slog.w(TAG, "Ignoring orientation update to "
                        + screenOrientationToString(requestedOrientation)
                        + " due to relaunching after setRequestedOrientation for "
                        + mActivityRecord);
                return true;
            }
            if (isCameraCompatTreatmentActive()) {
                Slog.w(TAG, "Ignoring orientation update to "
                        + screenOrientationToString(requestedOrientation)
                        + " due to camera compat treatment for " + mActivityRecord);
                return true;
            }
        }

        if (shouldIgnoreOrientationRequestLoop()) {
            Slog.w(TAG, "Ignoring orientation update to "
                    + screenOrientationToString(requestedOrientation)
                    + " as orientation request loop was detected for "
                    + mActivityRecord);
            return true;
        }
        return false;
    boolean shouldEnableIgnoreOrientationRequest() {
        return mIgnoreRequestedOrientationOptProp.shouldEnableWithOverrideAndProperty(
                isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION));
    }

    /**
@@ -183,20 +134,6 @@ class AppCompatOrientationOverrides {
        return mActivityRecord.info.isChangeEnabled(overrideChangeId);
    }

    /**
     * @return {@code true} if the App Compat Camera Policy is active for the current activity.
     */
    // TODO(b/346253439): Remove after defining dependency with Camera capabilities.
    private boolean isCameraCompatTreatmentActive() {
        DisplayContent displayContent = mActivityRecord.mDisplayContent;
        if (displayContent == null) {
            return false;
        }
        return displayContent.mDisplayRotationCompatPolicy != null
                && displayContent.mDisplayRotationCompatPolicy
                .isTreatmentEnabledForActivity(mActivityRecord);
    }

    static class OrientationOverridesState {
        // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR
        final boolean mIsOverrideToNosensorOrientationEnabled;
Loading