Home | History | Annotate | Download | only in cros
      1 # Copyright 2018 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import contextlib
      6 import json
      7 import logging
      8 from lxml import etree
      9 import os
     10 import StringIO
     11 
     12 from autotest_lib.client.common_lib import utils
     13 
     14 
     15 class ChartFixture:
     16     """Sets up chart tablet to display dummy scene image."""
     17     DISPLAY_SCRIPT = '/usr/local/autotest/bin/display_chart.py'
     18 
     19     def __init__(self, chart_host, scene_uri):
     20         self.host = chart_host
     21         self.scene_uri = scene_uri
     22         self.display_pid = None
     23 
     24     def initialize(self):
     25         """Prepare scene file and display it on chart host."""
     26         logging.info('Prepare scene file')
     27         chart_dir = self.host.get_tmp_dir()
     28         scene_path = os.path.join(
     29                 chart_dir, self.scene_uri[self.scene_uri.rfind('/') + 1:])
     30         self.host.run('wget', args=('-O', scene_path, self.scene_uri))
     31         self.host.run('chmod', args=('-R', '755', chart_dir))
     32 
     33         logging.info('Display scene file')
     34         self.display_pid = self.host.run_background(
     35                 'python %s %s' % (self.DISPLAY_SCRIPT, scene_path))
     36         # TODO(inker): Suppose chart should be displayed very soon. Or require
     37         # of waiting until chart actually displayed.
     38 
     39     def cleanup(self):
     40         """Cleanup display script."""
     41         if self.display_pid is not None:
     42             self.host.run('kill', args=('-2', str(self.display_pid)))
     43 
     44 
     45 def get_chart_address(host_address, args):
     46     """Get address of chart tablet from commandline args or mapping logic in
     47     test lab.
     48 
     49     @param host_address: a list of hostname strings.
     50     @param args: a dict parse from commandline args.
     51     @return:
     52         A list of strings for chart tablet addresses.
     53     """
     54     address = utils.args_to_dict(args).get('chart')
     55     if address is not None:
     56         return address.split(',')
     57     elif utils.is_in_container():
     58         return [
     59                 utils.get_lab_chart_address(host)
     60                 for host in host_address
     61         ]
     62     else:
     63         return None
     64 
     65 
     66 class DUTFixture:
     67     """Sets up camera filter for target camera facing on DUT."""
     68     TEST_CONFIG_PATH = '/var/cache/camera/test_config.json'
     69     GENERATE_CAMERA_PROFILE = os.path.join('/usr', 'bin',
     70                                            'generate_camera_profile')
     71     GENERATE_CAMERA_PROFILE_BACKUP = GENERATE_CAMERA_PROFILE + '.bak'
     72     CAMERA_PROFILE_PATH = ('/mnt/stateful_partition/encrypted/var/cache/camera'
     73                            '/media_profiles.xml')
     74 
     75     def __init__(self, test, host, facing):
     76         self.test = test
     77         self.host = host
     78         self.facing = facing
     79 
     80     @contextlib.contextmanager
     81     def _set_selinux_permissive(self):
     82         selinux_mode = self.host.run_output('getenforce')
     83         self.host.run('setenforce 0')
     84         yield
     85         self.host.run('setenforce', args=(selinux_mode, ))
     86 
     87     def _filter_camera_profile(self, content, facing):
     88         """Filter camera profile of target facing from content of camera
     89         profile.
     90 
     91         @return:
     92             New camera profile with only target facing, camera ids are
     93             renumbered from 0.
     94         """
     95         tree = etree.parse(
     96                 StringIO.StringIO(content),
     97                 parser=etree.XMLParser(compact=False))
     98         root = tree.getroot()
     99         profiles = root.findall('CamcorderProfiles')
    100         logging.debug('%d number of camera(s) found in camera profile',
    101                       len(profiles))
    102         assert 1 <= len(profiles) <= 2
    103         if len(profiles) == 2:
    104             cam_id = 0 if facing == 'back' else 1
    105             for p in profiles:
    106                 if cam_id == int(p.attrib['cameraId']):
    107                     p.attrib['cameraId'] = '0'
    108                 else:
    109                     root.remove(p)
    110         else:
    111             with self.test._login_chrome(
    112                     board=self.test._get_board_name(),
    113                     reboot=False), self._set_selinux_permissive():
    114                 has_front_camera = (
    115                         'feature:android.hardware.camera.front' in self.host.
    116                         run_output('android-sh -c "pm list features"'))
    117                 logging.debug('has_front_camera=%s', has_front_camera)
    118             if (facing == 'front') != has_front_camera:
    119                 root.remove(profiles[0])
    120         return etree.tostring(
    121                 tree, xml_declaration=True, encoding=tree.docinfo.encoding)
    122 
    123     def _read_file(self, filepath):
    124         """Read content of filepath from host."""
    125         tmp_path = os.path.join(self.test.tmpdir, os.path.basename(filepath))
    126         self.host.get_file(filepath, tmp_path, delete_dest=True)
    127         with open(tmp_path) as f:
    128             return f.read()
    129 
    130     def _write_file(self, filepath, content, permission=None, owner=None):
    131         """Write content to filepath on remote host.
    132         @param permission: set permission to 0xxx octal number of remote file.
    133         @param owner: set owner of remote file.
    134         """
    135         tmp_path = os.path.join(self.test.tmpdir, os.path.basename(filepath))
    136         with open(tmp_path, 'w') as f:
    137             f.write(content)
    138         if permission is not None:
    139             os.chmod(tmp_path, permission)
    140         self.host.send_file(tmp_path, filepath, delete_dest=True)
    141         if owner is not None:
    142             self.host.run('chown', args=(owner, filepath))
    143 
    144     def initialize(self):
    145         """Filter out camera other than target facing on DUT."""
    146         logging.info('Restart camera service with filter option')
    147         self._write_file(
    148                 self.TEST_CONFIG_PATH,
    149                 json.dumps({
    150                         'enable_back_camera': self.facing == 'back',
    151                         'enable_front_camera': self.facing == 'front',
    152                         'enable_external_camera': False
    153                 }),
    154                 owner='arc-camera')
    155         self.host.run('restart cros-camera')
    156 
    157         # To replace camera profile in ARC++ container, arc_setup will run
    158         # GENERATE_CAMERA_PROFILE and mount its generated profile under
    159         # CAMERA_PROFILE_PATH into container.
    160         logging.info('Replace camera profile in ARC++ container')
    161         self.host.run(
    162                 'mv',
    163                 args=(self.GENERATE_CAMERA_PROFILE,
    164                       self.GENERATE_CAMERA_PROFILE_BACKUP))
    165         self._write_file(
    166                 self.GENERATE_CAMERA_PROFILE,
    167                 '''\
    168 #!/bin/bash
    169 # Put this executable file to %r to make sure arc-setup knows
    170 # we're using dynamic media_profiles.xml copy from host path
    171 # %r''' % (self.GENERATE_CAMERA_PROFILE, self.CAMERA_PROFILE_PATH),
    172                 permission=0755)
    173         profile = self._read_file(self.CAMERA_PROFILE_PATH)
    174         new_profile = self._filter_camera_profile(profile, self.facing)
    175         self._write_file(self.CAMERA_PROFILE_PATH, new_profile)
    176         self.host.run('restart ui')
    177 
    178     def cleanup(self):
    179         """Cleanup camera filter."""
    180         logging.info('Remove filter option and restore camera service')
    181         self.host.run('rm', args=('-f', self.TEST_CONFIG_PATH))
    182         self.host.run('restart cros-camera')
    183 
    184         # Restore GENERATE_CAMERA_PROFILE to regenerate camera profile on DUT.
    185         logging.info('Restore camera profile in ARC++ container')
    186         self.host.run(
    187                 'mv',
    188                 args=(self.GENERATE_CAMERA_PROFILE_BACKUP,
    189                       self.GENERATE_CAMERA_PROFILE),
    190                 ignore_status=True)
    191         self.host.run('restart ui')
    192