1# 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 27 28class ItsSession(object): 29 """Controls a device over adb to run ITS scripts. 30 31 The script importing this module (on the host machine) prepares JSON 32 objects encoding CaptureRequests, specifying sets of parameters to use 33 when capturing an image using the Camera2 APIs. This class encapsulates 34 sending the requests to the device, monitoring the device's progress, and 35 copying the resultant captures back to the host machine when done. TCP 36 forwarded over adb is the transport mechanism used. 37 38 The device must have CtsVerifier.apk installed. 39 40 Attributes: 41 sock: The open socket. 42 """ 43 44 # Open a connection to localhost:<host_port>, forwarded to port 6000 on the 45 # device. <host_port> is determined at run-time to support multiple 46 # connected devices. 47 IPADDR = '127.0.0.1' 48 REMOTE_PORT = 6000 49 BUFFER_SIZE = 4096 50 51 # LOCK_PORT is used as a mutex lock to protect the list of forwarded ports 52 # among all processes. The script assumes LOCK_PORT is available and will 53 # try to use ports between CLIENT_PORT_START and 54 # CLIENT_PORT_START+MAX_NUM_PORTS-1 on host for ITS sessions. 55 CLIENT_PORT_START = 6000 56 MAX_NUM_PORTS = 100 57 LOCK_PORT = CLIENT_PORT_START + MAX_NUM_PORTS 58 59 # Seconds timeout on each socket operation. 60 SOCK_TIMEOUT = 10.0 61 SEC_TO_NSEC = 1000*1000*1000.0 62 63 PACKAGE = 'com.android.cts.verifier.camera.its' 64 INTENT_START = 'com.android.cts.verifier.camera.its.START' 65 ACTION_ITS_RESULT = 'com.android.cts.verifier.camera.its.ACTION_ITS_RESULT' 66 EXTRA_CAMERA_ID = 'camera.its.extra.CAMERA_ID' 67 EXTRA_SUCCESS = 'camera.its.extra.SUCCESS' 68 EXTRA_SUMMARY = 'camera.its.extra.SUMMARY' 69 70 adb = "adb -d" 71 device_id = "" 72 73 # Definitions for some of the common output format options for do_capture(). 74 # Each gets images of full resolution for each requested format. 75 CAP_RAW = {"format":"raw"} 76 CAP_DNG = {"format":"dng"} 77 CAP_YUV = {"format":"yuv"} 78 CAP_JPEG = {"format":"jpeg"} 79 CAP_RAW_YUV = [{"format":"raw"}, {"format":"yuv"}] 80 CAP_DNG_YUV = [{"format":"dng"}, {"format":"yuv"}] 81 CAP_RAW_JPEG = [{"format":"raw"}, {"format":"jpeg"}] 82 CAP_DNG_JPEG = [{"format":"dng"}, {"format":"jpeg"}] 83 CAP_YUV_JPEG = [{"format":"yuv"}, {"format":"jpeg"}] 84 CAP_RAW_YUV_JPEG = [{"format":"raw"}, {"format":"yuv"}, {"format":"jpeg"}] 85 CAP_DNG_YUV_JPEG = [{"format":"dng"}, {"format":"yuv"}, {"format":"jpeg"}] 86 87 # Initialize the socket port for the host to forward requests to the device. 88 # This method assumes localhost's LOCK_PORT is available and will try to 89 # use ports between CLIENT_PORT_START and CLIENT_PORT_START+MAX_NUM_PORTS-1 90 def __init_socket_port(self): 91 NUM_RETRIES = 100 92 RETRY_WAIT_TIME_SEC = 0.05 93 94 # Bind a socket to use as mutex lock 95 socket_lock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 96 for i in range(NUM_RETRIES): 97 try: 98 socket_lock.bind((ItsSession.IPADDR, ItsSession.LOCK_PORT)) 99 break 100 except socket.error: 101 if i == NUM_RETRIES - 1: 102 raise its.error.Error(self.device_id, 103 "acquiring socket lock timed out") 104 else: 105 time.sleep(RETRY_WAIT_TIME_SEC) 106 107 # Check if a port is already assigned to the device. 108 command = "adb forward --list" 109 proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE) 110 output, error = proc.communicate() 111 112 port = None 113 used_ports = [] 114 for line in output.split(os.linesep): 115 # each line should be formatted as: 116 # "<device_id> tcp:<host_port> tcp:<remote_port>" 117 forward_info = line.split() 118 if len(forward_info) >= 3 and \ 119 len(forward_info[1]) > 4 and forward_info[1][:4] == "tcp:" and \ 120 len(forward_info[2]) > 4 and forward_info[2][:4] == "tcp:": 121 local_p = int(forward_info[1][4:]) 122 remote_p = int(forward_info[2][4:]) 123 if forward_info[0] == self.device_id and \ 124 remote_p == ItsSession.REMOTE_PORT: 125 port = local_p 126 break; 127 else: 128 used_ports.append(local_p) 129 130 # Find the first available port if no port is assigned to the device. 131 if port is None: 132 for p in range(ItsSession.CLIENT_PORT_START, 133 ItsSession.CLIENT_PORT_START + 134 ItsSession.MAX_NUM_PORTS): 135 if p not in used_ports: 136 # Try to run "adb forward" with the port 137 command = "%s forward tcp:%d tcp:%d" % \ 138 (self.adb, p, self.REMOTE_PORT) 139 proc = subprocess.Popen(command.split(), 140 stdout=subprocess.PIPE, 141 stderr=subprocess.PIPE) 142 output, error = proc.communicate() 143 144 # Check if there is no error 145 if error is None or error.find("error") < 0: 146 port = p 147 break 148 149 if port is None: 150 raise its.error.Error(self.device_id, " cannot find an available " + 151 "port") 152 153 # Release the socket as mutex unlock 154 socket_lock.close() 155 156 # Connect to the socket 157 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 158 self.sock.connect((self.IPADDR, port)) 159 self.sock.settimeout(self.SOCK_TIMEOUT) 160 161 # Reboot the device if needed and wait for the service to be ready for 162 # connection. 163 def __wait_for_service(self): 164 # This also includes the optional reboot handling: if the user 165 # provides a "reboot" or "reboot=N" arg, then reboot the device, 166 # waiting for N seconds (default 30) before returning. 167 for s in sys.argv[1:]: 168 if s[:6] == "reboot": 169 duration = 30 170 if len(s) > 7 and s[6] == "=": 171 duration = int(s[7:]) 172 print "Rebooting device" 173 _run("%s reboot" % (self.adb)); 174 _run("%s wait-for-device" % (self.adb)) 175 time.sleep(duration) 176 print "Reboot complete" 177 178 # TODO: Figure out why "--user 0" is needed, and fix the problem. 179 _run('%s shell am force-stop --user 0 %s' % (self.adb, self.PACKAGE)) 180 _run(('%s shell am startservice --user 0 -t text/plain ' 181 '-a %s') % (self.adb, self.INTENT_START)) 182 183 # Wait until the socket is ready to accept a connection. 184 proc = subprocess.Popen( 185 self.adb.split() + ["logcat"], 186 stdout=subprocess.PIPE) 187 logcat = proc.stdout 188 while True: 189 line = logcat.readline().strip() 190 if line.find('ItsService ready') >= 0: 191 break 192 proc.kill() 193 194 def __init__(self): 195 # Initialize device id and adb command. 196 self.device_id = get_device_id() 197 self.adb = "adb -s " + self.device_id 198 199 self.__wait_for_service() 200 self.__init_socket_port() 201 202 self.__close_camera() 203 self.__open_camera() 204 205 def __del__(self): 206 if hasattr(self, 'sock') and self.sock: 207 self.__close_camera() 208 self.sock.close() 209 210 def __enter__(self): 211 return self 212 213 def __exit__(self, type, value, traceback): 214 return False 215 216 def __read_response_from_socket(self): 217 # Read a line (newline-terminated) string serialization of JSON object. 218 chars = [] 219 while len(chars) == 0 or chars[-1] != '\n': 220 ch = self.sock.recv(1) 221 if len(ch) == 0: 222 # Socket was probably closed; otherwise don't get empty strings 223 raise its.error.Error('Problem with socket on device side') 224 chars.append(ch) 225 line = ''.join(chars) 226 jobj = json.loads(line) 227 # Optionally read a binary buffer of a fixed size. 228 buf = None 229 if jobj.has_key("bufValueSize"): 230 n = jobj["bufValueSize"] 231 buf = bytearray(n) 232 view = memoryview(buf) 233 while n > 0: 234 nbytes = self.sock.recv_into(view, n) 235 view = view[nbytes:] 236 n -= nbytes 237 buf = numpy.frombuffer(buf, dtype=numpy.uint8) 238 return jobj, buf 239 240 def __open_camera(self): 241 # Get the camera ID to open as an argument. 242 camera_id = 0 243 for s in sys.argv[1:]: 244 if s[:7] == "camera=" and len(s) > 7: 245 camera_id = int(s[7:]) 246 cmd = {"cmdName":"open", "cameraId":camera_id} 247 self.sock.send(json.dumps(cmd) + "\n") 248 data,_ = self.__read_response_from_socket() 249 if data['tag'] != 'cameraOpened': 250 raise its.error.Error('Invalid command response') 251 252 def __close_camera(self): 253 cmd = {"cmdName":"close"} 254 self.sock.send(json.dumps(cmd) + "\n") 255 data,_ = self.__read_response_from_socket() 256 if data['tag'] != 'cameraClosed': 257 raise its.error.Error('Invalid command response') 258 259 def do_vibrate(self, pattern): 260 """Cause the device to vibrate to a specific pattern. 261 262 Args: 263 pattern: Durations (ms) for which to turn on or off the vibrator. 264 The first value indicates the number of milliseconds to wait 265 before turning the vibrator on. The next value indicates the 266 number of milliseconds for which to keep the vibrator on 267 before turning it off. Subsequent values alternate between 268 durations in milliseconds to turn the vibrator off or to turn 269 the vibrator on. 270 271 Returns: 272 Nothing. 273 """ 274 cmd = {} 275 cmd["cmdName"] = "doVibrate" 276 cmd["pattern"] = pattern 277 self.sock.send(json.dumps(cmd) + "\n") 278 data,_ = self.__read_response_from_socket() 279 if data['tag'] != 'vibrationStarted': 280 raise its.error.Error('Invalid command response') 281 282 def start_sensor_events(self): 283 """Start collecting sensor events on the device. 284 285 See get_sensor_events for more info. 286 287 Returns: 288 Nothing. 289 """ 290 cmd = {} 291 cmd["cmdName"] = "startSensorEvents" 292 self.sock.send(json.dumps(cmd) + "\n") 293 data,_ = self.__read_response_from_socket() 294 if data['tag'] != 'sensorEventsStarted': 295 raise its.error.Error('Invalid command response') 296 297 def get_sensor_events(self): 298 """Get a trace of all sensor events on the device. 299 300 The trace starts when the start_sensor_events function is called. If 301 the test runs for a long time after this call, then the device's 302 internal memory can fill up. Calling get_sensor_events gets all events 303 from the device, and then stops the device from collecting events and 304 clears the internal buffer; to start again, the start_sensor_events 305 call must be used again. 306 307 Events from the accelerometer, compass, and gyro are returned; each 308 has a timestamp and x,y,z values. 309 310 Note that sensor events are only produced if the device isn't in its 311 standby mode (i.e.) if the screen is on. 312 313 Returns: 314 A Python dictionary with three keys ("accel", "mag", "gyro") each 315 of which maps to a list of objects containing "time","x","y","z" 316 keys. 317 """ 318 cmd = {} 319 cmd["cmdName"] = "getSensorEvents" 320 self.sock.send(json.dumps(cmd) + "\n") 321 data,_ = self.__read_response_from_socket() 322 if data['tag'] != 'sensorEvents': 323 raise its.error.Error('Invalid command response') 324 return data['objValue'] 325 326 def get_camera_ids(self): 327 """Get a list of camera device Ids that can be opened. 328 329 Returns: 330 a list of camera ID string 331 """ 332 cmd = {} 333 cmd["cmdName"] = "getCameraIds" 334 self.sock.send(json.dumps(cmd) + "\n") 335 data,_ = self.__read_response_from_socket() 336 if data['tag'] != 'cameraIds': 337 raise its.error.Error('Invalid command response') 338 return data['objValue']['cameraIdArray'] 339 340 def get_camera_properties(self): 341 """Get the camera properties object for the device. 342 343 Returns: 344 The Python dictionary object for the CameraProperties object. 345 """ 346 cmd = {} 347 cmd["cmdName"] = "getCameraProperties" 348 self.sock.send(json.dumps(cmd) + "\n") 349 data,_ = self.__read_response_from_socket() 350 if data['tag'] != 'cameraProperties': 351 raise its.error.Error('Invalid command response') 352 return data['objValue']['cameraProperties'] 353 354 def do_3a(self, regions_ae=[[0,0,1,1,1]], 355 regions_awb=[[0,0,1,1,1]], 356 regions_af=[[0,0,1,1,1]], 357 do_ae=True, do_awb=True, do_af=True, 358 lock_ae=False, lock_awb=False, 359 get_results=False, 360 ev_comp=0): 361 """Perform a 3A operation on the device. 362 363 Triggers some or all of AE, AWB, and AF, and returns once they have 364 converged. Uses the vendor 3A that is implemented inside the HAL. 365 366 Throws an assertion if 3A fails to converge. 367 368 Args: 369 regions_ae: List of weighted AE regions. 370 regions_awb: List of weighted AWB regions. 371 regions_af: List of weighted AF regions. 372 do_ae: Trigger AE and wait for it to converge. 373 do_awb: Wait for AWB to converge. 374 do_af: Trigger AF and wait for it to converge. 375 lock_ae: Request AE lock after convergence, and wait for it. 376 lock_awb: Request AWB lock after convergence, and wait for it. 377 get_results: Return the 3A results from this function. 378 ev_comp: An EV compensation value to use when running AE. 379 380 Region format in args: 381 Arguments are lists of weighted regions; each weighted region is a 382 list of 5 values, [x,y,w,h, wgt], and each argument is a list of 383 these 5-value lists. The coordinates are given as normalized 384 rectangles (x,y,w,h) specifying the region. For example: 385 [[0.0, 0.0, 1.0, 0.5, 5], [0.0, 0.5, 1.0, 0.5, 10]]. 386 Weights are non-negative integers. 387 388 Returns: 389 Five values are returned if get_results is true:: 390 * AE sensitivity; None if do_ae is False 391 * AE exposure time; None if do_ae is False 392 * AWB gains (list); None if do_awb is False 393 * AWB transform (list); None if do_awb is false 394 * AF focus position; None if do_af is false 395 Otherwise, it returns five None values. 396 """ 397 print "Running vendor 3A on device" 398 cmd = {} 399 cmd["cmdName"] = "do3A" 400 cmd["regions"] = {"ae": sum(regions_ae, []), 401 "awb": sum(regions_awb, []), 402 "af": sum(regions_af, [])} 403 cmd["triggers"] = {"ae": do_ae, "af": do_af} 404 if lock_ae: 405 cmd["aeLock"] = True 406 if lock_awb: 407 cmd["awbLock"] = True 408 if ev_comp != 0: 409 cmd["evComp"] = ev_comp 410 self.sock.send(json.dumps(cmd) + "\n") 411 412 # Wait for each specified 3A to converge. 413 ae_sens = None 414 ae_exp = None 415 awb_gains = None 416 awb_transform = None 417 af_dist = None 418 converged = False 419 while True: 420 data,_ = self.__read_response_from_socket() 421 vals = data['strValue'].split() 422 if data['tag'] == 'aeResult': 423 ae_sens, ae_exp = [int(i) for i in vals] 424 elif data['tag'] == 'afResult': 425 af_dist = float(vals[0]) 426 elif data['tag'] == 'awbResult': 427 awb_gains = [float(f) for f in vals[:4]] 428 awb_transform = [float(f) for f in vals[4:]] 429 elif data['tag'] == '3aConverged': 430 converged = True 431 elif data['tag'] == '3aDone': 432 break 433 else: 434 raise its.error.Error('Invalid command response') 435 if converged and not get_results: 436 return None,None,None,None,None 437 if (do_ae and ae_sens == None or do_awb and awb_gains == None 438 or do_af and af_dist == None or not converged): 439 raise its.error.Error('3A failed to converge') 440 return ae_sens, ae_exp, awb_gains, awb_transform, af_dist 441 442 def do_capture(self, cap_request, out_surfaces=None, reprocess_format=None): 443 """Issue capture request(s), and read back the image(s) and metadata. 444 445 The main top-level function for capturing one or more images using the 446 device. Captures a single image if cap_request is a single object, and 447 captures a burst if it is a list of objects. 448 449 The out_surfaces field can specify the width(s), height(s), and 450 format(s) of the captured image. The formats may be "yuv", "jpeg", 451 "dng", "raw", "raw10", "raw12", or "rawStats". The default is a YUV420 452 frame ("yuv") corresponding to a full sensor frame. 453 454 Note that one or more surfaces can be specified, allowing a capture to 455 request images back in multiple formats (e.g.) raw+yuv, raw+jpeg, 456 yuv+jpeg, raw+yuv+jpeg. If the size is omitted for a surface, the 457 default is the largest resolution available for the format of that 458 surface. At most one output surface can be specified for a given format, 459 and raw+dng, raw10+dng, and raw+raw10 are not supported as combinations. 460 461 If reprocess_format is not None, for each request, an intermediate 462 buffer of the given reprocess_format will be captured from camera and 463 the intermediate buffer will be reprocessed to the output surfaces. The 464 following settings will be turned off when capturing the intermediate 465 buffer and will be applied when reprocessing the intermediate buffer. 466 1. android.noiseReduction.mode 467 2. android.edge.mode 468 3. android.reprocess.effectiveExposureFactor 469 470 Supported reprocess format are "yuv" and "private". Supported output 471 surface formats when reprocessing is enabled are "yuv" and "jpeg". 472 473 Example of a single capture request: 474 475 { 476 "android.sensor.exposureTime": 100*1000*1000, 477 "android.sensor.sensitivity": 100 478 } 479 480 Example of a list of capture requests: 481 482 [ 483 { 484 "android.sensor.exposureTime": 100*1000*1000, 485 "android.sensor.sensitivity": 100 486 }, 487 { 488 "android.sensor.exposureTime": 100*1000*1000, 489 "android.sensor.sensitivity": 200 490 } 491 ] 492 493 Examples of output surface specifications: 494 495 { 496 "width": 640, 497 "height": 480, 498 "format": "yuv" 499 } 500 501 [ 502 { 503 "format": "jpeg" 504 }, 505 { 506 "format": "raw" 507 } 508 ] 509 510 The following variables defined in this class are shortcuts for 511 specifying one or more formats where each output is the full size for 512 that format; they can be used as values for the out_surfaces arguments: 513 514 CAP_RAW 515 CAP_DNG 516 CAP_YUV 517 CAP_JPEG 518 CAP_RAW_YUV 519 CAP_DNG_YUV 520 CAP_RAW_JPEG 521 CAP_DNG_JPEG 522 CAP_YUV_JPEG 523 CAP_RAW_YUV_JPEG 524 CAP_DNG_YUV_JPEG 525 526 If multiple formats are specified, then this function returns multiple 527 capture objects, one for each requested format. If multiple formats and 528 multiple captures (i.e. a burst) are specified, then this function 529 returns multiple lists of capture objects. In both cases, the order of 530 the returned objects matches the order of the requested formats in the 531 out_surfaces parameter. For example: 532 533 yuv_cap = do_capture( req1 ) 534 yuv_cap = do_capture( req1, yuv_fmt ) 535 yuv_cap, raw_cap = do_capture( req1, [yuv_fmt,raw_fmt] ) 536 yuv_caps = do_capture( [req1,req2], yuv_fmt ) 537 yuv_caps, raw_caps = do_capture( [req1,req2], [yuv_fmt,raw_fmt] ) 538 539 The "rawStats" format processes the raw image and returns a new image 540 of statistics from the raw image. The format takes additional keys, 541 "gridWidth" and "gridHeight" which are size of grid cells in a 2D grid 542 of the raw image. For each grid cell, the mean and variance of each raw 543 channel is computed, and the do_capture call returns two 4-element float 544 images of dimensions (rawWidth / gridWidth, rawHeight / gridHeight), 545 concatenated back-to-back, where the first iamge contains the 4-channel 546 means and the second contains the 4-channel variances. 547 548 For the rawStats format, if the gridWidth is not provided then the raw 549 image width is used as the default, and similarly for gridHeight. With 550 this, the following is an example of a output description that computes 551 the mean and variance across each image row: 552 553 { 554 "gridHeight": 1, 555 "format": "rawStats" 556 } 557 558 Args: 559 cap_request: The Python dict/list specifying the capture(s), which 560 will be converted to JSON and sent to the device. 561 out_surfaces: (Optional) specifications of the output image formats 562 and sizes to use for each capture. 563 reprocess_format: (Optional) The reprocessing format. If not None, 564 reprocessing will be enabled. 565 566 Returns: 567 An object, list of objects, or list of lists of objects, where each 568 object contains the following fields: 569 * data: the image data as a numpy array of bytes. 570 * width: the width of the captured image. 571 * height: the height of the captured image. 572 * format: image the format, in [ 573 "yuv","jpeg","raw","raw10","raw12","rawStats","dng"]. 574 * metadata: the capture result object (Python dictionary). 575 """ 576 cmd = {} 577 if reprocess_format != None: 578 cmd["cmdName"] = "doReprocessCapture" 579 cmd["reprocessFormat"] = reprocess_format 580 else: 581 cmd["cmdName"] = "doCapture" 582 if not isinstance(cap_request, list): 583 cmd["captureRequests"] = [cap_request] 584 else: 585 cmd["captureRequests"] = cap_request 586 if out_surfaces is not None: 587 if not isinstance(out_surfaces, list): 588 cmd["outputSurfaces"] = [out_surfaces] 589 else: 590 cmd["outputSurfaces"] = out_surfaces 591 formats = [c["format"] if c.has_key("format") else "yuv" 592 for c in cmd["outputSurfaces"]] 593 formats = [s if s != "jpg" else "jpeg" for s in formats] 594 else: 595 formats = ['yuv'] 596 ncap = len(cmd["captureRequests"]) 597 nsurf = 1 if out_surfaces is None else len(cmd["outputSurfaces"]) 598 if len(formats) > len(set(formats)): 599 raise its.error.Error('Duplicate format requested') 600 raw_formats = 0; 601 raw_formats += 1 if "dng" in formats else 0 602 raw_formats += 1 if "raw" in formats else 0 603 raw_formats += 1 if "raw10" in formats else 0 604 raw_formats += 1 if "raw12" in formats else 0 605 raw_formats += 1 if "rawStats" in formats else 0 606 if raw_formats > 1: 607 raise its.error.Error('Different raw formats not supported') 608 609 # Detect long exposure time and set timeout accordingly 610 longest_exp_time = 0 611 for req in cmd["captureRequests"]: 612 if "android.sensor.exposureTime" in req and \ 613 req["android.sensor.exposureTime"] > longest_exp_time: 614 longest_exp_time = req["android.sensor.exposureTime"] 615 616 extended_timeout = longest_exp_time / self.SEC_TO_NSEC + \ 617 self.SOCK_TIMEOUT 618 self.sock.settimeout(extended_timeout) 619 620 print "Capturing %d frame%s with %d format%s [%s]" % ( 621 ncap, "s" if ncap>1 else "", nsurf, "s" if nsurf>1 else "", 622 ",".join(formats)) 623 self.sock.send(json.dumps(cmd) + "\n") 624 625 # Wait for ncap*nsurf images and ncap metadata responses. 626 # Assume that captures come out in the same order as requested in 627 # the burst, however individual images of different formats can come 628 # out in any order for that capture. 629 nbufs = 0 630 bufs = {"yuv":[], "raw":[], "raw10":[], "raw12":[], 631 "rawStats":[], "dng":[], "jpeg":[]} 632 mds = [] 633 widths = None 634 heights = None 635 while nbufs < ncap*nsurf or len(mds) < ncap: 636 jsonObj,buf = self.__read_response_from_socket() 637 if jsonObj['tag'] in ['jpegImage', 'yuvImage', 'rawImage', \ 638 'raw10Image', 'raw12Image', 'rawStatsImage', 'dngImage'] \ 639 and buf is not None: 640 fmt = jsonObj['tag'][:-5] 641 bufs[fmt].append(buf) 642 nbufs += 1 643 elif jsonObj['tag'] == 'captureResults': 644 mds.append(jsonObj['objValue']['captureResult']) 645 outputs = jsonObj['objValue']['outputs'] 646 widths = [out['width'] for out in outputs] 647 heights = [out['height'] for out in outputs] 648 else: 649 # Just ignore other tags 650 None 651 rets = [] 652 for j,fmt in enumerate(formats): 653 objs = [] 654 for i in range(ncap): 655 obj = {} 656 obj["data"] = bufs[fmt][i] 657 obj["width"] = widths[j] 658 obj["height"] = heights[j] 659 obj["format"] = fmt 660 obj["metadata"] = mds[i] 661 objs.append(obj) 662 rets.append(objs if ncap>1 else objs[0]) 663 self.sock.settimeout(self.SOCK_TIMEOUT) 664 return rets if len(rets)>1 else rets[0] 665 666def get_device_id(): 667 """ Return the ID of the device that the test is running on. 668 669 Return the device ID provided in the command line if it's connected. If no 670 device ID is provided in the command line and there is only one device 671 connected, return the device ID by parsing the result of "adb devices". 672 673 Raise an exception if no device is connected; or the device ID provided in 674 the command line is not connected; or no device ID is provided in the 675 command line and there are more than 1 device connected. 676 677 Returns: 678 Device ID string. 679 """ 680 device_id = None 681 for s in sys.argv[1:]: 682 if s[:7] == "device=" and len(s) > 7: 683 device_id = str(s[7:]) 684 685 # Get a list of connected devices 686 devices = [] 687 command = "adb devices" 688 proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE) 689 output, error = proc.communicate() 690 for line in output.split(os.linesep): 691 device_info = line.split() 692 if len(device_info) == 2 and device_info[1] == "device": 693 devices.append(device_info[0]) 694 695 if len(devices) == 0: 696 raise its.error.Error("No device is connected!") 697 elif device_id is not None and device_id not in devices: 698 raise its.error.Error(device_id + " is not connected!") 699 elif device_id is None and len(devices) >= 2: 700 raise its.error.Error("More than 1 device are connected. " + 701 "Use device=<device_id> to specify a device to test.") 702 elif len(devices) == 1: 703 device_id = devices[0] 704 705 return device_id 706 707def report_result(device_id, camera_id, success, summary_path=None): 708 """Send a pass/fail result to the device, via an intent. 709 710 Args: 711 device_id: The ID string of the device to report the results to. 712 camera_id: The ID string of the camera for which to report pass/fail. 713 success: Boolean, indicating if the result was pass or fail. 714 summary_path: (Optional) path to ITS summary file on host PC 715 716 Returns: 717 Nothing. 718 """ 719 adb = "adb -s " + device_id 720 device_summary_path = "/sdcard/camera_" + camera_id + "_its_summary.txt" 721 if summary_path is not None: 722 _run("%s push %s %s" % ( 723 adb, summary_path, device_summary_path)) 724 _run("%s shell am broadcast -a %s --es %s %s --es %s %s --es %s %s" % ( 725 adb, ItsSession.ACTION_ITS_RESULT, 726 ItsSession.EXTRA_CAMERA_ID, camera_id, 727 ItsSession.EXTRA_SUCCESS, 'True' if success else 'False', 728 ItsSession.EXTRA_SUMMARY, device_summary_path)) 729 else: 730 _run("%s shell am broadcast -a %s --es %s %s --es %s %s --es %s %s" % ( 731 adb, ItsSession.ACTION_ITS_RESULT, 732 ItsSession.EXTRA_CAMERA_ID, camera_id, 733 ItsSession.EXTRA_SUCCESS, 'True' if success else 'False', 734 ItsSession.EXTRA_SUMMARY, "null")) 735 736def _run(cmd): 737 """Replacement for os.system, with hiding of stdout+stderr messages. 738 """ 739 with open(os.devnull, 'wb') as devnull: 740 subprocess.check_call( 741 cmd.split(), stdout=devnull, stderr=subprocess.STDOUT) 742 743class __UnitTest(unittest.TestCase): 744 """Run a suite of unit tests on this module. 745 """ 746 747 # TODO: Add some unit tests. 748 None 749 750if __name__ == '__main__': 751 unittest.main() 752 753