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", or "raw12". The default is a YUV420 frame ("yuv") 452 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 Args: 540 cap_request: The Python dict/list specifying the capture(s), which 541 will be converted to JSON and sent to the device. 542 out_surfaces: (Optional) specifications of the output image formats 543 and sizes to use for each capture. 544 reprocess_format: (Optional) The reprocessing format. If not None, 545 reprocessing will be enabled. 546 547 Returns: 548 An object, list of objects, or list of lists of objects, where each 549 object contains the following fields: 550 * data: the image data as a numpy array of bytes. 551 * width: the width of the captured image. 552 * height: the height of the captured image. 553 * format: image the format, in ["yuv","jpeg","raw","raw10","dng"]. 554 * metadata: the capture result object (Python dictionary). 555 """ 556 cmd = {} 557 if reprocess_format != None: 558 cmd["cmdName"] = "doReprocessCapture" 559 cmd["reprocessFormat"] = reprocess_format 560 else: 561 cmd["cmdName"] = "doCapture" 562 if not isinstance(cap_request, list): 563 cmd["captureRequests"] = [cap_request] 564 else: 565 cmd["captureRequests"] = cap_request 566 if out_surfaces is not None: 567 if not isinstance(out_surfaces, list): 568 cmd["outputSurfaces"] = [out_surfaces] 569 else: 570 cmd["outputSurfaces"] = out_surfaces 571 formats = [c["format"] if c.has_key("format") else "yuv" 572 for c in cmd["outputSurfaces"]] 573 formats = [s if s != "jpg" else "jpeg" for s in formats] 574 else: 575 formats = ['yuv'] 576 ncap = len(cmd["captureRequests"]) 577 nsurf = 1 if out_surfaces is None else len(cmd["outputSurfaces"]) 578 if len(formats) > len(set(formats)): 579 raise its.error.Error('Duplicate format requested') 580 if "dng" in formats and "raw" in formats or \ 581 "dng" in formats and "raw10" in formats or \ 582 "raw" in formats and "raw10" in formats: 583 raise its.error.Error('Different raw formats not supported') 584 585 # Detect long exposure time and set timeout accordingly 586 longest_exp_time = 0 587 for req in cmd["captureRequests"]: 588 if "android.sensor.exposureTime" in req and \ 589 req["android.sensor.exposureTime"] > longest_exp_time: 590 longest_exp_time = req["android.sensor.exposureTime"] 591 592 extended_timeout = longest_exp_time / self.SEC_TO_NSEC + \ 593 self.SOCK_TIMEOUT 594 self.sock.settimeout(extended_timeout) 595 596 print "Capturing %d frame%s with %d format%s [%s]" % ( 597 ncap, "s" if ncap>1 else "", nsurf, "s" if nsurf>1 else "", 598 ",".join(formats)) 599 self.sock.send(json.dumps(cmd) + "\n") 600 601 # Wait for ncap*nsurf images and ncap metadata responses. 602 # Assume that captures come out in the same order as requested in 603 # the burst, however individual images of different formats can come 604 # out in any order for that capture. 605 nbufs = 0 606 bufs = {"yuv":[], "raw":[], "raw10":[], "dng":[], "jpeg":[]} 607 mds = [] 608 widths = None 609 heights = None 610 while nbufs < ncap*nsurf or len(mds) < ncap: 611 jsonObj,buf = self.__read_response_from_socket() 612 if jsonObj['tag'] in ['jpegImage', 'yuvImage', 'rawImage', \ 613 'raw10Image', 'dngImage'] and buf is not None: 614 fmt = jsonObj['tag'][:-5] 615 bufs[fmt].append(buf) 616 nbufs += 1 617 elif jsonObj['tag'] == 'captureResults': 618 mds.append(jsonObj['objValue']['captureResult']) 619 outputs = jsonObj['objValue']['outputs'] 620 widths = [out['width'] for out in outputs] 621 heights = [out['height'] for out in outputs] 622 else: 623 # Just ignore other tags 624 None 625 rets = [] 626 for j,fmt in enumerate(formats): 627 objs = [] 628 for i in range(ncap): 629 obj = {} 630 obj["data"] = bufs[fmt][i] 631 obj["width"] = widths[j] 632 obj["height"] = heights[j] 633 obj["format"] = fmt 634 obj["metadata"] = mds[i] 635 objs.append(obj) 636 rets.append(objs if ncap>1 else objs[0]) 637 self.sock.settimeout(self.SOCK_TIMEOUT) 638 return rets if len(rets)>1 else rets[0] 639 640def get_device_id(): 641 """ Return the ID of the device that the test is running on. 642 643 Return the device ID provided in the command line if it's connected. If no 644 device ID is provided in the command line and there is only one device 645 connected, return the device ID by parsing the result of "adb devices". 646 647 Raise an exception if no device is connected; or the device ID provided in 648 the command line is not connected; or no device ID is provided in the 649 command line and there are more than 1 device connected. 650 651 Returns: 652 Device ID string. 653 """ 654 device_id = None 655 for s in sys.argv[1:]: 656 if s[:7] == "device=" and len(s) > 7: 657 device_id = str(s[7:]) 658 659 # Get a list of connected devices 660 devices = [] 661 command = "adb devices" 662 proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE) 663 output, error = proc.communicate() 664 for line in output.split(os.linesep): 665 device_info = line.split() 666 if len(device_info) == 2 and device_info[1] == "device": 667 devices.append(device_info[0]) 668 669 if len(devices) == 0: 670 raise its.error.Error("No device is connected!") 671 elif device_id is not None and device_id not in devices: 672 raise its.error.Error(device_id + " is not connected!") 673 elif device_id is None and len(devices) >= 2: 674 raise its.error.Error("More than 1 device are connected. " + 675 "Use device=<device_id> to specify a device to test.") 676 elif len(devices) == 1: 677 device_id = devices[0] 678 679 return device_id 680 681def report_result(device_id, camera_id, success, summary_path=None): 682 """Send a pass/fail result to the device, via an intent. 683 684 Args: 685 device_id: The ID string of the device to report the results to. 686 camera_id: The ID string of the camera for which to report pass/fail. 687 success: Boolean, indicating if the result was pass or fail. 688 summary_path: (Optional) path to ITS summary file on host PC 689 690 Returns: 691 Nothing. 692 """ 693 adb = "adb -s " + device_id 694 device_summary_path = "/sdcard/camera_" + camera_id + "_its_summary.txt" 695 if summary_path is not None: 696 _run("%s push %s %s" % ( 697 adb, summary_path, device_summary_path)) 698 _run("%s shell am broadcast -a %s --es %s %s --es %s %s --es %s %s" % ( 699 adb, ItsSession.ACTION_ITS_RESULT, 700 ItsSession.EXTRA_CAMERA_ID, camera_id, 701 ItsSession.EXTRA_SUCCESS, 'True' if success else 'False', 702 ItsSession.EXTRA_SUMMARY, device_summary_path)) 703 else: 704 _run("%s shell am broadcast -a %s --es %s %s --es %s %s --es %s %s" % ( 705 adb, ItsSession.ACTION_ITS_RESULT, 706 ItsSession.EXTRA_CAMERA_ID, camera_id, 707 ItsSession.EXTRA_SUCCESS, 'True' if success else 'False', 708 ItsSession.EXTRA_SUMMARY, "null")) 709 710def _run(cmd): 711 """Replacement for os.system, with hiding of stdout+stderr messages. 712 """ 713 with open(os.devnull, 'wb') as devnull: 714 subprocess.check_call( 715 cmd.split(), stdout=devnull, stderr=subprocess.STDOUT) 716 717class __UnitTest(unittest.TestCase): 718 """Run a suite of unit tests on this module. 719 """ 720 721 # TODO: Add some unit tests. 722 None 723 724if __name__ == '__main__': 725 unittest.main() 726 727