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

Commit 076518a4 authored by Mina Granic's avatar Mina Granic
Browse files

Extract tracking camera policies from CameraStateMonitor.

This is to simplify `CameraStateMonitor` implementation by abstracting dealing with multiple listeners/policies.

Flag: EXEMPT simple refactoring
Bug: 380840084
Test: atest WmTests:CameraStateMonitorTests
Change-Id: I31f597b77736b1ac9de4eeaf9dbad587e55cef9a
parent 997cc66f
Loading
Loading
Loading
Loading
+10 −4
Original line number Diff line number Diff line
@@ -58,14 +58,20 @@ class AppCompatCameraPolicy {
                DesktopModeFlags.ENABLE_CAMERA_COMPAT_SIMULATE_REQUESTED_ORIENTATION.isTrue()
                        && DesktopModeHelper.canEnterDesktopMode(wmService.mContext);
        if (needsDisplayRotationCompatPolicy || needsCameraCompatFreeformPolicy) {
            mCameraStateMonitor = new CameraStateMonitor(displayContent, wmService.mH);
            final AppCompatCameraStateSource cameraStateListenerDelegate =
                    new AppCompatCameraStateSource();
            mCameraStateMonitor = new CameraStateMonitor(displayContent, wmService.mH,
                    cameraStateListenerDelegate);
            mActivityRefresher = new ActivityRefresher(wmService, wmService.mH);
            mDisplayRotationCompatPolicy =
                    needsDisplayRotationCompatPolicy ? new DisplayRotationCompatPolicy(
                            displayContent, mCameraStateMonitor, mActivityRefresher) : null;
                            displayContent, mCameraStateMonitor, cameraStateListenerDelegate,
                            mActivityRefresher)
                            : null;
            mCameraCompatFreeformPolicy =
                    needsCameraCompatFreeformPolicy ? new CameraCompatFreeformPolicy(displayContent,
                            mCameraStateMonitor, mActivityRefresher) : null;
                            mCameraStateMonitor, cameraStateListenerDelegate, mActivityRefresher)
                            : null;
        } else {
            mDisplayRotationCompatPolicy = null;
            mCameraCompatFreeformPolicy = null;
@@ -158,7 +164,7 @@ class AppCompatCameraPolicy {
            mCameraCompatFreeformPolicy.dispose();
        }
        if (mCameraStateMonitor != null) {
            mCameraStateMonitor.dispose();
            mCameraStateMonitor.stopListeningToCameraState();
        }
    }

+44 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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 android.annotation.NonNull;

/**
 * Interface that camera compat policies to implement to be notified of camera open/close signals.
 */
interface AppCompatCameraStatePolicy {
    /**
     * Notifies the compat listener that a task has opened camera.
     */
    void onCameraOpened(@NonNull ActivityRecord cameraActivity);

    /**
     * Checks whether a listener is ready to do a cleanup when camera is closed.
     *
     * <p>The notifier might try again if false is returned.
     */
    // TODO(b/336474959): try to decouple `cameraId` from the listeners, as the treatment does not
    //  change based on the cameraId - CameraStateMonitor should keep track of this.
    //  This method actually checks "did an activity only temporarily close the camera", because a
    //  refresh for compatibility is triggered.
    boolean canCameraBeClosed(@NonNull String cameraId);

    /**
     * Notifies the compat listener that camera is closed.
     */
    void onCameraClosed();
}
+65 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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 androidx.annotation.NonNull;

import java.util.ArrayList;

/**
 * Adapter for camera state updates, which notifies all camera policies of camera state changes.
 */
class AppCompatCameraStateSource implements AppCompatCameraStatePolicy {
    private final ArrayList<AppCompatCameraStatePolicy> mCameraStatePolicies = new ArrayList<>();

    /** Adds a policy to notify when camera is opened and closed. */
    public void addCameraStatePolicy(@NonNull AppCompatCameraStatePolicy policy) {
        mCameraStatePolicies.add(policy);
    }

    /** Removes a policy to notify about camera opened/closed signals. */
    public void removeCameraStatePolicy(@NonNull AppCompatCameraStatePolicy policy) {
        mCameraStatePolicies.remove(policy);
    }

    @Override
    public void onCameraOpened(@NonNull ActivityRecord cameraActivity) {
        for (int i = 0; i < mCameraStatePolicies.size(); i++) {
            mCameraStatePolicies.get(i).onCameraOpened(cameraActivity);
        }
    }

    /**
     * @return {@code false} if any listener has reported that they cannot process camera close now.
     */
    @Override
    public boolean canCameraBeClosed(@NonNull String cameraId) {
        for (int i = 0; i < mCameraStatePolicies.size(); i++) {
            if (!mCameraStatePolicies.get(i).canCameraBeClosed(cameraId)) {
                return false;
            }
        }
        return true;
    }

    @Override
    public void onCameraClosed() {
        for (int i = 0; i < mCameraStatePolicies.size(); i++) {
            mCameraStatePolicies.get(i).onCameraClosed();
        }
    }
}
+7 −3
Original line number Diff line number Diff line
@@ -58,7 +58,7 @@ import com.android.internal.protolog.WmProtoLogGroups;
 * 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,
final class CameraCompatFreeformPolicy implements AppCompatCameraStatePolicy,
        ActivityRefresher.Evaluator {
    private static final String TAG = TAG_WITH_CLASS_NAME ? "CameraCompatFreeformPolicy" : TAG_WM;

@@ -67,6 +67,8 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
    @NonNull
    private final ActivityRefresher mActivityRefresher;
    @NonNull
    private final AppCompatCameraStateSource mCameraStateNotifier;
    @NonNull
    private final CameraStateMonitor mCameraStateMonitor;

    // TODO(b/380840084): Consider moving this to the CameraStateMonitor, and keeping track of
@@ -82,14 +84,16 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa

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

    void start() {
        mCameraStateMonitor.addCameraStateListener(this);
        mCameraStateNotifier.addCameraStatePolicy(this);
        mActivityRefresher.addEvaluator(this);
        mIsRunning = true;
    }
@@ -101,7 +105,7 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa

    /** Releases camera callback listener. */
    void dispose() {
        mCameraStateMonitor.removeCameraStateListener(this);
        mCameraStateNotifier.removeCameraStatePolicy(this);
        mActivityRefresher.removeEvaluator(this);
        mIsRunning = false;
    }
+21 −65
Original line number Diff line number Diff line
@@ -76,13 +76,14 @@ class CameraStateMonitor {
    // TODO(b/336474959): should/can this go in the compat listeners?
    private final Set<String> mScheduledCompatModeUpdateCameraIdSet = new ArraySet<>();

    private final ArrayList<CameraCompatStateListener> mCameraStateListeners = new ArrayList<>();
    @VisibleForTesting
    final AppCompatCameraStatePolicy mAppCompatCameraStatePolicy;

    /**
     * Value toggled on {@link #startListeningToCameraState()} to {@code true} and on {@link
     * #dispose()} to {@code false}.
     * #stopListeningToCameraState()} to {@code false}.
     */
    private boolean mIsRunning;
    private boolean mIsListeningToCameraState;

    private final CameraManager.AvailabilityCallback mAvailabilityCallback =
            new  CameraManager.AvailabilityCallback() {
@@ -100,42 +101,41 @@ class CameraStateMonitor {
                }
            };

    CameraStateMonitor(@NonNull DisplayContent displayContent, @NonNull Handler handler) {
    CameraStateMonitor(@NonNull DisplayContent displayContent, @NonNull Handler handler,
            @NonNull AppCompatCameraStatePolicy appCompatCameraStatePolicy) {
        // This constructor is called from DisplayContent constructor. Don't use any fields in
        // DisplayContent here since they aren't guaranteed to be set.
        mHandler = handler;
        mDisplayContent = displayContent;
        mAppCompatCameraStatePolicy = appCompatCameraStatePolicy;
        mWmService = displayContent.mWmService;
        mCameraManager = mWmService.mContext.getSystemService(CameraManager.class);
    }

    /** Starts listening to camera opened/closed signals. */
    void startListeningToCameraState() {
        if (mCameraManager != null) {
            mCameraManager.registerAvailabilityCallback(
                    mWmService.mContext.getMainExecutor(), mAvailabilityCallback);
        }
        mIsRunning = true;
        mIsListeningToCameraState = true;
    }

    /** Releases camera callback listener. */
    void dispose() {
    /** Stops listening to camera opened/closed signals. */
    public void stopListeningToCameraState() {
        if (mCameraManager != null) {
            mCameraManager.unregisterAvailabilityCallback(mAvailabilityCallback);
        }
        mIsRunning = false;
        mIsListeningToCameraState = false;
    }

    /**
     * Returns whether {@link CameraStateMonitor} is listening to camera opened/closed
     * signals.
     */
    @VisibleForTesting
    boolean isRunning() {
        return mIsRunning;
    }

    void addCameraStateListener(CameraCompatStateListener listener) {
        mCameraStateListeners.add(listener);
    }

    void removeCameraStateListener(CameraCompatStateListener listener) {
        mCameraStateListeners.remove(listener);
    boolean isListeningToCameraState() {
        return mIsListeningToCameraState;
    }

    private void notifyCameraOpenedWithDelay(@NonNull String cameraId,
@@ -169,14 +169,7 @@ class CameraStateMonitor {
            if (cameraActivity == null || cameraActivity.getTask() == null) {
                return;
            }
            notifyListenersCameraOpened(cameraActivity);
        }
    }

    private void notifyListenersCameraOpened(@NonNull ActivityRecord cameraActivity) {
        for (int i = 0; i < mCameraStateListeners.size(); i++) {
            CameraCompatStateListener listener = mCameraStateListeners.get(i);
            listener.onCameraOpened(cameraActivity);
            mAppCompatCameraStatePolicy.onCameraOpened(cameraActivity);
        }
    }

@@ -229,11 +222,11 @@ class CameraStateMonitor {
                // Already reconnected to this camera, no need to clean up.
                return;
            }
            final boolean canClose = checkCanCloseForAllListeners(cameraId);
            final boolean canClose = mAppCompatCameraStatePolicy.canCameraBeClosed(cameraId);
            if (canClose) {
                // Finish cleaning up.
                mCameraIdPackageBiMapping.removeCameraId(cameraId);
                notifyListenersCameraClosed();
                mAppCompatCameraStatePolicy.onCameraClosed();
            } else {
                // Not ready to process closure yet - the camera activity might be refreshing.
                // Try again later.
@@ -242,24 +235,6 @@ class CameraStateMonitor {
        }
    }

    /**
     * @return {@code false} if any listener has reported that they cannot process camera close now.
     */
    private boolean checkCanCloseForAllListeners(@NonNull String cameraId) {
        for (int i = 0; i < mCameraStateListeners.size(); i++) {
            if (!mCameraStateListeners.get(i).canCameraBeClosed(cameraId)) {
                return false;
            }
        }
        return true;
    }

    private void notifyListenersCameraClosed() {
        for (int i = 0; i < mCameraStateListeners.size(); i++) {
            mCameraStateListeners.get(i).onCameraClosed();
        }
    }

    // TODO(b/335165310): verify that this works in multi instance and permission dialogs.
    /**
     * Finds a visible activity with the given package name.
@@ -303,23 +278,4 @@ class CameraStateMonitor {
                + mCameraIdPackageBiMapping
                .getSummaryForDisplayRotationHistoryRecord();
    }

    interface CameraCompatStateListener {
        /**
         * Notifies the compat listener that an activity has opened camera.
         */
        void onCameraOpened(@NonNull ActivityRecord cameraActivity);
        /**
         * Checks whether a listener is ready to do a cleanup when camera is closed.
         *
         * <p>The notifier might try again if false is returned.
         */
        // TODO(b/336474959): try to decouple `cameraId` from the listeners.
        boolean canCameraBeClosed(@NonNull String cameraId);

        /**
         * Notifies the compat listener that camera is closed.
         */
        void onCameraClosed();
    }
}
Loading