content: /*
   * Copyright (C) 2014 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 android.hardware.camera2.legacy;
  
  import android.graphics.Rect;
  import android.hardware.Camera;
  import android.hardware.Camera.FaceDetectionListener;
  import android.hardware.camera2.impl.CameraMetadataNative;
  import android.hardware.camera2.legacy.ParameterUtils.ZoomData;
  import android.hardware.camera2.CameraCharacteristics;
  import android.hardware.camera2.CaptureRequest;
  import android.hardware.camera2.CaptureResult;
  import android.hardware.camera2.params.Face;
  import android.hardware.camera2.utils.ListUtils;
  import android.hardware.camera2.utils.ParamsUtils;
  import android.util.Log;
  import android.util.Size;
  
  import com.android.internal.util.ArrayUtils;
  
  import java.util.ArrayList;
  import java.util.List;
  
  import static android.hardware.camera2.CaptureRequest.*;
  import static com.android.internal.util.Preconditions.*;
  
  /**
   * Map legacy face detect callbacks into face detection results.
   */
  @SuppressWarnings("deprecation")
  public class LegacyFaceDetectMapper {
      private static String TAG = "LegacyFaceDetectMapper";
      private static final boolean DEBUG = false;
  
      private final Camera mCamera;
      /** Is the camera capable of face detection? */
      private final boolean mFaceDetectSupported;
      /** Is the camera is running face detection? */
      private boolean mFaceDetectEnabled = false;
      /** Did the last request say to use SCENE_MODE = FACE_PRIORITY? */
      private boolean mFaceDetectScenePriority = false;
      /** Did the last request enable the face detect mode to ON? */
      private boolean mFaceDetectReporting = false;
  
      /** Synchronize access to all fields */
      private final Object mLock = new Object();
      private Camera.Face[] mFaces;
      private Camera.Face[] mFacesPrev;
      /**
       * Instantiate a new face detect mapper.
       *
       * @param camera a non-{@code null} camera1 device
       * @param characteristics a  non-{@code null} camera characteristics for that camera1
       *
       * @throws NullPointerException if any of the args were {@code null}
       */
      public LegacyFaceDetectMapper(Camera camera, CameraCharacteristics characteristics) {
          mCamera = checkNotNull(camera, "camera must not be null");
          checkNotNull(characteristics, "characteristics must not be null");
  
          mFaceDetectSupported = ArrayUtils.contains(
                  characteristics.get(
                          CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES),
                  STATISTICS_FACE_DETECT_MODE_SIMPLE);
  
          if (!mFaceDetectSupported) {
              return;
          }
  
         mCamera.setFaceDetectionListener(new FaceDetectionListener() {
  
          @Override
          public void onFaceDetection(Camera.Face[] faces, Camera camera) {
              int lengthFaces = faces == null ? 0 : faces.length;
              synchronized (mLock) {
                  if (mFaceDetectEnabled) {
                      mFaces = faces;
                  } else if (lengthFaces > 0) {
                      // stopFaceDetectMode could race against the requests, print a debug log
                      Log.d(TAG,
                              "onFaceDetection - Ignored some incoming faces since" +
                              "face detection was disabled");
                  }
              }
  
              if (DEBUG) {
                  Log.v(TAG, "onFaceDetection - read " + lengthFaces + " faces");
              }
          }
         });
      }
  
      /**
       * Process the face detect mode from the capture request into an api1 face detect toggle.
       *
       * <p>This method should be called after the parameters are {@link LegacyRequestMapper mapped}
       * with the request.</p>
       *
       * <p>Callbacks are processed in the background, and the next call to {@link #mapResultTriggers}
       * will have the latest faces detected as reflected by the camera1 callbacks.</p>
       *
       * <p>None of the arguments will be mutated.</p>
       *
       * @param captureRequest a non-{@code null} request
       * @param parameters a non-{@code null} parameters corresponding to this request (read-only)
       */
      public void processFaceDetectMode(CaptureRequest captureRequest,
              Camera.Parameters parameters) {
          checkNotNull(captureRequest, "captureRequest must not be null");
  
          /*
           * statistics.faceDetectMode
           */
          int fdMode = ParamsUtils.getOrDefault(captureRequest, STATISTICS_FACE_DETECT_MODE,
                  STATISTICS_FACE_DETECT_MODE_OFF);
  
          if (fdMode != STATISTICS_FACE_DETECT_MODE_OFF && !mFaceDetectSupported) {
              Log.w(TAG,
                      "processFaceDetectMode - Ignoring statistics.faceDetectMode; " +
                      "face detection is not available");
              return;
          }
  
          /*
           * control.sceneMode
           */
          int sceneMode = ParamsUtils.getOrDefault(captureRequest, CONTROL_SCENE_MODE,
                  CONTROL_SCENE_MODE_DISABLED);
          if (sceneMode == CONTROL_SCENE_MODE_FACE_PRIORITY && !mFaceDetectSupported) {
              Log.w(TAG, "processFaceDetectMode - ignoring control.sceneMode == FACE_PRIORITY; " +
                      "face detection is not available");
              return;
          }
  
          // Print some warnings out in case the values were wrong
          switch (fdMode) {
              case STATISTICS_FACE_DETECT_MODE_OFF:
              case STATISTICS_FACE_DETECT_MODE_SIMPLE:
                  break;
              case STATISTICS_FACE_DETECT_MODE_FULL:
                  Log.w(TAG,
                          "processFaceDetectMode - statistics.faceDetectMode == FULL unsupported, " +
                          "downgrading to SIMPLE");
                  break;
              default:
                  Log.w(TAG, "processFaceDetectMode - ignoring unknown statistics.faceDetectMode = "
                          + fdMode);
                  return;
          }
  
          boolean enableFaceDetect = (fdMode != STATISTICS_FACE_DETECT_MODE_OFF)
                  || (sceneMode == CONTROL_SCENE_MODE_FACE_PRIORITY);
          synchronized (mLock) {
              // Enable/disable face detection if it's changed since last time
              if (enableFaceDetect != mFaceDetectEnabled) {
                  if (enableFaceDetect) {
                      mCamera.startFaceDetection();
  
                      if (DEBUG) {
                          Log.v(TAG, "processFaceDetectMode - start face detection");
                      }
                  } else {
                      mCamera.stopFaceDetection();
  
                      if (DEBUG) {
                          Log.v(TAG, "processFaceDetectMode - stop face detection");
                      }
  
                      mFaces = null;
                  }
  
                  mFaceDetectEnabled = enableFaceDetect;
                  mFaceDetectScenePriority = sceneMode == CONTROL_SCENE_MODE_FACE_PRIORITY;
                  mFaceDetectReporting = fdMode != STATISTICS_FACE_DETECT_MODE_OFF;
              }
          }
      }
  
      /**
       * Update the {@code result} camera metadata map with the new value for the
       * {@code statistics.faces} and {@code statistics.faceDetectMode}.
       *
       * <p>Face detect callbacks are processed in the background, and each call to
       * {@link #mapResultFaces} will have the latest faces as reflected by the camera1 callbacks.</p>
       *
       * <p>If the scene mode was set to {@code FACE_PRIORITY} but face detection is disabled,
       * the camera will still run face detection in the background, but no faces will be reported
       * in the capture result.</p>
       *
       * @param result a non-{@code null} result
       * @param legacyRequest a non-{@code null} request (read-only)
       */
      public void mapResultFaces(CameraMetadataNative result, LegacyRequest legacyRequest) {
          checkNotNull(result, "result must not be null");
          checkNotNull(legacyRequest, "legacyRequest must not be null");
  
          Camera.Face[] faces, previousFaces;
          int fdMode;
          boolean fdScenePriority;
          synchronized (mLock) {
              fdMode = mFaceDetectReporting ?
                              STATISTICS_FACE_DETECT_MODE_SIMPLE : STATISTICS_FACE_DETECT_MODE_OFF;
  
              if (mFaceDetectReporting) {
                  faces = mFaces;
              } else {
                  faces = null;
              }
  
              fdScenePriority = mFaceDetectScenePriority;
  
              previousFaces = mFacesPrev;
              mFacesPrev = faces;
          }
  
          CameraCharacteristics characteristics = legacyRequest.characteristics;
          CaptureRequest request = legacyRequest.captureRequest;
          Size previewSize = legacyRequest.previewSize;
          Camera.Parameters params = legacyRequest.parameters;
  
          Rect activeArray = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
          ZoomData zoomData = ParameterUtils.convertScalerCropRegion(activeArray,
                  request.get(CaptureRequest.SCALER_CROP_REGION), previewSize, params);
  
          List<Face> convertedFaces = new ArrayList<>();
          if (faces != null) {
              for (Camera.Face face : faces) {
                  if (face != null) {
                      convertedFaces.add(
                              ParameterUtils.convertFaceFromLegacy(face, activeArray, zoomData));
                  } else {
                      Log.w(TAG, "mapResultFaces - read NULL face from camera1 device");
                  }
              }
          }
  
          if (DEBUG && previousFaces != faces) { // Log only in verbose and IF the faces changed
              Log.v(TAG, "mapResultFaces - changed to " + ListUtils.listToString(convertedFaces));
          }
  
          result.set(CaptureResult.STATISTICS_FACES, convertedFaces.toArray(new Face[0]));
          result.set(CaptureResult.STATISTICS_FACE_DETECT_MODE, fdMode);
  
          // Override scene mode with FACE_PRIORITY if the request was using FACE_PRIORITY
          if (fdScenePriority) {
              result.set(CaptureResult.CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_FACE_PRIORITY);
          }
      }
  }
  