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.ImageFormat;
  import android.graphics.PixelFormat;
  import android.graphics.Rect;
  import android.hardware.Camera;
  import android.hardware.Camera.CameraInfo;
  import android.hardware.Camera.Parameters;
  import android.hardware.camera2.CameraCharacteristics;
  import android.hardware.camera2.CameraDevice;
  import android.hardware.camera2.CameraMetadata;
  import android.hardware.camera2.CaptureRequest;
  import android.hardware.camera2.CaptureResult;
  import android.hardware.camera2.impl.CameraMetadataNative;
  import android.hardware.camera2.params.MeteringRectangle;
  import android.hardware.camera2.params.StreamConfiguration;
  import android.hardware.camera2.params.StreamConfigurationDuration;
  import android.hardware.camera2.utils.ArrayUtils;
  import android.hardware.camera2.utils.ListUtils;
  import android.hardware.camera2.utils.ParamsUtils;
  import android.util.Log;
  import android.util.Range;
  import android.util.Size;
  import android.util.SizeF;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.List;
  
  import static com.android.internal.util.Preconditions.*;
  import static android.hardware.camera2.CameraCharacteristics.*;
  import static android.hardware.camera2.legacy.ParameterUtils.*;
  
  /**
   * Provide legacy-specific implementations of camera2 metadata for legacy devices, such as the
   * camera characteristics.
   */
  @SuppressWarnings("deprecation")
  public class LegacyMetadataMapper {
      private static final String TAG = "LegacyMetadataMapper";
      private static final boolean DEBUG = false;
  
      private static final long NS_PER_MS = 1000000;
  
      // from graphics.h
      public static final int HAL_PIXEL_FORMAT_RGBA_8888 = PixelFormat.RGBA_8888;
      public static final int HAL_PIXEL_FORMAT_BGRA_8888 = 0x5;
      public static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22;
      public static final int HAL_PIXEL_FORMAT_BLOB = 0x21;
  
      // for metadata
      private static final float LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS = 0.0f;
  
      private static final int REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_RAW = 0; // no raw support
      private static final int REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_PROC = 3; // preview, video, cb
      private static final int REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_PROC_STALL = 1; // 1 jpeg only
      private static final int REQUEST_MAX_NUM_INPUT_STREAMS_COUNT = 0; // no reprocessing
  
      /** Assume 3 HAL1 stages: Exposure, Read-out, Post-Processing */
      private static final int REQUEST_PIPELINE_MAX_DEPTH_HAL1 = 3;
      /** Assume 3 shim stages: Preview input, Split output, Format conversion for output */
      private static final int REQUEST_PIPELINE_MAX_DEPTH_OURS = 3;
      /* TODO: Update above maxDepth values once we do more performance measurements */
  
      // For approximating JPEG stall durations
      private static final long APPROXIMATE_CAPTURE_DELAY_MS = 200; // 200 milliseconds
      private static final long APPROXIMATE_SENSOR_AREA_PX = (1 << 23); // 8 megapixels
      private static final long APPROXIMATE_JPEG_ENCODE_TIME_MS = 600; // 600 milliseconds
  
      static final int UNKNOWN_MODE = -1;
  
      // Maximum difference between a preview size aspect ratio and a jpeg size aspect ratio
      private static final float PREVIEW_ASPECT_RATIO_TOLERANCE = 0.01f;
  
      /*
       * Development hijinks: Lie about not supporting certain capabilities
       *
       * - Unblock some CTS tests from running whose main intent is not the metadata itself
       *
       * TODO: Remove these constants and strip out any code that previously relied on them
       * being set to true.
       */
      static final boolean LIE_ABOUT_AE_STATE = false;
      static final boolean LIE_ABOUT_AE_MAX_REGIONS = false;
      static final boolean LIE_ABOUT_AF = false;
      static final boolean LIE_ABOUT_AF_MAX_REGIONS = false;
      static final boolean LIE_ABOUT_AWB_STATE = false;
      static final boolean LIE_ABOUT_AWB = false;
  
  
      /**
       * Create characteristics for a legacy device by mapping the {@code parameters}
       * and {@code info}
       *
       * @param parameters A non-{@code null} parameters set
       * @param info Camera info with camera facing direction and angle of orientation
       * @param cameraId Current camera Id
       * @param displaySize Device display size
       *
       * @return static camera characteristics for a camera device
       *
       * @throws NullPointerException if any of the args were {@code null}
       */
      public static CameraCharacteristics createCharacteristics(Camera.Parameters parameters,
              CameraInfo info, int cameraId, Size displaySize) {
          checkNotNull(parameters, "parameters must not be null");
          checkNotNull(info, "info must not be null");
  
          String paramStr = parameters.flatten();
          android.hardware.CameraInfo outerInfo = new android.hardware.CameraInfo();
          outerInfo.info = info;
  
          return createCharacteristics(paramStr, outerInfo, cameraId, displaySize);
      }
  
      /**
       * Create characteristics for a legacy device by mapping the {@code parameters}
       * and {@code info}
       *
       * @param parameters A string parseable by {@link Camera.Parameters#unflatten}
       * @param info Camera info with camera facing direction and angle of orientation
       * @param cameraId Current camera id
       * @param displaySize Device display size
       * @return static camera characteristics for a camera device
       *
       * @throws NullPointerException if any of the args were {@code null}
       */
      public static CameraCharacteristics createCharacteristics(String parameters,
              android.hardware.CameraInfo info, int cameraId, Size displaySize) {
          checkNotNull(parameters, "parameters must not be null");
          checkNotNull(info, "info must not be null");
          checkNotNull(info.info, "info.info must not be null");
  
          CameraMetadataNative m = new CameraMetadataNative();
  
          mapCharacteristicsFromInfo(m, info.info);
  
          Camera.Parameters params = Camera.getEmptyParameters();
          params.unflatten(parameters);
          mapCharacteristicsFromParameters(m, params);
  
          if (DEBUG) {
              Log.v(TAG, "createCharacteristics metadata:");
              Log.v(TAG, "--------------------------------------------------- (start)");
              m.dumpToLog();
              Log.v(TAG, "--------------------------------------------------- (end)");
          }
  
          m.setCameraId(cameraId);
          m.setDisplaySize(displaySize);
  
          return new CameraCharacteristics(m);
      }
  
      private static void mapCharacteristicsFromInfo(CameraMetadataNative m, CameraInfo i) {
          m.set(LENS_FACING, i.facing == CameraInfo.CAMERA_FACING_BACK ?
                  LENS_FACING_BACK : LENS_FACING_FRONT);
          m.set(SENSOR_ORIENTATION, i.orientation);
      }
  
      private static void mapCharacteristicsFromParameters(CameraMetadataNative m,
              Camera.Parameters p) {
  
          /*
           * colorCorrection.*
           */
          m.set(COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES,
                  new int[] { COLOR_CORRECTION_ABERRATION_MODE_FAST,
                              COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY });
          /*
           * control.ae*
           */
          mapControlAe(m, p);
          /*
           * control.af*
           */
          mapControlAf(m, p);
          /*
           * control.awb*
           */
          mapControlAwb(m, p);
          /*
           * control.*
           * - Anything that doesn't have a set of related fields
           */
          mapControlOther(m, p);
          /*
           * lens.*
           */
          mapLens(m, p);
          /*
           * flash.*
           */
          mapFlash(m, p);
          /*
           * jpeg.*
           */
          mapJpeg(m, p);
  
          /*
           * noiseReduction.*
           */
          m.set(NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES,
                  new int[] { NOISE_REDUCTION_MODE_FAST,
                              NOISE_REDUCTION_MODE_HIGH_QUALITY});
  
          /*
           * scaler.*
           */
          mapScaler(m, p);
  
          /*
           * sensor.*
           */
          mapSensor(m, p);
  
          /*
           * statistics.*
           */
          mapStatistics(m, p);
  
          /*
           * sync.*
           */
          mapSync(m, p);
  
          /*
           * info.supportedHardwareLevel
           */
          m.set(INFO_SUPPORTED_HARDWARE_LEVEL, INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);
  
          /*
           * scaler.availableStream*, scaler.available*Durations, sensor.info.maxFrameDuration
           */
          mapScalerStreamConfigs(m, p);
  
          // Order matters below: Put this last so that we can read the metadata set previously
  
          /*
           * request.*
           */
          mapRequest(m, p);
  
      }
  
      private static void mapScalerStreamConfigs(CameraMetadataNative m, Camera.Parameters p) {
  
          ArrayList<StreamConfiguration> availableStreamConfigs = new ArrayList<>();
          /*
           * Implementation-defined (preview, recording, etc) -> use camera1 preview sizes
           * YUV_420_888 cpu callbacks -> use camera1 preview sizes
           * Other preview callbacks (CPU) -> use camera1 preview sizes
           * JPEG still capture -> use camera1 still capture sizes
           *
           * Use platform-internal format constants here, since StreamConfigurationMap does the
           * remapping to public format constants.
           */
          List<Camera.Size> previewSizes = p.getSupportedPreviewSizes();
          List<Camera.Size> jpegSizes = p.getSupportedPictureSizes();
          /*
           * Work-around for b/17589233:
           * - Some HALs's largest preview size aspect ratio does not match the largest JPEG size AR
           * - This causes a large amount of problems with focus/metering because it's relative to
           *   preview, making the difference between the JPEG and preview viewport inaccessible
           * - This boils down to metering or focusing areas being "arbitrarily" cropped
           *   in the capture result.
           * - Work-around the HAL limitations by removing all of the largest preview sizes
           *   until we get one with the same aspect ratio as the jpeg size.
           */
          {
              SizeAreaComparator areaComparator = new SizeAreaComparator();
  
              // Sort preview to min->max
              Collections.sort(previewSizes, areaComparator);
  
              Camera.Size maxJpegSize = SizeAreaComparator.findLargestByArea(jpegSizes);
              float jpegAspectRatio = maxJpegSize.width * 1.0f / maxJpegSize.height;
  
              if (DEBUG) {
                  Log.v(TAG, String.format("mapScalerStreamConfigs - largest JPEG area %dx%d, AR=%f",
                          maxJpegSize.width, maxJpegSize.height, jpegAspectRatio));
              }
  
              // Now remove preview sizes from the end (largest->smallest) until aspect ratio matches
              while (!previewSizes.isEmpty()) {
                  int index = previewSizes.size() - 1; // max is always at the end
                  Camera.Size size = previewSizes.get(index);
  
                  float previewAspectRatio = size.width * 1.0f / size.height;
  
                  if (Math.abs(jpegAspectRatio - previewAspectRatio) >=
                          PREVIEW_ASPECT_RATIO_TOLERANCE) {
                      previewSizes.remove(index); // Assume removing from end is O(1)
  
                      if (DEBUG) {
                          Log.v(TAG, String.format(
                                  "mapScalerStreamConfigs - removed preview size %dx%d, AR=%f "
                                          + "was not the same",
                                  size.width, size.height, previewAspectRatio));
                      }
                  } else {
                      break;
                  }
              }
  
              if (previewSizes.isEmpty()) {
                  // Fall-back to the original faulty behavior, but at least work
                  Log.w(TAG, "mapScalerStreamConfigs - failed to find any preview size matching " +
                          "JPEG aspect ratio " + jpegAspectRatio);
                  previewSizes = p.getSupportedPreviewSizes();
              }
  
              // Sort again, this time in descending order max->min
              Collections.sort(previewSizes, Collections.reverseOrder(areaComparator));
          }
  
          appendStreamConfig(availableStreamConfigs,
                  HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, previewSizes);
          appendStreamConfig(availableStreamConfigs,
                  ImageFormat.YUV_420_888, previewSizes);
          for (int format : p.getSupportedPreviewFormats()) {
              if (ImageFormat.isPublicFormat(format) && format != ImageFormat.NV21) {
                  appendStreamConfig(availableStreamConfigs, format, previewSizes);
              } else if (DEBUG) {
                  /*
                   *  Do not add any formats unknown to us
                   * (since it would fail runtime checks in StreamConfigurationMap)
                   */
                  Log.v(TAG,
                          String.format("mapStreamConfigs - Skipping format %x", format));
              }
          }
  
          appendStreamConfig(availableStreamConfigs,
                  HAL_PIXEL_FORMAT_BLOB, p.getSupportedPictureSizes());
          /*
           * scaler.availableStreamConfigurations
           */
          m.set(SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
                  availableStreamConfigs.toArray(new StreamConfiguration[0]));
  
          /*
           * scaler.availableMinFrameDurations
           */
          // No frame durations available
          m.set(SCALER_AVAILABLE_MIN_FRAME_DURATIONS, new StreamConfigurationDuration[0]);
  
          StreamConfigurationDuration[] jpegStalls =
                  new StreamConfigurationDuration[jpegSizes.size()];
          int i = 0;
          long longestStallDuration = -1;
          for (Camera.Size s : jpegSizes) {
              long stallDuration =  calculateJpegStallDuration(s);
              jpegStalls[i++] = new StreamConfigurationDuration(HAL_PIXEL_FORMAT_BLOB, s.width,
                      s.height, stallDuration);
              if (longestStallDuration < stallDuration) {
                  longestStallDuration = stallDuration;
              }
          }
          /*
           * scaler.availableStallDurations
           */
          // Set stall durations for jpeg, other formats use default stall duration
          m.set(SCALER_AVAILABLE_STALL_DURATIONS, jpegStalls);
  
          /*
           * sensor.info.maxFrameDuration
           */
          m.set(SENSOR_INFO_MAX_FRAME_DURATION, longestStallDuration);
      }
  
      @SuppressWarnings({"unchecked"})
      private static void mapControlAe(CameraMetadataNative m, Camera.Parameters p) {
          /*
           * control.aeAvailableAntiBandingModes
           */
          List<String> antiBandingModes = p.getSupportedAntibanding();
          if (antiBandingModes != null && antiBandingModes.size() > 0) { // antibanding is optional
              int[] modes = new int[antiBandingModes.size()];
              int j = 0;
              for (String mode : antiBandingModes) {
                  int convertedMode = convertAntiBandingMode(mode);
                  if (DEBUG && convertedMode == -1) {
                      Log.v(TAG, "Antibanding mode " + ((mode == null) ? "NULL" : mode) +
                              " not supported, skipping...");
                  } else {
                      modes[j++] = convertedMode;
                  }
              }
              m.set(CONTROL_AE_AVAILABLE_ANTIBANDING_MODES, Arrays.copyOf(modes, j));
          } else {
              m.set(CONTROL_AE_AVAILABLE_ANTIBANDING_MODES, new int[0]);
          }
  
          /*
           * control.aeAvailableTargetFpsRanges
           */
          {
              List<int[]> fpsRanges = p.getSupportedPreviewFpsRange();
              if (fpsRanges == null) {
                  throw new AssertionError("Supported FPS ranges cannot be null.");
              }
              int rangesSize = fpsRanges.size();
              if (rangesSize <= 0) {
                  throw new AssertionError("At least one FPS range must be supported.");
              }
              Range<Integer>[] ranges = new Range[rangesSize];
              int i = 0;
              for (int[] r : fpsRanges) {
                  ranges[i++] = Range.create(
                          (int) Math.floor(r[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] / 1000.0),
                          (int) Math.ceil(r[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] / 1000.0));
              }
              m.set(CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES, ranges);
          }
  
          /*
           * control.aeAvailableModes
           */
          {
              List<String> flashModes = p.getSupportedFlashModes();
  
              String[] flashModeStrings = new String[] {
                      Camera.Parameters.FLASH_MODE_OFF,
                      Camera.Parameters.FLASH_MODE_AUTO,
                      Camera.Parameters.FLASH_MODE_ON,
                      Camera.Parameters.FLASH_MODE_RED_EYE,
                      // Map these manually
                      Camera.Parameters.FLASH_MODE_TORCH,
              };
              int[] flashModeInts = new int[] {
                      CONTROL_AE_MODE_ON,
                      CONTROL_AE_MODE_ON_AUTO_FLASH,
                      CONTROL_AE_MODE_ON_ALWAYS_FLASH,
                      CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE
              };
              int[] aeAvail = ArrayUtils.convertStringListToIntArray(
                      flashModes, flashModeStrings, flashModeInts);
  
              // No flash control -> AE is always on
              if (aeAvail == null || aeAvail.length == 0) {
                  aeAvail = new int[] {
                          CONTROL_AE_MODE_ON
                  };
              }
  
              // Note that AE_MODE_OFF is never available.
              m.set(CONTROL_AE_AVAILABLE_MODES, aeAvail);
          }
  
          /*
           * control.aeCompensationRanges
           */
          {
              int min = p.getMinExposureCompensation();
              int max = p.getMaxExposureCompensation();
  
              m.set(CONTROL_AE_COMPENSATION_RANGE, Range.create(min, max));
          }
  
          /*
           * control.aeCompensationStep
           */
          {
              float step = p.getExposureCompensationStep();
  
              m.set(CONTROL_AE_COMPENSATION_STEP, ParamsUtils.createRational(step));
          }
  
          /*
           * control.aeLockAvailable
           */
          {
              boolean aeLockAvailable = p.isAutoExposureLockSupported();
  
              m.set(CONTROL_AE_LOCK_AVAILABLE, aeLockAvailable);
          }
      }
  
  
      @SuppressWarnings({"unchecked"})
      private static void mapControlAf(CameraMetadataNative m, Camera.Parameters p) {
          /*
           * control.afAvailableModes
           */
          {
              List<String> focusModes = p.getSupportedFocusModes();
  
              String[] focusModeStrings = new String[] {
                      Camera.Parameters.FOCUS_MODE_AUTO,
                      Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE,
                      Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO,
                      Camera.Parameters.FOCUS_MODE_EDOF,
                      Camera.Parameters.FOCUS_MODE_INFINITY,
                      Camera.Parameters.FOCUS_MODE_MACRO,
                      Camera.Parameters.FOCUS_MODE_FIXED,
              };
  
              int[] focusModeInts = new int[] {
                      CONTROL_AF_MODE_AUTO,
                      CONTROL_AF_MODE_CONTINUOUS_PICTURE,
                      CONTROL_AF_MODE_CONTINUOUS_VIDEO,
                      CONTROL_AF_MODE_EDOF,
                      CONTROL_AF_MODE_OFF,
                      CONTROL_AF_MODE_MACRO,
                      CONTROL_AF_MODE_OFF
              };
  
              List<Integer> afAvail = ArrayUtils.convertStringListToIntList(
                      focusModes, focusModeStrings, focusModeInts);
  
              // No AF modes supported? That's unpossible!
              if (afAvail == null || afAvail.size() == 0) {
                  Log.w(TAG, "No AF modes supported (HAL bug); defaulting to AF_MODE_OFF only");
                  afAvail = new ArrayList<Integer>(/*capacity*/1);
                  afAvail.add(CONTROL_AF_MODE_OFF);
              }
  
              m.set(CONTROL_AF_AVAILABLE_MODES, ArrayUtils.toIntArray(afAvail));
  
              if (DEBUG) {
                  Log.v(TAG, "mapControlAf - control.afAvailableModes set to " +
                          ListUtils.listToString(afAvail));
              }
          }
      }
  
      private static void mapControlAwb(CameraMetadataNative m, Camera.Parameters p) {
          /*
           * control.awbAvailableModes
           */
  
          {
              List<String> wbModes = p.getSupportedWhiteBalance();
  
              String[] wbModeStrings = new String[] {
                      Camera.Parameters.WHITE_BALANCE_AUTO                    ,
                      Camera.Parameters.WHITE_BALANCE_INCANDESCENT            ,
                      Camera.Parameters.WHITE_BALANCE_FLUORESCENT             ,
                      Camera.Parameters.WHITE_BALANCE_WARM_FLUORESCENT        ,
                      Camera.Parameters.WHITE_BALANCE_DAYLIGHT                ,
                      Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT         ,
                      Camera.Parameters.WHITE_BALANCE_TWILIGHT                ,
                      Camera.Parameters.WHITE_BALANCE_SHADE                   ,
              };
  
              int[] wbModeInts = new int[] {
                      CONTROL_AWB_MODE_AUTO,
                      CONTROL_AWB_MODE_INCANDESCENT            ,
                      CONTROL_AWB_MODE_FLUORESCENT             ,
                      CONTROL_AWB_MODE_WARM_FLUORESCENT        ,
                      CONTROL_AWB_MODE_DAYLIGHT                ,
                      CONTROL_AWB_MODE_CLOUDY_DAYLIGHT         ,
                      CONTROL_AWB_MODE_TWILIGHT                ,
                      CONTROL_AWB_MODE_SHADE                   ,
                      // Note that CONTROL_AWB_MODE_OFF is unsupported
              };
  
              List<Integer> awbAvail = ArrayUtils.convertStringListToIntList(
                          wbModes, wbModeStrings, wbModeInts);
  
              // No AWB modes supported? That's unpossible!
              if (awbAvail == null || awbAvail.size() == 0) {
                  Log.w(TAG, "No AWB modes supported (HAL bug); defaulting to AWB_MODE_AUTO only");
                  awbAvail = new ArrayList<Integer>(/*capacity*/1);
                  awbAvail.add(CONTROL_AWB_MODE_AUTO);
              }
  
              m.set(CONTROL_AWB_AVAILABLE_MODES, ArrayUtils.toIntArray(awbAvail));
  
              if (DEBUG) {
                  Log.v(TAG, "mapControlAwb - control.awbAvailableModes set to " +
                          ListUtils.listToString(awbAvail));
              }
  
  
              /*
               * control.awbLockAvailable
               */
              {
                  boolean awbLockAvailable = p.isAutoWhiteBalanceLockSupported();
  
                  m.set(CONTROL_AWB_LOCK_AVAILABLE, awbLockAvailable);
              }
          }
      }
  
      private static void mapControlOther(CameraMetadataNative m, Camera.Parameters p) {
          /*
           * android.control.availableVideoStabilizationModes
           */
          {
              int stabModes[] = p.isVideoStabilizationSupported() ?
                      new int[] { CONTROL_VIDEO_STABILIZATION_MODE_OFF,
                                  CONTROL_VIDEO_STABILIZATION_MODE_ON } :
                      new int[] { CONTROL_VIDEO_STABILIZATION_MODE_OFF };
  
              m.set(CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES, stabModes);
          }
  
          /*
           * android.control.maxRegions
           */
          final int AE = 0, AWB = 1, AF = 2;
  
          int[] maxRegions = new int[3];
          maxRegions[AE] = p.getMaxNumMeteringAreas();
          maxRegions[AWB] = 0; // AWB regions not supported in API1
          maxRegions[AF] = p.getMaxNumFocusAreas();
  
          if (LIE_ABOUT_AE_MAX_REGIONS) {
              maxRegions[AE] = 0;
          }
          if (LIE_ABOUT_AF_MAX_REGIONS) {
              maxRegions[AF] = 0;
          }
  
          m.set(CONTROL_MAX_REGIONS, maxRegions);
  
          /*
           * android.control.availableEffects
           */
          List<String> effectModes = p.getSupportedColorEffects();
          int[] supportedEffectModes = (effectModes == null) ? new int[0] :
                  ArrayUtils.convertStringListToIntArray(effectModes, sLegacyEffectMode,
                          sEffectModes);
          m.set(CONTROL_AVAILABLE_EFFECTS, supportedEffectModes);
  
          /*
           * android.control.availableSceneModes
           */
          int maxNumDetectedFaces = p.getMaxNumDetectedFaces();
          List<String> sceneModes = p.getSupportedSceneModes();
          List<Integer> supportedSceneModes =
                  ArrayUtils.convertStringListToIntList(sceneModes, sLegacySceneModes, sSceneModes);
  
          // Special case where the only scene mode listed is AUTO => no scene mode
          if (sceneModes != null && sceneModes.size() == 1 &&
                  sceneModes.get(0).equals(Parameters.SCENE_MODE_AUTO)) {
              supportedSceneModes = null;
          }
  
          boolean sceneModeSupported = true;
          if (supportedSceneModes == null && maxNumDetectedFaces == 0) {
              sceneModeSupported = false;
          }
  
          if (sceneModeSupported) {
              if (supportedSceneModes == null) {
                  supportedSceneModes = new ArrayList<Integer>();
              }
              if (maxNumDetectedFaces > 0) { // always supports FACE_PRIORITY when face detecting
                  supportedSceneModes.add(CONTROL_SCENE_MODE_FACE_PRIORITY);
              }
              // Remove all DISABLED occurrences
              if (supportedSceneModes.contains(CONTROL_SCENE_MODE_DISABLED)) {
                  while(supportedSceneModes.remove(new Integer(CONTROL_SCENE_MODE_DISABLED))) {}
              }
              m.set(CONTROL_AVAILABLE_SCENE_MODES, ArrayUtils.toIntArray(supportedSceneModes));
          } else {
              m.set(CONTROL_AVAILABLE_SCENE_MODES, new int[] {CONTROL_SCENE_MODE_DISABLED});
          }
  
          /*
           * android.control.availableModes
           */
          m.set(CONTROL_AVAILABLE_MODES, sceneModeSupported ?
                  new int[] { CONTROL_MODE_AUTO, CONTROL_MODE_USE_SCENE_MODE } :
                  new int[] { CONTROL_MODE_AUTO });
      }
  
      private static void mapLens(CameraMetadataNative m, Camera.Parameters p) {
          /*
           *  We can tell if the lens is fixed focus;
           *  but if it's not, we can't tell the minimum focus distance, so leave it null then.
           */
          if (DEBUG) {
              Log.v(TAG, "mapLens - focus-mode='" + p.getFocusMode() + "'");
          }
  
          if (Camera.Parameters.FOCUS_MODE_FIXED.equals(p.getFocusMode())) {
              /*
               * lens.info.minimumFocusDistance
               */
              m.set(LENS_INFO_MINIMUM_FOCUS_DISTANCE, LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS);
  
              if (DEBUG) {
                  Log.v(TAG, "mapLens - lens.info.minimumFocusDistance = 0");
              }
          } else {
              if (DEBUG) {
                  Log.v(TAG, "mapLens - lens.info.minimumFocusDistance is unknown");
              }
          }
  
          float[] focalLengths = new float[] { p.getFocalLength() };
          m.set(LENS_INFO_AVAILABLE_FOCAL_LENGTHS, focalLengths);
      }
  
      private static void mapFlash(CameraMetadataNative m, Camera.Parameters p) {
          boolean flashAvailable = false;
          List<String> supportedFlashModes = p.getSupportedFlashModes();
  
          if (supportedFlashModes != null) {
              // If only 'OFF' is available, we don't really have flash support
              flashAvailable = !ListUtils.listElementsEqualTo(
                      supportedFlashModes, Camera.Parameters.FLASH_MODE_OFF);
          }
  
          /*
           * flash.info.available
           */
          m.set(FLASH_INFO_AVAILABLE, flashAvailable);
      }
  
      private static void mapJpeg(CameraMetadataNative m, Camera.Parameters p) {
          List<Camera.Size> thumbnailSizes = p.getSupportedJpegThumbnailSizes();
  
          if (thumbnailSizes != null) {
              Size[] sizes = convertSizeListToArray(thumbnailSizes);
              Arrays.sort(sizes, new android.hardware.camera2.utils.SizeAreaComparator());
              m.set(JPEG_AVAILABLE_THUMBNAIL_SIZES, sizes);
          }
      }
  
      private static void mapRequest(CameraMetadataNative m, Parameters p) {
          /*
           * request.availableCapabilities
           */
          int[] capabilities = { REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE };
          m.set(REQUEST_AVAILABLE_CAPABILITIES, capabilities);
  
          /*
           * request.availableCharacteristicsKeys
           */
          {
              // TODO: check if the underlying key is supported before listing a key as available
  
              // Note: We only list public keys. Native HALs should list ALL keys regardless of visibility.
  
              Key<?> availableKeys[] = new Key<?>[] {
                      CameraCharacteristics.COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES     ,
                      CameraCharacteristics.CONTROL_AE_AVAILABLE_ANTIBANDING_MODES          ,
                      CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES                      ,
                      CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES          ,
                      CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE                   ,
                      CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP                    ,
                      CameraCharacteristics.CONTROL_AE_LOCK_AVAILABLE                       ,
                      CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES                      ,
                      CameraCharacteristics.CONTROL_AVAILABLE_EFFECTS                       ,
                      CameraCharacteristics.CONTROL_AVAILABLE_MODES                         ,
                      CameraCharacteristics.CONTROL_AVAILABLE_SCENE_MODES                   ,
                      CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES     ,
                      CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES                     ,
                      CameraCharacteristics.CONTROL_AWB_LOCK_AVAILABLE                      ,
                      CameraCharacteristics.CONTROL_MAX_REGIONS                             ,
                      CameraCharacteristics.FLASH_INFO_AVAILABLE                            ,
                      CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL                   ,
                      CameraCharacteristics.JPEG_AVAILABLE_THUMBNAIL_SIZES                  ,
                      CameraCharacteristics.LENS_FACING                                     ,
                      CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS               ,
                      CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES ,
                      CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES                  ,
                      CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_STREAMS                  ,
                      CameraCharacteristics.REQUEST_PARTIAL_RESULT_COUNT                    ,
                      CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH                      ,
                      CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM               ,
  //                    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP                 ,
                      CameraCharacteristics.SCALER_CROPPING_TYPE                            ,
                      CameraCharacteristics.SENSOR_AVAILABLE_TEST_PATTERN_MODES             ,
                      CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE                   ,
                      CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE                       ,
                      CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE                    ,
                      CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE    ,
                      CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE                    ,
                      CameraCharacteristics.SENSOR_ORIENTATION                              ,
                      CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES     ,
                      CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT                  ,
                      CameraCharacteristics.SYNC_MAX_LATENCY                                ,
              };
              List<Key<?>> characteristicsKeys = new ArrayList<>(Arrays.asList(availableKeys));
  
              /*
               * Add the conditional keys
               */
              if (m.get(LENS_INFO_MINIMUM_FOCUS_DISTANCE) != null) {
                  characteristicsKeys.add(LENS_INFO_MINIMUM_FOCUS_DISTANCE);
              }
  
              m.set(REQUEST_AVAILABLE_CHARACTERISTICS_KEYS,
                      getTagsForKeys(characteristicsKeys.toArray(new Key<?>[0])));
          }
  
          /*
           * request.availableRequestKeys
           */
          {
              CaptureRequest.Key<?> defaultAvailableKeys[] = new CaptureRequest.Key<?>[] {
                      CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE,
                      CaptureRequest.CONTROL_AE_ANTIBANDING_MODE,
                      CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION,
                      CaptureRequest.CONTROL_AE_LOCK,
                      CaptureRequest.CONTROL_AE_MODE,
                      CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
                      CaptureRequest.CONTROL_AF_MODE,
                      CaptureRequest.CONTROL_AF_TRIGGER,
                      CaptureRequest.CONTROL_AWB_LOCK,
                      CaptureRequest.CONTROL_AWB_MODE,
                      CaptureRequest.CONTROL_CAPTURE_INTENT,
                      CaptureRequest.CONTROL_EFFECT_MODE,
                      CaptureRequest.CONTROL_MODE,
                      CaptureRequest.CONTROL_SCENE_MODE,
                      CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
                      CaptureRequest.FLASH_MODE,
                      CaptureRequest.JPEG_GPS_COORDINATES,
                      CaptureRequest.JPEG_GPS_PROCESSING_METHOD,
                      CaptureRequest.JPEG_GPS_TIMESTAMP,
                      CaptureRequest.JPEG_ORIENTATION,
                      CaptureRequest.JPEG_QUALITY,
                      CaptureRequest.JPEG_THUMBNAIL_QUALITY,
                      CaptureRequest.JPEG_THUMBNAIL_SIZE,
                      CaptureRequest.LENS_FOCAL_LENGTH,
                      CaptureRequest.NOISE_REDUCTION_MODE,
                      CaptureRequest.SCALER_CROP_REGION,
                      CaptureRequest.STATISTICS_FACE_DETECT_MODE,
              };
              ArrayList<CaptureRequest.Key<?>> availableKeys =
                      new ArrayList<CaptureRequest.Key<?>>(Arrays.asList(defaultAvailableKeys));
  
              if (p.getMaxNumMeteringAreas() > 0) {
                  availableKeys.add(CaptureRequest.CONTROL_AE_REGIONS);
              }
              if (p.getMaxNumFocusAreas() > 0) {
                  availableKeys.add(CaptureRequest.CONTROL_AF_REGIONS);
              }
  
              CaptureRequest.Key<?> availableRequestKeys[] =
                      new CaptureRequest.Key<?>[availableKeys.size()];
              availableKeys.toArray(availableRequestKeys);
              m.set(REQUEST_AVAILABLE_REQUEST_KEYS, getTagsForKeys(availableRequestKeys));
          }
  
          /*
           * request.availableResultKeys
           */
          {
              CaptureResult.Key<?> defaultAvailableKeys[] = new CaptureResult.Key<?>[] {
                      CaptureResult.COLOR_CORRECTION_ABERRATION_MODE                 ,
                      CaptureResult.CONTROL_AE_ANTIBANDING_MODE                      ,
                      CaptureResult.CONTROL_AE_EXPOSURE_COMPENSATION                 ,
                      CaptureResult.CONTROL_AE_LOCK                                  ,
                      CaptureResult.CONTROL_AE_MODE                                  ,
                      CaptureResult.CONTROL_AF_MODE                                  ,
                      CaptureResult.CONTROL_AF_STATE                                 ,
                      CaptureResult.CONTROL_AWB_MODE                                 ,
                      CaptureResult.CONTROL_AWB_LOCK                                 ,
                      CaptureResult.CONTROL_MODE                                     ,
                      CaptureResult.FLASH_MODE                                       ,
                      CaptureResult.JPEG_GPS_COORDINATES                             ,
                      CaptureResult.JPEG_GPS_PROCESSING_METHOD                       ,
                      CaptureResult.JPEG_GPS_TIMESTAMP                               ,
                      CaptureResult.JPEG_ORIENTATION                                 ,
                      CaptureResult.JPEG_QUALITY                                     ,
                      CaptureResult.JPEG_THUMBNAIL_QUALITY                           ,
                      CaptureResult.LENS_FOCAL_LENGTH                                ,
                      CaptureResult.NOISE_REDUCTION_MODE                             ,
                      CaptureResult.REQUEST_PIPELINE_DEPTH                           ,
                      CaptureResult.SCALER_CROP_REGION                               ,
                      CaptureResult.SENSOR_TIMESTAMP                                 ,
                      CaptureResult.STATISTICS_FACE_DETECT_MODE                      ,
  //                    CaptureResult.STATISTICS_FACES                                 ,
              };
              List<CaptureResult.Key<?>> availableKeys =
                      new ArrayList<CaptureResult.Key<?>>(Arrays.asList(defaultAvailableKeys));
  
              if (p.getMaxNumMeteringAreas() > 0) {
                  availableKeys.add(CaptureResult.CONTROL_AE_REGIONS);
              }
              if (p.getMaxNumFocusAreas() > 0) {
                  availableKeys.add(CaptureResult.CONTROL_AF_REGIONS);
              }
  
              CaptureResult.Key<?> availableResultKeys[] =
                      new CaptureResult.Key<?>[availableKeys.size()];
              availableKeys.toArray(availableResultKeys);
              m.set(REQUEST_AVAILABLE_RESULT_KEYS, getTagsForKeys(availableResultKeys));
          }
  
          /*
           * request.maxNumOutputStreams
           */
          int[] outputStreams = {
                  /* RAW */
                  REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_RAW,
                  /* Processed & Not-Stalling */
                  REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_PROC,
                  /* Processed & Stalling */
                  REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_PROC_STALL,
          };
          m.set(REQUEST_MAX_NUM_OUTPUT_STREAMS, outputStreams);
  
          /*
           * request.maxNumInputStreams
           */
          m.set(REQUEST_MAX_NUM_INPUT_STREAMS, REQUEST_MAX_NUM_INPUT_STREAMS_COUNT);
  
          /*
           * request.partialResultCount
           */
          m.set(REQUEST_PARTIAL_RESULT_COUNT, 1); // No partial results supported
  
          /*
           * request.pipelineMaxDepth
           */
          m.set(REQUEST_PIPELINE_MAX_DEPTH,
                  (byte)(REQUEST_PIPELINE_MAX_DEPTH_HAL1 + REQUEST_PIPELINE_MAX_DEPTH_OURS));
      }
  
      private static void mapScaler(CameraMetadataNative m, Parameters p) {
          /*
           * scaler.availableMaxDigitalZoom
           */
          m.set(SCALER_AVAILABLE_MAX_DIGITAL_ZOOM, ParameterUtils.getMaxZoomRatio(p));
  
          /*
           * scaler.croppingType = CENTER_ONLY
           */
          m.set(SCALER_CROPPING_TYPE, SCALER_CROPPING_TYPE_CENTER_ONLY);
      }
  
      private static void mapSensor(CameraMetadataNative m, Parameters p) {
          // Use the largest jpeg size (by area) for both active array and pixel array
          Size largestJpegSize = getLargestSupportedJpegSizeByArea(p);
          /*
           * sensor.info.activeArraySize, and preCorrectionActiveArraySize
           */
          {
              Rect activeArrayRect = ParamsUtils.createRect(largestJpegSize);
              m.set(SENSOR_INFO_ACTIVE_ARRAY_SIZE, activeArrayRect);
              m.set(SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, activeArrayRect);
          }
  
          /*
           * sensor.availableTestPatternModes
           */
          {
              // Only "OFF" test pattern mode is available
              m.set(SENSOR_AVAILABLE_TEST_PATTERN_MODES, new int[] { SENSOR_TEST_PATTERN_MODE_OFF });
          }
  
          /*
           * sensor.info.pixelArraySize
           */
          m.set(SENSOR_INFO_PIXEL_ARRAY_SIZE, largestJpegSize);
  
          /*
           * sensor.info.physicalSize
           */
          {
              /*
               * Assume focal length is at infinity focus and that the lens is rectilinear.
               */
              float focalLength = p.getFocalLength(); // in mm
              double angleHor = p.getHorizontalViewAngle() * Math.PI / 180; // to radians
              double angleVer = p.getVerticalViewAngle() * Math.PI / 180; // to radians
  
              float height = (float)Math.abs(2 * focalLength * Math.tan(angleVer / 2));
              float width = (float)Math.abs(2 * focalLength * Math.tan(angleHor / 2));
  
              m.set(SENSOR_INFO_PHYSICAL_SIZE, new SizeF(width, height)); // in mm
          }
  
          /*
           * sensor.info.timestampSource
           */
          {
              m.set(SENSOR_INFO_TIMESTAMP_SOURCE, SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN);
          }
      }
  
      private static void mapStatistics(CameraMetadataNative m, Parameters p) {
          /*
           * statistics.info.availableFaceDetectModes
           */
          int[] fdModes;
  
          if (p.getMaxNumDetectedFaces() > 0) {
              fdModes = new int[] {
                  STATISTICS_FACE_DETECT_MODE_OFF,
                  STATISTICS_FACE_DETECT_MODE_SIMPLE
                  // FULL is never-listed, since we have no way to query it statically
              };
          } else {
              fdModes = new int[] {
                  STATISTICS_FACE_DETECT_MODE_OFF
              };
          }
          m.set(STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES, fdModes);
  
          /*
           * statistics.info.maxFaceCount
           */
          m.set(STATISTICS_INFO_MAX_FACE_COUNT, p.getMaxNumDetectedFaces());
      }
  
      private static void mapSync(CameraMetadataNative m, Parameters p) {
          /*
           * sync.maxLatency
           */
          m.set(SYNC_MAX_LATENCY, SYNC_MAX_LATENCY_UNKNOWN);
      }
  
      private static void appendStreamConfig(
              ArrayList<StreamConfiguration> configs, int format, List<Camera.Size> sizes) {
          for (Camera.Size size : sizes) {
              StreamConfiguration config =
                      new StreamConfiguration(format, size.width, size.height, /*input*/false);
              configs.add(config);
          }
      }
  
      private final static String[] sLegacySceneModes = {
          Parameters.SCENE_MODE_AUTO,
          Parameters.SCENE_MODE_ACTION,
          Parameters.SCENE_MODE_PORTRAIT,
          Parameters.SCENE_MODE_LANDSCAPE,
          Parameters.SCENE_MODE_NIGHT,
          Parameters.SCENE_MODE_NIGHT_PORTRAIT,
          Parameters.SCENE_MODE_THEATRE,
          Parameters.SCENE_MODE_BEACH,
          Parameters.SCENE_MODE_SNOW,
          Parameters.SCENE_MODE_SUNSET,
          Parameters.SCENE_MODE_STEADYPHOTO,
          Parameters.SCENE_MODE_FIREWORKS,
          Parameters.SCENE_MODE_SPORTS,
          Parameters.SCENE_MODE_PARTY,
          Parameters.SCENE_MODE_CANDLELIGHT,
          Parameters.SCENE_MODE_BARCODE,
          Parameters.SCENE_MODE_HDR,
      };
  
      private final static int[] sSceneModes = {
          CameraCharacteristics.CONTROL_SCENE_MODE_DISABLED,
          CameraCharacteristics.CONTROL_SCENE_MODE_ACTION,
          CameraCharacteristics.CONTROL_SCENE_MODE_PORTRAIT,
          CameraCharacteristics.CONTROL_SCENE_MODE_LANDSCAPE,
          CameraCharacteristics.CONTROL_SCENE_MODE_NIGHT,
          CameraCharacteristics.CONTROL_SCENE_MODE_NIGHT_PORTRAIT,
          CameraCharacteristics.CONTROL_SCENE_MODE_THEATRE,
          CameraCharacteristics.CONTROL_SCENE_MODE_BEACH,
          CameraCharacteristics.CONTROL_SCENE_MODE_SNOW,
          CameraCharacteristics.CONTROL_SCENE_MODE_SUNSET,
          CameraCharacteristics.CONTROL_SCENE_MODE_STEADYPHOTO,
          CameraCharacteristics.CONTROL_SCENE_MODE_FIREWORKS,
          CameraCharacteristics.CONTROL_SCENE_MODE_SPORTS,
          CameraCharacteristics.CONTROL_SCENE_MODE_PARTY,
          CameraCharacteristics.CONTROL_SCENE_MODE_CANDLELIGHT,
          CameraCharacteristics.CONTROL_SCENE_MODE_BARCODE,
          CameraCharacteristics.CONTROL_SCENE_MODE_HDR,
      };
  
      static int convertSceneModeFromLegacy(String mode) {
          if (mode == null) {
              return CameraCharacteristics.CONTROL_SCENE_MODE_DISABLED;
          }
          int index = ArrayUtils.getArrayIndex(sLegacySceneModes, mode);
          if (index < 0) {
              return UNKNOWN_MODE;
          }
          return sSceneModes[index];
      }
  
      static String convertSceneModeToLegacy(int mode) {
          if (mode == CONTROL_SCENE_MODE_FACE_PRIORITY) {
              // OK: Let LegacyFaceDetectMapper handle turning face detection on/off
              return Parameters.SCENE_MODE_AUTO;
          }
  
          int index = ArrayUtils.getArrayIndex(sSceneModes, mode);
          if (index < 0) {
              return null;
          }
          return sLegacySceneModes[index];
      }
  
      private final static String[] sLegacyEffectMode = {
          Parameters.EFFECT_NONE,
          Parameters.EFFECT_MONO,
          Parameters.EFFECT_NEGATIVE,
          Parameters.EFFECT_SOLARIZE,
          Parameters.EFFECT_SEPIA,
          Parameters.EFFECT_POSTERIZE,
          Parameters.EFFECT_WHITEBOARD,
          Parameters.EFFECT_BLACKBOARD,
          Parameters.EFFECT_AQUA,
      };
  
      private final static int[] sEffectModes = {
          CameraCharacteristics.CONTROL_EFFECT_MODE_OFF,
          CameraCharacteristics.CONTROL_EFFECT_MODE_MONO,
          CameraCharacteristics.CONTROL_EFFECT_MODE_NEGATIVE,
          CameraCharacteristics.CONTROL_EFFECT_MODE_SOLARIZE,
          CameraCharacteristics.CONTROL_EFFECT_MODE_SEPIA,
          CameraCharacteristics.CONTROL_EFFECT_MODE_POSTERIZE,
          CameraCharacteristics.CONTROL_EFFECT_MODE_WHITEBOARD,
          CameraCharacteristics.CONTROL_EFFECT_MODE_BLACKBOARD,
          CameraCharacteristics.CONTROL_EFFECT_MODE_AQUA,
      };
  
      static int convertEffectModeFromLegacy(String mode) {
          if (mode == null) {
              return CameraCharacteristics.CONTROL_EFFECT_MODE_OFF;
          }
          int index = ArrayUtils.getArrayIndex(sLegacyEffectMode, mode);
          if (index < 0) {
              return UNKNOWN_MODE;
          }
          return sEffectModes[index];
      }
  
      static String convertEffectModeToLegacy(int mode) {
          int index = ArrayUtils.getArrayIndex(sEffectModes, mode);
          if (index < 0) {
              return null;
          }
          return sLegacyEffectMode[index];
      }
  
      /**
       * Convert the ae antibanding mode from api1 into api2.
       *
       * @param mode the api1 mode, {@code null} is allowed and will return {@code -1}.
       *
       * @return The api2 value, or {@code -1} by default if conversion failed
       */
      private static int convertAntiBandingMode(String mode) {
          if (mode == null) {
              return -1;
          }
  
          switch (mode) {
              case Camera.Parameters.ANTIBANDING_OFF: {
                  return CONTROL_AE_ANTIBANDING_MODE_OFF;
              }
              case Camera.Parameters.ANTIBANDING_50HZ: {
                  return CONTROL_AE_ANTIBANDING_MODE_50HZ;
              }
              case Camera.Parameters.ANTIBANDING_60HZ: {
                  return CONTROL_AE_ANTIBANDING_MODE_60HZ;
              }
              case Camera.Parameters.ANTIBANDING_AUTO: {
                  return CONTROL_AE_ANTIBANDING_MODE_AUTO;
              }
              default: {
                  Log.w(TAG, "convertAntiBandingMode - Unknown antibanding mode " + mode);
                  return -1;
              }
          }
      }
  
      /**
       * Convert the ae antibanding mode from api1 into api2.
       *
       * @param mode the api1 mode, {@code null} is allowed and will return {@code MODE_OFF}.
       *
       * @return The api2 value, or {@code MODE_OFF} by default if conversion failed
       */
      static int convertAntiBandingModeOrDefault(String mode) {
          int antiBandingMode = convertAntiBandingMode(mode);
          if (antiBandingMode == -1) {
              return CONTROL_AE_ANTIBANDING_MODE_OFF;
          }
  
          return antiBandingMode;
      }
  
      private static int[] convertAeFpsRangeToLegacy(Range<Integer> fpsRange) {
          int[] legacyFps = new int[2];
          legacyFps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] = fpsRange.getLower();
          legacyFps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] = fpsRange.getUpper();
          return legacyFps;
      }
  
      /**
       * Return the stall duration for a given output jpeg size in nanoseconds.
       *
       * <p>An 8mp image is chosen to have a stall duration of 0.8 seconds.</p>
       */
      private static long calculateJpegStallDuration(Camera.Size size) {
          long baseDuration = APPROXIMATE_CAPTURE_DELAY_MS * NS_PER_MS; // 200ms for capture
          long area = size.width * (long) size.height;
          long stallPerArea = APPROXIMATE_JPEG_ENCODE_TIME_MS * NS_PER_MS /
                  APPROXIMATE_SENSOR_AREA_PX; // 600ms stall for 8mp
          return baseDuration + area * stallPerArea;
      }
  
      /**
       * Set the legacy parameters using the {@link LegacyRequest legacy request}.
       *
       * <p>The legacy request's parameters are changed as a side effect of calling this
       * method.</p>
       *
       * @param request a non-{@code null} legacy request
       */
      public static void convertRequestMetadata(LegacyRequest request) {
          LegacyRequestMapper.convertRequestMetadata(request);
      }
  
      private static final int[] sAllowedTemplates = {
              CameraDevice.TEMPLATE_PREVIEW,
              CameraDevice.TEMPLATE_STILL_CAPTURE,
              CameraDevice.TEMPLATE_RECORD,
              // Disallowed templates in legacy mode:
              // CameraDevice.TEMPLATE_VIDEO_SNAPSHOT,
              // CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG,
              // CameraDevice.TEMPLATE_MANUAL
      };
  
      /**
       * Create a request template
       *
       * @param c a non-{@code null} camera characteristics for this camera
       * @param templateId a non-negative template ID
       *
       * @return a non-{@code null} request template
       *
       * @throws IllegalArgumentException if {@code templateId} was invalid
       *
       * @see android.hardware.camera2.CameraDevice#TEMPLATE_MANUAL
       */
      public static CameraMetadataNative createRequestTemplate(
              CameraCharacteristics c, int templateId) {
          if (!ArrayUtils.contains(sAllowedTemplates, templateId)) {
              throw new IllegalArgumentException("templateId out of range");
          }
  
          CameraMetadataNative m = new CameraMetadataNative();
  
          /*
           * NOTE: If adding new code here and it needs to query the static info,
           * query the camera characteristics, so we can reuse this for api2 code later
           * to create our own templates in the framework
           */
  
          /*
           * control.*
           */
  
          // control.awbMode
          m.set(CaptureRequest.CONTROL_AWB_MODE, CameraMetadata.CONTROL_AWB_MODE_AUTO);
          // AWB is always unconditionally available in API1 devices
  
          // control.aeAntibandingMode
          m.set(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE, CONTROL_AE_ANTIBANDING_MODE_AUTO);
  
          // control.aeExposureCompensation
          m.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 0);
  
          // control.aeLock
          m.set(CaptureRequest.CONTROL_AE_LOCK, false);
  
          // control.aePrecaptureTrigger
          m.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
  
          // control.afTrigger
          m.set(CaptureRequest.CONTROL_AF_TRIGGER, CONTROL_AF_TRIGGER_IDLE);
  
          // control.awbMode
          m.set(CaptureRequest.CONTROL_AWB_MODE, CONTROL_AWB_MODE_AUTO);
  
          // control.awbLock
          m.set(CaptureRequest.CONTROL_AWB_LOCK, false);
  
          // control.aeRegions, control.awbRegions, control.afRegions
          {
              Rect activeArray = c.get(SENSOR_INFO_ACTIVE_ARRAY_SIZE);
              MeteringRectangle[] activeRegions =  new MeteringRectangle[] {
                      new MeteringRectangle(/*x*/0, /*y*/0, /*width*/activeArray.width() - 1,
                      /*height*/activeArray.height() - 1,/*weight*/0)};
              m.set(CaptureRequest.CONTROL_AE_REGIONS, activeRegions);
              m.set(CaptureRequest.CONTROL_AWB_REGIONS, activeRegions);
              m.set(CaptureRequest.CONTROL_AF_REGIONS, activeRegions);
          }
  
          // control.captureIntent
          {
              int captureIntent;
              switch (templateId) {
                  case CameraDevice.TEMPLATE_PREVIEW:
                      captureIntent = CONTROL_CAPTURE_INTENT_PREVIEW;
                      break;
                  case CameraDevice.TEMPLATE_STILL_CAPTURE:
                      captureIntent = CONTROL_CAPTURE_INTENT_STILL_CAPTURE;
                      break;
                  case CameraDevice.TEMPLATE_RECORD:
                      captureIntent = CONTROL_CAPTURE_INTENT_VIDEO_RECORD;
                      break;
                  default:
                      // Can't get anything else since it's guarded by the IAE check
                      throw new AssertionError("Impossible; keep in sync with sAllowedTemplates");
              }
              m.set(CaptureRequest.CONTROL_CAPTURE_INTENT, captureIntent);
          }
  
          // control.aeMode
          m.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_ON);
          // AE is always unconditionally available in API1 devices
  
          // control.mode
          m.set(CaptureRequest.CONTROL_MODE, CONTROL_MODE_AUTO);
  
          // control.afMode
          {
              Float minimumFocusDistance = c.get(LENS_INFO_MINIMUM_FOCUS_DISTANCE);
  
              int afMode;
              if (minimumFocusDistance != null &&
                      minimumFocusDistance == LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS) {
                  // Cannot control auto-focus with fixed-focus cameras
                  afMode = CameraMetadata.CONTROL_AF_MODE_OFF;
              } else {
                  // If a minimum focus distance is reported; the camera must have AF
                  afMode = CameraMetadata.CONTROL_AF_MODE_AUTO;
  
                  if (templateId == CameraDevice.TEMPLATE_RECORD ||
                          templateId == CameraDevice.TEMPLATE_VIDEO_SNAPSHOT) {
                      if (ArrayUtils.contains(c.get(CONTROL_AF_AVAILABLE_MODES),
                              CONTROL_AF_MODE_CONTINUOUS_VIDEO)) {
                          afMode = CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO;
                      }
                  } else if (templateId == CameraDevice.TEMPLATE_PREVIEW ||
                          templateId == CameraDevice.TEMPLATE_STILL_CAPTURE) {
                      if (ArrayUtils.contains(c.get(CONTROL_AF_AVAILABLE_MODES),
                              CONTROL_AF_MODE_CONTINUOUS_PICTURE)) {
                          afMode = CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
                      }
                  }
              }
  
              if (DEBUG) {
                  Log.v(TAG, "createRequestTemplate (templateId=" + templateId + ")," +
                          " afMode=" + afMode + ", minimumFocusDistance=" + minimumFocusDistance);
              }
  
              m.set(CaptureRequest.CONTROL_AF_MODE, afMode);
          }
  
          {
              // control.aeTargetFpsRange
              Range<Integer>[] availableFpsRange = c.
                      get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
  
              // Pick FPS range with highest max value, tiebreak on higher min value
              Range<Integer> bestRange = availableFpsRange[0];
              for (Range<Integer> r : availableFpsRange) {
                  if (bestRange.getUpper() < r.getUpper()) {
                      bestRange = r;
                  } else if (bestRange.getUpper() == r.getUpper() &&
                          bestRange.getLower() < r.getLower()) {
                      bestRange = r;
                  }
              }
              m.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, bestRange);
          }
  
          // control.sceneMode -- DISABLED is always available
          m.set(CaptureRequest.CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_DISABLED);
  
          /*
           * statistics.*
           */
  
          // statistics.faceDetectMode
          m.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, STATISTICS_FACE_DETECT_MODE_OFF);
  
          /*
           * flash.*
           */
  
          // flash.mode
          m.set(CaptureRequest.FLASH_MODE, FLASH_MODE_OFF);
  
          /*
           * noiseReduction.*
           */
          if (templateId == CameraDevice.TEMPLATE_STILL_CAPTURE) {
              m.set(CaptureRequest.NOISE_REDUCTION_MODE, NOISE_REDUCTION_MODE_HIGH_QUALITY);
          } else {
              m.set(CaptureRequest.NOISE_REDUCTION_MODE, NOISE_REDUCTION_MODE_FAST);
          }
  
          /*
          * colorCorrection.*
          */
          if (templateId == CameraDevice.TEMPLATE_STILL_CAPTURE) {
              m.set(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE,
                      COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY);
          } else {
              m.set(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE,
                      COLOR_CORRECTION_ABERRATION_MODE_FAST);
          }
  
          /*
           * lens.*
           */
  
          // lens.focalLength
          m.set(CaptureRequest.LENS_FOCAL_LENGTH,
                  c.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)[0]);
  
          /*
           * jpeg.*
           */
  
          // jpeg.thumbnailSize - set smallest non-zero size if possible
          Size[] sizes = c.get(CameraCharacteristics.JPEG_AVAILABLE_THUMBNAIL_SIZES);
          m.set(CaptureRequest.JPEG_THUMBNAIL_SIZE, (sizes.length > 1) ? sizes[1] : sizes[0]);
  
          // TODO: map other request template values
          return m;
      }
  
      private static int[] getTagsForKeys(Key<?>[] keys) {
          int[] tags = new int[keys.length];
  
          for (int i = 0; i < keys.length; ++i) {
              tags[i] = keys[i].getNativeKey().getTag();
          }
  
          return tags;
      }
  
      private static int[] getTagsForKeys(CaptureRequest.Key<?>[] keys) {
          int[] tags = new int[keys.length];
  
          for (int i = 0; i < keys.length; ++i) {
              tags[i] = keys[i].getNativeKey().getTag();
          }
  
          return tags;
      }
  
      private static int[] getTagsForKeys(CaptureResult.Key<?>[] keys) {
          int[] tags = new int[keys.length];
  
          for (int i = 0; i < keys.length; ++i) {
              tags[i] = keys[i].getNativeKey().getTag();
          }
  
          return tags;
      }
  
      /**
       * Convert the requested AF mode into its equivalent supported parameter.
       *
       * @param mode {@code CONTROL_AF_MODE}
       * @param supportedFocusModes list of camera1's supported focus modes
       * @return the stringified af mode, or {@code null} if its not supported
       */
      static String convertAfModeToLegacy(int mode, List<String> supportedFocusModes) {
          if (supportedFocusModes == null || supportedFocusModes.isEmpty()) {
              Log.w(TAG, "No focus modes supported; API1 bug");
              return null;
          }
  
          String param = null;
          switch (mode) {
              case CONTROL_AF_MODE_AUTO:
                  param = Parameters.FOCUS_MODE_AUTO;
                  break;
              case CONTROL_AF_MODE_CONTINUOUS_PICTURE:
                  param = Parameters.FOCUS_MODE_CONTINUOUS_PICTURE;
                  break;
              case CONTROL_AF_MODE_CONTINUOUS_VIDEO:
                  param = Parameters.FOCUS_MODE_CONTINUOUS_VIDEO;
                  break;
              case CONTROL_AF_MODE_EDOF:
                  param = Parameters.FOCUS_MODE_EDOF;
                  break;
              case CONTROL_AF_MODE_MACRO:
                  param = Parameters.FOCUS_MODE_MACRO;
                  break;
              case CONTROL_AF_MODE_OFF:
                  if (supportedFocusModes.contains(Parameters.FOCUS_MODE_FIXED)) {
                      param = Parameters.FOCUS_MODE_FIXED;
                  } else {
                      param = Parameters.FOCUS_MODE_INFINITY;
                  }
          }
  
          if (!supportedFocusModes.contains(param)) {
              // Weed out bad user input by setting to the first arbitrary focus mode
              String defaultMode = supportedFocusModes.get(0);
              Log.w(TAG,
                      String.format(
                              "convertAfModeToLegacy - ignoring unsupported mode %d, " +
                              "defaulting to %s", mode, defaultMode));
              param = defaultMode;
          }
  
          return param;
      }
  }
  