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

Commit 47ca0de2 authored by Mina Karadzic's avatar Mina Karadzic Committed by Android (Google) Code Review
Browse files

Merge "Make activity refresh for camera compat more robust." into main

parents 9cb78c19 d6b0b740
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -8793,6 +8793,14 @@ final class ActivityRecord extends WindowToken {
        if (mAtmService.mSuppressResizeConfigChanges && preserveWindow) {
            return;
        }

        // Notify that the activity is already relaunching, therefore there's no need to refresh
        // the activity if it was requested. Activity refresher will track activity lifecycle
        // if needed.
        if (Flags.enableCameraCompatSandboxDisplayRotationOnExternalDisplaysBugfix()) {
            AppCompatCameraPolicy.onActivityRelaunching(this);
        }

        if (!preserveWindow) {
            // If the activity is the IME input target, ensure storing the last IME shown state
            // before relaunching it for restoring the IME visibility once its new window focused.
+101 −20
Original line number Diff line number Diff line
@@ -19,6 +19,9 @@ import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;

import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STATES;
import static com.android.server.wm.AppCompatCameraOverrides.NONE;
import static com.android.server.wm.AppCompatCameraOverrides.IN_PROGRESS;
import static com.android.server.wm.AppCompatCameraOverrides.REQUESTED;

import android.annotation.NonNull;
import android.app.servertransaction.RefreshCallbackItem;
@@ -28,15 +31,15 @@ import android.os.Handler;

import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.window.flags.Flags;

import java.util.ArrayList;

/**
 * Class that refreshes the activity (through stop/pause -> resume) based on configuration change.
 *
 * <p>This class queries all of its {@link Evaluator}s and restarts the activity if any of them
 * return {@code true} in {@link Evaluator#shouldRefreshActivity}. {@link ActivityRefresher} cycles
 * through either stop or pause and then resume, based on the global config and per-app override.
 * <p>{@link ActivityRefresher} cycles the activity through either stop or pause and then resume,
 * based on the global config and per-app override.
 */
class ActivityRefresher {
    // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The
@@ -46,6 +49,7 @@ class ActivityRefresher {

    @NonNull private final WindowManagerService mWmService;
    @NonNull private final Handler mHandler;
    // TODO(b/395063101): remove once external camera sandboxing is launched.
    @NonNull private final ArrayList<Evaluator> mEvaluators = new ArrayList<>();

    ActivityRefresher(@NonNull WindowManagerService wmService, @NonNull Handler handler) {
@@ -53,14 +57,40 @@ class ActivityRefresher {
        mHandler = handler;
    }

    // TODO(b/395063101): remove once external display sandboxing for camera is launched.
    void addEvaluator(@NonNull Evaluator evaluator) {
        mEvaluators.add(evaluator);
    }

    // TODO(b/395063101): remove once external display sandboxing for camera is launched.
    void removeEvaluator(@NonNull Evaluator evaluator) {
        mEvaluators.remove(evaluator);
    }

    /**
     * If the activity refresh is requested, mark as pending since activity is already relaunching.
     *
     * <p>This is to avoid unnecessary refresh (i.e. cycling activity though stop/pause -> resume),
     * if refresh is requested for camera compat.
     *
     * <p>As camera connection will most likely close and reopen due to relaunch/refresh - which is
     * the goal, to setup camera with new parameters - this method is setting state to
     * {@code IN_PROGRESS} instead of {@code NONE} to avoid unnecessary tear down and setup of
     * camera compat mode.
     */
    void onActivityRelaunching(@NonNull ActivityRecord activity) {
        if (getActivityRefreshState(activity) == REQUESTED) {
            // No need to cycle through stop/pause -> resume if the activity is already relaunching
            // due to config change.
            activity.mAppCompatController.getCameraOverrides().setActivityRefreshState(IN_PROGRESS);
        }
    }

    void requestRefresh(@NonNull ActivityRecord activity) {
        activity.mAppCompatController.getCameraOverrides().setActivityRefreshState(REQUESTED);
    }

    // TODO(b/395063101): remove once external display sandboxing for camera is launched.
    /**
     * "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
@@ -73,13 +103,43 @@ class ActivityRefresher {
            return;
        }

        activity.mAppCompatController.getCameraOverrides().setIsRefreshRequested(true);
        if (refreshActivity(activity)) {
            scheduleClearIsRefreshing(activity);
        } else {
            activity.mAppCompatController.getCameraOverrides().setIsRefreshRequested(false);
        }
    }

    /**
     * "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 refreshActivityIfEnabled(@NonNull ActivityRecord activity) {
        if (!shouldRefreshActivity(activity)) {
            activity.mAppCompatController.getCameraOverrides().setActivityRefreshState(NONE);
            return;
        }

        if (getActivityRefreshState(activity) == REQUESTED) {
            activity.mAppCompatController.getCameraOverrides().setActivityRefreshState(IN_PROGRESS);
            if (!refreshActivity(activity)) {
                clearRefreshState(activity);
                return;
            }
        }

        scheduleClearIsRefreshing(activity);
    }

    private boolean refreshActivity(@NonNull ActivityRecord activity) {
        final boolean cycleThroughStop =
                mWmService.mAppCompatConfiguration
                        .isCameraCompatRefreshCycleThroughStopEnabled()
                        && !activity.mAppCompatController.getCameraOverrides()
                        .shouldRefreshActivityViaPauseForCameraCompat();

        activity.mAppCompatController.getCameraOverrides().setIsRefreshRequested(true);
        ProtoLog.v(WM_DEBUG_STATES,
                "Refreshing activity for freeform camera compatibility treatment, "
                        + "activityRecord=%s", activity);
@@ -87,31 +147,46 @@ class ActivityRefresher {
                new RefreshCallbackItem(activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
        final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(
                activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
        final boolean isSuccessful = activity.mAtmService.getLifecycleManager()
                .scheduleTransactionItems(activity.app.getThread(), refreshCallbackItem,
                        resumeActivityItem);
        if (isSuccessful) {
        return activity.mAtmService.getLifecycleManager().scheduleTransactionItems(
                activity.app.getThread(), refreshCallbackItem, resumeActivityItem);
    }

    private void scheduleClearIsRefreshing(@NonNull ActivityRecord activity) {
        // Clear refresh state after a delay in case something goes wrong.
        mHandler.postDelayed(() -> {
            synchronized (mWmService.mGlobalLock) {
                onActivityRefreshed(activity);
            }
        }, REFRESH_CALLBACK_TIMEOUT_MS);
        } else {
            activity.mAppCompatController.getCameraOverrides().setIsRefreshRequested(false);
    }

    void clearRefreshState(@NonNull ActivityRecord activity) {
        if (Flags.enableCameraCompatSandboxDisplayRotationOnExternalDisplaysBugfix()) {
            activity.mAppCompatController.getCameraOverrides().setActivityRefreshState(NONE);
        }
        activity.mAppCompatController.getCameraOverrides().setIsRefreshRequested(false);
    }

    boolean isActivityRefreshing(@NonNull ActivityRecord activity) {
        return activity.mAppCompatController.getCameraOverrides().isRefreshRequested();
        return Flags.enableCameraCompatSandboxDisplayRotationOnExternalDisplaysBugfix()
                ? getActivityRefreshState(activity) == IN_PROGRESS
                : activity.mAppCompatController.getCameraOverrides().isRefreshRequested();
    }

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

    private boolean shouldRefreshActivity(@NonNull ActivityRecord activity) {
        return mWmService.mAppCompatConfiguration.isCameraCompatRefreshEnabled()
                && activity.mAppCompatController.getCameraOverrides()
                .shouldRefreshActivityForCameraCompat()
                && getActivityRefreshState(activity) != NONE;
    }

    // TODO(b/395063101): remove once external display sandboxing for camera is launched.
    private boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
            @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
        return mWmService.mAppCompatConfiguration.isCameraCompatRefreshEnabled()
@@ -122,6 +197,12 @@ class ActivityRefresher {
                        .shouldRefreshActivity(activity, newConfig, lastReportedConfig)) != null;
    }

    private @AppCompatCameraOverrides.ActivityRefreshState int getActivityRefreshState(
            @NonNull ActivityRecord activity) {
        return activity.mAppCompatController.getCameraOverrides().getActivityRefreshState();
    }

    // TODO(b/395063101): remove once external display sandboxing for camera is launched.
    /**
     * Interface for classes that would like to refresh the recently updated activity, based on the
     * configuration change.
+45 −2
Original line number Diff line number Diff line
@@ -36,12 +36,15 @@ 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;

/**
@@ -52,6 +55,24 @@ 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
@@ -213,6 +234,22 @@ class AppCompatCameraOverrides {
        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}.
     */
@@ -248,9 +285,15 @@ class AppCompatCameraOverrides {
    }

    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 force rotation in
        // DisplayRotationCompatPolicy.
        // ActivityRecord#activityResumedLocked following the camera compat treatment applied by
        // AppCompatCameraDisplayRotationPolicy or AppCompatCameraSimReqOrientationPolicy.
        private boolean mIsRefreshRequested;
    }
}
+13 −0
Original line number Diff line number Diff line
@@ -105,6 +105,19 @@ class AppCompatCameraPolicy {
        }
    }

    /**
     * Notifies {@link ActivityRefresher} that the activity is already relaunching.
     *
     * <p>This is to avoid unnecessary refresh (i.e. cycling activity though stop/pause -> resume),
     * if refresh is pending for camera compat.
     */
    static void onActivityRelaunching(@NonNull ActivityRecord activity) {
        final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
        if (cameraPolicy != null && cameraPolicy.mActivityRefresher != null) {
            cameraPolicy.mActivityRefresher.onActivityRelaunching(activity);
        }
    }

    /**
     * Notifies that animation in {@link ScreenRotationAnimation} has finished.
     *
+28 −12
Original line number Diff line number Diff line
@@ -95,7 +95,9 @@ final class AppCompatCameraSimReqOrientationPolicy implements AppCompatCameraSta

    void start() {
        mCameraStateNotifier.addCameraStatePolicy(this);
        if (!Flags.enableCameraCompatSandboxDisplayRotationOnExternalDisplaysBugfix()) {
            mActivityRefresher.addEvaluator(this);
        }
        mCameraDisplayRotationProvider.start();
        mIsRunning = true;
    }
@@ -103,7 +105,9 @@ final class AppCompatCameraSimReqOrientationPolicy implements AppCompatCameraSta
    /** Releases camera callback listener. */
    void dispose() {
        mCameraStateNotifier.removeCameraStatePolicy(this);
        if (!Flags.enableCameraCompatSandboxDisplayRotationOnExternalDisplaysBugfix()) {
            mActivityRefresher.removeEvaluator(this);
        }
        mCameraDisplayRotationProvider.dispose();
        mIsRunning = false;
    }
@@ -204,32 +208,37 @@ final class AppCompatCameraSimReqOrientationPolicy implements AppCompatCameraSta
    private void updateAndDispatchCameraConfiguration(@Nullable WindowProcessController app,
            @Nullable Task task) {
        final ActivityRecord activity = getTopActivityFromCameraTask(task);
        final boolean isCompatActivity = activity != null
                && isCompatibilityTreatmentEnabledForActivity(activity,
                /*checkOrientation=*/ false);
        // Only apps that need letterboxing (compatibility apps) need to recalculate configuration.
        if (isCompatActivity) {
        if (activity != null) {
            activity.recomputeConfiguration();
        }
        if (task != null && isCompatActivity) {
        if (task != null) {
            task.dispatchTaskInfoChangedIfNeeded(/* force= */ true);
        }
        if (app != null) {
            updateCompatibilityInfo(app, activity);
            final boolean refreshNeeded = updateCompatibilityInfo(app, activity);
            if (activity != null
                    && Flags.enableCameraCompatSandboxDisplayRotationOnExternalDisplaysBugfix()
                    && refreshNeeded) {
                mActivityRefresher.requestRefresh(activity);
            }
        }
        if (activity != null) {
            // Refresh the activity, to get the app to reconfigure the camera setup.
            activity.ensureActivityConfiguration(/* ignoreVisibility= */ true);
            if (Flags.enableCameraCompatSandboxDisplayRotationOnExternalDisplaysBugfix()) {
                mActivityRefresher.refreshActivityIfEnabled(activity);
            }
        }
    }

    private void updateCompatibilityInfo(@NonNull WindowProcessController app,
    private boolean updateCompatibilityInfo(@NonNull WindowProcessController app,
            @Nullable ActivityRecord activityRecord) {
        if (app.getThread() == null || app.mInfo == null) {
            Slog.w(TAG, "Insufficient app information. Cannot revert display rotation sandboxing.");
            return;
            return false;
        }

        boolean needsRefresh = false;
        // CompatibilityInfo fields are static, so even if task or activity has been closed, this
        // state should be updated in case the app process is still alive.
        final CompatibilityInfo compatibilityInfo = mAtmService
@@ -253,6 +262,7 @@ final class AppCompatCameraSimReqOrientationPolicy implements AppCompatCameraSta
                                displayRotation))
                        // TODO(b/365725400): support landscape cameras.
                        .setShouldOverrideSensorOrientation(false);
                needsRefresh = true;
            } else if (mCameraStateMonitor.isCameraRunningForActivity(activityRecord)) {
                // Sandbox only display rotation if needed, for external display.
                // TODO(b/395063101): signal the camera to not apply
@@ -262,6 +272,7 @@ final class AppCompatCameraSimReqOrientationPolicy implements AppCompatCameraSta
                //  framework has no information to avoid doing this.
                cameraCompatibilityInfoBuilder.setDisplayRotationSandbox(
                        mCameraDisplayRotationProvider.getCameraDeviceRotation());
                needsRefresh = true;
            }
        }

@@ -273,7 +284,10 @@ final class AppCompatCameraSimReqOrientationPolicy implements AppCompatCameraSta
        } catch (RemoteException e) {
            ProtoLog.w(WmProtoLogGroups.WM_DEBUG_STATES,
                    "Unable to update CompatibilityInfo for app %s", app);
            return false;
        }

        return needsRefresh;
    }

    /**
@@ -514,6 +528,8 @@ final class AppCompatCameraSimReqOrientationPolicy implements AppCompatCameraSta
                || !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
            return false;
        }
        return topActivity.mAppCompatController.getCameraOverrides().isRefreshRequested();
        return Flags.enableCameraCompatSandboxDisplayRotationOnExternalDisplaysBugfix()
                ? mActivityRefresher.isActivityRefreshing(topActivity)
                : topActivity.mAppCompatController.getCameraOverrides().isRefreshRequested();
    }
}
Loading