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