ConvergenceUtils.java

/*
 * Copyright 2023 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 androidx.camera.core.impl;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.camera.core.Logger;

import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;

/**
 * Utility class for checking the convergence of 3A states.
 */
@RequiresApi(21)
public class ConvergenceUtils {

    private static final String TAG = "ConvergenceUtils";

    private static final Set<CameraCaptureMetaData.AfState> AF_CONVERGED_STATE_SET =
            Collections.unmodifiableSet(EnumSet.of(
                    CameraCaptureMetaData.AfState.PASSIVE_FOCUSED,
                    CameraCaptureMetaData.AfState.PASSIVE_NOT_FOCUSED,
                    CameraCaptureMetaData.AfState.LOCKED_FOCUSED,
                    CameraCaptureMetaData.AfState.LOCKED_NOT_FOCUSED
            ));

    private static final Set<CameraCaptureMetaData.AwbState> AWB_CONVERGED_STATE_SET =
            Collections.unmodifiableSet(EnumSet.of(
                    CameraCaptureMetaData.AwbState.CONVERGED,
                    // Unknown means cannot get valid state from CaptureResult
                    CameraCaptureMetaData.AwbState.UNKNOWN
            ));

    private static final Set<CameraCaptureMetaData.AeState> AE_CONVERGED_STATE_SET =
            Collections.unmodifiableSet(EnumSet.of(
                    CameraCaptureMetaData.AeState.CONVERGED,
                    CameraCaptureMetaData.AeState.FLASH_REQUIRED,
                    // Unknown means cannot get valid state from CaptureResult
                    CameraCaptureMetaData.AeState.UNKNOWN
            ));

    private static final Set<CameraCaptureMetaData.AeState> AE_TORCH_AS_FLASH_CONVERGED_STATE_SET;

    static {
        EnumSet<CameraCaptureMetaData.AeState> aeStateSet = EnumSet.copyOf(AE_CONVERGED_STATE_SET);

        // Some devices always show FLASH_REQUIRED when the torch is opened, so it cannot be
        // treated as the AE converge signal.
        aeStateSet.remove(CameraCaptureMetaData.AeState.FLASH_REQUIRED);

        // AeState.UNKNOWN means it doesn't have valid AE info. For this kind of device, we tend
        // to wait for a few more seconds for the auto exposure update. So the UNKNOWN state
        // should not be treated as the AE converge signal.
        aeStateSet.remove(CameraCaptureMetaData.AeState.UNKNOWN);

        AE_TORCH_AS_FLASH_CONVERGED_STATE_SET = Collections.unmodifiableSet(aeStateSet);
    }

    private ConvergenceUtils() {
    }

    /**
     * Check if the capture result is converged.
     */
    public static boolean is3AConverged(@NonNull CameraCaptureResult captureResult,
            boolean isTorchAsFlash) {

        // If afMode is OFF or UNKNOWN , no need for waiting.
        // otherwise wait until af is locked or focused.
        boolean isAfReady = captureResult.getAfMode() == CameraCaptureMetaData.AfMode.OFF
                || captureResult.getAfMode() == CameraCaptureMetaData.AfMode.UNKNOWN
                || AF_CONVERGED_STATE_SET.contains(captureResult.getAfState());

        boolean isAeReady;
        boolean isAeModeOff = captureResult.getAeMode() == CameraCaptureMetaData.AeMode.OFF;
        if (isTorchAsFlash) {
            isAeReady = isAeModeOff
                    || AE_TORCH_AS_FLASH_CONVERGED_STATE_SET.contains(captureResult.getAeState());
        } else {
            isAeReady = isAeModeOff || AE_CONVERGED_STATE_SET.contains(captureResult.getAeState());
        }

        boolean isAwbModeOff = captureResult.getAwbMode() == CameraCaptureMetaData.AwbMode.OFF;
        boolean isAwbReady = isAwbModeOff
                || AWB_CONVERGED_STATE_SET.contains(captureResult.getAwbState());

        Logger.d(TAG, "checkCaptureResult, AE=" + captureResult.getAeState()
                + " AF =" + captureResult.getAfState()
                + " AWB=" + captureResult.getAwbState());
        return isAfReady && isAeReady && isAwbReady;
    }
}