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
27import string
28
29CMD_DELAY = 1  # seconds
30
31
32class 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
766def 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
816def 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
860def 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
887def _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
894class __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
913if __name__ == '__main__':
914    unittest.main()
915
916