• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright 2020 - The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Config Android SDK information.
18
19In order to create the configuration of Android SDK in IntelliJ automatically,
20parses the Android SDK information from the Android SDK path.
21
22Usage example:
23    android_sdk = AndroidSDK()
24    android_sdk.path_analysis(default_sdk_path)
25    api_level = android_sdk.max_api_level
26    android_sdk_path = android_sdk.android_sdk_path
27    platform_mapping = android_sdk.platform_mapping
28"""
29
30from __future__ import absolute_import
31
32import glob
33import os
34import re
35
36from aidegen.lib import common_util
37
38
39class AndroidSDK:
40    """Configures API level from the Android SDK path.
41
42    Attributes:
43        _android_sdk_path: The path to the Android SDK,
44                           None if the Android SDK doesn't exist.
45        _max_api_level: An integer, the max API level in the platforms folder.
46        _max_code_name: A string, the code name of the max API level.
47        _max_folder_name: A string, the folder name corresponding
48                          to the max API level.
49        _platform_mapping: A dictionary of Android platform versions mapping to
50                           the API level and the Android version code name. e.g.
51                           {
52                             'android-29': {
53                               'api_level': 29,
54                               'code_name': '29',
55                             },
56                             'android-Q': {
57                               'api_level': 29,
58                               'code_name': 'Q',
59                             }
60                           }
61    """
62
63    _API_LEVEL = 'api_level'
64    _CODE_NAME = 'code_name'
65    _FOLDER_NAME = 'folder_name'
66    _RE_API_LEVEL = re.compile(
67        r'AndroidVersion\.ApiLevel=(?P<api_level>[\d]+)')
68    _RE_CODE_NAME = re.compile(
69        r'AndroidVersion\.CodeName=(?P<code_name>[a-zA-Z]+)')
70    _GLOB_PROPERTIES_FILE = os.path.join('platforms', 'android-*',
71                                         'source.properties')
72    _INPUT_QUERY_TIMES = 3
73    _ENTER_ANDROID_SDK_PATH = ('\nThe Android SDK folder:{} doesn\'t exist. '
74                               'The debug function "Attach debugger to Android '
75                               'process" is disabled without Android SDK in '
76                               'IntelliJ or Android Studio. Please set it up '
77                               'to enable the function. \nPlease enter the '
78                               'absolute path to Android SDK:')
79    _WARNING_NO_ANDROID_SDK = ('Please install the Android SDK, otherwise the '
80                               'debug function "Attach debugger to Android '
81                               'process" cannot be enabled in IntelliJ or '
82                               'Android Studio.')
83
84    def __init__(self):
85        """Initializes AndroidSDK."""
86        self._max_api_level = 0
87        self._max_code_name = None
88        self._max_folder_name = None
89        self._platform_mapping = {}
90        self._android_sdk_path = None
91
92    @property
93    def max_api_level(self):
94        """Gets the max API level."""
95        return self._max_api_level
96
97    @property
98    def max_code_name(self):
99        """Gets the max code name."""
100        return self._max_code_name
101
102    @property
103    def max_folder_name(self):
104        """Gets the max folder name."""
105        return self._max_folder_name
106
107    @property
108    def platform_mapping(self):
109        """Gets the Android platform mapping."""
110        return self._platform_mapping
111
112    @property
113    def android_sdk_path(self):
114        """Gets the Android SDK path."""
115        return self._android_sdk_path
116
117    def _parse_max_api_level(self):
118        """Parses the max API level from self._platform_mapping.
119
120        Returns:
121            An integer of API level and 0 means no Android platform exists.
122        """
123        return max(
124            [v[self._API_LEVEL] for v in self._platform_mapping.values()],
125            default=0)
126
127    def _parse_max_code_name(self):
128        """Parses the max code name from self._platform_mapping.
129
130        Returns:
131            A string of code name.
132        """
133        code_name = ''
134        for data in self._platform_mapping.values():
135            if (data[self._API_LEVEL] == self._max_api_level
136                    and data[self._CODE_NAME] > code_name):
137                code_name = data[self._CODE_NAME]
138        return code_name
139
140    def _get_max_folder_name(self):
141        """Gets the max folder name from self._platform_mapping.
142
143        Returns:
144            A string of the folder name corresponding to the max API level.
145        """
146        folder_name = ''
147        for platform, data in self._platform_mapping.items():
148            if (data[self._API_LEVEL] == self.max_api_level
149                    and data[self._CODE_NAME] == self._max_code_name):
150                folder_name = platform
151                break
152        return folder_name
153
154    def _parse_api_info(self, properties_file):
155        """Parses the API information from the source.properties file.
156
157        For the preview platform like android-Q, the source.properties file
158        contains two properties named AndroidVersion.ApiLevel, API level of
159        the platform, and AndroidVersion.CodeName such as Q, the code name of
160        the platform.
161        However, the formal platform like android-29, there is no property
162        AndroidVersion.CodeName.
163
164        Args:
165            properties_file: A path of the source.properties file.
166
167        Returns:
168            A tuple contains the API level and Code name of the
169            source.properties file.
170            API level: An integer of the platform, e.g. 29.
171            Code name: A string, e.g. 29 or Q.
172        """
173        api_level = 0
174        properties = common_util.read_file_content(properties_file)
175        match_api_level = self._RE_API_LEVEL.search(properties)
176        if match_api_level:
177            api_level = match_api_level.group(self._API_LEVEL)
178        match_code_name = self._RE_CODE_NAME.search(properties)
179        if match_code_name:
180            code_name = match_code_name.group(self._CODE_NAME)
181        else:
182            code_name = api_level
183        return api_level, code_name
184
185    def _gen_platform_mapping(self, path):
186        """Generates the Android platforms mapping.
187
188        Args:
189            path: A string, the absolute path of Android SDK.
190
191        Returns:
192            True when successful generates platform mapping, otherwise False.
193        """
194        prop_files = glob.glob(os.path.join(path, self._GLOB_PROPERTIES_FILE))
195        for prop_file in prop_files:
196            api_level, code_name = self._parse_api_info(prop_file)
197            if not api_level:
198                continue
199            platform = os.path.basename(os.path.dirname(prop_file))
200            self._platform_mapping[platform] = {
201                self._API_LEVEL: int(api_level),
202                self._CODE_NAME: code_name
203            }
204        return bool(self._platform_mapping)
205
206    def is_android_sdk_path(self, path):
207        """Checks if the Android SDK path is correct.
208
209        Confirm the Android SDK path is correct by checking if it has
210        platform versions.
211
212        Args:
213            path: A string, the path of Android SDK user input.
214
215        Returns:
216            True when get a platform version, otherwise False.
217        """
218        if self._gen_platform_mapping(path):
219            self._android_sdk_path = path
220            self._max_api_level = self._parse_max_api_level()
221            self._max_code_name = self._parse_max_code_name()
222            self._max_folder_name = self._get_max_folder_name()
223            return True
224        return False
225
226    def path_analysis(self, sdk_path):
227        """Analyses the Android SDK path.
228
229        Confirm the path is an Android SDK folder. If it's not correct, ask user
230        to enter a new one. Skip asking when enter nothing.
231
232        Args:
233            sdk_path: A string, the path of Android SDK.
234
235        Returns:
236            True when get an Android SDK path, otherwise False.
237        """
238        for _ in range(self._INPUT_QUERY_TIMES):
239            if self.is_android_sdk_path(sdk_path):
240                return True
241            sdk_path = input(common_util.COLORED_FAIL(
242                self._ENTER_ANDROID_SDK_PATH.format(sdk_path)))
243            if not sdk_path:
244                break
245        print('\n{} {}\n'.format(common_util.COLORED_INFO('Warning:'),
246                                 self._WARNING_NO_ANDROID_SDK))
247        return False
248