1# Copyright 2016 The Chromium 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"""
6This module provides utilities needed to provision and run test on Android
7devices.
8"""
9
10
11import logging
12import re
13
14import common
15from autotest_lib.client.common_lib import global_config
16
17
18CONFIG = global_config.global_config
19
20def get_config_value_regex(section, regex):
21    """Get config values from global config based on regex of the key.
22
23    @param section: Section of the config, e.g., CLIENT.
24    @param regex: Regular expression of the key pattern.
25
26    @return: A dictionary of all config values matching the regex. Value is
27             assumed to be comma separated, and is converted to a list.
28    """
29    configs = CONFIG.get_config_value_regex(section, regex)
30    result = {}
31    for key, value in configs.items():
32        match = re.match(regex, key)
33        result[match.group(1)] = [v.strip() for v in value.split(',')
34                                  if v.strip()]
35    return result
36
37
38class AndroidAliases(object):
39    """Wrapper class for getting alias names for an android device.
40
41    On android it is only possible to get a devices product name
42    (eg. marlin, sailfish). However a product may have several aliases
43    that it is called by such as the name of its board, or a public name,
44    etc. This wrapper allows for mapping the product name to different
45    aliases.
46
47    Terms:
48        product: The name a device reports itself as.
49        board: The name of the hardware board in a device.
50        alias: Some name a device is called, this includes both product and
51               board.
52    """
53
54    # regex pattern for CLIENT/android_aliases_[product]. For example,
55    # global config can have following config in CLIENT section to indicate that
56    # android product name `zzz` has following aliases.
57    # ['my_board', 'xyz'].
58    # android_board_aliases_zzz: my_board,xyz
59    ALIASES_PATTERN = 'android_aliases_(.*)'
60
61    # A dict of product:aliases for product aliases, can be defined in global
62    # config CLIENT/android_aliases_[product]
63    aliases_map = get_config_value_regex('CLIENT',
64                                         ALIASES_PATTERN)
65
66    # regex pattern for CLIENT/android_board_name[product]. For example,
67    # global config can have following config in CLIENT section to indicate that
68    # android product `zzz` has following board name.
69    # xyz.
70    # android_board_name_zzz: xyz
71    BOARD_NAME_PATTERN = 'android_board_name_(.*)'
72
73
74    # A dict of product:board for product board names, can be defined in global
75    # config CLIENT/android_board_name_[product]
76    board_name_map = get_config_value_regex('CLIENT', BOARD_NAME_PATTERN)
77
78    @classmethod
79    def get_product_aliases(cls, product):
80        """Get all aliases for a android product name.
81
82        Androids can have multiple aliases for a single product. These aliases
83        may be what the device is called in different configs. For example
84        bat has a board name of bat_land. Therefore the product name bat
85        can be referenced as either bat or batland.
86
87        @param product: The name of the product that is reported for a device.
88        @returns: All aliases for that product (including the product name).
89        """
90        aliases = set(cls.aliases_map.get(product, []))
91        aliases.add(cls.get_board_name(product))
92        aliases.add(product)
93
94        return aliases
95
96    @classmethod
97    def get_board_name(cls, product):
98        """Get the board name of a product.
99
100        The board name of an android device is what the hardware is named.
101        In many cases this is the same name as the reported product name,
102        however some devices have boards that differ from the product name.
103
104        @param product: The name of the product.
105        @returns: The board name of the given product.
106        """
107        boards = cls.board_name_map.get(product, None)
108        if boards:
109            return boards[0]
110        return product
111
112
113class AndroidImageFiles(object):
114    """A wrapper class for constants and methods related to image files.
115    """
116
117    BOOTLOADER = 'bootloader.img'
118    RADIO = 'radio.img'
119    BOOT = 'boot.img'
120    SYSTEM = 'system.img'
121    VENDOR = 'vendor.img'
122
123    # Image files not inside the image zip file. These files should be
124    # downloaded directly from devserver.
125    DEFAULT_STANDALONE_IMAGES = [BOOTLOADER, RADIO]
126
127    # Default image files that are packaged in a zip file, e.g.,
128    # shamu-img-123456.zip
129    DEFAULT_ZIPPED_IMAGES = [BOOT, SYSTEM, VENDOR]
130
131    # Default image files to be flashed to an Android device.
132    DEFAULT_IMAGES = DEFAULT_STANDALONE_IMAGES + DEFAULT_ZIPPED_IMAGES
133
134    # regex pattern for CLIENT/android_standalone_images_[board]. For example,
135    # global config can have following config in CLIENT section to indicate that
136    # android board `xyz` has following standalone images.
137    # ['bootloader_image', 'radio_image'].
138    # android_standalone_xyz: bootloader.img,radio.img
139    STANDALONE_IMAGES_PATTERN = 'android_standalone_images_(.*)'
140
141    # A dict of board:images for standalone images, can be defined in global
142    # config CLIENT/android_standalone_images_[board]
143    standalone_images_map = get_config_value_regex('CLIENT',
144                                                   STANDALONE_IMAGES_PATTERN)
145
146    # regex pattern for CLIENT/android_standalone_images_[board]. For example,
147    # global config can have following config in CLIENT section to indicate that
148    # android board `xyz` has following standalone images.
149    # ['bootloader_image', 'radio_image'].
150    # android_zipped_xyz: bootloader.img,radio.img
151    ZIPPED_IMAGES_PATTERN = 'android_zipped_images_(.*)'
152
153    # A dict of board:images for zipped images, can be defined in global
154    # config CLIENT/android_zipped_images_[board]
155    zipped_images_map = get_config_value_regex('CLIENT', ZIPPED_IMAGES_PATTERN)
156
157    @classmethod
158    def get_standalone_images(cls, board):
159        """Get a list of standalone image files for given board.
160
161        @param board: Name of the board.
162
163        @return: A list of standalone image files.
164        """
165        if board in cls.standalone_images_map:
166            logging.debug('Found override of standalone image files for board '
167                          '%s: %s', board, cls.standalone_images_map[board])
168            return cls.standalone_images_map[board]
169        else:
170            return cls.DEFAULT_STANDALONE_IMAGES
171
172
173    @classmethod
174    def get_zipped_images(cls, board):
175        """Get a list of image files from zip_images artifact for given board.
176
177        @param board: Name of the board.
178
179        @return: A list of image files from `zip_images`.
180        """
181        if board in cls.zipped_images_map:
182            logging.debug('Found override of zip image files for board '
183                          '%s: %s', board, cls.zipped_images_map[board])
184            return cls.zipped_images_map[board]
185        else:
186            return cls.DEFAULT_ZIPPED_IMAGES
187
188
189class AndroidArtifacts(object):
190    """A wrapper class for constants and methods related to artifacts.
191    """
192
193    BOOTLOADER_IMAGE = 'bootloader_image'
194    DTB = 'dtb'
195    RADIO_IMAGE = 'radio_image'
196    TARGET_FILES = 'target_files'
197    VENDOR_PARTITIONS = 'vendor_partitions'
198    ZIP_IMAGE = 'zip_images'
199
200    # (os, board) = 'artifacts'
201    DEFAULT_ARTIFACTS_MAP = {
202        ('android', 'default'): [BOOTLOADER_IMAGE, RADIO_IMAGE, ZIP_IMAGE],
203        ('brillo', 'default'):  [ZIP_IMAGE, VENDOR_PARTITIONS],
204        ('emulated_brillo', 'default'): [TARGET_FILES, DTB],
205    }
206
207    # Default artifacts for Android provision
208    DEFAULT_ARTIFACTS_TO_BE_STAGED_FOR_IMAGE = (
209            ','.join([BOOTLOADER_IMAGE, RADIO_IMAGE, ZIP_IMAGE]))
210
211    # regex pattern for CLIENT/android_artifacts_[board]. For example, global
212    # config can have following config in CLIENT section to indicate that
213    # android board `xyz` needs to stage artifacts
214    # ['bootloader_image', 'radio_image'] for provision.
215    # android_artifacts_xyz: bootloader_image,radio_image
216    ARTIFACTS_LIST_PATTERN = 'android_artifacts_(.*)'
217
218    # A dict of board:artifacts, can be defined in global config
219    # CLIENT/android_artifacts_[board]
220    artifacts_map = get_config_value_regex('CLIENT', ARTIFACTS_LIST_PATTERN)
221
222    @classmethod
223    def get_artifacts_for_reimage(cls, board, os='android'):
224        """Get artifacts need to be staged for reimage for given board.
225
226        @param board: Name of the board.
227
228        @return: A string of artifacts to be staged.
229        """
230        logging.debug('artifacts for %s %s', os, board)
231        if board in cls.artifacts_map:
232            logging.debug('Found override of artifacts for board %s: %s', board,
233                          cls.artifacts_map[board])
234            artifacts = cls.artifacts_map[board]
235        elif (os, board) in cls.DEFAULT_ARTIFACTS_MAP:
236            artifacts = cls.DEFAULT_ARTIFACTS_MAP[(os, board)]
237        else:
238            artifacts = cls.DEFAULT_ARTIFACTS_MAP[(os, 'default')]
239        logging.debug('found %s', ','.join(artifacts))
240        return ','.join(artifacts)
241