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