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