Camera: various external camera CTS fixes

1. Update the FPS range to list (0.5*fps, fps) only
   as webcams tends to skip a lot of frames and not
   able to output at stable framerate.
2. Exif: don't expect focal length to present
3. Thumbnail: allow 0x0 size for no thumbnail output
4. Allow retry some ioctl during configureStream as
   some webcams seems having problem in quick close
   reopen operation.

Test: CTS CameraTest
Bug: 72261912
Change-Id: Ic23b7fb293b7579694c59240e854d750c842886d
diff --git a/camera/common/1.0/default/Exif.cpp b/camera/common/1.0/default/Exif.cpp
index 3e894f9..6054999 100644
--- a/camera/common/1.0/default/Exif.cpp
+++ b/camera/common/1.0/default/Exif.cpp
@@ -983,15 +983,15 @@
     camera_metadata_ro_entry entry = metadata.find(ANDROID_LENS_FOCAL_LENGTH);
     if (entry.count) {
         focal_length = entry.data.f[0];
+
+        if (!setFocalLength(
+                        static_cast<uint32_t>(focal_length * kRationalPrecision),
+                        kRationalPrecision)) {
+            ALOGE("%s: setting focal length failed.", __FUNCTION__);
+            return false;
+        }
     } else {
-        ALOGE("%s: Cannot find focal length in metadata.", __FUNCTION__);
-        return false;
-    }
-    if (!setFocalLength(
-                    static_cast<uint32_t>(focal_length * kRationalPrecision),
-                    kRationalPrecision)) {
-        ALOGE("%s: setting focal length failed.", __FUNCTION__);
-        return false;
+        ALOGV("%s: Cannot find focal length in metadata.", __FUNCTION__);
     }
 
     if (metadata.exists(ANDROID_JPEG_GPS_COORDINATES)) {
diff --git a/camera/device/3.4/default/ExternalCameraDevice.cpp b/camera/device/3.4/default/ExternalCameraDevice.cpp
index 9a02ce8..037cef8 100644
--- a/camera/device/3.4/default/ExternalCameraDevice.cpp
+++ b/camera/device/3.4/default/ExternalCameraDevice.cpp
@@ -639,14 +639,14 @@
     }
 
     std::vector<int32_t> fpsRanges;
-    // Variable range
-    fpsRanges.push_back(minFps);
-    fpsRanges.push_back(maxFps);
-    // Fixed ranges
+    // FPS ranges
     for (const auto& framerate : framerates) {
-        fpsRanges.push_back(framerate);
+        // Empirical: webcams often have close to 2x fps error and cannot support fixed fps range
+        fpsRanges.push_back(framerate / 2);
         fpsRanges.push_back(framerate);
     }
+    maxFrameDuration *= 2;
+
     UPDATE(ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES, fpsRanges.data(),
            fpsRanges.size());
 
diff --git a/camera/device/3.4/default/ExternalCameraDeviceSession.cpp b/camera/device/3.4/default/ExternalCameraDeviceSession.cpp
index 5569439..29aa1c3 100644
--- a/camera/device/3.4/default/ExternalCameraDeviceSession.cpp
+++ b/camera/device/3.4/default/ExternalCameraDeviceSession.cpp
@@ -47,6 +47,9 @@
 const int kBadFramesAfterStreamOn = 1; // drop x frames after streamOn to get rid of some initial
                                        // bad frames. TODO: develop a better bad frame detection
                                        // method
+constexpr int MAX_RETRY = 15; // Allow retry some ioctl failures a few times to account for some
+                             // webcam showing temporarily ioctl failures.
+constexpr int IOCTL_RETRY_SLEEP_US = 33000; // 33ms * MAX_RETRY = 5 seconds
 
 bool tryLock(Mutex& mutex)
 {
@@ -1480,6 +1483,7 @@
 
     int jpegQuality, thumbQuality;
     Size thumbSize;
+    bool outputThumbnail = true;
 
     if (req->setting.exists(ANDROID_JPEG_QUALITY)) {
         camera_metadata_entry entry =
@@ -1505,6 +1509,9 @@
         thumbSize = Size { static_cast<uint32_t>(entry.data.i32[0]),
                            static_cast<uint32_t>(entry.data.i32[1])
         };
+        if (thumbSize.width == 0 && thumbSize.height == 0) {
+            outputThumbnail = false;
+        }
     } else {
         return lfail(
             "%s: ANDROID_JPEG_THUMBNAIL_SIZE not set", __FUNCTION__);
@@ -1532,14 +1539,16 @@
     /* Hold actual thumbnail and main image code sizes */
     size_t thumbCodeSize = 0, jpegCodeSize = 0;
     /* Temporary thumbnail code buffer */
-    std::vector<uint8_t> thumbCode(maxThumbCodeSize);
+    std::vector<uint8_t> thumbCode(outputThumbnail ? maxThumbCodeSize : 0);
 
     YCbCrLayout yu12Thumb;
-    ret = cropAndScaleThumbLocked(mYu12Frame, thumbSize, &yu12Thumb);
+    if (outputThumbnail) {
+        ret = cropAndScaleThumbLocked(mYu12Frame, thumbSize, &yu12Thumb);
 
-    if (ret != 0) {
-        return lfail(
-            "%s: crop and scale thumbnail failed!", __FUNCTION__);
+        if (ret != 0) {
+            return lfail(
+                "%s: crop and scale thumbnail failed!", __FUNCTION__);
+        }
     }
 
     /* Scale and crop main jpeg */
@@ -1550,12 +1559,14 @@
     }
 
     /* Encode the thumbnail image */
-    ret = encodeJpegYU12(thumbSize, yu12Thumb,
-            thumbQuality, 0, 0,
-            &thumbCode[0], maxThumbCodeSize, thumbCodeSize);
+    if (outputThumbnail) {
+        ret = encodeJpegYU12(thumbSize, yu12Thumb,
+                thumbQuality, 0, 0,
+                &thumbCode[0], maxThumbCodeSize, thumbCodeSize);
 
-    if (ret != 0) {
-        return lfail("%s: encodeJpegYU12 failed with %d",__FUNCTION__, ret);
+        if (ret != 0) {
+            return lfail("%s: thumbnail encodeJpegYU12 failed with %d",__FUNCTION__, ret);
+        }
     }
 
     /* Combine camera characteristics with request settings to form EXIF
@@ -1570,11 +1581,7 @@
 
     utils->setFromMetadata(meta, jpegSize.width, jpegSize.height);
 
-    /* Check if we made a non-zero-sized thumbnail. Currently not possible
-     * that we got this far and the code is size 0, but if this code moves
-     * around it might become relevant again */
-
-    ret = utils->generateApp1(thumbCodeSize ? &thumbCode[0] : 0, thumbCodeSize);
+    ret = utils->generateApp1(outputThumbnail ? &thumbCode[0] : 0, thumbCodeSize);
 
     if (!ret) {
         return lfail("%s: generating APP1 failed", __FUNCTION__);
@@ -2109,8 +2116,20 @@
     fmt.fmt.pix.pixelformat = v4l2Fmt.fourcc;
     ret = TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_S_FMT, &fmt));
     if (ret < 0) {
-        ALOGE("%s: S_FMT ioctl failed: %s", __FUNCTION__, strerror(errno));
-        return -errno;
+        int numAttempt = 0;
+        while (ret < 0) {
+            ALOGW("%s: VIDIOC_S_FMT failed, wait 33ms and try again", __FUNCTION__);
+            usleep(IOCTL_RETRY_SLEEP_US); // sleep 100 ms and try again
+            ret = TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_S_FMT, &fmt));
+            if (numAttempt == MAX_RETRY) {
+                break;
+            }
+            numAttempt++;
+        }
+        if (ret < 0) {
+            ALOGE("%s: S_FMT ioctl failed: %s", __FUNCTION__, strerror(errno));
+            return -errno;
+        }
     }
 
     if (v4l2Fmt.width != fmt.fmt.pix.width || v4l2Fmt.height != fmt.fmt.pix.height ||
@@ -2199,9 +2218,22 @@
 
     // VIDIOC_STREAMON: start streaming
     v4l2_buf_type capture_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-    if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_STREAMON, &capture_type)) < 0) {
-        ALOGE("%s: VIDIOC_STREAMON failed: %s", __FUNCTION__, strerror(errno));
-        return -errno;
+    ret = TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_STREAMON, &capture_type));
+    if (ret < 0) {
+        int numAttempt = 0;
+        while (ret < 0) {
+            ALOGW("%s: VIDIOC_STREAMON failed, wait 33ms and try again", __FUNCTION__);
+            usleep(IOCTL_RETRY_SLEEP_US); // sleep 100 ms and try again
+            ret = TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_STREAMON, &capture_type));
+            if (numAttempt == MAX_RETRY) {
+                break;
+            }
+            numAttempt++;
+        }
+        if (ret < 0) {
+            ALOGE("%s: VIDIOC_STREAMON ioctl failed: %s", __FUNCTION__, strerror(errno));
+            return -errno;
+        }
     }
 
     // Swallow first few frames after streamOn to account for bad frames from some devices
@@ -2220,6 +2252,8 @@
         }
     }
 
+    ALOGI("%s: start V4L2 streaming %dx%d@%ffps",
+                __FUNCTION__, v4l2Fmt.width, v4l2Fmt.height, fps);
     mV4l2StreamingFmt = v4l2Fmt;
     mV4l2Streaming = true;
     return OK;