1# Copyright (C) 2024 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 15"""Android Nearby device setup.""" 16 17import base64 18import dataclasses 19import datetime 20import time 21from typing import Mapping 22 23from mobly.controllers import android_device 24from mobly.controllers.android_device_lib import adb 25 26from betocq import gms_auto_updates_util 27from betocq import nc_constants 28 29WIFI_COUNTRYCODE_CONFIG_TIME_SEC = 3 30TOGGLE_AIRPLANE_MODE_WAIT_TIME_SEC = 2 31PH_FLAG_WRITE_WAIT_TIME_SEC = 3 32WIFI_DISCONNECTION_DELAY_SEC = 3 33ADB_RETRY_WAIT_TIME_SEC = 2 34 35_DISABLE_ENABLE_GMS_UPDATE_WAIT_TIME_SEC = 2 36 37 38read_ph_flag_failed = False 39 40LOG_TAGS = [ 41 'Nearby', 42 'NearbyMessages', 43 'NearbyDiscovery', 44 'NearbyConnections', 45 'NearbyMediums', 46 'NearbySetup', 47] 48 49 50def set_country_code( 51 ad: android_device.AndroidDevice, country_code: str 52) -> None: 53 """Sets Wi-Fi and Telephony country code. 54 55 When you set the phone to EU or JP, the available 5GHz channels shrinks. 56 Some phones, like Pixel 2, can't use Wi-Fi Direct or Hotspot on 5GHz 57 in these countries. Pixel 3+ can, but only on some channels. 58 Not all of them. So, test Nearby Share or Nearby Connections without 59 Wi-Fi LAN to catch any bugs and make sure we don't break it later. 60 61 Args: 62 ad: AndroidDevice, Mobly Android Device. 63 country_code: WiFi and Telephony Country Code. 64 """ 65 try: 66 _do_set_country_code(ad, country_code) 67 except adb.AdbError: 68 ad.log.exception( 69 f'Failed to set country code on device "{ad.serial}, try again.' 70 ) 71 time.sleep(ADB_RETRY_WAIT_TIME_SEC) 72 _do_set_country_code(ad, country_code) 73 74 75def _do_set_country_code( 76 ad: android_device.AndroidDevice, country_code: str 77) -> None: 78 """Sets Wi-Fi and Telephony country code.""" 79 if not ad.is_adb_root: 80 ad.log.info( 81 f'Skipped setting wifi country code on device "{ad.serial}" ' 82 'because we do not set country code on unrooted phone.' 83 ) 84 return 85 86 ad.log.info(f'Set Wi-Fi and Telephony country code to {country_code}.') 87 ad.adb.shell('cmd wifi set-wifi-enabled disabled') 88 time.sleep(WIFI_COUNTRYCODE_CONFIG_TIME_SEC) 89 ad.adb.shell( 90 'am broadcast -a com.android.internal.telephony.action.COUNTRY_OVERRIDE' 91 f' --es country {country_code}' 92 ) 93 ad.adb.shell(f'cmd wifi force-country-code enabled {country_code}') 94 enable_airplane_mode(ad) 95 time.sleep(WIFI_COUNTRYCODE_CONFIG_TIME_SEC) 96 disable_airplane_mode(ad) 97 ad.adb.shell('cmd wifi set-wifi-enabled enabled') 98 telephony_country_code = ( 99 ad.adb.shell('dumpsys wifi | grep mTelephonyCountryCode') 100 .decode('utf-8') 101 .strip() 102 ) 103 ad.log.info(f'Telephony country code: {telephony_country_code}') 104 105 106def enable_logs(ad: android_device.AndroidDevice) -> None: 107 """Enables Nearby related logs.""" 108 ad.log.info('Enable Nearby loggings.') 109 for tag in LOG_TAGS: 110 ad.adb.shell(f'setprop log.tag.{tag} VERBOSE') 111 112 113def grant_manage_external_storage_permission( 114 ad: android_device.AndroidDevice, package_name: str 115) -> None: 116 """Grants MANAGE_EXTERNAL_STORAGE permission to Nearby snippet.""" 117 try: 118 _do_grant_manage_external_storage_permission(ad, package_name) 119 except adb.AdbError: 120 ad.log.exception( 121 'Failed to grant MANAGE_EXTERNAL_STORAGE permission on device' 122 f' "{ad.serial}", try again.' 123 ) 124 time.sleep(ADB_RETRY_WAIT_TIME_SEC) 125 _do_grant_manage_external_storage_permission(ad, package_name) 126 127 128def _do_grant_manage_external_storage_permission( 129 ad: android_device.AndroidDevice, package_name: str 130) -> None: 131 """Grants MANAGE_EXTERNAL_STORAGE permission to Nearby snippet.""" 132 build_version_sdk = int(ad.build_info['build_version_sdk']) 133 if build_version_sdk < 30: 134 return 135 ad.log.info( 136 f'Grant MANAGE_EXTERNAL_STORAGE permission on device "{ad.serial}".' 137 ) 138 _grant_manage_external_storage_permission(ad, package_name) 139 140 141def dump_gms_version(ad: android_device.AndroidDevice) -> Mapping[str, str]: 142 """Dumps GMS version from dumpsys to sponge properties.""" 143 try: 144 gms_version = _do_dump_gms_version(ad) 145 except adb.AdbError: 146 ad.log.exception( 147 f'Failed to dump GMS version on device "{ad.serial}", try again.' 148 ) 149 time.sleep(ADB_RETRY_WAIT_TIME_SEC) 150 gms_version = _do_dump_gms_version(ad) 151 return gms_version 152 153 154def _do_dump_gms_version(ad: android_device.AndroidDevice) -> Mapping[str, str]: 155 """Dumps GMS version from dumpsys to sponge properties.""" 156 out = ( 157 ad.adb.shell( 158 'dumpsys package com.google.android.gms | grep "versionCode="' 159 ) 160 .decode('utf-8') 161 .strip() 162 ) 163 return {f'GMS core version on {ad.serial}': out} 164 165 166def toggle_airplane_mode(ad: android_device.AndroidDevice) -> None: 167 """Toggles airplane mode on the given device.""" 168 ad.log.info('turn on airplane mode') 169 enable_airplane_mode(ad) 170 ad.log.info('turn off airplane mode') 171 disable_airplane_mode(ad) 172 173 174def connect_to_wifi_sta_till_success( 175 ad: android_device.AndroidDevice, wifi_ssid: str, wifi_password: str 176) -> datetime.timedelta: 177 """Connecting to the specified wifi STA/AP.""" 178 ad.log.info('Start connecting to wifi STA/AP') 179 wifi_connect_start = datetime.datetime.now() 180 if not wifi_password: 181 wifi_password = None 182 connect_to_wifi(ad, wifi_ssid, wifi_password) 183 return datetime.datetime.now() - wifi_connect_start 184 185 186def connect_to_wifi( 187 ad: android_device.AndroidDevice, 188 ssid: str, 189 password: str | None = None, 190) -> None: 191 if not ad.nearby.wifiIsEnabled(): 192 ad.nearby.wifiEnable() 193 # return until the wifi is connected. 194 password = password or None 195 ad.log.info('Connect to wifi: ssid: %s, password: %s', ssid, password) 196 ad.nearby.wifiConnectSimple(ssid, password) 197 198 199def disconnect_from_wifi(ad: android_device.AndroidDevice) -> None: 200 if not ad.is_adb_root: 201 ad.log.info("Can't clear wifi network in non-rooted device") 202 return 203 ad.nearby.wifiClearConfiguredNetworks() 204 time.sleep(WIFI_DISCONNECTION_DELAY_SEC) 205 206 207def _grant_manage_external_storage_permission( 208 ad: android_device.AndroidDevice, package_name: str 209) -> None: 210 """Grants MANAGE_EXTERNAL_STORAGE permission to Nearby snippet. 211 212 This permission will not grant automatically by '-g' option of adb install, 213 you can check the all permission granted by: 214 am start -a android.settings.APPLICATION_DETAILS_SETTINGS 215 -d package:{YOUR_PACKAGE} 216 217 Reference for MANAGE_EXTERNAL_STORAGE: 218 https://developer.android.com/training/data-storage/manage-all-files 219 220 This permission will reset to default "Allow access to media only" after 221 reboot if you never grant "Allow management of all files" through system UI. 222 The appops command and MANAGE_EXTERNAL_STORAGE only available on API 30+. 223 224 Args: 225 ad: AndroidDevice, Mobly Android Device. 226 package_name: The nearbu snippet package name. 227 """ 228 try: 229 ad.adb.shell( 230 f'appops set --uid {package_name} MANAGE_EXTERNAL_STORAGE allow' 231 ) 232 except adb.Error: 233 ad.log.info('Failed to grant MANAGE_EXTERNAL_STORAGE permission.') 234 235 236def enable_airplane_mode(ad: android_device.AndroidDevice) -> None: 237 """Enables airplane mode on the given device.""" 238 try: 239 _do_enable_airplane_mode(ad) 240 except adb.AdbError: 241 ad.log.exception( 242 f'Failed to enable airplane mode on device "{ad.serial}", try again.' 243 ) 244 time.sleep(ADB_RETRY_WAIT_TIME_SEC) 245 _do_enable_airplane_mode(ad) 246 247 248def _do_enable_airplane_mode(ad: android_device.AndroidDevice) -> None: 249 if (ad.is_adb_root): 250 ad.adb.shell(['settings', 'put', 'global', 'airplane_mode_on', '1']) 251 ad.adb.shell([ 252 'am', 'broadcast', '-a', 'android.intent.action.AIRPLANE_MODE', '--ez', 253 'state', 'true' 254 ]) 255 ad.adb.shell(['svc', 'wifi', 'disable']) 256 ad.adb.shell(['svc', 'bluetooth', 'disable']) 257 time.sleep(TOGGLE_AIRPLANE_MODE_WAIT_TIME_SEC) 258 259 260def disable_airplane_mode(ad: android_device.AndroidDevice) -> None: 261 """Disables airplane mode on the given device.""" 262 try: 263 _do_disable_airplane_mode(ad) 264 except adb.AdbError: 265 ad.log.exception( 266 f'Failed to disable airplane mode on device "{ad.serial}", try again.' 267 ) 268 time.sleep(ADB_RETRY_WAIT_TIME_SEC) 269 _do_disable_airplane_mode(ad) 270 271 272def _do_disable_airplane_mode(ad: android_device.AndroidDevice) -> None: 273 if (ad.is_adb_root): 274 ad.adb.shell(['settings', 'put', 'global', 'airplane_mode_on', '0']) 275 ad.adb.shell([ 276 'am', 'broadcast', '-a', 'android.intent.action.AIRPLANE_MODE', '--ez', 277 'state', 'false' 278 ]) 279 ad.adb.shell(['svc', 'wifi', 'enable']) 280 ad.adb.shell(['svc', 'bluetooth', 'enable']) 281 time.sleep(TOGGLE_AIRPLANE_MODE_WAIT_TIME_SEC) 282 283 284def check_if_ph_flag_committed( 285 ad: android_device.AndroidDevice, 286 pname: str, 287 flag_name: str, 288) -> bool: 289 """Check if P/H flag is committed. 290 291 Some devices don't support to check the flag with sqlite3. After the flag 292 check fails for the first time, it won't try it again. 293 294 Args: 295 ad: AndroidDevice, Mobly Android Device. 296 pname: The package name of the P/H flag. 297 flag_name: The name of the P/H flag. 298 299 Returns: 300 True if the P/H flag is committed. 301 """ 302 global read_ph_flag_failed 303 if read_ph_flag_failed: 304 return False 305 sql_str = ( 306 'sqlite3 /data/data/com.google.android.gms/databases/phenotype.db' 307 ' "select name, quote(coalesce(intVal, boolVal, floatVal, stringVal,' 308 ' extensionVal)) from FlagOverrides where committed=1 AND' 309 f' packageName=\'{pname}\';"' 310 ) 311 try: 312 flag_result = ad.adb.shell(sql_str).decode('utf-8').strip() 313 return flag_name in flag_result 314 except adb.AdbError: 315 read_ph_flag_failed = True 316 ad.log.exception('Failed to check PH flag') 317 return False 318 319 320def write_ph_flag( 321 ad: android_device.AndroidDevice, 322 pname: str, 323 flag_name: str, 324 flag_type: str, 325 flag_value: str, 326) -> None: 327 """Write P/H flag.""" 328 ad.adb.shell( 329 'am broadcast -a "com.google.android.gms.phenotype.FLAG_OVERRIDE" ' 330 f'--es package "{pname}" --es user "*" ' 331 f'--esa flags "{flag_name}" ' 332 f'--esa types "{flag_type}" --esa values "{flag_value}" ' 333 'com.google.android.gms' 334 ) 335 time.sleep(PH_FLAG_WRITE_WAIT_TIME_SEC) 336 337 338def check_and_try_to_write_ph_flag( 339 ad: android_device.AndroidDevice, 340 pname: str, 341 flag_name: str, 342 flag_type: str, 343 flag_value: str, 344) -> None: 345 """Check and try to enable the given flag on the given device.""" 346 if(not ad.is_adb_root): 347 ad.log.info( 348 "Can't read or write P/H flag value in non-rooted device. Use Mobile" 349 ' Utility app to config instead.' 350 ) 351 return 352 353 if check_if_ph_flag_committed(ad, pname, flag_name): 354 ad.log.info(f'{flag_name} is already committed.') 355 return 356 ad.log.info(f'write {flag_name}.') 357 write_ph_flag(ad, pname, flag_name, flag_type, flag_value) 358 359 if check_if_ph_flag_committed(ad, pname, flag_name): 360 ad.log.info(f'{flag_name} is configured successfully.') 361 else: 362 ad.log.info(f'failed to configure {flag_name}.') 363 364 365def enable_bluetooth_multiplex(ad: android_device.AndroidDevice) -> None: 366 """Enable bluetooth multiplex on the given device.""" 367 pname = 'com.google.android.gms.nearby' 368 flag_name = 'mediums_supports_bluetooth_multiplex_socket' 369 flag_type = 'boolean' 370 flag_value = 'true' 371 check_and_try_to_write_ph_flag(ad, pname, flag_name, flag_type, flag_value) 372 373 374def enable_wifi_aware(ad: android_device.AndroidDevice) -> None: 375 """Enable wifi aware on the given device.""" 376 pname = 'com.google.android.gms.nearby' 377 flag_name = 'mediums_supports_wifi_aware' 378 flag_type = 'boolean' 379 flag_value = 'true' 380 381 check_and_try_to_write_ph_flag(ad, pname, flag_name, flag_type, flag_value) 382 383 384def enable_dfs_scc(ad: android_device.AndroidDevice) -> None: 385 """Enable WFD/WIFI_HOTSPOT in a STA-associated DFS channel.""" 386 pname = 'com.google.android.gms.nearby' 387 flag_name = 'mediums_lower_dfs_channel_priority' 388 flag_type = 'boolean' 389 flag_value = 'false' 390 391 check_and_try_to_write_ph_flag(ad, pname, flag_name, flag_type, flag_value) 392 393 394def disable_wlan_deny_list(ad: android_device.AndroidDevice) -> None: 395 """Enable WFD/WIFI_HOTSPOT in a STA-associated DFS channel.""" 396 pname = 'com.google.android.gms.nearby' 397 flag_name = 'wifi_lan_blacklist_verify_bssid_interval_hours' 398 flag_type = 'long' 399 flag_value = '0' 400 401 check_and_try_to_write_ph_flag(ad, pname, flag_name, flag_type, flag_value) 402 403 flag_name = 'mediums_wifi_lan_temporary_blacklist_verify_bssid_interval_hours' 404 check_and_try_to_write_ph_flag(ad, pname, flag_name, flag_type, flag_value) 405 406 407def enable_ble_scan_throttling_during_2g_transfer( 408 ad: android_device.AndroidDevice, enable_ble_scan_throttling: bool = False 409) -> None: 410 """Enable BLE scan throttling during 2G transfer. 411 """ 412 413 # The default values for the following parameters are 3 mins which are long 414 # enough for the performance test. 415 # mediums_ble_client_wifi_24_ghz_warming_up_duration 416 # fast_pair_wifi_24_ghz_warming_up_duration 417 # sharing_wifi_24_ghz_warming_up_duration 418 419 pname = 'com.google.android.gms.nearby' 420 flag_name = 'fast_pair_enable_connection_state_changed_listener' 421 flag_type = 'boolean' 422 flag_value = 'true' if enable_ble_scan_throttling else 'false' 423 check_and_try_to_write_ph_flag(ad, pname, flag_name, flag_type, flag_value) 424 425 flag_name = 'sharing_enable_connection_state_changed_listener' 426 check_and_try_to_write_ph_flag(ad, pname, flag_name, flag_type, flag_value) 427 428 flag_name = 'mediums_ble_client_enable_connection_state_changed_listener' 429 check_and_try_to_write_ph_flag(ad, pname, flag_name, flag_type, flag_value) 430 431 432def disable_redaction(ad: android_device.AndroidDevice) -> None: 433 """Disable info log redaction on the given device.""" 434 pname = 'com.google.android.gms' 435 flag_name = 'ClientLogging__enable_info_log_redaction' 436 flag_type = 'boolean' 437 flag_value = 'false' 438 439 check_and_try_to_write_ph_flag(ad, pname, flag_name, flag_type, flag_value) 440 441 442def install_apk(ad: android_device.AndroidDevice, apk_path: str) -> None: 443 """Installs the apk on the given device.""" 444 ad.adb.install(['-r', '-g', '-t', apk_path]) 445 446 447def disable_gms_auto_updates(ad: android_device.AndroidDevice) -> None: 448 """Disable GMS auto updates on the given device.""" 449 if not ad.is_adb_root: 450 ad.log.warning( 451 'You should disable the play store auto updates manually on a' 452 'unrooted device, otherwise the test may be broken unexpected') 453 ad.log.info('try to disable GMS Auto Updates.') 454 gms_auto_updates_util.GmsAutoUpdatesUtil(ad).disable_gms_auto_updates() 455 time.sleep(_DISABLE_ENABLE_GMS_UPDATE_WAIT_TIME_SEC) 456 457 458def enable_gms_auto_updates(ad: android_device.AndroidDevice) -> None: 459 """Enable GMS auto updates on the given device.""" 460 if not ad.is_adb_root: 461 ad.log.warning( 462 'You may enable the play store auto updates manually on a' 463 'unrooted device after test.') 464 ad.log.info('try to enable GMS Auto Updates.') 465 gms_auto_updates_util.GmsAutoUpdatesUtil(ad).enable_gms_auto_updates() 466 time.sleep(_DISABLE_ENABLE_GMS_UPDATE_WAIT_TIME_SEC) 467 468 469def get_wifi_sta_frequency(ad: android_device.AndroidDevice) -> int: 470 """Get wifi STA frequency on the given device.""" 471 wifi_sta_status = dump_wifi_sta_status(ad) 472 if not wifi_sta_status: 473 return nc_constants.INVALID_INT 474 prefix = 'Frequency:' 475 postfix = 'MHz' 476 return get_int_between_prefix_postfix(wifi_sta_status, prefix, postfix) 477 478 479def get_wifi_p2p_frequency(ad: android_device.AndroidDevice) -> int: 480 """Get wifi p2p frequency on the given device.""" 481 wifi_p2p_status = dump_wifi_p2p_status(ad) 482 if not wifi_p2p_status: 483 return nc_constants.INVALID_INT 484 prefix = 'channelFrequency=' 485 postfix = ', groupRole=GroupOwner' 486 return get_int_between_prefix_postfix(wifi_p2p_status, prefix, postfix) 487 488 489def get_wifi_sta_max_link_speed(ad: android_device.AndroidDevice) -> int: 490 """Get wifi STA max supported Tx link speed on the given device.""" 491 wifi_sta_status = dump_wifi_sta_status(ad) 492 if not wifi_sta_status: 493 return nc_constants.INVALID_INT 494 prefix = 'Max Supported Tx Link speed:' 495 postfix = 'Mbps' 496 return get_int_between_prefix_postfix(wifi_sta_status, prefix, postfix) 497 498 499def get_int_between_prefix_postfix( 500 string: str, prefix: str, postfix: str 501) -> int: 502 left_index = string.rfind(prefix) 503 right_index = string.rfind(postfix) 504 if left_index > 0 and right_index > left_index: 505 try: 506 return int(string[left_index + len(prefix): right_index].strip()) 507 except ValueError: 508 return nc_constants.INVALID_INT 509 return nc_constants.INVALID_INT 510 511 512def dump_wifi_sta_status(ad: android_device.AndroidDevice) -> str: 513 """Dumps wifi STA status on the given device.""" 514 try: 515 return ( 516 ad.adb.shell('cmd wifi status | grep WifiInfo').decode('utf-8').strip() 517 ) 518 except adb.AdbError: 519 return '' 520 521 522def dump_wifi_p2p_status(ad: android_device.AndroidDevice) -> str: 523 """Dumps wifi p2p status on the given device.""" 524 try: 525 return ( 526 ad.adb.shell('dumpsys wifip2p').decode('utf-8').strip() 527 ) 528 except adb.AdbError: 529 return '' 530 531 532def get_hardware(ad: android_device.AndroidDevice) -> str: 533 """Gets hardware information on the given device.""" 534 return ad.adb.getprop('ro.hardware') 535