Merge "ITS: Enable hidden physical camera ITS test" into qt-dev
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index f8aad38..9b837cd 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -25,6 +25,7 @@
import its.error
import numpy
+from collections import namedtuple
class ItsSession(object):
"""Controls a device over adb to run ITS scripts.
@@ -218,8 +219,9 @@
break
proc.kill()
- def __init__(self, camera_id=None):
+ def __init__(self, camera_id=None, hidden_physical_id=None):
self._camera_id = camera_id
+ self._hidden_physical_id = hidden_physical_id
def __enter__(self):
# Initialize device id and adb command.
@@ -230,7 +232,7 @@
self.__init_socket_port()
self.__close_camera()
- self.__open_camera(self._camera_id)
+ self.__open_camera()
return self
def __exit__(self, type, value, traceback):
@@ -263,18 +265,26 @@
buf = numpy.frombuffer(buf, dtype=numpy.uint8)
return jobj, buf
- def __open_camera(self, camera_id):
+ def __open_camera(self):
# Get the camera ID to open if it is an argument as a single camera.
# This allows passing camera=# to individual tests at command line
# and camera=#,#,# or an no camera argv with tools/run_all_tests.py.
- if not camera_id:
- camera_id = 0
+ #
+ # In case the camera is a logical multi-camera, to run ITS on the
+ # hidden physical sub-camera, pass camera=[logical ID]:[physical ID]
+ # to an individual test at the command line, and same applies to multiple
+ # camera IDs for tools/run_all_tests.py: camera=#,#:#,#:#,#
+ if not self._camera_id:
+ self._camera_id = 0
for s in sys.argv[1:]:
if s[:7] == "camera=" and len(s) > 7:
- camera_ids = s[7:].split(",")
- if len(camera_ids) == 1:
- camera_id = camera_ids[0]
- cmd = {"cmdName":"open", "cameraId":camera_id}
+ camera_ids = s[7:].split(',')
+ camera_id_combos = parse_camera_ids(camera_ids)
+ if len(camera_id_combos) == 1:
+ self._camera_id = camera_id_combos[0].id
+ self._hidden_physical_id = camera_id_combos[0].sub_id
+
+ cmd = {"cmdName":"open", "cameraId":self._camera_id}
self.sock.send(json.dumps(cmd) + "\n")
data,_ = self.__read_response_from_socket()
if data['tag'] != 'cameraOpened':
@@ -402,6 +412,23 @@
raise its.error.Error('Version mismatch ItsService(%s) vs host script(%s)' % (
server_version, ITS_SERVICE_VERSION))
+ def override_with_hidden_physical_camera_props(self, props):
+ """If current session is for a hidden physical camera, check that it is a valid
+ sub-camera backing the logical camera, and return the
+ characteristics of sub-camera. Otherwise, return "props" directly.
+
+ Returns: The properties of the hidden physical camera if possible
+ """
+ if self._hidden_physical_id:
+ e_msg = 'Camera %s is not a logical multi-camera' % self._camera_id
+ assert its.caps.logical_multi_camera(props), e_msg
+ physical_ids = its.caps.logical_multi_camera_physical_ids(props)
+ e_msg = 'Camera %s is not a hidden sub-camera of camera %s' % (
+ self._hidden_physical_id, self._camera_id)
+ assert self._hidden_physical_id in physical_ids, e_msg
+ props = self.get_camera_properties_by_id(self._hidden_physical_id)
+ return props
+
def get_camera_properties(self):
"""Get the camera properties object for the device.
@@ -758,6 +785,9 @@
physical_cam_format = None
logical_cam_formats = []
for i,s in enumerate(cmd["outputSurfaces"]):
+ if self._hidden_physical_id:
+ s['physicalCamera'] = self._hidden_physical_id
+
if "format" in s and s["format"] in ["yuv", "raw", "raw10", "raw12"]:
if "physicalCamera" in s:
if physical_cam_format is not None and s["format"] != physical_cam_format:
@@ -1050,6 +1080,20 @@
return device_bfp
+def parse_camera_ids(ids):
+ """ Parse the string of camera IDs into array of CameraIdCombo tuples.
+ """
+ CameraIdCombo = namedtuple('CameraIdCombo', ['id', 'sub_id'])
+ id_combos = []
+ for one_id in ids:
+ one_combo = one_id.split(':')
+ if len(one_combo) == 1:
+ id_combos.append(CameraIdCombo(one_combo[0], None))
+ elif len(one_combo) == 2:
+ id_combos.append(CameraIdCombo(one_combo[0], one_combo[1]))
+ else:
+ assert(False), 'Camera id parameters must be either ID, or ID:SUB_ID'
+ return id_combos
def _run(cmd):
"""Replacement for os.system, with hiding of stdout+stderr messages.
diff --git a/apps/CameraITS/tests/scene0/test_read_write.py b/apps/CameraITS/tests/scene0/test_read_write.py
index 357bd02..0f8a7a6 100644
--- a/apps/CameraITS/tests/scene0/test_read_write.py
+++ b/apps/CameraITS/tests/scene0/test_read_write.py
@@ -29,6 +29,7 @@
with its.device.ItsSession() as cam:
props = cam.get_camera_properties()
+ props = cam.override_with_hidden_physical_camera_props(props)
its.caps.skip_unless(its.caps.manual_sensor(props) and
its.caps.per_frame_control(props))
@@ -59,7 +60,8 @@
print 'format: %s' % fmt
size = its.objects.get_available_output_sizes(fmt, props)[-1]
out_surface = {'width': size[0], 'height': size[1], 'format': fmt}
-
+ if cam._hidden_physical_id:
+ out_surface['physicalCamera'] = cam._hidden_physical_id
reqs = []
index_list = []
for exp in exp_range:
diff --git a/apps/CameraITS/tests/scene1/test_dng_noise_model.py b/apps/CameraITS/tests/scene1/test_dng_noise_model.py
index c60f71c..ba8fd7d 100644
--- a/apps/CameraITS/tests/scene1/test_dng_noise_model.py
+++ b/apps/CameraITS/tests/scene1/test_dng_noise_model.py
@@ -39,14 +39,15 @@
# (since ITS doesn't require a perfectly uniformly lit scene).
with its.device.ItsSession() as cam:
-
props = cam.get_camera_properties()
+ props = cam.override_with_hidden_physical_camera_props(props)
its.caps.skip_unless(its.caps.raw(props) and
- its.caps.raw16(props) and
- its.caps.manual_sensor(props) and
- its.caps.read_3a(props) and
- its.caps.per_frame_control(props) and
- not its.caps.mono_camera(props))
+ its.caps.raw16(props) and
+ its.caps.manual_sensor(props) and
+ its.caps.read_3a(props) and
+ its.caps.per_frame_control(props) and
+ not its.caps.mono_camera(props))
+
debug = its.caps.debug_mode()
white_level = float(props['android.sensor.info.whiteLevel'])
diff --git a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
index 77f729e..25296b6 100644
--- a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
+++ b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
@@ -429,6 +429,7 @@
"""
with its.device.ItsSession() as cam:
props = cam.get_camera_properties()
+ props = cam.override_with_hidden_physical_camera_props(props)
its.caps.skip_unless(its.caps.read_3a and
its.caps.sensor_fusion(props) and
props["android.lens.facing"] != FACING_EXTERNAL and
diff --git a/apps/CameraITS/tools/dng_noise_model.py b/apps/CameraITS/tools/dng_noise_model.py
index f35c193..b490d19 100644
--- a/apps/CameraITS/tools/dng_noise_model.py
+++ b/apps/CameraITS/tools/dng_noise_model.py
@@ -87,6 +87,7 @@
with its.device.ItsSession() as cam:
props = cam.get_camera_properties()
+ props = cam.override_with_hidden_physical_camera_props(props)
# Get basic properties we need.
sens_min, sens_max = props['android.sensor.info.sensitivityRange']
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 26a2c8d..0cdd008 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -72,6 +72,24 @@
'sensor_fusion': []
}
+# Must match mHiddenPhysicalCameraSceneIds in ItsTestActivity.java
+HIDDEN_PHYSICAL_CAMERA_TESTS = {
+ 'scene0': [
+ 'test_read_write'
+ ],
+ 'scene1': [
+ 'test_dng_noise_model'
+ ],
+ 'scene2': [],
+ 'scene2b': [],
+ 'scene2c': [],
+ 'scene3': [],
+ 'scene4': [],
+ 'scene5': [],
+ 'sensor_fusion': [
+ 'test_sensor_fusion'
+ ]
+}
def run_subprocess_with_timeout(cmd, fout, ferr, outdir):
"""Run subprocess with a timeout.
@@ -103,10 +121,11 @@
return test_code
-def calc_camera_fov(camera_id):
+def calc_camera_fov(camera_id, hidden_physical_id):
"""Determine the camera field of view from internal params."""
- with ItsSession(camera_id) as cam:
+ with ItsSession(camera_id, hidden_physical_id) as cam:
props = cam.get_camera_properties()
+ props = cam.override_with_hidden_physical_camera_props(props)
focal_ls = props['android.lens.info.availableFocalLengths']
if len(focal_ls) > 1:
print 'Doing capture to determine logical camera focal length'
@@ -204,7 +223,7 @@
"scene5": ["doAF=False"]
}
- camera_ids = []
+ camera_id_combos = []
scenes = []
chart_host_id = None
result_device_id = None
@@ -213,10 +232,13 @@
skip_scene_validation = False
chart_distance = CHART_DISTANCE
chart_level = CHART_LEVEL
+ one_camera_argv = sys.argv[1:]
- for s in sys.argv[1:]:
+ for s in list(sys.argv[1:]):
if s[:7] == "camera=" and len(s) > 7:
camera_ids = s[7:].split(',')
+ camera_id_combos = its.device.parse_camera_ids(camera_ids)
+ one_camera_argv.remove(s)
elif s[:7] == "scenes=" and len(s) > 7:
scenes = s[7:].split(',')
elif s[:6] == 'chart=' and len(s) > 6:
@@ -342,11 +364,12 @@
assert device_bfp == result_device_bfp, assert_err_msg
# user doesn't specify camera id, run through all cameras
- if not camera_ids:
+ if not camera_id_combos:
with its.device.ItsSession() as cam:
camera_ids = cam.get_camera_ids()
+ camera_id_combos = its.device.parse_camera_ids(camera_ids);
- print "Running ITS on camera: %s, scene %s" % (camera_ids, scenes)
+ print "Running ITS on camera: %s, scene %s" % (camera_id_combos, scenes)
if auto_scene_switch:
# merge_result only supports run_parallel_tests
@@ -362,15 +385,20 @@
wake_code = subprocess.call(cmd)
assert wake_code == 0
- for camera_id in camera_ids:
- camera_fov = calc_camera_fov(camera_id)
+ for id_combo in camera_id_combos:
+ camera_fov = calc_camera_fov(id_combo.id, id_combo.sub_id)
+ id_combo_string = id_combo.id;
+ has_hidden_sub_camera = id_combo.sub_id is not None
+ if has_hidden_sub_camera:
+ id_combo_string += ":" + id_combo.sub_id
+ scenes = [scene for scene in scenes if HIDDEN_PHYSICAL_CAMERA_TESTS[scene]]
# Loop capturing images until user confirm test scene is correct
- camera_id_arg = "camera=" + camera_id
- print "Preparing to run ITS on camera", camera_id
+ camera_id_arg = "camera=" + id_combo.id
+ print "Preparing to run ITS on camera", id_combo_string, "for scenes ", scenes
- os.mkdir(os.path.join(topdir, camera_id))
+ os.mkdir(os.path.join(topdir, id_combo_string))
for d in scenes:
- os.mkdir(os.path.join(topdir, camera_id, d))
+ os.mkdir(os.path.join(topdir, id_combo_string, d))
tot_tests = []
tot_pass = 0
@@ -382,17 +410,17 @@
tests.sort()
tot_tests.extend(tests)
- summary = "Cam" + camera_id + " " + scene + "\n"
+ summary = "Cam" + id_combo_string + " " + scene + "\n"
numpass = 0
numskip = 0
num_not_mandated_fail = 0
numfail = 0
validate_switch = True
if scene_req[scene] is not None:
- out_path = os.path.join(topdir, camera_id, scene+".jpg")
+ out_path = os.path.join(topdir, id_combo_string, scene+".jpg")
out_arg = "out=" + out_path
if scene == 'sensor_fusion':
- skip_code = skip_sensor_fusion(camera_id)
+ skip_code = skip_sensor_fusion(id_combo.id)
if rot_rig_id or skip_code == SKIP_RET_CODE:
validate_switch = False
if skip_scene_validation:
@@ -400,7 +428,7 @@
cmd = None
if auto_scene_switch:
if (not merge_result_switch or
- (merge_result_switch and camera_ids[0] == '0')):
+ (merge_result_switch and id_combo_string == '0')):
scene_arg = 'scene=' + scene
fov_arg = 'fov=' + camera_fov
cmd = ['python',
@@ -421,7 +449,7 @@
if cmd is not None:
valid_scene_code = subprocess.call(cmd, cwd=topdir)
assert valid_scene_code == 0
- print 'Start running ITS on camera %s, %s' % (camera_id, scene)
+ print 'Start running ITS on camera %s, %s' % (id_combo_string, scene)
# Extract chart from scene for scene3 once up front
chart_loc_arg = ''
chart_height = CHART_HEIGHT
@@ -431,14 +459,19 @@
chart = its.cv2image.Chart(SCENE3_FILE, chart_height,
chart_distance, CHART_SCALE_START,
CHART_SCALE_STOP, CHART_SCALE_STEP,
- camera_id)
+ id_combo.id)
chart_loc_arg = 'chart_loc=%.2f,%.2f,%.2f,%.2f,%.3f' % (
chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm,
chart.scale)
# Run each test, capturing stdout and stderr.
for (testname, testpath) in tests:
+ # Only pick predefined tests for hidden physical camera
+ if has_hidden_sub_camera and \
+ testname not in HIDDEN_PHYSICAL_CAMERA_TESTS[scene]:
+ numskip += 1
+ continue
if auto_scene_switch:
- if merge_result_switch and camera_ids[0] == '0':
+ if merge_result_switch and id_combo_string == '0':
# Send an input event to keep the screen not dimmed.
# Since we are not using camera of chart screen, FOCUS event
# should do nothing but keep the screen from dimming.
@@ -450,7 +483,7 @@
subprocess.call(cmd.split())
t0 = time.time()
for num_try in range(NUM_TRYS):
- outdir = os.path.join(topdir, camera_id, scene)
+ outdir = os.path.join(topdir, id_combo_string, scene)
outpath = os.path.join(outdir, testname+'_stdout.txt')
errpath = os.path.join(outdir, testname+'_stderr.txt')
if scene == 'sensor_fusion':
@@ -466,7 +499,7 @@
test_code = skip_code
if skip_code is not SKIP_RET_CODE:
cmd = ['python', os.path.join(os.getcwd(), testpath)]
- cmd += sys.argv[1:] + [camera_id_arg] + [chart_loc_arg]
+ cmd += one_camera_argv + ["Camera="+id_combo_string] + [chart_loc_arg]
cmd += [chart_dist_arg]
with open(outpath, 'w') as fout, open(errpath, 'w') as ferr:
test_code = run_subprocess_with_timeout(
@@ -527,7 +560,7 @@
print "%s compatibility score: %.f/100\n" % (
scene, 100.0 * numpass / len(tests))
- summary_path = os.path.join(topdir, camera_id, scene, "summary.txt")
+ summary_path = os.path.join(topdir, id_combo_string, scene, "summary.txt")
with open(summary_path, "w") as f:
f.write(summary)
@@ -536,7 +569,10 @@
else ItsSession.RESULT_FAIL)
results[scene][ItsSession.SUMMARY_KEY] = summary_path
- print "Compatibility Score: %.f/100" % (100.0 * tot_pass / len(tot_tests))
+ if tot_tests:
+ print "Compatibility Score: %.f/100" % (100.0 * tot_pass / len(tot_tests))
+ else:
+ print "Compatibility Score: 0/100"
msg = "Reporting ITS result to CtsVerifier"
print msg
@@ -544,9 +580,10 @@
if merge_result_switch:
# results are modified by report_result
results_backup = copy.deepcopy(results)
- its.device.report_result(result_device_id, camera_id, results_backup)
+ its.device.report_result(result_device_id, id_combo_string, results_backup)
- its.device.report_result(device_id, camera_id, results)
+ # Report hidden_physical_id results as well.
+ its.device.report_result(device_id, id_combo_string, results)
if auto_scene_switch:
if merge_result_switch:
diff --git a/apps/CameraITS/tools/run_sensor_fusion_box.py b/apps/CameraITS/tools/run_sensor_fusion_box.py
index 3c9199a..82f915d 100644
--- a/apps/CameraITS/tools/run_sensor_fusion_box.py
+++ b/apps/CameraITS/tools/run_sensor_fusion_box.py
@@ -91,7 +91,7 @@
print 'Testing device ' + device_id
# ensure camera_id is valid
- avail_camera_ids = find_avail_camera_ids(device_id_arg, tmpdir)
+ avail_camera_ids = find_avail_camera_ids()
if camera_id not in avail_camera_ids:
print 'Need to specify valid camera_id in ', avail_camera_ids
sys.exit()
@@ -220,12 +220,9 @@
return line
return None
-def find_avail_camera_ids(device_id_arg, tmpdir):
+def find_avail_camera_ids():
"""Find the available camera IDs.
- Args:
- devices_id_arg(str): device=###
- tmpdir(str): generated tmp dir for run
Returns:
list of available cameras
"""
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
index f5306c3..47137b4 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
@@ -159,6 +159,7 @@
private CameraCharacteristics mCameraCharacteristics = null;
private HashMap<String, CameraCharacteristics> mPhysicalCameraChars =
new HashMap<String, CameraCharacteristics>();
+ private ItsUtils.ItsCameraIdList mItsCameraIdList = null;
private Vibrator mVibrator = null;
@@ -362,11 +363,13 @@
try {
if (mMemoryQuota == -1) {
// Initialize memory quota on this device
- List<String> devices = ItsUtils.getItsCompatibleCameraIds(mCameraManager);
- if (devices.size() == 0) {
+ if (mItsCameraIdList == null) {
+ mItsCameraIdList = ItsUtils.getItsCompatibleCameraIds(mCameraManager);
+ }
+ if (mItsCameraIdList.mCameraIds.size() == 0) {
throw new ItsException("No camera devices");
}
- for (String camId : devices) {
+ for (String camId : mItsCameraIdList.mCameraIds) {
CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(camId);
Size maxYuvSize = ItsUtils.getMaxOutputSize(
chars, ImageFormat.YUV_420_888);
@@ -949,15 +952,17 @@
}
private void doGetCameraIds() throws ItsException {
- List<String> devices = ItsUtils.getItsCompatibleCameraIds(mCameraManager);
- if (devices.size() == 0) {
+ if (mItsCameraIdList == null) {
+ mItsCameraIdList = ItsUtils.getItsCompatibleCameraIds(mCameraManager);
+ }
+ if (mItsCameraIdList.mCameraIdCombos.size() == 0) {
throw new ItsException("No camera devices");
}
try {
JSONObject obj = new JSONObject();
JSONArray array = new JSONArray();
- for (String id : devices) {
+ for (String id : mItsCameraIdList.mCameraIdCombos) {
array.put(id);
}
obj.put("cameraIdArray", array);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
index fd62ed2..901fe4c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
@@ -91,6 +91,13 @@
add("scene5");
add("sensor_fusion");
} };
+ // This must match scenes of HIDDEN_PHYSICAL_CAMERA_TESTS in run_all_tests.py
+ private static final ArrayList<String> mHiddenPhysicalCameraSceneIds =
+ new ArrayList<String> () { {
+ add("scene0");
+ add("scene1");
+ add("sensor_fusion");
+ }};
// TODO: cache the following in saved bundle
private Set<ResultKey> mAllScenes = null;
@@ -332,7 +339,8 @@
// Hide the test if all camera devices are legacy
CameraManager manager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);
try {
- mToBeTestedCameraIds = ItsUtils.getItsCompatibleCameraIds(manager);
+ ItsUtils.ItsCameraIdList cameraIdList = ItsUtils.getItsCompatibleCameraIds(manager);
+ mToBeTestedCameraIds = cameraIdList.mCameraIdCombos;
} catch (ItsException e) {
Toast.makeText(ItsTestActivity.this,
"Received error from camera service while checking device capabilities: "
@@ -366,7 +374,8 @@
protected void setupItsTests(ArrayTestListAdapter adapter) {
for (String cam : mToBeTestedCameraIds) {
- for (String scene : mSceneIds) {
+ List<String> scenes = cam.contains(":") ? mHiddenPhysicalCameraSceneIds : mSceneIds;
+ for (String scene : scenes) {
adapter.add(new DialogTestListItem(this,
testTitle(cam, scene),
testId(cam, scene)));
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
index 6fcaf69..cd739b5 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
@@ -31,17 +31,25 @@
import android.media.Image.Plane;
import android.net.Uri;
import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
+import com.android.ex.camera2.blocking.BlockingCameraManager;
+import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
+import com.android.ex.camera2.blocking.BlockingStateCallback;
+
import org.json.JSONArray;
import org.json.JSONObject;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.concurrent.Semaphore;
import java.util.List;
+import java.util.Set;
public class ItsUtils {
@@ -306,13 +314,23 @@
}
}
- public static List<String> getItsCompatibleCameraIds(CameraManager manager)
+ public static class ItsCameraIdList {
+ // Short form camera Ids (including both CameraIdList and hidden physical cameras
+ public List<String> mCameraIds;
+ // Camera Id combos (ids from CameraIdList, and hidden physical camera Ids
+ // in the form of [logical camera id]:[hidden physical camera id]
+ public List<String> mCameraIdCombos;
+ }
+
+ public static ItsCameraIdList getItsCompatibleCameraIds(CameraManager manager)
throws ItsException {
if (manager == null) {
throw new IllegalArgumentException("CameraManager is null");
}
- ArrayList<String> outList = new ArrayList<String>();
+ ItsCameraIdList outList = new ItsCameraIdList();
+ outList.mCameraIds = new ArrayList<String>();
+ outList.mCameraIdCombos = new ArrayList<String>();
try {
String[] cameraIds = manager.getCameraIdList();
for (String id : cameraIds) {
@@ -320,12 +338,17 @@
int[] actualCapabilities = characteristics.get(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
boolean haveBC = false;
+ boolean isMultiCamera = false;
final int BACKWARD_COMPAT =
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE;
+ final int LOGICAL_MULTI_CAMERA =
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA;
for (int capability : actualCapabilities) {
if (capability == BACKWARD_COMPAT) {
haveBC = true;
- break;
+ }
+ if (capability == LOGICAL_MULTI_CAMERA) {
+ isMultiCamera = true;
}
}
@@ -339,7 +362,39 @@
// Skip LEGACY and EXTERNAL devices
continue;
}
- outList.add(id);
+ outList.mCameraIds.add(id);
+ outList.mCameraIdCombos.add(id);
+
+ // Only add hidden physical cameras for multi-camera.
+ if (!isMultiCamera) continue;
+
+ float defaultFocalLength = getLogicalCameraDefaultFocalLength(manager, id);
+ Set<String> physicalIds = characteristics.getPhysicalCameraIds();
+ for (String physicalId : physicalIds) {
+ if (Arrays.asList(cameraIds).contains(physicalId)) continue;
+
+ CameraCharacteristics physicalChar =
+ manager.getCameraCharacteristics(physicalId);
+ hwLevel = characteristics.get(
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+ if (hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY ||
+ hwLevel ==
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL) {
+ // Skip LEGACY and EXTERNAL devices
+ continue;
+ }
+
+ // To reduce duplicate tests, only additionally test hidden physical cameras
+ // with different focal length compared to the default focal length of the
+ // logical camera.
+ float[] physicalFocalLengths = physicalChar.get(
+ CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
+ if (defaultFocalLength != physicalFocalLengths[0]) {
+ outList.mCameraIds.add(physicalId);
+ outList.mCameraIdCombos.add(id + ":" + physicalId);
+ }
+ }
+
}
} catch (CameraAccessException e) {
Logt.e(TAG,
@@ -348,4 +403,32 @@
}
return outList;
}
+
+ public static float getLogicalCameraDefaultFocalLength(CameraManager manager,
+ String cameraId) throws ItsException {
+ BlockingCameraManager blockingManager = new BlockingCameraManager(manager);
+ BlockingStateCallback listener = new BlockingStateCallback();
+ HandlerThread cameraThread = new HandlerThread("ItsUtilThread");
+ cameraThread.start();
+ Handler cameraHandler = new Handler(cameraThread.getLooper());
+ CameraDevice camera = null;
+ float defaultFocalLength = 0.0f;
+
+ try {
+ camera = blockingManager.openCamera(cameraId, listener, cameraHandler);
+ CaptureRequest.Builder previewBuilder =
+ camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ defaultFocalLength = previewBuilder.get(CaptureRequest.LENS_FOCAL_LENGTH);
+ } catch (Exception e) {
+ throw new ItsException("Failed to query default focal length for logical camera", e);
+ } finally {
+ if (camera != null) {
+ camera.close();
+ }
+ if (cameraThread != null) {
+ cameraThread.quitSafely();
+ }
+ }
+ return defaultFocalLength;
+ }
}