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

Commit 6f37bc6e authored by Mina Granic's avatar Mina Granic Committed by Android (Google) Code Review
Browse files

Merge "[1/n] Turn on camera compat mode for fixed orientation freeform...

Merge "[1/n] Turn on camera compat mode for fixed orientation freeform activities when using camera." into main
parents eca8ee30 880dfc1c
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
import static android.app.CameraCompatTaskInfo.cameraCompatControlStateToString;
import static android.app.WaitResult.INVALID_DELAY;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
@@ -46,6 +47,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -855,7 +857,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
    @CameraCompatControlState
    private int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;
    // The callback that allows to ask the calling View to apply the treatment for stretched
    // issues affecting camera viewfinders when the user clicks on the camera compat control.
    @Nullable
@@ -8556,11 +8557,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        // and back which can cause visible issues (see b/184078928).
        final int parentWindowingMode =
                newParentConfiguration.windowConfiguration.getWindowingMode();
        final boolean isInCameraCompatFreeform = parentWindowingMode == WINDOWING_MODE_FREEFORM
                && mLetterboxUiController.getFreeformCameraCompatMode()
                        != CAMERA_COMPAT_FREEFORM_NONE;
        // Bubble activities should always fill their parent and should not be letterboxed.
        final boolean isFixedOrientationLetterboxAllowed = !getLaunchedFromBubble()
                && (parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
                        || parentWindowingMode == WINDOWING_MODE_FULLSCREEN
                        || isInCameraCompatFreeform
                        // When starting to switch between PiP and fullscreen, the task is pinned
                        // and the activity is fullscreen. But only allow to apply letterbox if the
                        // activity is exiting PiP because an entered PiP should fill the task.
+196 −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.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;

import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;

import android.annotation.NonNull;
import android.app.CameraCompatTaskInfo;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.window.flags.Flags;

/**
 * Policy for camera compatibility freeform treatment.
 *
 * <p>The treatment is applied to a fixed-orientation camera activity in freeform 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).
 */
final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompatStateListener,
        ActivityRefresher.Evaluator {
    private static final String TAG = TAG_WITH_CLASS_NAME ? "CameraCompatFreeformPolicy" : TAG_WM;

    @NonNull
    private final DisplayContent mDisplayContent;
    @NonNull
    private final ActivityRefresher mActivityRefresher;
    @NonNull
    private final CameraStateMonitor mCameraStateMonitor;

    private boolean mIsCameraCompatTreatmentPending = false;

    CameraCompatFreeformPolicy(@NonNull DisplayContent displayContent,
            @NonNull CameraStateMonitor cameraStateMonitor,
            @NonNull ActivityRefresher activityRefresher) {
        mDisplayContent = displayContent;
        mCameraStateMonitor = cameraStateMonitor;
        mActivityRefresher = activityRefresher;
    }

    void start() {
        mCameraStateMonitor.addCameraStateListener(this);
        mActivityRefresher.addEvaluator(this);
    }

    /** Releases camera callback listener. */
    void dispose() {
        mCameraStateMonitor.removeCameraStateListener(this);
        mActivityRefresher.removeEvaluator(this);
    }

    // Refreshing only when configuration changes after rotation or camera split screen aspect ratio
    // treatment is enabled.
    @Override
    public boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
            @NonNull Configuration newConfig,
            @NonNull Configuration lastReportedConfig) {
        return isTreatmentEnabledForActivity(activity) && mIsCameraCompatTreatmentPending;
    }

    /**
     * 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>
     */
    @VisibleForTesting
    boolean shouldApplyFreeformTreatmentForCameraCompat(@NonNull ActivityRecord activity) {
        return Flags.cameraCompatForFreeform() && !activity.info.isChangeEnabled(
                ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT);
    }

    @Override
    public boolean onCameraOpened(@NonNull ActivityRecord cameraActivity,
            @NonNull String cameraId) {
        if (!isTreatmentEnabledForActivity(cameraActivity)) {
            return false;
        }
        final int existingCameraCompatMode =
                cameraActivity.mLetterboxUiController.getFreeformCameraCompatMode();
        final int newCameraCompatMode = getCameraCompatMode(cameraActivity);
        if (newCameraCompatMode != existingCameraCompatMode) {
            mIsCameraCompatTreatmentPending = true;
            cameraActivity.mLetterboxUiController.setFreeformCameraCompatMode(newCameraCompatMode);
            forceUpdateActivityAndTask(cameraActivity);
            return true;
        } else {
            mIsCameraCompatTreatmentPending = false;
        }
        return false;
    }

    @Override
    public boolean onCameraClosed(@NonNull ActivityRecord cameraActivity,
            @NonNull String cameraId) {
        if (isActivityForCameraIdRefreshing(cameraId)) {
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_STATES,
                    "Display id=%d is notified that Camera %s is closed but activity is"
                            + " still refreshing. Rescheduling an update.",
                    mDisplayContent.mDisplayId, cameraId);
            return false;
        }
        cameraActivity.mLetterboxUiController.setFreeformCameraCompatMode(
                CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE);
        forceUpdateActivityAndTask(cameraActivity);
        mIsCameraCompatTreatmentPending = false;
        return true;
    }

    private void forceUpdateActivityAndTask(ActivityRecord cameraActivity) {
        cameraActivity.recomputeConfiguration();
        cameraActivity.updateReportedConfigurationAndSend();
        Task cameraTask = cameraActivity.getTask();
        if (cameraTask != null) {
            cameraTask.dispatchTaskInfoChangedIfNeeded(/* force= */ true);
        }
    }

    private static int getCameraCompatMode(@NonNull ActivityRecord topActivity) {
        return switch (topActivity.getRequestedConfigurationOrientation()) {
            case ORIENTATION_PORTRAIT -> CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT;
            case ORIENTATION_LANDSCAPE -> CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_LANDSCAPE;
            default -> CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
        };
    }

    /**
     * Whether camera compat treatment is applicable for the given activity, ignoring its windowing
     * mode.
     *
     * <p>Conditions that need to be met:
     * <ul>
     *     <li>Treatment is enabled.
     *     <li>Camera is active for the package.
     *     <li>The app has a fixed orientation.
     *     <li>The app is in freeform windowing mode.
     * </ul>
     */
    private boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity) {
        int orientation = activity.getRequestedConfigurationOrientation();
        return shouldApplyFreeformTreatmentForCameraCompat(activity)
                && mCameraStateMonitor.isCameraRunningForActivity(activity)
                && orientation != ORIENTATION_UNDEFINED
                && activity.inFreeformWindowingMode()
                // "locked" and "nosensor" values are often used by camera apps that can't
                // handle dynamic changes so we shouldn't force-letterbox them.
                && activity.getRequestedOrientation() != SCREEN_ORIENTATION_NOSENSOR
                && activity.getRequestedOrientation() != SCREEN_ORIENTATION_LOCKED
                // TODO(b/332665280): investigate whether we can support activity embedding.
                && !activity.isEmbedded();
    }

    private boolean isActivityForCameraIdRefreshing(@NonNull String cameraId) {
        final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
                /* considerKeyguardState= */ true);
        if (topActivity == null || !isTreatmentEnabledForActivity(topActivity)
                || mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
            return false;
        }
        return topActivity.mLetterboxUiController.isRefreshRequested();
    }
}
+27 −5
Original line number Diff line number Diff line
@@ -263,6 +263,7 @@ import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.utils.RegionUtils;
import com.android.server.wm.utils.RotationCache;
import com.android.server.wm.utils.WmDisplayCutout;
import com.android.window.flags.Flags;

import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -477,6 +478,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
    @Nullable
    final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
    @Nullable
    final CameraCompatFreeformPolicy mCameraCompatFreeformPolicy;
    @Nullable
    final CameraStateMonitor mCameraStateMonitor;
    @Nullable
    final ActivityRefresher mActivityRefresher;
@@ -683,7 +686,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
     */
    private InputTarget mLastImeInputTarget;


    /**
     * Tracks the windowToken of the input method input target and the corresponding
     * {@link WindowContainerListener} for monitoring changes (e.g. the requested visibility
@@ -1233,11 +1235,26 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
        // without the need to restart the device.
        final boolean shouldCreateDisplayRotationCompatPolicy =
                mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabledAtBuildTime();
        if (shouldCreateDisplayRotationCompatPolicy) {
        final boolean shouldCreateCameraCompatFreeformPolicy = Flags.cameraCompatForFreeform()
                && DesktopModeLaunchParamsModifier.canEnterDesktopMode(mWmService.mContext);
        if (shouldCreateDisplayRotationCompatPolicy || shouldCreateCameraCompatFreeformPolicy) {
            mCameraStateMonitor = new CameraStateMonitor(this, mWmService.mH);
            mActivityRefresher = new ActivityRefresher(mWmService, mWmService.mH);
            mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(
                    this, mCameraStateMonitor, mActivityRefresher);
            if (shouldCreateDisplayRotationCompatPolicy) {
                mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(this,
                        mCameraStateMonitor, mActivityRefresher);
                mDisplayRotationCompatPolicy.start();
            } else {
                mDisplayRotationCompatPolicy = null;
            }

            if (shouldCreateCameraCompatFreeformPolicy) {
                mCameraCompatFreeformPolicy = new CameraCompatFreeformPolicy(this,
                        mCameraStateMonitor, mActivityRefresher);
                mCameraCompatFreeformPolicy.start();
            } else {
                mCameraCompatFreeformPolicy = null;
            }

            mCameraStateMonitor.startListeningToCameraState();
        } else {
@@ -1245,9 +1262,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
            mCameraStateMonitor = null;
            mActivityRefresher = null;
            mDisplayRotationCompatPolicy = null;
            mCameraCompatFreeformPolicy = null;
        }


        mRotationReversionController = new DisplayRotationReversionController(this);

        mInputMonitor = new InputMonitor(mWmService, this);
@@ -3350,6 +3367,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
        if (mDisplayRotationCompatPolicy != null) {
            mDisplayRotationCompatPolicy.dispose();
        }

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

        if (mCameraStateMonitor != null) {
            mCameraStateMonitor.dispose();
        }
+5 −2
Original line number Diff line number Diff line
@@ -80,8 +80,11 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
        mDisplayContent = displayContent;
        mWmService = displayContent.mWmService;
        mCameraStateMonitor = cameraStateMonitor;
        mCameraStateMonitor.addCameraStateListener(this);
        mActivityRefresher = activityRefresher;
    }

    void start() {
        mCameraStateMonitor.addCameraStateListener(this);
        mActivityRefresher.addEvaluator(this);
    }

@@ -365,7 +368,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
    }

    // TODO(b/336474959): Do we need cameraId here?
    private boolean isActivityForCameraIdRefreshing(String cameraId) {
    private boolean isActivityForCameraIdRefreshing(@NonNull String cameraId) {
        final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
                /* considerKeyguardState= */ true);
        if (!isTreatmentEnabledForActivity(topActivity)
+14 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.wm;

import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
@@ -103,6 +104,7 @@ import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTy
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager.TaskDescription;
import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode;
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
@@ -231,6 +233,9 @@ final class LetterboxUiController {

    private boolean mDoubleTapEvent;

    @FreeformCameraCompatMode
    private int mFreeformCameraCompatMode = CAMERA_COMPAT_FREEFORM_NONE;

    LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
        mLetterboxConfiguration = wmService.mLetterboxConfiguration;
        // Given activityRecord may not be fully constructed since LetterboxUiController
@@ -711,6 +716,15 @@ final class LetterboxUiController {
                        .isTreatmentEnabledForActivity(mActivityRecord);
    }

    @FreeformCameraCompatMode
    int getFreeformCameraCompatMode() {
        return mFreeformCameraCompatMode;
    }

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

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