CameraITS: Support connecting multiple devices
Add "device=" command line argument to support running Camera ITS
on a specific device when multiple devices are connected.
Bug: 21755489
Change-Id: I7962a6335310cdf849c3fa3ddabdaa84bd5f65ae
diff --git a/apps/CameraITS/CameraITS.pdf b/apps/CameraITS/CameraITS.pdf
index 5a511c0..8953af9 100644
--- a/apps/CameraITS/CameraITS.pdf
+++ b/apps/CameraITS/CameraITS.pdf
Binary files differ
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index 8773335..dec37db 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -41,12 +41,21 @@
sock: The open socket.
"""
- # Open a connection to localhost:6000, forwarded to port 6000 on the device.
- # TODO: Support multiple devices running over different TCP ports.
+ # Open a connection to localhost:<host_port>, forwarded to port 6000 on the
+ # device. <host_port> is determined at run-time to support multiple
+ # connected devices.
IPADDR = '127.0.0.1'
- PORT = 6000
+ REMOTE_PORT = 6000
BUFFER_SIZE = 4096
+ # LOCK_PORT is used as a mutex lock to protect the list of forwarded ports
+ # among all processes. The script assumes LOCK_PORT is available and will
+ # try to use ports between CLIENT_PORT_START and
+ # CLIENT_PORT_START+MAX_NUM_PORTS-1 on host for ITS sessions.
+ CLIENT_PORT_START = 6000
+ MAX_NUM_PORTS = 100
+ LOCK_PORT = CLIENT_PORT_START + MAX_NUM_PORTS
+
# Seconds timeout on each socket operation.
SOCK_TIMEOUT = 10.0
@@ -57,8 +66,8 @@
EXTRA_SUCCESS = 'camera.its.extra.SUCCESS'
EXTRA_SUMMARY = 'camera.its.extra.SUMMARY'
- # TODO: Handle multiple connected devices.
- ADB = "adb -d"
+ adb = "adb -d"
+ device_id = ""
# Definitions for some of the common output format options for do_capture().
# Each gets images of full resolution for each requested format.
@@ -74,12 +83,83 @@
CAP_RAW_YUV_JPEG = [{"format":"raw"}, {"format":"yuv"}, {"format":"jpeg"}]
CAP_DNG_YUV_JPEG = [{"format":"dng"}, {"format":"yuv"}, {"format":"jpeg"}]
- # Method to handle the case where the service isn't already running.
- # This occurs when a test is invoked directly from the command line, rather
- # than as a part of a separate test harness which is setting up the device
- # and the TCP forwarding.
- def __pre_init(self):
+ # Initialize the socket port for the host to forward requests to the device.
+ # This method assumes localhost's LOCK_PORT is available and will try to
+ # use ports between CLIENT_PORT_START and CLIENT_PORT_START+MAX_NUM_PORTS-1
+ def __init_socket_port(self):
+ NUM_RETRIES = 100
+ RETRY_WAIT_TIME_SEC = 0.05
+ # Bind a socket to use as mutex lock
+ socket_lock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ for i in range(NUM_RETRIES):
+ try:
+ socket_lock.bind((ItsSession.IPADDR, ItsSession.LOCK_PORT))
+ break
+ except socket.error:
+ if i == NUM_RETRIES - 1:
+ raise its.error.Error(self.device_id,
+ "acquiring socket lock timed out")
+ else:
+ time.sleep(RETRY_WAIT_TIME_SEC)
+
+ # Check if a port is already assigned to the device.
+ command = "adb forward --list"
+ proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
+ output, error = proc.communicate()
+
+ port = None
+ used_ports = []
+ for line in output.split(os.linesep):
+ # each line should be formatted as:
+ # "<device_id> tcp:<host_port> tcp:<remote_port>"
+ forward_info = line.split()
+ if len(forward_info) >= 3 and \
+ len(forward_info[1]) > 4 and forward_info[1][:4] == "tcp:" and \
+ len(forward_info[2]) > 4 and forward_info[2][:4] == "tcp:":
+ local_p = int(forward_info[1][4:])
+ remote_p = int(forward_info[2][4:])
+ if forward_info[0] == self.device_id and \
+ remote_p == ItsSession.REMOTE_PORT:
+ port = local_p
+ break;
+ else:
+ used_ports.append(local_p)
+
+ # Find the first available port if no port is assigned to the device.
+ if port is None:
+ for p in range(ItsSession.CLIENT_PORT_START,
+ ItsSession.CLIENT_PORT_START +
+ ItsSession.MAX_NUM_PORTS):
+ if p not in used_ports:
+ # Try to run "adb forward" with the port
+ command = "%s forward tcp:%d tcp:%d" % \
+ (self.adb, p, self.REMOTE_PORT)
+ proc = subprocess.Popen(command.split(),
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ output, error = proc.communicate()
+
+ # Check if there is no error
+ if error is None or error.find("error") < 0:
+ port = p
+ break
+
+ if port is None:
+ raise its.error.Error(self.device_id, " cannot find an available " +
+ "port")
+
+ # Release the socket as mutex unlock
+ socket_lock.close()
+
+ # Connect to the socket
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.sock.connect((self.IPADDR, port))
+ self.sock.settimeout(self.SOCK_TIMEOUT)
+
+ # Reboot the device if needed and wait for the service to be ready for
+ # connection.
+ def __wait_for_service(self):
# This also includes the optional reboot handling: if the user
# provides a "reboot" or "reboot=N" arg, then reboot the device,
# waiting for N seconds (default 30) before returning.
@@ -89,19 +169,19 @@
if len(s) > 7 and s[6] == "=":
duration = int(s[7:])
print "Rebooting device"
- _run("%s reboot" % (ItsSession.ADB));
- _run("%s wait-for-device" % (ItsSession.ADB))
+ _run("%s reboot" % (self.adb));
+ _run("%s wait-for-device" % (self.adb))
time.sleep(duration)
print "Reboot complete"
# TODO: Figure out why "--user 0" is needed, and fix the problem.
- _run('%s shell am force-stop --user 0 %s' % (ItsSession.ADB, self.PACKAGE))
+ _run('%s shell am force-stop --user 0 %s' % (self.adb, self.PACKAGE))
_run(('%s shell am startservice --user 0 -t text/plain '
- '-a %s') % (ItsSession.ADB, self.INTENT_START))
+ '-a %s') % (self.adb, self.INTENT_START))
# Wait until the socket is ready to accept a connection.
proc = subprocess.Popen(
- ItsSession.ADB.split() + ["logcat"],
+ self.adb.split() + ["logcat"],
stdout=subprocess.PIPE)
logcat = proc.stdout
while True:
@@ -110,15 +190,14 @@
break
proc.kill()
- # Setup the TCP-over-ADB forwarding.
- _run('%s forward tcp:%d tcp:%d' % (ItsSession.ADB,self.PORT,self.PORT))
-
def __init__(self):
- if "noinit" not in sys.argv:
- self.__pre_init()
- self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.sock.connect((self.IPADDR, self.PORT))
- self.sock.settimeout(self.SOCK_TIMEOUT)
+ # Initialize device id and adb command.
+ self.device_id = get_device_id()
+ self.adb = "adb -s " + self.device_id
+
+ self.__wait_for_service()
+ self.__init_socket_port()
+
self.__close_camera()
self.__open_camera()
@@ -544,10 +623,52 @@
rets.append(objs if ncap>1 else objs[0])
return rets if len(rets)>1 else rets[0]
-def report_result(camera_id, success, summary_path=None):
+def get_device_id():
+ """ Return the ID of the device that the test is running on.
+
+ Return the device ID provided in the command line if it's connected. If no
+ device ID is provided in the command line and there is only one device
+ connected, return the device ID by parsing the result of "adb devices".
+
+ Raise an exception if no device is connected; or the device ID provided in
+ the command line is not connected; or no device ID is provided in the
+ command line and there are more than 1 device connected.
+
+ Returns:
+ Device ID string.
+ """
+ device_id = None
+ for s in sys.argv[1:]:
+ if s[:7] == "device=" and len(s) > 7:
+ device_id = str(s[7:])
+
+ # Get a list of connected devices
+ devices = []
+ command = "adb devices"
+ proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
+ output, error = proc.communicate()
+ for line in output.split(os.linesep):
+ device_info = line.split()
+ if len(device_info) == 2 and device_info[1] == "device":
+ devices.append(device_info[0])
+
+ if len(devices) == 0:
+ raise its.error.Error("No device is connected!")
+ elif device_id is not None and device_id not in devices:
+ raise its.error.Error(device_id + " is not connected!")
+ elif device_id is None and len(devices) >= 2:
+ raise its.error.Error("More than 1 device are connected. " +
+ "Use device=<device_id> to specify a device to test.")
+ elif len(devices) == 1:
+ device_id = devices[0]
+
+ return device_id
+
+def report_result(device_id, camera_id, success, summary_path=None):
"""Send a pass/fail result to the device, via an intent.
Args:
+ device_id: The ID string of the device to report the results to.
camera_id: The ID string of the camera for which to report pass/fail.
success: Boolean, indicating if the result was pass or fail.
summary_path: (Optional) path to ITS summary file on host PC
@@ -555,18 +676,19 @@
Returns:
Nothing.
"""
+ adb = "adb -s " + device_id
device_summary_path = "/sdcard/camera_" + camera_id + "_its_summary.txt"
if summary_path is not None:
_run("%s push %s %s" % (
- ItsSession.ADB, summary_path, device_summary_path))
+ adb, summary_path, device_summary_path))
_run("%s shell am broadcast -a %s --es %s %s --es %s %s --es %s %s" % (
- ItsSession.ADB, ItsSession.ACTION_ITS_RESULT,
+ adb, ItsSession.ACTION_ITS_RESULT,
ItsSession.EXTRA_CAMERA_ID, camera_id,
ItsSession.EXTRA_SUCCESS, 'True' if success else 'False',
ItsSession.EXTRA_SUMMARY, device_summary_path))
else:
_run("%s shell am broadcast -a %s --es %s %s --es %s %s --es %s %s" % (
- ItsSession.ADB, ItsSession.ACTION_ITS_RESULT,
+ adb, ItsSession.ACTION_ITS_RESULT,
ItsSession.EXTRA_CAMERA_ID, camera_id,
ItsSession.EXTRA_SUCCESS, 'True' if success else 'False',
ItsSession.EXTRA_SUMMARY, "null"))
diff --git a/apps/CameraITS/tools/config.py b/apps/CameraITS/tools/config.py
index 6e83412..52929aa 100644
--- a/apps/CameraITS/tools/config.py
+++ b/apps/CameraITS/tools/config.py
@@ -44,7 +44,7 @@
# Command line args, ignoring any args that will be passed down to the
# ItsSession constructor.
args = [s for s in sys.argv if s[:6] not in \
- ["reboot", "camera", "target", "noinit"]]
+ ["reboot", "camera", "target", "device"]]
if len(args) == 1:
with its.device.ItsSession() as cam:
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 625ebaa..dd12512 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -66,6 +66,10 @@
topdir = tempfile.mkdtemp()
print "Saving output files to:", topdir, "\n"
+ device_id = its.device.get_device_id()
+ device_id_arg = "device=" + device_id
+ print "Testing device " + device_id
+
camera_ids = []
for s in sys.argv[1:]:
if s[:7] == "camera=" and len(s) > 7:
@@ -76,7 +80,8 @@
camera_ids_path = os.path.join(topdir, "camera_ids.txt")
out_arg = "out=" + camera_ids_path
cmd = ['python',
- os.path.join(os.getcwd(),"tools/get_camera_ids.py"), out_arg]
+ os.path.join(os.getcwd(),"tools/get_camera_ids.py"), out_arg,
+ device_id_arg]
retcode = subprocess.call(cmd,cwd=topdir)
assert(retcode == 0)
with open(camera_ids_path, "r") as f:
@@ -110,7 +115,7 @@
scene_arg = "scene=" + scene_req[scene]
cmd = ['python',
os.path.join(os.getcwd(),"tools/validate_scene.py"),
- camera_id_arg, out_arg, scene_arg]
+ camera_id_arg, out_arg, scene_arg, device_id_arg]
retcode = subprocess.call(cmd,cwd=topdir)
assert(retcode == 0)
print "Start running tests for", scene
@@ -170,7 +175,7 @@
summary_path = os.path.join(topdir, camera_id, "summary.txt")
with open(summary_path, "w") as f:
f.write(summary)
- its.device.report_result(camera_id, result, summary_path)
+ its.device.report_result(device_id, camera_id, result, summary_path)
print "ITS tests finished. Please go back to CtsVerifier and proceed"