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.Parameters;
  import android.hardware.camera2.CameraCharacteristics;
  import android.hardware.camera2.CaptureRequest;
  import android.hardware.camera2.CaptureResult;
  import android.hardware.camera2.impl.CameraMetadataNative;
  import android.hardware.camera2.legacy.ParameterUtils.WeightedRectangle;
  import android.hardware.camera2.legacy.ParameterUtils.ZoomData;
  import android.hardware.camera2.params.MeteringRectangle;
  import android.hardware.camera2.utils.ListUtils;
  import android.hardware.camera2.utils.ParamsUtils;
  import android.util.Log;
  import android.util.Size;
  
  import java.util.ArrayList;
  import java.util.List;
  
  import static android.hardware.camera2.CaptureResult.*;
  
  /**
   * Provide legacy-specific implementations of camera2 CaptureResult for legacy devices.
   */
  @SuppressWarnings("deprecation")
  public class LegacyResultMapper {
      private static final String TAG = "LegacyResultMapper";
      private static final boolean DEBUG = false;
  
      private LegacyRequest mCachedRequest = null;
      private CameraMetadataNative mCachedResult = null;
  
      /**
       * Generate capture result metadata from the legacy camera request.
       *
       * <p>This method caches and reuses the result from the previous call to this method if
       * the {@code parameters} of the subsequent {@link LegacyRequest} passed to this method
       * have not changed.</p>
       *
       * @param legacyRequest a non-{@code null} legacy request containing the latest parameters
       * @param timestamp the timestamp to use for this result in nanoseconds.
       *
       * @return {@link CameraMetadataNative} object containing result metadata.
       */
      public CameraMetadataNative cachedConvertResultMetadata(
              LegacyRequest legacyRequest, long timestamp) {
          CameraMetadataNative result;
          boolean cached;
  
          /*
           * Attempt to look up the result from the cache if the parameters haven't changed
           */
          if (mCachedRequest != null &&
                  legacyRequest.parameters.same(mCachedRequest.parameters) &&
                  legacyRequest.captureRequest.equals(mCachedRequest.captureRequest)) {
              result = new CameraMetadataNative(mCachedResult);
              cached = true;
          } else {
              result = convertResultMetadata(legacyRequest);
              cached = false;
  
              // Always cache a *copy* of the metadata result,
              // since api2's client side takes ownership of it after it receives a result
              mCachedRequest = legacyRequest;
              mCachedResult = new CameraMetadataNative(result);
          }
  
          /*
           * Unconditionally set fields that change in every single frame
           */
          {
              // sensor.timestamp
              result.set(SENSOR_TIMESTAMP, timestamp);
          }
  
          if (DEBUG) {
              Log.v(TAG, "cachedConvertResultMetadata - cached? " + cached +
                      " timestamp = " + timestamp);
  
              Log.v(TAG, "----- beginning of result dump ------");
              result.dumpToLog();
              Log.v(TAG, "----- end of result dump ------");
          }
  
          return result;
      }
  
      /**
       * Generate capture result metadata from the legacy camera request.
       *
       * @param legacyRequest a non-{@code null} legacy request containing the latest parameters
       * @return a {@link CameraMetadataNative} object containing result metadata.
       */
      private static CameraMetadataNative convertResultMetadata(LegacyRequest legacyRequest) {
          CameraCharacteristics characteristics = legacyRequest.characteristics;
          CaptureRequest request = legacyRequest.captureRequest;
          Size previewSize = legacyRequest.previewSize;
          Camera.Parameters params = legacyRequest.parameters;
  
          CameraMetadataNative result = new CameraMetadataNative();
  
          Rect activeArraySize = characteristics.get(
                  CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
          ZoomData zoomData = ParameterUtils.convertScalerCropRegion(activeArraySize,
                  request.get(CaptureRequest.SCALER_CROP_REGION), previewSize, params);
  
          /*
           * colorCorrection
           */
          // colorCorrection.aberrationMode
          {
              result.set(COLOR_CORRECTION_ABERRATION_MODE,
                      request.get(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE));
          }
  
          /*
           * control
           */
  
          /*
           * control.ae*
           */
          mapAe(result, characteristics, request, activeArraySize, zoomData, /*out*/params);
  
          /*
           * control.af*
           */
          mapAf(result, activeArraySize, zoomData, /*out*/params);
  
          /*
           * control.awb*
           */
          mapAwb(result, /*out*/params);
  
          /*
           * control.captureIntent
           */
          {
              int captureIntent = ParamsUtils.getOrDefault(request,
                      CaptureRequest.CONTROL_CAPTURE_INTENT,
                      /*defaultValue*/CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW);
  
              captureIntent = LegacyRequestMapper.filterSupportedCaptureIntent(captureIntent);
  
              result.set(CONTROL_CAPTURE_INTENT, captureIntent);
          }
  
          /*
           * control.mode
           */
          {
              int controlMode = ParamsUtils.getOrDefault(request, CaptureRequest.CONTROL_MODE,
                      CONTROL_MODE_AUTO);
              if (controlMode == CaptureResult.CONTROL_MODE_USE_SCENE_MODE) {
                  result.set(CONTROL_MODE, CONTROL_MODE_USE_SCENE_MODE);
              } else {
                  result.set(CONTROL_MODE, CONTROL_MODE_AUTO);
              }
          }
  
          /*
           * control.sceneMode
           */
          {
              String legacySceneMode = params.getSceneMode();
              int mode = LegacyMetadataMapper.convertSceneModeFromLegacy(legacySceneMode);
              if (mode != LegacyMetadataMapper.UNKNOWN_MODE) {
                  result.set(CaptureResult.CONTROL_SCENE_MODE, mode);
                  // In case of SCENE_MODE == FACE_PRIORITY, LegacyFaceDetectMapper will override
                  // the result to say SCENE_MODE == FACE_PRIORITY.
              }  else {
                  Log.w(TAG, "Unknown scene mode " + legacySceneMode +
                          " returned by camera HAL, setting to disabled.");
                  result.set(CaptureResult.CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_DISABLED);
              }
          }
  
          /*
           * control.effectMode
           */
          {
              String legacyEffectMode = params.getColorEffect();
              int mode = LegacyMetadataMapper.convertEffectModeFromLegacy(legacyEffectMode);
              if (mode != LegacyMetadataMapper.UNKNOWN_MODE) {
                  result.set(CaptureResult.CONTROL_EFFECT_MODE, mode);
              } else {
                  Log.w(TAG, "Unknown effect mode " + legacyEffectMode +
                          " returned by camera HAL, setting to off.");
                  result.set(CaptureResult.CONTROL_EFFECT_MODE, CONTROL_EFFECT_MODE_OFF);
              }
          }
  
          // control.videoStabilizationMode
          {
              int stabMode =
                      (params.isVideoStabilizationSupported() && params.getVideoStabilization()) ?
                          CONTROL_VIDEO_STABILIZATION_MODE_ON :
                          CONTROL_VIDEO_STABILIZATION_MODE_OFF;
              result.set(CONTROL_VIDEO_STABILIZATION_MODE, stabMode);
          }
  
          /*
           * flash
           */
          {
              // flash.mode, flash.state mapped in mapAeAndFlashMode
          }
  
          /*
           * lens
           */
          // lens.focusDistance
          {
              if (Parameters.FOCUS_MODE_INFINITY.equals(params.getFocusMode())) {
                  result.set(CaptureResult.LENS_FOCUS_DISTANCE, 0.0f);
              }
          }
  
          // lens.focalLength
          result.set(CaptureResult.LENS_FOCAL_LENGTH, params.getFocalLength());
  
          /*
           * request
           */
          // request.pipelineDepth
          result.set(REQUEST_PIPELINE_DEPTH,
                  characteristics.get(CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH));
  
          /*
           * scaler
           */
          mapScaler(result, zoomData, /*out*/params);
  
          /*
           * sensor
           */
          // sensor.timestamp varies every frame; mapping is done in #cachedConvertResultMetadata
          {
              // Unconditionally no test patterns
              result.set(SENSOR_TEST_PATTERN_MODE, SENSOR_TEST_PATTERN_MODE_OFF);
          }
  
          /*
           * jpeg
           */
          // jpeg.gpsLocation
          result.set(JPEG_GPS_LOCATION, request.get(CaptureRequest.JPEG_GPS_LOCATION));
  
          // jpeg.orientation
          result.set(JPEG_ORIENTATION, request.get(CaptureRequest.JPEG_ORIENTATION));
  
          // jpeg.quality
          result.set(JPEG_QUALITY, (byte) params.getJpegQuality());
  
          // jpeg.thumbnailQuality
          result.set(JPEG_THUMBNAIL_QUALITY, (byte) params.getJpegThumbnailQuality());
  
          // jpeg.thumbnailSize
          Camera.Size s = params.getJpegThumbnailSize();
          if (s != null) {
              result.set(JPEG_THUMBNAIL_SIZE, ParameterUtils.convertSize(s));
          } else {
              Log.w(TAG, "Null thumbnail size received from parameters.");
          }
  
          /*
           * noiseReduction.*
           */
          // noiseReduction.mode
          result.set(NOISE_REDUCTION_MODE, request.get(CaptureRequest.NOISE_REDUCTION_MODE));
  
          return result;
      }
  
      private static void mapAe(CameraMetadataNative m,
              CameraCharacteristics characteristics,
              CaptureRequest request, Rect activeArray, ZoomData zoomData, /*out*/Parameters p) {
          // control.aeAntiBandingMode
          {
              int antiBandingMode = LegacyMetadataMapper.convertAntiBandingModeOrDefault(
                      p.getAntibanding());
              m.set(CONTROL_AE_ANTIBANDING_MODE, antiBandingMode);
          }
  
          // control.aeExposureCompensation
          {
              m.set(CONTROL_AE_EXPOSURE_COMPENSATION, p.getExposureCompensation());
          }
  
          // control.aeLock
          {
              boolean lock = p.isAutoExposureLockSupported() ? p.getAutoExposureLock() : false;
              m.set(CONTROL_AE_LOCK, lock);
              if (DEBUG) {
                  Log.v(TAG,
                          "mapAe - android.control.aeLock = " + lock +
                          ", supported = " + p.isAutoExposureLockSupported());
              }
  
              Boolean requestLock = request.get(CaptureRequest.CONTROL_AE_LOCK);
              if (requestLock != null && requestLock != lock) {
                  Log.w(TAG,
                          "mapAe - android.control.aeLock was requested to " + requestLock +
                          " but resulted in " + lock);
              }
          }
  
          // control.aeMode, flash.mode, flash.state
          mapAeAndFlashMode(m, characteristics, p);
  
          // control.aeState
          if (LegacyMetadataMapper.LIE_ABOUT_AE_STATE) {
              // Lie to pass CTS temporarily.
              // TODO: Implement precapture trigger, after which we can report CONVERGED ourselves
              m.set(CONTROL_AE_STATE, CONTROL_AE_STATE_CONVERGED);
          }
  
          // control.aeRegions
          if (p.getMaxNumMeteringAreas() > 0) {
              if (DEBUG) {
                  String meteringAreas = p.get("metering-areas");
                  Log.v(TAG, "mapAe - parameter dump; metering-areas: " + meteringAreas);
              }
  
              MeteringRectangle[] meteringRectArray = getMeteringRectangles(activeArray,
                      zoomData, p.getMeteringAreas(), "AE");
  
              m.set(CONTROL_AE_REGIONS, meteringRectArray);
          }
  
      }
  
      private static void mapAf(CameraMetadataNative m,
              Rect activeArray, ZoomData zoomData, Camera.Parameters p) {
          // control.afMode
          m.set(CaptureResult.CONTROL_AF_MODE, convertLegacyAfMode(p.getFocusMode()));
  
          // control.afRegions
          if (p.getMaxNumFocusAreas() > 0) {
              if (DEBUG) {
                  String focusAreas = p.get("focus-areas");
                  Log.v(TAG, "mapAe - parameter dump; focus-areas: " + focusAreas);
              }
  
              MeteringRectangle[] meteringRectArray = getMeteringRectangles(activeArray,
                      zoomData, p.getFocusAreas(), "AF");
  
              m.set(CONTROL_AF_REGIONS, meteringRectArray);
          }
      }
  
      private static void mapAwb(CameraMetadataNative m, Camera.Parameters p) {
          // control.awbLock
          {
              boolean lock = p.isAutoWhiteBalanceLockSupported() ?
                      p.getAutoWhiteBalanceLock() : false;
              m.set(CONTROL_AWB_LOCK, lock);
          }
  
          // control.awbMode
          {
              int awbMode = convertLegacyAwbMode(p.getWhiteBalance());
              m.set(CONTROL_AWB_MODE, awbMode);
          }
      }
  
      private static MeteringRectangle[] getMeteringRectangles(Rect activeArray, ZoomData zoomData,
              List<Camera.Area> meteringAreaList, String regionName) {
          List<MeteringRectangle> meteringRectList = new ArrayList<>();
          if (meteringAreaList != null) {
              for (Camera.Area area : meteringAreaList) {
                  WeightedRectangle rect =
                          ParameterUtils.convertCameraAreaToActiveArrayRectangle(
                                  activeArray, zoomData, area);
  
                  meteringRectList.add(rect.toMetering());
              }
          }
  
          if (DEBUG) {
              Log.v(TAG,
                      "Metering rectangles for " + regionName + ": "
                       + ListUtils.listToString(meteringRectList));
          }
  
          return meteringRectList.toArray(new MeteringRectangle[0]);
      }
  
      /** Map results for control.aeMode, flash.mode, flash.state */
      private static void mapAeAndFlashMode(CameraMetadataNative m,
              CameraCharacteristics characteristics, Parameters p) {
          // Default: AE mode on but flash never fires
          int flashMode = FLASH_MODE_OFF;
          // If there is no flash on this camera, the state is always unavailable
          // , otherwise it's only known for TORCH/SINGLE modes
          Integer flashState = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE)
                  ? null : FLASH_STATE_UNAVAILABLE;
          int aeMode = CONTROL_AE_MODE_ON;
  
          String flashModeSetting = p.getFlashMode();
  
          if (flashModeSetting != null) {
              switch (flashModeSetting) {
                  case Parameters.FLASH_MODE_OFF:
                      break; // ok, using default
                  case Parameters.FLASH_MODE_AUTO:
                      aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH;
                      break;
                  case Parameters.FLASH_MODE_ON:
                      // flashMode = SINGLE + aeMode = ON is indistinguishable from ON_ALWAYS_FLASH
                      flashMode = FLASH_MODE_SINGLE;
                      aeMode = CONTROL_AE_MODE_ON_ALWAYS_FLASH;
                      flashState = FLASH_STATE_FIRED;
                      break;
                  case Parameters.FLASH_MODE_RED_EYE:
                      aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE;
                      break;
                  case Parameters.FLASH_MODE_TORCH:
                      flashMode = FLASH_MODE_TORCH;
                      flashState = FLASH_STATE_FIRED;
                      break;
                  default:
                      Log.w(TAG,
                              "mapAeAndFlashMode - Ignoring unknown flash mode " + p.getFlashMode());
              }
          }
  
          // flash.state
          m.set(FLASH_STATE, flashState);
          // flash.mode
          m.set(FLASH_MODE, flashMode);
          // control.aeMode
          m.set(CONTROL_AE_MODE, aeMode);
      }
  
      private static int convertLegacyAfMode(String mode) {
          if (mode == null) {
              Log.w(TAG, "convertLegacyAfMode - no AF mode, default to OFF");
              return CONTROL_AF_MODE_OFF;
          }
  
          switch (mode) {
              case Parameters.FOCUS_MODE_AUTO:
                  return CONTROL_AF_MODE_AUTO;
              case Parameters.FOCUS_MODE_CONTINUOUS_PICTURE:
                  return CONTROL_AF_MODE_CONTINUOUS_PICTURE;
              case Parameters.FOCUS_MODE_CONTINUOUS_VIDEO:
                  return CONTROL_AF_MODE_CONTINUOUS_VIDEO;
              case Parameters.FOCUS_MODE_EDOF:
                  return CONTROL_AF_MODE_EDOF;
              case Parameters.FOCUS_MODE_MACRO:
                  return CONTROL_AF_MODE_MACRO;
              case Parameters.FOCUS_MODE_FIXED:
                  return CONTROL_AF_MODE_OFF;
              case Parameters.FOCUS_MODE_INFINITY:
                  return CONTROL_AF_MODE_OFF;
              default:
                  Log.w(TAG, "convertLegacyAfMode - unknown mode " + mode + " , ignoring");
                  return CONTROL_AF_MODE_OFF;
          }
      }
  
      private static int convertLegacyAwbMode(String mode) {
          if (mode == null) {
              // OK: camera1 api may not support changing WB modes; assume AUTO
              return CONTROL_AWB_MODE_AUTO;
          }
  
          switch (mode) {
              case Camera.Parameters.WHITE_BALANCE_AUTO:
                  return CONTROL_AWB_MODE_AUTO;
              case Camera.Parameters.WHITE_BALANCE_INCANDESCENT:
                  return CONTROL_AWB_MODE_INCANDESCENT;
              case Camera.Parameters.WHITE_BALANCE_FLUORESCENT:
                  return CONTROL_AWB_MODE_FLUORESCENT;
              case Camera.Parameters.WHITE_BALANCE_WARM_FLUORESCENT:
                  return CONTROL_AWB_MODE_WARM_FLUORESCENT;
              case Camera.Parameters.WHITE_BALANCE_DAYLIGHT:
                  return CONTROL_AWB_MODE_DAYLIGHT;
              case Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT:
                  return CONTROL_AWB_MODE_CLOUDY_DAYLIGHT;
              case Camera.Parameters.WHITE_BALANCE_TWILIGHT:
                  return CONTROL_AWB_MODE_TWILIGHT;
              case Camera.Parameters.WHITE_BALANCE_SHADE:
                  return CONTROL_AWB_MODE_SHADE;
              default:
                  Log.w(TAG, "convertAwbMode - unrecognized WB mode " + mode);
                  return CONTROL_AWB_MODE_AUTO;
          }
      }
  
      /** Map results for scaler.* */
      private static void mapScaler(CameraMetadataNative m,
              ZoomData zoomData,
              /*out*/Parameters p) {
          /*
           * scaler.cropRegion
           */
          {
              m.set(SCALER_CROP_REGION, zoomData.reportedCrop);
          }
      }
  }
  