blob: cb3f51a599cda595e827cc65438e07ed4326c0b9 [file] [log] [blame]
Ruben Brunk370e2432014-10-14 18:33:23 -07001# Copyright 2013 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import its.error
16import os
17import os.path
18import sys
19import re
20import json
21import time
22import unittest
23import socket
24import subprocess
25import hashlib
26import numpy
Sam Linf2f66002017-02-27 21:08:11 -080027import string
Shuzhen Wang80822a92018-01-29 09:27:42 -080028import unicodedata
Ruben Brunk370e2432014-10-14 18:33:23 -070029
Clemenz Portmanna2815fa2017-05-23 08:31:19 -070030
Ruben Brunk370e2432014-10-14 18:33:23 -070031class ItsSession(object):
32 """Controls a device over adb to run ITS scripts.
33
34 The script importing this module (on the host machine) prepares JSON
35 objects encoding CaptureRequests, specifying sets of parameters to use
Chien-Yu Chen682faa22014-10-22 17:34:44 -070036 when capturing an image using the Camera2 APIs. This class encapsulates
Ruben Brunk370e2432014-10-14 18:33:23 -070037 sending the requests to the device, monitoring the device's progress, and
38 copying the resultant captures back to the host machine when done. TCP
39 forwarded over adb is the transport mechanism used.
40
41 The device must have CtsVerifier.apk installed.
42
43 Attributes:
44 sock: The open socket.
45 """
46
Chien-Yu Chen1da23132015-07-22 15:24:41 -070047 # Open a connection to localhost:<host_port>, forwarded to port 6000 on the
48 # device. <host_port> is determined at run-time to support multiple
49 # connected devices.
Ruben Brunk370e2432014-10-14 18:33:23 -070050 IPADDR = '127.0.0.1'
Chien-Yu Chen1da23132015-07-22 15:24:41 -070051 REMOTE_PORT = 6000
Ruben Brunk370e2432014-10-14 18:33:23 -070052 BUFFER_SIZE = 4096
53
Chien-Yu Chen1da23132015-07-22 15:24:41 -070054 # LOCK_PORT is used as a mutex lock to protect the list of forwarded ports
55 # among all processes. The script assumes LOCK_PORT is available and will
56 # try to use ports between CLIENT_PORT_START and
57 # CLIENT_PORT_START+MAX_NUM_PORTS-1 on host for ITS sessions.
58 CLIENT_PORT_START = 6000
59 MAX_NUM_PORTS = 100
60 LOCK_PORT = CLIENT_PORT_START + MAX_NUM_PORTS
61
Ruben Brunk370e2432014-10-14 18:33:23 -070062 # Seconds timeout on each socket operation.
Yin-Chia Yehea1c1a62016-07-12 15:29:30 -070063 SOCK_TIMEOUT = 20.0
Yin-Chia Yehc9d554d2016-06-01 17:30:32 -070064 # Additional timeout in seconds when ITS service is doing more complicated
65 # operations, for example: issuing warmup requests before actual capture.
66 EXTRA_SOCK_TIMEOUT = 5.0
67
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -070068 SEC_TO_NSEC = 1000*1000*1000.0
Ruben Brunk370e2432014-10-14 18:33:23 -070069
70 PACKAGE = 'com.android.cts.verifier.camera.its'
71 INTENT_START = 'com.android.cts.verifier.camera.its.START'
72 ACTION_ITS_RESULT = 'com.android.cts.verifier.camera.its.ACTION_ITS_RESULT'
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -070073 EXTRA_VERSION = 'camera.its.extra.VERSION'
74 CURRENT_ITS_VERSION = '1.0' # version number to sync with CtsVerifier
Yin-Chia Yehab98ada2015-03-05 13:28:53 -080075 EXTRA_CAMERA_ID = 'camera.its.extra.CAMERA_ID'
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -070076 EXTRA_RESULTS = 'camera.its.extra.RESULTS'
Sam Linf2f66002017-02-27 21:08:11 -080077 ITS_TEST_ACTIVITY = 'com.android.cts.verifier/.camera.its.ItsTestActivity'
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -070078
79 RESULT_PASS = 'PASS'
80 RESULT_FAIL = 'FAIL'
81 RESULT_NOT_EXECUTED = 'NOT_EXECUTED'
82 RESULT_VALUES = {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}
83 RESULT_KEY = 'result'
84 SUMMARY_KEY = 'summary'
Ruben Brunk370e2432014-10-14 18:33:23 -070085
Chien-Yu Chen1da23132015-07-22 15:24:41 -070086 adb = "adb -d"
87 device_id = ""
Ruben Brunk370e2432014-10-14 18:33:23 -070088
89 # Definitions for some of the common output format options for do_capture().
90 # Each gets images of full resolution for each requested format.
91 CAP_RAW = {"format":"raw"}
92 CAP_DNG = {"format":"dng"}
93 CAP_YUV = {"format":"yuv"}
94 CAP_JPEG = {"format":"jpeg"}
95 CAP_RAW_YUV = [{"format":"raw"}, {"format":"yuv"}]
96 CAP_DNG_YUV = [{"format":"dng"}, {"format":"yuv"}]
97 CAP_RAW_JPEG = [{"format":"raw"}, {"format":"jpeg"}]
98 CAP_DNG_JPEG = [{"format":"dng"}, {"format":"jpeg"}]
99 CAP_YUV_JPEG = [{"format":"yuv"}, {"format":"jpeg"}]
100 CAP_RAW_YUV_JPEG = [{"format":"raw"}, {"format":"yuv"}, {"format":"jpeg"}]
101 CAP_DNG_YUV_JPEG = [{"format":"dng"}, {"format":"yuv"}, {"format":"jpeg"}]
102
Lu75f22fc2016-02-19 10:54:07 -0800103 # Predefine camera props. Save props extracted from the function,
104 # "get_camera_properties".
105 props = None
106
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700107 # Initialize the socket port for the host to forward requests to the device.
108 # This method assumes localhost's LOCK_PORT is available and will try to
109 # use ports between CLIENT_PORT_START and CLIENT_PORT_START+MAX_NUM_PORTS-1
110 def __init_socket_port(self):
111 NUM_RETRIES = 100
112 RETRY_WAIT_TIME_SEC = 0.05
Ruben Brunk370e2432014-10-14 18:33:23 -0700113
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700114 # Bind a socket to use as mutex lock
115 socket_lock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
116 for i in range(NUM_RETRIES):
117 try:
118 socket_lock.bind((ItsSession.IPADDR, ItsSession.LOCK_PORT))
119 break
Clemenz Portmannb0e76152018-02-01 09:12:57 -0800120 except socket.error or socket.timeout:
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700121 if i == NUM_RETRIES - 1:
122 raise its.error.Error(self.device_id,
Clemenz Portmannb0e76152018-02-01 09:12:57 -0800123 "socket lock returns error")
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700124 else:
125 time.sleep(RETRY_WAIT_TIME_SEC)
126
127 # Check if a port is already assigned to the device.
128 command = "adb forward --list"
129 proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
130 output, error = proc.communicate()
131
132 port = None
133 used_ports = []
134 for line in output.split(os.linesep):
135 # each line should be formatted as:
136 # "<device_id> tcp:<host_port> tcp:<remote_port>"
137 forward_info = line.split()
138 if len(forward_info) >= 3 and \
139 len(forward_info[1]) > 4 and forward_info[1][:4] == "tcp:" and \
140 len(forward_info[2]) > 4 and forward_info[2][:4] == "tcp:":
141 local_p = int(forward_info[1][4:])
142 remote_p = int(forward_info[2][4:])
143 if forward_info[0] == self.device_id and \
144 remote_p == ItsSession.REMOTE_PORT:
145 port = local_p
146 break;
147 else:
148 used_ports.append(local_p)
149
150 # Find the first available port if no port is assigned to the device.
151 if port is None:
152 for p in range(ItsSession.CLIENT_PORT_START,
153 ItsSession.CLIENT_PORT_START +
154 ItsSession.MAX_NUM_PORTS):
155 if p not in used_ports:
156 # Try to run "adb forward" with the port
157 command = "%s forward tcp:%d tcp:%d" % \
158 (self.adb, p, self.REMOTE_PORT)
159 proc = subprocess.Popen(command.split(),
160 stdout=subprocess.PIPE,
161 stderr=subprocess.PIPE)
162 output, error = proc.communicate()
163
164 # Check if there is no error
165 if error is None or error.find("error") < 0:
166 port = p
167 break
168
169 if port is None:
170 raise its.error.Error(self.device_id, " cannot find an available " +
171 "port")
172
173 # Release the socket as mutex unlock
174 socket_lock.close()
175
176 # Connect to the socket
177 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
178 self.sock.connect((self.IPADDR, port))
179 self.sock.settimeout(self.SOCK_TIMEOUT)
180
181 # Reboot the device if needed and wait for the service to be ready for
182 # connection.
183 def __wait_for_service(self):
Ruben Brunk370e2432014-10-14 18:33:23 -0700184 # This also includes the optional reboot handling: if the user
185 # provides a "reboot" or "reboot=N" arg, then reboot the device,
186 # waiting for N seconds (default 30) before returning.
187 for s in sys.argv[1:]:
188 if s[:6] == "reboot":
189 duration = 30
190 if len(s) > 7 and s[6] == "=":
191 duration = int(s[7:])
192 print "Rebooting device"
Lu75f22fc2016-02-19 10:54:07 -0800193 _run("%s reboot" % (self.adb))
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700194 _run("%s wait-for-device" % (self.adb))
Ruben Brunk370e2432014-10-14 18:33:23 -0700195 time.sleep(duration)
196 print "Reboot complete"
197
Yin-Chia Yeh23a7b422016-03-25 14:48:47 -0700198 # Flush logcat so following code won't be misled by previous
199 # 'ItsService ready' log.
200 _run('%s logcat -c' % (self.adb))
201 time.sleep(1)
202
Ruben Brunk370e2432014-10-14 18:33:23 -0700203 # TODO: Figure out why "--user 0" is needed, and fix the problem.
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700204 _run('%s shell am force-stop --user 0 %s' % (self.adb, self.PACKAGE))
Yin-Chia Yeh6bdae902018-01-02 15:17:13 -0800205 _run(('%s shell am start-foreground-service --user 0 -t text/plain '
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700206 '-a %s') % (self.adb, self.INTENT_START))
Ruben Brunk370e2432014-10-14 18:33:23 -0700207
208 # Wait until the socket is ready to accept a connection.
209 proc = subprocess.Popen(
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700210 self.adb.split() + ["logcat"],
Ruben Brunk370e2432014-10-14 18:33:23 -0700211 stdout=subprocess.PIPE)
212 logcat = proc.stdout
213 while True:
214 line = logcat.readline().strip()
215 if line.find('ItsService ready') >= 0:
216 break
217 proc.kill()
218
Ruben Brunk370e2432014-10-14 18:33:23 -0700219 def __init__(self):
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700220 # Initialize device id and adb command.
221 self.device_id = get_device_id()
222 self.adb = "adb -s " + self.device_id
223
224 self.__wait_for_service()
225 self.__init_socket_port()
226
Ruben Brunk370e2432014-10-14 18:33:23 -0700227 self.__close_camera()
228 self.__open_camera()
229
230 def __del__(self):
231 if hasattr(self, 'sock') and self.sock:
232 self.__close_camera()
233 self.sock.close()
234
235 def __enter__(self):
236 return self
237
238 def __exit__(self, type, value, traceback):
239 return False
240
241 def __read_response_from_socket(self):
242 # Read a line (newline-terminated) string serialization of JSON object.
243 chars = []
244 while len(chars) == 0 or chars[-1] != '\n':
245 ch = self.sock.recv(1)
246 if len(ch) == 0:
247 # Socket was probably closed; otherwise don't get empty strings
248 raise its.error.Error('Problem with socket on device side')
249 chars.append(ch)
250 line = ''.join(chars)
251 jobj = json.loads(line)
252 # Optionally read a binary buffer of a fixed size.
253 buf = None
254 if jobj.has_key("bufValueSize"):
255 n = jobj["bufValueSize"]
256 buf = bytearray(n)
257 view = memoryview(buf)
258 while n > 0:
259 nbytes = self.sock.recv_into(view, n)
260 view = view[nbytes:]
261 n -= nbytes
262 buf = numpy.frombuffer(buf, dtype=numpy.uint8)
263 return jobj, buf
264
265 def __open_camera(self):
266 # Get the camera ID to open as an argument.
267 camera_id = 0
268 for s in sys.argv[1:]:
269 if s[:7] == "camera=" and len(s) > 7:
270 camera_id = int(s[7:])
271 cmd = {"cmdName":"open", "cameraId":camera_id}
272 self.sock.send(json.dumps(cmd) + "\n")
273 data,_ = self.__read_response_from_socket()
274 if data['tag'] != 'cameraOpened':
275 raise its.error.Error('Invalid command response')
276
277 def __close_camera(self):
278 cmd = {"cmdName":"close"}
279 self.sock.send(json.dumps(cmd) + "\n")
280 data,_ = self.__read_response_from_socket()
281 if data['tag'] != 'cameraClosed':
282 raise its.error.Error('Invalid command response')
283
284 def do_vibrate(self, pattern):
285 """Cause the device to vibrate to a specific pattern.
286
287 Args:
288 pattern: Durations (ms) for which to turn on or off the vibrator.
289 The first value indicates the number of milliseconds to wait
290 before turning the vibrator on. The next value indicates the
291 number of milliseconds for which to keep the vibrator on
292 before turning it off. Subsequent values alternate between
293 durations in milliseconds to turn the vibrator off or to turn
294 the vibrator on.
295
296 Returns:
297 Nothing.
298 """
299 cmd = {}
300 cmd["cmdName"] = "doVibrate"
301 cmd["pattern"] = pattern
302 self.sock.send(json.dumps(cmd) + "\n")
303 data,_ = self.__read_response_from_socket()
304 if data['tag'] != 'vibrationStarted':
305 raise its.error.Error('Invalid command response')
306
Daniel Hung-yu Wu130411c2018-03-30 15:05:18 +0800307 def get_sensors(self):
308 """Get all sensors on the device.
309
310 Returns:
311 A Python dictionary that returns keys and booleans for each sensor.
312 """
313 cmd = {}
314 cmd["cmdName"] = "checkSensorExistence"
315 self.sock.send(json.dumps(cmd) + "\n")
316 data,_ = self.__read_response_from_socket()
317 if data['tag'] != 'sensorExistence':
318 raise its.error.Error('Invalid command response')
319 return data['objValue']
320
Ruben Brunk370e2432014-10-14 18:33:23 -0700321 def start_sensor_events(self):
322 """Start collecting sensor events on the device.
323
324 See get_sensor_events for more info.
325
326 Returns:
327 Nothing.
328 """
329 cmd = {}
330 cmd["cmdName"] = "startSensorEvents"
331 self.sock.send(json.dumps(cmd) + "\n")
332 data,_ = self.__read_response_from_socket()
333 if data['tag'] != 'sensorEventsStarted':
334 raise its.error.Error('Invalid command response')
335
336 def get_sensor_events(self):
337 """Get a trace of all sensor events on the device.
338
339 The trace starts when the start_sensor_events function is called. If
340 the test runs for a long time after this call, then the device's
341 internal memory can fill up. Calling get_sensor_events gets all events
342 from the device, and then stops the device from collecting events and
343 clears the internal buffer; to start again, the start_sensor_events
344 call must be used again.
345
346 Events from the accelerometer, compass, and gyro are returned; each
347 has a timestamp and x,y,z values.
348
349 Note that sensor events are only produced if the device isn't in its
350 standby mode (i.e.) if the screen is on.
351
352 Returns:
353 A Python dictionary with three keys ("accel", "mag", "gyro") each
354 of which maps to a list of objects containing "time","x","y","z"
355 keys.
356 """
357 cmd = {}
358 cmd["cmdName"] = "getSensorEvents"
359 self.sock.send(json.dumps(cmd) + "\n")
Yin-Chia Yehf16d6872016-06-06 13:54:42 -0700360 timeout = self.SOCK_TIMEOUT + self.EXTRA_SOCK_TIMEOUT
361 self.sock.settimeout(timeout)
Ruben Brunk370e2432014-10-14 18:33:23 -0700362 data,_ = self.__read_response_from_socket()
363 if data['tag'] != 'sensorEvents':
364 raise its.error.Error('Invalid command response')
Yin-Chia Yehf16d6872016-06-06 13:54:42 -0700365 self.sock.settimeout(self.SOCK_TIMEOUT)
Ruben Brunk370e2432014-10-14 18:33:23 -0700366 return data['objValue']
367
Yin-Chia Yehab98ada2015-03-05 13:28:53 -0800368 def get_camera_ids(self):
369 """Get a list of camera device Ids that can be opened.
370
371 Returns:
372 a list of camera ID string
373 """
374 cmd = {}
375 cmd["cmdName"] = "getCameraIds"
376 self.sock.send(json.dumps(cmd) + "\n")
377 data,_ = self.__read_response_from_socket()
378 if data['tag'] != 'cameraIds':
379 raise its.error.Error('Invalid command response')
380 return data['objValue']['cameraIdArray']
381
Ruben Brunk370e2432014-10-14 18:33:23 -0700382 def get_camera_properties(self):
383 """Get the camera properties object for the device.
384
385 Returns:
386 The Python dictionary object for the CameraProperties object.
387 """
388 cmd = {}
389 cmd["cmdName"] = "getCameraProperties"
390 self.sock.send(json.dumps(cmd) + "\n")
391 data,_ = self.__read_response_from_socket()
392 if data['tag'] != 'cameraProperties':
393 raise its.error.Error('Invalid command response')
Lu75f22fc2016-02-19 10:54:07 -0800394 self.props = data['objValue']['cameraProperties']
Ruben Brunk370e2432014-10-14 18:33:23 -0700395 return data['objValue']['cameraProperties']
396
Yin-Chia Yehb1801f92018-02-14 16:08:12 -0800397 def get_camera_properties_by_id(self, camera_id):
398 """Get the camera properties object for device with camera_id
399
400 Args:
401 camera_id: The ID string of the camera
402
403 Returns:
404 The Python dictionary object for the CameraProperties object. Empty
405 if no such device exists.
406
407 """
408 cmd = {}
409 cmd["cmdName"] = "getCameraPropertiesById"
410 cmd["cameraId"] = camera_id
411 self.sock.send(json.dumps(cmd) + "\n")
412 data,_ = self.__read_response_from_socket()
413 if data['tag'] != 'cameraProperties':
414 raise its.error.Error('Invalid command response')
415 return data['objValue']['cameraProperties']
416
Ruben Brunk370e2432014-10-14 18:33:23 -0700417 def do_3a(self, regions_ae=[[0,0,1,1,1]],
418 regions_awb=[[0,0,1,1,1]],
419 regions_af=[[0,0,1,1,1]],
420 do_ae=True, do_awb=True, do_af=True,
421 lock_ae=False, lock_awb=False,
Zhijun Heeb3ff472014-11-20 13:47:11 -0800422 get_results=False,
Clemenz Portmann4c869052018-03-27 13:31:09 -0700423 ev_comp=0, mono_camera=False):
Ruben Brunk370e2432014-10-14 18:33:23 -0700424 """Perform a 3A operation on the device.
425
426 Triggers some or all of AE, AWB, and AF, and returns once they have
427 converged. Uses the vendor 3A that is implemented inside the HAL.
Clemenz Portmann3c97e772017-03-23 13:22:06 -0700428 Note: do_awb is always enabled regardless of do_awb flag
Ruben Brunk370e2432014-10-14 18:33:23 -0700429
430 Throws an assertion if 3A fails to converge.
431
432 Args:
433 regions_ae: List of weighted AE regions.
434 regions_awb: List of weighted AWB regions.
435 regions_af: List of weighted AF regions.
436 do_ae: Trigger AE and wait for it to converge.
437 do_awb: Wait for AWB to converge.
438 do_af: Trigger AF and wait for it to converge.
439 lock_ae: Request AE lock after convergence, and wait for it.
440 lock_awb: Request AWB lock after convergence, and wait for it.
441 get_results: Return the 3A results from this function.
Zhijun Heeb3ff472014-11-20 13:47:11 -0800442 ev_comp: An EV compensation value to use when running AE.
Clemenz Portmann4c869052018-03-27 13:31:09 -0700443 mono_camera: Boolean for monochrome camera.
Ruben Brunk370e2432014-10-14 18:33:23 -0700444
445 Region format in args:
446 Arguments are lists of weighted regions; each weighted region is a
447 list of 5 values, [x,y,w,h, wgt], and each argument is a list of
448 these 5-value lists. The coordinates are given as normalized
449 rectangles (x,y,w,h) specifying the region. For example:
450 [[0.0, 0.0, 1.0, 0.5, 5], [0.0, 0.5, 1.0, 0.5, 10]].
451 Weights are non-negative integers.
452
453 Returns:
454 Five values are returned if get_results is true::
455 * AE sensitivity; None if do_ae is False
456 * AE exposure time; None if do_ae is False
Clemenz Portmann3c97e772017-03-23 13:22:06 -0700457 * AWB gains (list);
458 * AWB transform (list);
Ruben Brunk370e2432014-10-14 18:33:23 -0700459 * AF focus position; None if do_af is false
460 Otherwise, it returns five None values.
461 """
462 print "Running vendor 3A on device"
463 cmd = {}
464 cmd["cmdName"] = "do3A"
465 cmd["regions"] = {"ae": sum(regions_ae, []),
466 "awb": sum(regions_awb, []),
467 "af": sum(regions_af, [])}
468 cmd["triggers"] = {"ae": do_ae, "af": do_af}
469 if lock_ae:
470 cmd["aeLock"] = True
471 if lock_awb:
472 cmd["awbLock"] = True
Zhijun Heeb3ff472014-11-20 13:47:11 -0800473 if ev_comp != 0:
474 cmd["evComp"] = ev_comp
Ruben Brunk370e2432014-10-14 18:33:23 -0700475 self.sock.send(json.dumps(cmd) + "\n")
476
477 # Wait for each specified 3A to converge.
478 ae_sens = None
479 ae_exp = None
480 awb_gains = None
481 awb_transform = None
482 af_dist = None
483 converged = False
484 while True:
485 data,_ = self.__read_response_from_socket()
486 vals = data['strValue'].split()
487 if data['tag'] == 'aeResult':
Clemenz Portmann3c97e772017-03-23 13:22:06 -0700488 if do_ae:
489 ae_sens, ae_exp = [int(i) for i in vals]
Ruben Brunk370e2432014-10-14 18:33:23 -0700490 elif data['tag'] == 'afResult':
Clemenz Portmann3c97e772017-03-23 13:22:06 -0700491 if do_af:
492 af_dist = float(vals[0])
Ruben Brunk370e2432014-10-14 18:33:23 -0700493 elif data['tag'] == 'awbResult':
494 awb_gains = [float(f) for f in vals[:4]]
495 awb_transform = [float(f) for f in vals[4:]]
496 elif data['tag'] == '3aConverged':
497 converged = True
498 elif data['tag'] == '3aDone':
499 break
500 else:
501 raise its.error.Error('Invalid command response')
502 if converged and not get_results:
503 return None,None,None,None,None
Clemenz Portmann4c869052018-03-27 13:31:09 -0700504 if (do_ae and ae_sens == None or (not mono_camera and do_awb and awb_gains == None)
Ruben Brunk370e2432014-10-14 18:33:23 -0700505 or do_af and af_dist == None or not converged):
Clemenz Portmann4c869052018-03-27 13:31:09 -0700506
Ruben Brunk370e2432014-10-14 18:33:23 -0700507 raise its.error.Error('3A failed to converge')
508 return ae_sens, ae_exp, awb_gains, awb_transform, af_dist
509
Yin-Chia Yeh9e5d9ac2016-03-30 12:04:12 -0700510 def do_capture(self, cap_request,
511 out_surfaces=None, reprocess_format=None, repeat_request=None):
Ruben Brunk370e2432014-10-14 18:33:23 -0700512 """Issue capture request(s), and read back the image(s) and metadata.
513
514 The main top-level function for capturing one or more images using the
515 device. Captures a single image if cap_request is a single object, and
516 captures a burst if it is a list of objects.
517
Yin-Chia Yeh9e5d9ac2016-03-30 12:04:12 -0700518 The optional repeat_request field can be used to assign a repeating
519 request list ran in background for 3 seconds to warm up the capturing
520 pipeline before start capturing. The repeat_requests will be ran on a
521 640x480 YUV surface without sending any data back. The caller needs to
522 make sure the stream configuration defined by out_surfaces and
523 repeat_request are valid or do_capture may fail because device does not
524 support such stream configuration.
525
Ruben Brunk370e2432014-10-14 18:33:23 -0700526 The out_surfaces field can specify the width(s), height(s), and
527 format(s) of the captured image. The formats may be "yuv", "jpeg",
Timothy Knight67d8ec92015-08-31 13:14:46 -0700528 "dng", "raw", "raw10", "raw12", or "rawStats". The default is a YUV420
529 frame ("yuv") corresponding to a full sensor frame.
Ruben Brunk370e2432014-10-14 18:33:23 -0700530
Shuzhen Wang80822a92018-01-29 09:27:42 -0800531 Optionally the out_surfaces field can specify physical camera id(s) if the
532 current camera device is a logical multi-camera. The physical camera id
533 must refer to a physical camera backing this logical camera device. And
534 only "yuv", "raw", "raw10", "raw12" support the physical camera id field.
535
536 Currently only 2 physical streams with the same format are supported, one
537 from each physical camera:
538 - yuv physical streams of the same size.
539 - raw physical streams with the same or different sizes, depending on
540 device capability. (Different physical cameras may have different raw sizes).
541
Ruben Brunk370e2432014-10-14 18:33:23 -0700542 Note that one or more surfaces can be specified, allowing a capture to
543 request images back in multiple formats (e.g.) raw+yuv, raw+jpeg,
544 yuv+jpeg, raw+yuv+jpeg. If the size is omitted for a surface, the
545 default is the largest resolution available for the format of that
546 surface. At most one output surface can be specified for a given format,
547 and raw+dng, raw10+dng, and raw+raw10 are not supported as combinations.
548
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700549 If reprocess_format is not None, for each request, an intermediate
550 buffer of the given reprocess_format will be captured from camera and
551 the intermediate buffer will be reprocessed to the output surfaces. The
552 following settings will be turned off when capturing the intermediate
553 buffer and will be applied when reprocessing the intermediate buffer.
554 1. android.noiseReduction.mode
555 2. android.edge.mode
556 3. android.reprocess.effectiveExposureFactor
557
558 Supported reprocess format are "yuv" and "private". Supported output
559 surface formats when reprocessing is enabled are "yuv" and "jpeg".
560
Ruben Brunk370e2432014-10-14 18:33:23 -0700561 Example of a single capture request:
562
563 {
564 "android.sensor.exposureTime": 100*1000*1000,
565 "android.sensor.sensitivity": 100
566 }
567
568 Example of a list of capture requests:
569
570 [
571 {
572 "android.sensor.exposureTime": 100*1000*1000,
573 "android.sensor.sensitivity": 100
574 },
575 {
576 "android.sensor.exposureTime": 100*1000*1000,
577 "android.sensor.sensitivity": 200
578 }
579 ]
580
581 Examples of output surface specifications:
582
583 {
584 "width": 640,
585 "height": 480,
586 "format": "yuv"
587 }
588
589 [
590 {
591 "format": "jpeg"
592 },
593 {
594 "format": "raw"
595 }
596 ]
597
598 The following variables defined in this class are shortcuts for
599 specifying one or more formats where each output is the full size for
600 that format; they can be used as values for the out_surfaces arguments:
601
602 CAP_RAW
603 CAP_DNG
604 CAP_YUV
605 CAP_JPEG
606 CAP_RAW_YUV
607 CAP_DNG_YUV
608 CAP_RAW_JPEG
609 CAP_DNG_JPEG
610 CAP_YUV_JPEG
611 CAP_RAW_YUV_JPEG
612 CAP_DNG_YUV_JPEG
613
Chien-Yu Chen682faa22014-10-22 17:34:44 -0700614 If multiple formats are specified, then this function returns multiple
Ruben Brunk370e2432014-10-14 18:33:23 -0700615 capture objects, one for each requested format. If multiple formats and
616 multiple captures (i.e. a burst) are specified, then this function
617 returns multiple lists of capture objects. In both cases, the order of
618 the returned objects matches the order of the requested formats in the
619 out_surfaces parameter. For example:
620
621 yuv_cap = do_capture( req1 )
622 yuv_cap = do_capture( req1, yuv_fmt )
623 yuv_cap, raw_cap = do_capture( req1, [yuv_fmt,raw_fmt] )
624 yuv_caps = do_capture( [req1,req2], yuv_fmt )
625 yuv_caps, raw_caps = do_capture( [req1,req2], [yuv_fmt,raw_fmt] )
626
Timothy Knight67d8ec92015-08-31 13:14:46 -0700627 The "rawStats" format processes the raw image and returns a new image
628 of statistics from the raw image. The format takes additional keys,
629 "gridWidth" and "gridHeight" which are size of grid cells in a 2D grid
630 of the raw image. For each grid cell, the mean and variance of each raw
631 channel is computed, and the do_capture call returns two 4-element float
632 images of dimensions (rawWidth / gridWidth, rawHeight / gridHeight),
633 concatenated back-to-back, where the first iamge contains the 4-channel
Timothy Knightff3032b2017-02-01 15:10:51 -0800634 means and the second contains the 4-channel variances. Note that only
635 pixels in the active array crop region are used; pixels outside this
636 region (for example optical black rows) are cropped out before the
637 gridding and statistics computation is performed.
Timothy Knight67d8ec92015-08-31 13:14:46 -0700638
639 For the rawStats format, if the gridWidth is not provided then the raw
640 image width is used as the default, and similarly for gridHeight. With
641 this, the following is an example of a output description that computes
642 the mean and variance across each image row:
643
644 {
645 "gridHeight": 1,
646 "format": "rawStats"
647 }
648
Ruben Brunk370e2432014-10-14 18:33:23 -0700649 Args:
650 cap_request: The Python dict/list specifying the capture(s), which
651 will be converted to JSON and sent to the device.
652 out_surfaces: (Optional) specifications of the output image formats
653 and sizes to use for each capture.
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700654 reprocess_format: (Optional) The reprocessing format. If not None,
655 reprocessing will be enabled.
Ruben Brunk370e2432014-10-14 18:33:23 -0700656
657 Returns:
658 An object, list of objects, or list of lists of objects, where each
659 object contains the following fields:
660 * data: the image data as a numpy array of bytes.
661 * width: the width of the captured image.
662 * height: the height of the captured image.
Timothy Knight67d8ec92015-08-31 13:14:46 -0700663 * format: image the format, in [
664 "yuv","jpeg","raw","raw10","raw12","rawStats","dng"].
Chien-Yu Chen682faa22014-10-22 17:34:44 -0700665 * metadata: the capture result object (Python dictionary).
Ruben Brunk370e2432014-10-14 18:33:23 -0700666 """
667 cmd = {}
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700668 if reprocess_format != None:
669 cmd["cmdName"] = "doReprocessCapture"
670 cmd["reprocessFormat"] = reprocess_format
671 else:
672 cmd["cmdName"] = "doCapture"
Yin-Chia Yeh9e5d9ac2016-03-30 12:04:12 -0700673
674 if repeat_request is not None and reprocess_format is not None:
675 raise its.error.Error('repeating request + reprocessing is not supported')
676
677 if repeat_request is None:
678 cmd["repeatRequests"] = []
679 elif not isinstance(repeat_request, list):
680 cmd["repeatRequests"] = [repeat_request]
681 else:
682 cmd["repeatRequests"] = repeat_request
683
Ruben Brunk370e2432014-10-14 18:33:23 -0700684 if not isinstance(cap_request, list):
685 cmd["captureRequests"] = [cap_request]
686 else:
687 cmd["captureRequests"] = cap_request
688 if out_surfaces is not None:
689 if not isinstance(out_surfaces, list):
690 cmd["outputSurfaces"] = [out_surfaces]
691 else:
692 cmd["outputSurfaces"] = out_surfaces
Lu75f22fc2016-02-19 10:54:07 -0800693 formats = [c["format"] if "format" in c else "yuv"
Ruben Brunk370e2432014-10-14 18:33:23 -0700694 for c in cmd["outputSurfaces"]]
695 formats = [s if s != "jpg" else "jpeg" for s in formats]
696 else:
Yin-Chia Yeh409f2e92016-03-16 18:55:31 -0700697 max_yuv_size = its.objects.get_available_output_sizes(
698 "yuv", self.props)[0]
Ruben Brunk370e2432014-10-14 18:33:23 -0700699 formats = ['yuv']
Yin-Chia Yeh409f2e92016-03-16 18:55:31 -0700700 cmd["outputSurfaces"] = [{"format": "yuv",
701 "width" : max_yuv_size[0],
702 "height": max_yuv_size[1]}]
Shuzhen Wang80822a92018-01-29 09:27:42 -0800703
704 # Figure out requested physical camera ids, physical and logical
705 # streams.
706 physical_cam_ids = {}
707 physical_buffers = {}
708 physical_cam_format = None
709 logical_cam_formats = []
710 for i,s in enumerate(cmd["outputSurfaces"]):
711 if "format" in s and s["format"] in ["yuv", "raw", "raw10", "raw12"]:
712 if "physicalCamera" in s:
713 if physical_cam_format is not None and s["format"] != physical_cam_format:
714 raise its.error.Error('ITS does not support capturing multiple ' +
715 'physical formats yet')
716 physical_cam_ids[i] = s["physicalCamera"]
717 physical_buffers[s["physicalCamera"]] = []
718 physical_cam_format = s["format"]
719 else:
720 logical_cam_formats.append(s["format"])
721 else:
722 logical_cam_formats.append(s["format"])
723
Ruben Brunk370e2432014-10-14 18:33:23 -0700724 ncap = len(cmd["captureRequests"])
725 nsurf = 1 if out_surfaces is None else len(cmd["outputSurfaces"])
Lu7c6f52e2015-12-15 14:42:18 -0800726 # Only allow yuv output to multiple targets
Shuzhen Wang80822a92018-01-29 09:27:42 -0800727 logical_yuv_surfaces = [s for s in cmd["outputSurfaces"] if s["format"]=="yuv"\
728 and "physicalCamera" not in s]
729 n_yuv = len(logical_yuv_surfaces)
Lu7c6f52e2015-12-15 14:42:18 -0800730 # Compute the buffer size of YUV targets
Lu75f22fc2016-02-19 10:54:07 -0800731 yuv_maxsize_1d = 0
Shuzhen Wang80822a92018-01-29 09:27:42 -0800732 for s in logical_yuv_surfaces:
Lu75f22fc2016-02-19 10:54:07 -0800733 if not ("width" in s and "height" in s):
734 if self.props is None:
735 raise its.error.Error('Camera props are unavailable')
736 yuv_maxsize_2d = its.objects.get_available_output_sizes(
737 "yuv", self.props)[0]
738 yuv_maxsize_1d = yuv_maxsize_2d[0] * yuv_maxsize_2d[1] * 3 / 2
739 break
740 yuv_sizes = [c["width"]*c["height"]*3/2
741 if "width" in c and "height" in c
742 else yuv_maxsize_1d
Shuzhen Wang80822a92018-01-29 09:27:42 -0800743 for c in logical_yuv_surfaces]
Lu7c6f52e2015-12-15 14:42:18 -0800744 # Currently we don't pass enough metadta from ItsService to distinguish
745 # different yuv stream of same buffer size
746 if len(yuv_sizes) != len(set(yuv_sizes)):
747 raise its.error.Error(
748 'ITS does not support yuv outputs of same buffer size')
Shuzhen Wang80822a92018-01-29 09:27:42 -0800749 if len(logical_cam_formats) > len(set(logical_cam_formats)):
750 if n_yuv != len(logical_cam_formats) - len(set(logical_cam_formats)) + 1:
Lu7c6f52e2015-12-15 14:42:18 -0800751 raise its.error.Error('Duplicate format requested')
752
Timothy Knight67d8ec92015-08-31 13:14:46 -0700753 raw_formats = 0;
754 raw_formats += 1 if "dng" in formats else 0
755 raw_formats += 1 if "raw" in formats else 0
756 raw_formats += 1 if "raw10" in formats else 0
757 raw_formats += 1 if "raw12" in formats else 0
758 raw_formats += 1 if "rawStats" in formats else 0
759 if raw_formats > 1:
Ruben Brunk370e2432014-10-14 18:33:23 -0700760 raise its.error.Error('Different raw formats not supported')
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -0700761
762 # Detect long exposure time and set timeout accordingly
763 longest_exp_time = 0
764 for req in cmd["captureRequests"]:
765 if "android.sensor.exposureTime" in req and \
766 req["android.sensor.exposureTime"] > longest_exp_time:
767 longest_exp_time = req["android.sensor.exposureTime"]
768
769 extended_timeout = longest_exp_time / self.SEC_TO_NSEC + \
770 self.SOCK_TIMEOUT
Yin-Chia Yehc9d554d2016-06-01 17:30:32 -0700771 if repeat_request:
772 extended_timeout += self.EXTRA_SOCK_TIMEOUT
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -0700773 self.sock.settimeout(extended_timeout)
774
Ruben Brunk370e2432014-10-14 18:33:23 -0700775 print "Capturing %d frame%s with %d format%s [%s]" % (
776 ncap, "s" if ncap>1 else "", nsurf, "s" if nsurf>1 else "",
777 ",".join(formats))
778 self.sock.send(json.dumps(cmd) + "\n")
779
780 # Wait for ncap*nsurf images and ncap metadata responses.
781 # Assume that captures come out in the same order as requested in
Chien-Yu Chen682faa22014-10-22 17:34:44 -0700782 # the burst, however individual images of different formats can come
Ruben Brunk370e2432014-10-14 18:33:23 -0700783 # out in any order for that capture.
784 nbufs = 0
Lu7c6f52e2015-12-15 14:42:18 -0800785 bufs = {"raw":[], "raw10":[], "raw12":[],
Timothy Knight67d8ec92015-08-31 13:14:46 -0700786 "rawStats":[], "dng":[], "jpeg":[]}
Lu7c6f52e2015-12-15 14:42:18 -0800787 yuv_bufs = {size:[] for size in yuv_sizes}
Ruben Brunk370e2432014-10-14 18:33:23 -0700788 mds = []
Shuzhen Wang96ecf752018-01-30 10:33:31 -0800789 physical_mds = []
Ruben Brunk370e2432014-10-14 18:33:23 -0700790 widths = None
791 heights = None
792 while nbufs < ncap*nsurf or len(mds) < ncap:
793 jsonObj,buf = self.__read_response_from_socket()
Lu7c6f52e2015-12-15 14:42:18 -0800794 if jsonObj['tag'] in ['jpegImage', 'rawImage', \
Timothy Knight67d8ec92015-08-31 13:14:46 -0700795 'raw10Image', 'raw12Image', 'rawStatsImage', 'dngImage'] \
796 and buf is not None:
Ruben Brunk370e2432014-10-14 18:33:23 -0700797 fmt = jsonObj['tag'][:-5]
798 bufs[fmt].append(buf)
799 nbufs += 1
Lu7c6f52e2015-12-15 14:42:18 -0800800 elif jsonObj['tag'] == 'yuvImage':
801 buf_size = numpy.product(buf.shape)
802 yuv_bufs[buf_size].append(buf)
803 nbufs += 1
Ruben Brunk370e2432014-10-14 18:33:23 -0700804 elif jsonObj['tag'] == 'captureResults':
805 mds.append(jsonObj['objValue']['captureResult'])
Shuzhen Wang96ecf752018-01-30 10:33:31 -0800806 physical_mds.append(jsonObj['objValue']['physicalResults'])
Ruben Brunk370e2432014-10-14 18:33:23 -0700807 outputs = jsonObj['objValue']['outputs']
808 widths = [out['width'] for out in outputs]
809 heights = [out['height'] for out in outputs]
810 else:
Shuzhen Wang80822a92018-01-29 09:27:42 -0800811 tagString = unicodedata.normalize('NFKD', jsonObj['tag']).encode('ascii', 'ignore');
812 for x in ['rawImage', 'raw10Image', 'raw12Image', 'yuvImage']:
813 if (tagString.startswith(x)):
814 physicalId = jsonObj['tag'][len(x):];
815 if physicalId in physical_cam_ids.values():
816 physical_buffers[physicalId].append(buf)
817 nbufs += 1
Ruben Brunk370e2432014-10-14 18:33:23 -0700818 rets = []
819 for j,fmt in enumerate(formats):
820 objs = []
821 for i in range(ncap):
822 obj = {}
Ruben Brunk370e2432014-10-14 18:33:23 -0700823 obj["width"] = widths[j]
824 obj["height"] = heights[j]
825 obj["format"] = fmt
Shuzhen Wang96ecf752018-01-30 10:33:31 -0800826 if j in physical_cam_ids:
827 for physical_md in physical_mds[i]:
828 if physical_cam_ids[j] in physical_md:
829 obj["metadata"] = physical_md[physical_cam_ids[j]]
830 break
831 else:
832 obj["metadata"] = mds[i]
Shuzhen Wang80822a92018-01-29 09:27:42 -0800833
834 if j in physical_cam_ids:
835 obj["data"] = physical_buffers[physical_cam_ids[j]][i]
836 elif fmt == 'yuv':
Lu7c6f52e2015-12-15 14:42:18 -0800837 buf_size = widths[j] * heights[j] * 3 / 2
838 obj["data"] = yuv_bufs[buf_size][i]
839 else:
840 obj["data"] = bufs[fmt][i]
Ruben Brunk370e2432014-10-14 18:33:23 -0700841 objs.append(obj)
842 rets.append(objs if ncap>1 else objs[0])
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -0700843 self.sock.settimeout(self.SOCK_TIMEOUT)
Ruben Brunk370e2432014-10-14 18:33:23 -0700844 return rets if len(rets)>1 else rets[0]
845
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700846def get_device_id():
847 """ Return the ID of the device that the test is running on.
848
849 Return the device ID provided in the command line if it's connected. If no
850 device ID is provided in the command line and there is only one device
851 connected, return the device ID by parsing the result of "adb devices".
Clemenz Portmannf7f23ee2016-08-18 13:00:59 -0700852 Also, if the environment variable ANDROID_SERIAL is set, use it as device
853 id. When both ANDROID_SERIAL and device argument present, device argument
854 takes priority.
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700855
856 Raise an exception if no device is connected; or the device ID provided in
857 the command line is not connected; or no device ID is provided in the
Clemenz Portmannf7f23ee2016-08-18 13:00:59 -0700858 command line or environment variable and there are more than 1 device
859 connected.
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700860
861 Returns:
862 Device ID string.
863 """
864 device_id = None
Clemenz Portmannf7f23ee2016-08-18 13:00:59 -0700865
866 # Check if device id is set in env
867 if "ANDROID_SERIAL" in os.environ:
868 device_id = os.environ["ANDROID_SERIAL"]
869
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700870 for s in sys.argv[1:]:
871 if s[:7] == "device=" and len(s) > 7:
872 device_id = str(s[7:])
873
874 # Get a list of connected devices
875 devices = []
876 command = "adb devices"
877 proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
878 output, error = proc.communicate()
879 for line in output.split(os.linesep):
880 device_info = line.split()
881 if len(device_info) == 2 and device_info[1] == "device":
882 devices.append(device_info[0])
883
884 if len(devices) == 0:
885 raise its.error.Error("No device is connected!")
886 elif device_id is not None and device_id not in devices:
887 raise its.error.Error(device_id + " is not connected!")
888 elif device_id is None and len(devices) >= 2:
889 raise its.error.Error("More than 1 device are connected. " +
890 "Use device=<device_id> to specify a device to test.")
891 elif len(devices) == 1:
892 device_id = devices[0]
893
894 return device_id
895
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700896def report_result(device_id, camera_id, results):
Timothy Knighted076002014-10-23 16:12:26 -0700897 """Send a pass/fail result to the device, via an intent.
898
899 Args:
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700900 device_id: The ID string of the device to report the results to.
Timothy Knighted076002014-10-23 16:12:26 -0700901 camera_id: The ID string of the camera for which to report pass/fail.
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700902 results: a dictionary contains all ITS scenes as key and result/summary
903 of current ITS run. See test_report_result unit test for
904 an example.
Timothy Knighted076002014-10-23 16:12:26 -0700905 Returns:
906 Nothing.
907 """
Yin-Chia Yeh89dfdda2018-04-26 08:15:10 -0700908 ACTIVITY_START_WAIT = 1.5 # seconds
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700909 adb = "adb -s " + device_id
Sam Linf2f66002017-02-27 21:08:11 -0800910
Yin-Chia Yeh89dfdda2018-04-26 08:15:10 -0700911 # Start ItsTestActivity to receive test results
Sam Linf2f66002017-02-27 21:08:11 -0800912 cmd = "%s shell am start %s --activity-brought-to-front" % (adb, ItsSession.ITS_TEST_ACTIVITY)
913 _run(cmd)
Yin-Chia Yeh89dfdda2018-04-26 08:15:10 -0700914 time.sleep(ACTIVITY_START_WAIT)
Sam Linf2f66002017-02-27 21:08:11 -0800915
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700916 # Validate/process results argument
917 for scene in results:
918 result_key = ItsSession.RESULT_KEY
919 summary_key = ItsSession.SUMMARY_KEY
920 if result_key not in results[scene]:
921 raise its.error.Error('ITS result not found for ' + scene)
922 if results[scene][result_key] not in ItsSession.RESULT_VALUES:
923 raise its.error.Error('Unknown ITS result for %s: %s' % (
924 scene, results[result_key]))
925 if summary_key in results[scene]:
926 device_summary_path = "/sdcard/its_camera%s_%s.txt" % (
927 camera_id, scene)
928 _run("%s push %s %s" % (
929 adb, results[scene][summary_key], device_summary_path))
930 results[scene][summary_key] = device_summary_path
Sam Linf2f66002017-02-27 21:08:11 -0800931
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700932 json_results = json.dumps(results)
933 cmd = "%s shell am broadcast -a %s --es %s %s --es %s %s --es %s \'%s\'" % (
934 adb, ItsSession.ACTION_ITS_RESULT,
935 ItsSession.EXTRA_VERSION, ItsSession.CURRENT_ITS_VERSION,
936 ItsSession.EXTRA_CAMERA_ID, camera_id,
937 ItsSession.EXTRA_RESULTS, json_results)
938 if len(cmd) > 4095:
939 print "ITS command string might be too long! len:", len(cmd)
940 _run(cmd)
Ruben Brunk370e2432014-10-14 18:33:23 -0700941
Yin-Chia Yeh89dfdda2018-04-26 08:15:10 -0700942def adb_log(device_id, msg):
943 """Send a log message to adb logcat
944
945 Args:
946 device_id: The ID string of the adb device
947 msg: the message string to be send to logcat
948
949 Returns:
950 Nothing.
951 """
952 adb = "adb -s " + device_id
953 cmd = "%s shell log -p i -t \"ItsTestHost\" %s" % (adb, msg)
954 _run(cmd)
955
Sam Linf2f66002017-02-27 21:08:11 -0800956def get_device_fingerprint(device_id):
957 """ Return the Build FingerPrint of the device that the test is running on.
958
959 Returns:
960 Device Build Fingerprint string.
961 """
962 device_bfp = None
963
964 # Get a list of connected devices
965
966 com = ('adb -s %s shell getprop | grep ro.build.fingerprint' % device_id)
967 proc = subprocess.Popen(com.split(), stdout=subprocess.PIPE)
968 output, error = proc.communicate()
969 assert error is None
970
971 lst = string.split( \
972 string.replace( \
973 string.replace( \
974 string.replace(output,
975 '\n', ''), '[', ''), ']', ''), \
976 ' ')
977
978 if lst[0].find('ro.build.fingerprint') != -1:
979 device_bfp = lst[1]
980
981 return device_bfp
982
Ruben Brunk370e2432014-10-14 18:33:23 -0700983def _run(cmd):
984 """Replacement for os.system, with hiding of stdout+stderr messages.
985 """
986 with open(os.devnull, 'wb') as devnull:
987 subprocess.check_call(
988 cmd.split(), stdout=devnull, stderr=subprocess.STDOUT)
989
990class __UnitTest(unittest.TestCase):
991 """Run a suite of unit tests on this module.
992 """
993
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700994 """
995 # TODO: this test currently needs connected device to pass
996 # Need to remove that dependency before enabling the test
997 def test_report_result(self):
998 device_id = get_device_id()
999 camera_id = "1"
1000 result_key = ItsSession.RESULT_KEY
1001 results = {"scene0":{result_key:"PASS"},
1002 "scene1":{result_key:"PASS"},
1003 "scene2":{result_key:"PASS"},
1004 "scene3":{result_key:"PASS"},
1005 "sceneNotExist":{result_key:"FAIL"}}
1006 report_result(device_id, camera_id, results)
1007 """
Ruben Brunk370e2432014-10-14 18:33:23 -07001008
1009if __name__ == '__main__':
1010 unittest.main()
1011