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