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