TorchFlashRequiredFor3aUpdateQuirk.java

/*
 * Copyright 2020 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.camera2.internal.compat.quirk;

import static android.hardware.camera2.CameraMetadata.LENS_FACING_FRONT;

import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.os.Build;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.camera.camera2.internal.Camera2CameraControlImpl;
import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
import androidx.camera.core.impl.Quirk;

import java.util.Arrays;
import java.util.List;
import java.util.Locale;

/**
 * <p>QuirkSummary
 *     Bug Id: 294870640
 *     Description: Quirk denoting the devices where {@link CaptureRequest#FLASH_MODE_TORCH} has
 *                  to be set for 3A states to be updated with good values (in some cases, AWB
 *                  scanning is not triggered at all). This results in problems like color tint
 *                  or bad exposure in captured image during captures where lighting condition
 *                  changes (e.g. screen flash capture). This maybe required even if a flash unit
 *                  is not available (e.g. with front camera) and
 *                  {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER} has been requested. If
 *                  {@link CaptureRequest#CONTROL_AE_MODE_ON_EXTERNAL_FLASH} is supported, it can
 *                  be used instead and thus setting {@code FLASH_MODE_TORCH} won't be required.
 *     Device(s): Pixel 6A, 6 PRO, 7, 7A, 7 PRO, 8, 8 PRO
 */
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public class TorchFlashRequiredFor3aUpdateQuirk implements Quirk {
    private static final List<String> AFFECTED_PIXEL_MODELS = Arrays.asList(
            "PIXEL 6A",
            "PIXEL 6 PRO",
            "PIXEL 7",
            "PIXEL 7A",
            "PIXEL 7 PRO",
            "PIXEL 8",
            "PIXEL 8 PRO"
    );

    @NonNull
    private final CameraCharacteristicsCompat mCameraCharacteristics;

    public TorchFlashRequiredFor3aUpdateQuirk(
            @NonNull CameraCharacteristicsCompat cameraCharacteristics) {
        mCameraCharacteristics = cameraCharacteristics;
    }

    /**
     * Checks if the quirk should be loaded based on device model info and camera lens facing.
     */
    static boolean load(@NonNull CameraCharacteristicsCompat cameraCharacteristics) {
        return isAffectedModel(cameraCharacteristics);
    }

    /**
     * Returns whether {@link CaptureRequest#FLASH_MODE_TORCH} is required to be set.
     *
     * <p> This will also check if the {@link CaptureRequest#CONTROL_AE_MODE_ON_EXTERNAL_FLASH} is
     * supported, which is more recommended than using a quirk like using {@code FLASH_MODE_TORCH}.
     */
    public boolean isFlashModeTorchRequired() {
        return !isExternalFlashAeModeSupported(mCameraCharacteristics);
    }

    private static boolean isAffectedModel(
            @NonNull CameraCharacteristicsCompat cameraCharacteristics) {
        return isAffectedPixelModel() && isFrontCamera(cameraCharacteristics);
    }

    private static boolean isAffectedPixelModel() {
        for (String model : AFFECTED_PIXEL_MODELS) {
            if (Build.MODEL.toUpperCase(Locale.US).equals(model)) {
                return true;
            }
        }
        return false;
    }

    private static boolean isFrontCamera(
            @NonNull CameraCharacteristicsCompat cameraCharacteristics) {
        return cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == LENS_FACING_FRONT;
    }

    private static boolean isExternalFlashAeModeSupported(
            @NonNull CameraCharacteristicsCompat cameraCharacteristics
    ) {
        if (Build.VERSION.SDK_INT < 28) {
            return false;
        }

        return Camera2CameraControlImpl.getSupportedAeMode(cameraCharacteristics,
                CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH)
                == CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH;
    }
}