|
6 | 6 | import re |
7 | 7 | import os |
8 | 8 | import subprocess |
9 | | -from typing import Union, List, Dict, Tuple |
| 9 | +from typing import Union, List, Dict, Tuple, Optional |
10 | 10 |
|
11 | 11 | from . import logger |
12 | 12 | from .utils import FreePort |
@@ -139,10 +139,64 @@ def install(self, apkpath: str): |
139 | 139 | raise HdcError("HDC install error", result.error) |
140 | 140 | return result |
141 | 141 |
|
142 | | - def list_apps(self) -> List[str]: |
143 | | - result = self.shell("bm dump -a") |
| 142 | + def list_apps(self, include_system_apps: bool = False) -> List[str]: |
| 143 | + """ |
| 144 | + List installed applications on the device. (Lazy loading, default: third-party apps) |
| 145 | +
|
| 146 | + Args: |
| 147 | + include_system_apps (bool): If True, include system apps in the list. |
| 148 | + If False, only list third-party apps. |
| 149 | +
|
| 150 | + Returns: |
| 151 | + List[str]: A list of application package names. |
| 152 | +
|
| 153 | + Note: |
| 154 | + - When include_system_apps is False, the list typically contains around 50 third-party apps. |
| 155 | + - When include_system_apps is True, the list typically contains around 200 apps in total. |
| 156 | + """ |
| 157 | + # Construct the shell command based on the include_system_apps flag |
| 158 | + if include_system_apps: |
| 159 | + command = "bm dump -a" |
| 160 | + else: |
| 161 | + command = "bm dump -a | grep -v 'com.huawei'" |
| 162 | + |
| 163 | + # Execute the shell command |
| 164 | + result = self.shell(command) |
144 | 165 | raw = result.output.split('\n') |
145 | | - return [item.strip() for item in raw] |
| 166 | + |
| 167 | + # Filter out strings starting with 'ID:' and empty strings |
| 168 | + return [item.strip() for item in raw if item.strip() and not re.match(r'^ID:', item.strip())] |
| 169 | + |
| 170 | + def app_version(self, bundlename: str) -> Dict[str, Optional[str]]: |
| 171 | + """ |
| 172 | + Get the version information of an app installed on the device. |
| 173 | +
|
| 174 | + Args: |
| 175 | + bundlename (str): The bundle name of the app. |
| 176 | +
|
| 177 | + Returns: |
| 178 | + dict: A dictionary containing the version information: |
| 179 | + - "versionName": The version name of the app. |
| 180 | + - "versionCode": The version code of the app. |
| 181 | + """ |
| 182 | + result = _execute_command(f"{self.hdc_prefix} -t {self.serial} shell bm dump -n {bundlename} | grep '\"versionCode\":\\|versionName\"'") |
| 183 | + |
| 184 | + matches = re.findall(r'"versionCode":\s*(\d+),\s*"versionName":\s*"([^"]*)"', result.output) |
| 185 | + if not matches: |
| 186 | + return dict( |
| 187 | + version_name='', |
| 188 | + version_code='' |
| 189 | + ) |
| 190 | + |
| 191 | + # Select the last match |
| 192 | + version_code, version_name = matches[-1] |
| 193 | + version_code = int(version_code) if version_code.isdigit() else None |
| 194 | + version_name = version_name if version_name != "" else None |
| 195 | + |
| 196 | + return dict( |
| 197 | + version_name=version_name, |
| 198 | + version_code=version_code |
| 199 | + ) |
146 | 200 |
|
147 | 201 | def has_app(self, package_name: str) -> bool: |
148 | 202 | data = self.shell("bm dump -a").output |
@@ -260,12 +314,38 @@ def swipe(self, x1, y1, x2, y2, speed=1000): |
260 | 314 | def input_text(self, x: int, y: int, text: str): |
261 | 315 | self.shell(f"uitest uiInput inputText {x} {y} {text}") |
262 | 316 |
|
263 | | - def screenshot(self, path: str) -> str: |
264 | | - _uuid = uuid.uuid4().hex |
265 | | - _tmp_path = f"/data/local/tmp/_tmp_{_uuid}.jpeg" |
266 | | - self.shell(f"snapshot_display -f {_tmp_path}") |
267 | | - self.recv_file(_tmp_path, path) |
268 | | - self.shell(f"rm -rf {_tmp_path}") # remove local path |
| 317 | + def screenshot(self, path: str, method: str = "snapshot_display") -> str: |
| 318 | + """ |
| 319 | + Take a screenshot using one of the two available methods. |
| 320 | +
|
| 321 | + Args: |
| 322 | + path (str): The local path where the screenshot will be saved. |
| 323 | + method (str): The screenshot method to use. Options are: |
| 324 | + - "snapshot_display" (default, recommended for better performance) |
| 325 | + This method is faster and more efficient, but the image quality is lower. |
| 326 | + - "screenCap" (alternative method) |
| 327 | + This method produces higher-quality images (5~20 times clearer), but it is slower. |
| 328 | +
|
| 329 | + Returns: |
| 330 | + str: The local path where the screenshot is saved. |
| 331 | + """ |
| 332 | + if method == "snapshot_display": |
| 333 | + # Use the recommended method (snapshot_display) |
| 334 | + _uuid = uuid.uuid4().hex |
| 335 | + _tmp_path = f"/data/local/tmp/_tmp_{_uuid}.jpeg" |
| 336 | + self.shell(f"snapshot_display -f {_tmp_path}") |
| 337 | + self.recv_file(_tmp_path, path) |
| 338 | + self.shell(f"rm -rf {_tmp_path}") |
| 339 | + elif method == "screenCap": |
| 340 | + # Use the alternative method (screenCap) |
| 341 | + _uuid = uuid.uuid4().hex |
| 342 | + _tmp_path = f"/data/local/tmp/{_uuid}.png" |
| 343 | + self.shell(f"uitest screenCap -p {_tmp_path}") |
| 344 | + self.recv_file(_tmp_path, path) |
| 345 | + self.shell(f"rm -rf {_tmp_path}") |
| 346 | + else: |
| 347 | + raise ValueError(f"Invalid screenshot method: {method}. Use 'snapshot_display' or 'screenCap'.") |
| 348 | + |
269 | 349 | return path |
270 | 350 |
|
271 | 351 | def dump_hierarchy(self) -> Dict: |
|
0 commit comments