# Copyright 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.
"""Utility functions to determine what functionality the camera supports."""


import logging
import math
import types

from mobly import asserts
import numpy as np

import capture_request_utils

FD_CAL_RTOL = 0.20
LENS_FACING = types.MappingProxyType({'FRONT': 0, 'BACK': 1, 'EXTERNAL': 2})
MULTI_CAMERA_SYNC_CALIBRATED = 1
NUM_DISTORTION_PARAMS = 5  # number of terms in lens.distortion
NUM_INTRINSIC_CAL_PARAMS = 5  # number of terms in intrinsic calibration
NUM_POSE_ROTATION_PARAMS = 4  # number of terms in poseRotation
NUM_POSE_TRANSLATION_PARAMS = 3  # number of terms in poseTranslation
SKIP_RET_MSG = 'Test skipped'
SOLID_COLOR_TEST_PATTERN = 1
COLOR_BARS_TEST_PATTERN = 2
USE_CASE_STILL_CAPTURE = 2
DEFAULT_AE_TARGET_FPS_RANGE = (15, 30)
COLOR_SPACES = [
    'SRGB', 'LINEAR_SRGB', 'EXTENDED_SRGB',
    'LINEAR_EXTENDED_SRGB', 'BT709', 'BT2020',
    'DCI_P3', 'DISPLAY_P3', 'NTSC_1953', 'SMPTE_C',
    'ADOBE_RGB', 'PRO_PHOTO_RGB', 'ACES', 'ACESCG',
    'CIE_XYZ', 'CIE_LAB', 'BT2020_HLG', 'BT2020_PQ'
]
SETTINGS_OVERRIDE_ZOOM = 1
STABILIZATION_MODE_OFF = 0
STABILIZATION_MODE_PREVIEW = 2
LENS_OPTICAL_STABILIZATION_MODE_ON = 1


def check_front_or_rear_camera(props):
  """Raises an error if not LENS_FACING FRONT or BACK.

  Args:
    props: Camera properties object.

  Raises:
    assertionError if not front or rear camera.
  """
  facing = props['android.lens.facing']
  if not (facing == LENS_FACING['BACK'] or facing == LENS_FACING['FRONT']):
    raise AssertionError('Unknown lens facing: {facing}.')


def legacy(props):
  """Returns whether a device is a LEGACY capability camera2 device.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device is a LEGACY camera.
  """
  return props.get('android.info.supportedHardwareLevel') == 2


def limited(props):
  """Returns whether a device is a LIMITED capability camera2 device.

  Args:
    props: Camera properties object.

  Returns:
     Boolean. True if device is a LIMITED camera.
  """
  return props.get('android.info.supportedHardwareLevel') == 0


def full_or_better(props):
  """Returns whether a device is a FULL or better camera2 device.

  Args:
    props: Camera properties object.

  Returns:
     Boolean. True if device is FULL or LEVEL3 camera.
  """
  return (props.get('android.info.supportedHardwareLevel') >= 1 and
          props.get('android.info.supportedHardwareLevel') != 2)


def level3(props):
  """Returns whether a device is a LEVEL3 capability camera2 device.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device is LEVEL3 camera.
  """
  return props.get('android.info.supportedHardwareLevel') == 3


def manual_sensor(props):
  """Returns whether a device supports MANUAL_SENSOR capabilities.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if devices supports MANUAL_SENSOR capabilities.
  """
  return 1 in props.get('android.request.availableCapabilities', [])


def manual_post_proc(props):
  """Returns whether a device supports MANUAL_POST_PROCESSING capabilities.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports MANUAL_POST_PROCESSING capabilities.
  """
  return 2 in props.get('android.request.availableCapabilities', [])


def raw(props):
  """Returns whether a device supports RAW capabilities.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports RAW capabilities.
  """
  return 3 in props.get('android.request.availableCapabilities', [])


def sensor_fusion(props):
  """Checks the camera and motion sensor timestamps.

  Returns whether the camera and motion sensor timestamps for the device
  are in the same time domain and can be compared directly.

  Args:
    props: Camera properties object.

  Returns:
     Boolean. True if camera and motion sensor timestamps in same time domain.
  """
  return props.get('android.sensor.info.timestampSource') == 1


def burst_capture_capable(props):
  """Returns whether a device supports burst capture.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if the device supports burst capture.
  """
  return 6 in props.get('android.request.availableCapabilities', [])


def logical_multi_camera(props):
  """Returns whether a device is a logical multi-camera.

  Args:
    props: Camera properties object.

  Returns:
     Boolean. True if the device is a logical multi-camera.
  """
  return 11 in props.get('android.request.availableCapabilities', [])


def logical_multi_camera_physical_ids(props):
  """Returns a logical multi-camera's underlying physical cameras.

  Args:
    props: Camera properties object.

  Returns:
    list of physical cameras backing the logical multi-camera.
  """
  physical_ids_list = []
  if logical_multi_camera(props):
    physical_ids_list = props['camera.characteristics.physicalCamIds']
  return physical_ids_list


def skip_unless(cond, msg=None):
  """Skips the test if the condition is false.

  If a test is skipped, then it is exited and returns the special code
  of 101 to the calling shell, which can be used by an external test
  harness to differentiate a skip from a pass or fail.

  Args:
    cond: Boolean, which must be true for the test to not skip.
    msg: String, reason for test to skip

  Returns:
     Nothing.
  """
  if not cond:
    skip_msg = SKIP_RET_MSG if not msg else f'{SKIP_RET_MSG}: {msg}'
    asserts.skip(skip_msg)


def backward_compatible(props):
  """Returns whether a device supports BACKWARD_COMPATIBLE.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if the devices supports BACKWARD_COMPATIBLE.
  """
  return 0 in props.get('android.request.availableCapabilities', [])


def lens_calibrated(props):
  """Returns whether lens position is calibrated or not.

  android.lens.info.focusDistanceCalibration has 3 modes.
  0: Uncalibrated
  1: Approximate
  2: Calibrated

  Args:
    props: Camera properties objects.

  Returns:
    Boolean. True if lens is CALIBRATED.
  """
  return 'android.lens.info.focusDistanceCalibration' in props and props[
      'android.lens.info.focusDistanceCalibration'] == 2


def lens_approx_calibrated(props):
  """Returns whether lens position is calibrated or not.

  android.lens.info.focusDistanceCalibration has 3 modes.
  0: Uncalibrated
  1: Approximate
  2: Calibrated

  Args:
   props: Camera properties objects.

  Returns:
    Boolean. True if lens is APPROXIMATE or CALIBRATED.
  """
  return props.get('android.lens.info.focusDistanceCalibration') in [1, 2]


def raw10(props):
  """Returns whether a device supports RAW10 capabilities.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports RAW10 capabilities.
  """
  if capture_request_utils.get_available_output_sizes('raw10', props):
    return True
  return False


def raw12(props):
  """Returns whether a device supports RAW12 capabilities.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports RAW12 capabilities.
  """
  if capture_request_utils.get_available_output_sizes('raw12', props):
    return True
  return False


def raw16(props):
  """Returns whether a device supports RAW16 output.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports RAW16 capabilities.
  """
  if capture_request_utils.get_available_output_sizes('raw', props):
    return True
  return False


def raw_output(props):
  """Returns whether a device supports any of the RAW output formats.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports any of the RAW output formats
  """
  return raw16(props) or raw10(props) or raw12(props)


def per_frame_control(props):
  """Returns whether a device supports per frame control.

  Args:
    props: Camera properties object.

  Returns: Boolean. True if devices supports per frame control.
  """
  return 'android.sync.maxLatency' in props and props[
      'android.sync.maxLatency'] == 0


def mono_camera(props):
  """Returns whether a device is monochromatic.

  Args:
    props: Camera properties object.
  Returns: Boolean. True if MONO camera.
  """
  return 12 in props.get('android.request.availableCapabilities', [])


def fixed_focus(props):
  """Returns whether a device is fixed focus.

  props[android.lens.info.minimumFocusDistance] == 0 is fixed focus

  Args:
    props: Camera properties objects.

  Returns:
    Boolean. True if device is a fixed focus camera.
  """
  return 'android.lens.info.minimumFocusDistance' in props and props[
      'android.lens.info.minimumFocusDistance'] == 0


def face_detect(props):
  """Returns whether a device has face detection mode.

  props['android.statistics.info.availableFaceDetectModes'] != 0

  Args:
    props: Camera properties objects.

  Returns:
    Boolean. True if device supports face detection.
  """
  return 'android.statistics.info.availableFaceDetectModes' in props and props[
      'android.statistics.info.availableFaceDetectModes'] != [0]


def read_3a(props):
  """Return whether a device supports reading out the below 3A settings.

  sensitivity
  exposure time
  awb gain
  awb cct
  focus distance

  Args:
    props: Camera properties object.

  Returns:
     Boolean. True if device supports reading out 3A settings.
  """
  return manual_sensor(props) and manual_post_proc(props)


def compute_target_exposure(props):
  """Return whether a device supports target exposure computation.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports target exposure computation.
  """
  return manual_sensor(props) and manual_post_proc(props)


def y8(props):
  """Returns whether a device supports Y8 output.

  Args:
    props: Camera properties object.

  Returns:
     Boolean. True if device suupports Y8 output.
  """
  if capture_request_utils.get_available_output_sizes('y8', props):
    return True
  return False


def jpeg_quality(props):
  """Returns whether a device supports JPEG quality."""
  return ('camera.characteristics.requestKeys' in props) and (
      'android.jpeg.quality' in props['camera.characteristics.requestKeys'])


def jpeg_orientation(props):
  """Returns whether a device supports JPEG orientation."""
  return ('camera.characteristics.requestKeys' in props) and (
      'android.jpeg.orientation' in props['camera.characteristics.requestKeys'])


def sensor_orientation(props):
  """Returns the sensor orientation of the camera."""
  return props['android.sensor.orientation']


def zoom_ratio_range(props):
  """Returns whether a device supports zoom capabilities.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports zoom capabilities.
  """
  return 'android.control.zoomRatioRange' in props and props[
      'android.control.zoomRatioRange'] is not None


def low_latency_zoom(props):
  """Returns whether a device supports low latency zoom via settings override.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports SETTINGS_OVERRIDE_ZOOM.
  """
  return ('android.control.availableSettingsOverrides') in props and (
      SETTINGS_OVERRIDE_ZOOM in props[
          'android.control.availableSettingsOverrides'])


def sync_latency(props):
  """Returns sync latency in number of frames.

  If undefined, 8 frames.

  Args:
    props: Camera properties object.

  Returns:
    integer number of frames.
  """
  latency = props['android.sync.maxLatency']
  if latency < 0:
    latency = 8
  return latency


def get_max_digital_zoom(props):
  """Returns the maximum amount of zooming possible by the camera device.

  Args:
    props: Camera properties object.

  Returns:
    A float indicating the maximum amount of zooming possible by the
    camera device.
  """
  z_max = 1.0
  if 'android.scaler.availableMaxDigitalZoom' in props:
    z_max = props['android.scaler.availableMaxDigitalZoom']
  return z_max


def get_ae_target_fps_ranges(props):
  """Returns the AE target FPS ranges supported by the camera device.

  Args:
    props: Camera properties object.

  Returns:
    A list of AE target FPS ranges supported by the camera device.
  """
  ranges = []  # return empty list instead of Boolean if no FPS range in props
  if 'android.control.aeAvailableTargetFpsRanges' in props:
    ranges = props['android.control.aeAvailableTargetFpsRanges']
  return ranges


def get_fps_range_to_test(fps_ranges):
  """Returns an AE target FPS range to test based on camera device properties.

  Args:
    fps_ranges: list of AE target FPS ranges supported by camera.
      e.g. [[7, 30], [24, 30], [30, 30]]
  Returns:
    An AE target FPS range for testing.
  """
  default_range_min, default_range_max = DEFAULT_AE_TARGET_FPS_RANGE
  default_range_size = default_range_max - default_range_min
  logging.debug('AE target FPS ranges: %s', fps_ranges)
  widest_fps_range = max(fps_ranges, key=lambda r: r[1] - r[0])
  if widest_fps_range[1] - widest_fps_range[0] < default_range_size:
    logging.debug('Default range %s is wider than widest '
                  'available AE target FPS range %s.',
                  DEFAULT_AE_TARGET_FPS_RANGE,
                  widest_fps_range)
  logging.debug('Accepted AE target FPS range: %s', widest_fps_range)
  return widest_fps_range


def ae_lock(props):
  """Returns whether a device supports AE lock.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports AE lock.
  """
  return 'android.control.aeLockAvailable' in props and props[
      'android.control.aeLockAvailable'] == 1


def awb_lock(props):
  """Returns whether a device supports AWB lock.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports AWB lock.
  """
  return 'android.control.awbLockAvailable' in props and props[
      'android.control.awbLockAvailable'] == 1


def ev_compensation(props):
  """Returns whether a device supports ev compensation.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports EV compensation.
  """
  return 'android.control.aeCompensationRange' in props and props[
      'android.control.aeCompensationRange'] != [0, 0]


def flash(props):
  """Returns whether a device supports flash control.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports flash control.
  """
  return 'android.flash.info.available' in props and props[
      'android.flash.info.available'] == 1


def distortion_correction(props):
  """Returns whether a device supports android.lens.distortion capabilities.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports lens distortion correction capabilities.
  """
  return 'android.lens.distortion' in props and props[
      'android.lens.distortion'] is not None


def distortion_correction_mode(props, mode):
  """Returns whether a device supports a distortionCorrection mode.

  Args:
    props: Camera properties object
    mode: Integer indicating distortion correction mode

  Returns:
    Boolean. True if device supports distortion correction mode(s).
  """
  if 'android.distortionCorrection.availableModes' in props:
    logging.debug('distortionCorrection.availableModes: %s',
                  props['android.distortionCorrection.availableModes'])
  else:
    logging.debug('distortionCorrection.availableModes not in props!')
  return ('android.distortionCorrection.availableModes' in props and
          mode in props['android.distortionCorrection.availableModes'])


def freeform_crop(props):
  """Returns whether a device supports freefrom cropping.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports freeform cropping.
  """
  return 'android.scaler.croppingType' in props and props[
      'android.scaler.croppingType'] == 1


def noise_reduction_mode(props, mode):
  """Returns whether a device supports the noise reduction mode.

  Args:
    props: Camera properties objects.
    mode: Integer indicating noise reduction mode to check for availability.

  Returns:
    Boolean. True if devices supports noise reduction mode(s).
  """
  return ('android.noiseReduction.availableNoiseReductionModes' in props and
          mode in props['android.noiseReduction.availableNoiseReductionModes'])


def lsc_map(props):
  """Returns whether a device supports lens shading map output.

  Args:
    props: Camera properties object.
  Returns: Boolean. True if device supports lens shading map output.
  """
  return 1 in props.get('android.statistics.info.availableLensShadingMapModes',
                        [])


def lsc_off(props):
  """Returns whether a device supports disabling lens shading correction.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports disabling lens shading correction.
  """
  return 0 in props.get('android.shading.availableModes', [])


def edge_mode(props, mode):
  """Returns whether a device supports the edge mode.

  Args:
    props: Camera properties objects.
    mode: Integer, indicating the edge mode to check for availability.

  Returns:
    Boolean. True if device supports edge mode(s).
  """
  return 'android.edge.availableEdgeModes' in props and mode in props[
      'android.edge.availableEdgeModes']


def tonemap_mode(props, mode):
  """Returns whether a device supports the tonemap mode.

  Args:
    props: Camera properties object.
    mode: Integer, indicating the tonemap mode to check for availability.

  Return:
    Boolean.
  """
  return 'android.tonemap.availableToneMapModes' in props and mode in props[
      'android.tonemap.availableToneMapModes']


def yuv_reprocess(props):
  """Returns whether a device supports YUV reprocessing.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports YUV reprocessing.
  """
  return 'android.request.availableCapabilities' in props and 7 in props[
      'android.request.availableCapabilities']


def private_reprocess(props):
  """Returns whether a device supports PRIVATE reprocessing.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports PRIVATE reprocessing.
  """
  return 'android.request.availableCapabilities' in props and 4 in props[
      'android.request.availableCapabilities']


def stream_use_case(props):
  """Returns whether a device has stream use case capability.

  Args:
    props: Camera properties object.

  Returns:
     Boolean. True if the device has stream use case capability.
  """
  return 'android.request.availableCapabilities' in props and 19 in props[
      'android.request.availableCapabilities']


def cropped_raw_stream_use_case(props):
  """Returns whether a device supports the CROPPED_RAW stream use case.

  Args:
    props: Camera properties object.

  Returns:
     Boolean. True if the device supports the CROPPED_RAW stream use case.
  """
  return stream_use_case(props) and 6 in props[
      'android.scaler.availableStreamUseCases']


def dynamic_range_ten_bit(props):
  """Returns whether a device supports the DYNAMIC_RANGE_TEN_BIT capability.

  Args:
    props: Camera properties object.

  Returns:
     Boolean. True if the device supports the DYNAMIC_RANGE_TEN_BIT capability.
  """
  return 'android.request.availableCapabilities' in props and 18 in props[
      'android.request.availableCapabilities']


def intrinsic_calibration(props):
  """Returns whether a device supports android.lens.intrinsicCalibration.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if device supports android.lens.intrinsicCalibratino.
  """
  return props.get('android.lens.intrinsicCalibration') is not None


def get_intrinsic_calibration(props, metadata, debug, fd=None):
  """Get intrinsicCalibration and create intrisic matrix.

  If intrinsic android.lens.intrinsicCalibration does not exist, return None.

  Args:
    props: camera properties.
    metadata: dict; camera capture metadata.
    debug: boolean; enable printing more information.
    fd: float; focal length from capture metadata.

  Returns:
    numpy array for intrinsic transformation matrix or None
    k = [[f_x, s, c_x],
         [0, f_y, c_y],
         [0,   0,   1]]
  """
  if metadata.get('android.lens.intrinsicCalibration'):
    ical = np.array(metadata['android.lens.intrinsicCalibration'])
    logging.debug('Using capture metadata android.lens.intrinsicCalibration')
  elif props.get('android.lens.intrinsicCalibration'):
    ical = np.array(props['android.lens.intrinsicCalibration'])
    logging.debug('Using camera property android.lens.intrinsicCalibration')
  else:
    logging.error('Camera does not have android.lens.intrinsicCalibration.')
    return None

  # basic checks for parameter correctness
  ical_len = len(ical)
  if ical_len != NUM_INTRINSIC_CAL_PARAMS:
    raise ValueError(
        f'instrisicCalibration has wrong number of params: {ical_len}.')

  if fd is not None:
    # detailed checks for parameter correctness
    # Intrinsic cal is of format: [f_x, f_y, c_x, c_y, s]
    # [f_x, f_y] is the horizontal and vertical focal lengths,
    # [c_x, c_y] is the position of the optical axis,
    # and s is skew of sensor plane vs lens plane.
    sensor_h = props['android.sensor.info.physicalSize']['height']
    sensor_w = props['android.sensor.info.physicalSize']['width']
    pixel_h = props['android.sensor.info.pixelArraySize']['height']
    pixel_w = props['android.sensor.info.pixelArraySize']['width']
    fd_w_pix = pixel_w * fd / sensor_w
    fd_h_pix = pixel_h * fd / sensor_h

    if not math.isclose(fd_w_pix, ical[0], rel_tol=FD_CAL_RTOL):
      raise ValueError(f'fd_w(pixels): {fd_w_pix:.2f}\tcal[0](pixels): '
                       f'{ical[0]:.2f}\tTOL=20%')
    if not math.isclose(fd_h_pix, ical[1], rel_tol=FD_CAL_RTOL):
      raise ValueError(f'fd_h(pixels): {fd_h_pix:.2f}\tcal[1](pixels): '
                       f'{ical[1]:.2f}\tTOL=20%')

  # generate instrinsic matrix
  k = np.array([[ical[0], ical[4], ical[2]],
                [0, ical[1], ical[3]],
                [0, 0, 1]])
  if debug:
    logging.debug('k: %s', str(k))
  return k


def get_translation_matrix(props, debug):
  """Get translation matrix.

  Args:
    props: dict of camera properties
    debug: boolean flag to log more info

  Returns:
    android.lens.poseTranslation matrix if it exists, otherwise None.
  """
  if props['android.lens.poseTranslation']:
    t = np.array(props['android.lens.poseTranslation'])
  else:
    logging.error('Device does not have android.lens.poseTranslation.')
    return None

  if debug:
    logging.debug('translation: %s', str(t))
  t_len = len(t)
  if t_len != NUM_POSE_TRANSLATION_PARAMS:
    raise ValueError(f'poseTranslation has wrong # of params: {t_len}.')
  return t


def get_rotation_matrix(props, debug):
  """Convert the rotation parameters to 3-axis data.

  Args:
    props: camera properties
    debug: boolean for more information

  Returns:
    3x3 matrix w/ rotation parameters if poseRotation exists, otherwise None
  """
  if props['android.lens.poseRotation']:
    rotation = np.array(props['android.lens.poseRotation'])
  else:
    logging.error('Device does not have android.lens.poseRotation.')
    return None

  if debug:
    logging.debug('rotation: %s', str(rotation))
    rotation_len = len(rotation)
    if rotation_len != NUM_POSE_ROTATION_PARAMS:
      raise ValueError(f'poseRotation has wrong # of params: {rotation_len}.')
  x = rotation[0]
  y = rotation[1]
  z = rotation[2]
  w = rotation[3]
  return np.array([[1-2*y**2-2*z**2, 2*x*y-2*z*w, 2*x*z+2*y*w],
                   [2*x*y+2*z*w, 1-2*x**2-2*z**2, 2*y*z-2*x*w],
                   [2*x*z-2*y*w, 2*y*z+2*x*w, 1-2*x**2-2*y**2]])


def get_distortion_matrix(props):
  """Get android.lens.distortion matrix and convert to cv2 fmt.

  Args:
    props: dict of camera properties

  Returns:
    cv2 reordered android.lens.distortion if it exists, otherwise None.
  """
  if props['android.lens.distortion']:
    dist = np.array(props['android.lens.distortion'])
  else:
    logging.error('Device does not have android.lens.distortion.')
    return None

  dist_len = len(dist)
  if len(dist) != NUM_DISTORTION_PARAMS:
    raise ValueError(f'lens.distortion has wrong # of params: {dist_len}.')
  cv2_distort = np.array([dist[0], dist[1], dist[3], dist[4], dist[2]])
  logging.debug('cv2 distortion params: %s', str(cv2_distort))
  return cv2_distort


def post_raw_sensitivity_boost(props):
  """Returns whether a device supports post RAW sensitivity boost.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if android.control.postRawSensitivityBoost is supported.
  """
  return (
      'android.control.postRawSensitivityBoostRange' in
      props['camera.characteristics.keys'] and
      props.get('android.control.postRawSensitivityBoostRange') != [100, 100])


def sensor_fusion_capable(props):
  """Determine if test_sensor_fusion is run."""
  return all([sensor_fusion(props),
              manual_sensor(props),
              props['android.lens.facing'] != LENS_FACING['EXTERNAL']])


def continuous_picture(props):
  """Returns whether a device supports CONTINUOUS_PICTURE.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if CONTINUOUS_PICTURE in android.control.afAvailableModes.
  """
  return 4 in props.get('android.control.afAvailableModes', [])


def af_scene_change(props):
  """Returns whether a device supports AF_SCENE_CHANGE.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if android.control.afSceneChange supported.
  """
  return 'android.control.afSceneChange' in props.get(
      'camera.characteristics.resultKeys')


def multi_camera_frame_sync_capable(props):
  """Determines if test_multi_camera_frame_sync can be run."""
  return all([
      read_3a(props),
      per_frame_control(props),
      logical_multi_camera(props),
      sensor_fusion(props),
  ])


def multi_camera_sync_calibrated(props):
  """Determines if multi-camera sync type is CALIBRATED or APPROXIMATE.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if android.logicalMultiCamera.sensorSyncType is CALIBRATED.
  """
  return props.get('android.logicalMultiCamera.sensorSyncType'
                  ) == MULTI_CAMERA_SYNC_CALIBRATED


def solid_color_test_pattern(props):
  """Determines if camera supports solid color test pattern.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if android.sensor.availableTestPatternModes has
             SOLID_COLOR_TEST_PATTERN.
  """
  return SOLID_COLOR_TEST_PATTERN in props.get(
      'android.sensor.availableTestPatternModes')


def color_bars_test_pattern(props):
  """Determines if camera supports color bars test pattern.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if android.sensor.availableTestPatternModes has
             COLOR_BARS_TEST_PATTERN.
  """
  return COLOR_BARS_TEST_PATTERN in props.get(
      'android.sensor.availableTestPatternModes')


def linear_tonemap(props):
  """Determines if camera supports CONTRAST_CURVE or GAMMA_VALUE in tonemap.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if android.tonemap.availableToneMapModes has
             CONTRAST_CURVE (0) or GAMMA_VALUE (3).
  """
  return ('android.tonemap.availableToneMapModes' in props and
          (0 in props.get('android.tonemap.availableToneMapModes') or
           3 in props.get('android.tonemap.availableToneMapModes')))


def get_reprocess_formats(props):
  """Retrieve the list of supported reprocess formats.

  Args:
    props: The camera properties.

  Returns:
    A list of supported reprocess formats.
  """
  reprocess_formats = []
  if yuv_reprocess(props):
    reprocess_formats.append('yuv')
  if private_reprocess(props):
    reprocess_formats.append('private')
  return reprocess_formats


def color_space_to_int(color_space):
  """Returns the integer ordinal of a named color space.

  Args:
    color_space: The color space string.

  Returns:
    Int. Ordinal of the color space.
  """
  if color_space == 'UNSPECIFIED':
    return -1

  return COLOR_SPACES.index(color_space)


def autoframing(props):
  """Returns whether a device supports autoframing.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if android.control.autoframing is supported.
  """
  return 'android.control.autoframingAvailable' in props and props[
      'android.control.autoframingAvailable'] == 1


def ae_regions(props):
  """Returns whether a device supports CONTROL_AE_REGIONS.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if android.control.aeRegions is supported.
  """
  return 'android.control.maxRegionsAe' in props and props[
      'android.control.maxRegionsAe'] != 0


def awb_regions(props):
  """Returns whether a device supports CONTROL_AWB_REGIONS.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if android.control.awbRegions is supported.
  """
  return 'android.control.maxRegionsAwb' in props and props[
      'android.control.maxRegionsAwb'] != 0


def preview_stabilization_supported(props):
  """Returns whether preview stabilization is supported.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if preview stabilization is supported.
  """
  supported_stabilization_modes = props[
      'android.control.availableVideoStabilizationModes'
  ]
  supported = (
      supported_stabilization_modes is not None and
      STABILIZATION_MODE_PREVIEW in supported_stabilization_modes
  )
  return supported


def optical_stabilization_supported(props):
  """Returns whether optical image stabilization is supported.

  Args:
    props: Camera properties object.

  Returns:
    Boolean. True if optical image stabilization is supported.
  """
  optical_stabilization_modes = props[
      'android.lens.info.availableOpticalStabilization'
    ]
  logging.debug('optical_stabilization_modes = %s',
                str(optical_stabilization_modes))

  # Check if OIS supported
  ois_supported = (optical_stabilization_modes is not None and
                   LENS_OPTICAL_STABILIZATION_MODE_ON in
                   optical_stabilization_modes)
  return ois_supported
