1# Copyright (C) 2023 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 datetime 18import time 19from typing import Mapping 20 21from mobly.controllers import android_device 22 23from performance_test import gms_auto_updates_util 24 25WIFI_COUNTRYCODE_CONFIG_TIME_SEC = 3 26TOGGLE_AIRPLANE_MODE_WAIT_TIME_SEC = 2 27PH_FLAG_WRITE_WAIT_TIME_SEC = 3 28 29_DISABLE_ENABLE_GMS_UPDATE_WAIT_TIME_SEC = 2 30 31LOG_TAGS = [ 32 'Nearby', 33 'NearbyMessages', 34 'NearbyDiscovery', 35 'NearbyConnections', 36 'NearbyMediums', 37 'NearbySetup', 38] 39 40 41def set_country_code( 42 ad: android_device.AndroidDevice, country_code: str 43) -> None: 44 """Sets Wi-Fi and Telephony country code. 45 46 When you set the phone to EU or JP, the available 5GHz channels shrinks. 47 Some phones, like Pixel 2, can't use Wi-Fi Direct or Hotspot on 5GHz 48 in these countries. Pixel 3+ can, but only on some channels. 49 Not all of them. So, test Nearby Share or Nearby Connections without 50 Wi-Fi LAN to catch any bugs and make sure we don't break it later. 51 52 Args: 53 ad: AndroidDevice, Mobly Android Device. 54 country_code: WiFi and Telephony Country Code. 55 """ 56 if (not ad.is_adb_root): 57 ad.log.info(f'Skipped setting wifi country code on device "{ad.serial}" ' 58 'because we do not set country code on unrooted phone.') 59 return 60 61 ad.log.info(f'Set Wi-Fi and Telephony country code to {country_code}.') 62 ad.adb.shell('cmd wifi set-wifi-enabled disabled') 63 time.sleep(WIFI_COUNTRYCODE_CONFIG_TIME_SEC) 64 ad.adb.shell( 65 'am broadcast -a com.android.internal.telephony.action.COUNTRY_OVERRIDE' 66 f' --es country {country_code}' 67 ) 68 ad.adb.shell(f'cmd wifi force-country-code enabled {country_code}') 69 enable_airplane_mode(ad) 70 time.sleep(WIFI_COUNTRYCODE_CONFIG_TIME_SEC) 71 disable_airplane_mode(ad) 72 ad.adb.shell('cmd wifi set-wifi-enabled enabled') 73 telephony_country_code = ( 74 ad.adb.shell('dumpsys wifi | grep mTelephonyCountryCode') 75 .decode('utf-8') 76 .strip() 77 ) 78 ad.log.info(f'Telephony country code: {telephony_country_code}') 79 80 81def enable_logs(ad: android_device.AndroidDevice) -> None: 82 """Enables Nearby related logs.""" 83 ad.log.info('Enable Nearby loggings.') 84 for tag in LOG_TAGS: 85 ad.adb.shell(f'setprop log.tag.{tag} VERBOSE') 86 87 88def grant_manage_external_storage_permission( 89 ad: android_device.AndroidDevice, package_name: str 90) -> None: 91 """Grants MANAGE_EXTERNAL_STORAGE permission to Nearby snippet.""" 92 build_version_sdk = int(ad.build_info['build_version_sdk']) 93 if (build_version_sdk < 30): 94 return 95 ad.log.info( 96 f'Grant MANAGE_EXTERNAL_STORAGE permission on device "{ad.serial}".' 97 ) 98 _grant_manage_external_storage_permission(ad, package_name) 99 100 101def dump_gms_version(ad: android_device.AndroidDevice) -> Mapping[str, str]: 102 """Dumps GMS version from dumpsys to sponge properties.""" 103 out = ( 104 ad.adb.shell( 105 'dumpsys package com.google.android.gms | grep "versionCode="' 106 ) 107 .decode('utf-8') 108 .strip() 109 ) 110 return {f'GMS core version on {ad.serial}': out} 111 112 113def toggle_airplane_mode(ad: android_device.AndroidDevice) -> None: 114 """Toggles airplane mode on the given device.""" 115 ad.log.info('turn on airplane mode') 116 enable_airplane_mode(ad) 117 ad.log.info('turn off airplane mode') 118 disable_airplane_mode(ad) 119 120 121def connect_to_wifi_wlan_till_success( 122 ad: android_device.AndroidDevice, wifi_ssid: str, wifi_password: str 123) -> datetime.timedelta: 124 """Connecting to the specified wifi WLAN.""" 125 ad.log.info('Start connecting to wifi WLAN') 126 wifi_connect_start = datetime.datetime.now() 127 if not wifi_password: 128 wifi_password = None 129 connect_to_wifi(ad, wifi_ssid, wifi_password) 130 return datetime.datetime.now() - wifi_connect_start 131 132 133def connect_to_wifi( 134 ad: android_device.AndroidDevice, 135 ssid: str, 136 password: str | None = None, 137) -> None: 138 if not ad.nearby.wifiIsEnabled(): 139 ad.nearby.wifiEnable() 140 # return until the wifi is connected. 141 ad.nearby.wifiConnectSimple(ssid, password) 142 143 144def _grant_manage_external_storage_permission( 145 ad: android_device.AndroidDevice, package_name: str 146) -> None: 147 """Grants MANAGE_EXTERNAL_STORAGE permission to Nearby snippet. 148 149 This permission will not grant automatically by '-g' option of adb install, 150 you can check the all permission granted by: 151 am start -a android.settings.APPLICATION_DETAILS_SETTINGS 152 -d package:{YOUR_PACKAGE} 153 154 Reference for MANAGE_EXTERNAL_STORAGE: 155 https://developer.android.com/training/data-storage/manage-all-files 156 157 This permission will reset to default "Allow access to media only" after 158 reboot if you never grant "Allow management of all files" through system UI. 159 The appops command and MANAGE_EXTERNAL_STORAGE only available on API 30+. 160 161 Args: 162 ad: AndroidDevice, Mobly Android Device. 163 package_name: The nearbu snippet package name. 164 """ 165 try: 166 ad.adb.shell( 167 f'appops set --uid {package_name} MANAGE_EXTERNAL_STORAGE allow' 168 ) 169 except Exception: 170 ad.log.info('Failed to grant MANAGE_EXTERNAL_STORAGE permission.') 171 172 173def enable_airplane_mode(ad: android_device.AndroidDevice) -> None: 174 """Enables airplane mode on the given device.""" 175 if (ad.is_adb_root): 176 ad.adb.shell(['settings', 'put', 'global', 'airplane_mode_on', '1']) 177 ad.adb.shell([ 178 'am', 'broadcast', '-a', 'android.intent.action.AIRPLANE_MODE', '--ez', 179 'state', 'true' 180 ]) 181 ad.adb.shell(['svc', 'wifi', 'disable']) 182 ad.adb.shell(['svc', 'bluetooth', 'disable']) 183 time.sleep(TOGGLE_AIRPLANE_MODE_WAIT_TIME_SEC) 184 185 186def disable_airplane_mode(ad: android_device.AndroidDevice) -> None: 187 """Disables airplane mode on the given device.""" 188 if (ad.is_adb_root): 189 ad.adb.shell(['settings', 'put', 'global', 'airplane_mode_on', '0']) 190 ad.adb.shell([ 191 'am', 'broadcast', '-a', 'android.intent.action.AIRPLANE_MODE', '--ez', 192 'state', 'false' 193 ]) 194 ad.adb.shell(['svc', 'wifi', 'enable']) 195 ad.adb.shell(['svc', 'bluetooth', 'enable']) 196 time.sleep(TOGGLE_AIRPLANE_MODE_WAIT_TIME_SEC) 197 198 199def check_if_ph_flag_committed( 200 ad: android_device.AndroidDevice, 201 pname: str, 202 flag_name: str, 203) -> bool: 204 """Check if P/H flag is committed.""" 205 sql_str = ( 206 'sqlite3 /data/data/com.google.android.gms/databases/phenotype.db' 207 ' "select name, quote(coalesce(intVal, boolVal, floatVal, stringVal,' 208 ' extensionVal)) from FlagOverrides where committed=1 AND' 209 f' packageName=\'{pname}\';"' 210 ) 211 flag_result = ad.adb.shell(sql_str).decode('utf-8').strip() 212 return flag_name in flag_result 213 214 215def write_ph_flag( 216 ad: android_device.AndroidDevice, 217 pname: str, 218 flag_name: str, 219 flag_type: str, 220 flag_value: str, 221) -> None: 222 """Write P/H flag.""" 223 ad.adb.shell( 224 'am broadcast -a "com.google.android.gms.phenotype.FLAG_OVERRIDE" ' 225 f'--es package "{pname}" --es user "*" ' 226 f'--esa flags "{flag_name}" ' 227 f'--esa types "{flag_type}" --esa values "{flag_value}" ' 228 'com.google.android.gms' 229 ) 230 time.sleep(PH_FLAG_WRITE_WAIT_TIME_SEC) 231 232 233def check_and_try_to_write_ph_flag( 234 ad: android_device.AndroidDevice, 235 pname: str, 236 flag_name: str, 237 flag_type: str, 238 flag_value: str, 239) -> None: 240 """Check and try to enable the given flag on the given device.""" 241 if(not ad.is_adb_root): 242 ad.log.info( 243 "Can't read or write P/H flag value in non-rooted device. Use Mobile" 244 ' Utility app to config instead.' 245 ) 246 return 247 248 if check_if_ph_flag_committed(ad, pname, flag_name): 249 ad.log.info(f'{flag_name} is already committed.') 250 return 251 ad.log.info(f'write {flag_name}.') 252 write_ph_flag(ad, pname, flag_name, flag_type, flag_value) 253 254 if check_if_ph_flag_committed(ad, pname, flag_name): 255 ad.log.info(f'{flag_name} is configured successfully.') 256 else: 257 ad.log.info(f'failed to configure {flag_name}.') 258 259 260def enable_bluetooth_multiplex(ad: android_device.AndroidDevice) -> None: 261 """Enable bluetooth multiplex on the given device.""" 262 pname = 'com.google.android.gms.nearby' 263 flag_name = 'mediums_supports_bluetooth_multiplex_socket' 264 flag_type = 'boolean' 265 flag_value = 'true' 266 check_and_try_to_write_ph_flag(ad, pname, flag_name, flag_type, flag_value) 267 268 269def enable_wifi_aware(ad: android_device.AndroidDevice) -> None: 270 """Enable wifi aware on the given device.""" 271 pname = 'com.google.android.gms.nearby' 272 flag_name = 'mediums_supports_wifi_aware' 273 flag_type = 'boolean' 274 flag_value = 'true' 275 276 check_and_try_to_write_ph_flag(ad, pname, flag_name, flag_type, flag_value) 277 278 279def enable_auto_reconnect(ad: android_device.AndroidDevice) -> None: 280 """Enable auto reconnect on the given device.""" 281 pname = 'com.google.android.gms.nearby' 282 flag_name = 'connection_safe_to_disconnect_auto_reconnect_enabled' 283 flag_type = 'boolean' 284 flag_value = 'true' 285 check_and_try_to_write_ph_flag(ad, pname, flag_name, flag_type, flag_value) 286 287 flag_name = 'connection_safe_to_disconnect_auto_resume_enabled' 288 flag_type = 'boolean' 289 flag_value = 'true' 290 check_and_try_to_write_ph_flag(ad, pname, flag_name, flag_type, flag_value) 291 292 flag_name = 'connection_safe_to_disconnect_version' 293 flag_type = 'long' 294 flag_value = '4' 295 check_and_try_to_write_ph_flag(ad, pname, flag_name, flag_type, flag_value) 296 297 298def disable_redaction(ad: android_device.AndroidDevice) -> None: 299 """Disable info log redaction on the given device.""" 300 pname = 'com.google.android.gms' 301 flag_name = 'ClientLogging__enable_info_log_redaction' 302 flag_type = 'boolean' 303 flag_value = 'false' 304 305 check_and_try_to_write_ph_flag(ad, pname, flag_name, flag_type, flag_value) 306 307 308def install_apk(ad: android_device.AndroidDevice, apk_path: str) -> None: 309 """Installs the apk on the given device.""" 310 ad.adb.install(['-r', '-g', '-t', apk_path]) 311 312 313def disable_gms_auto_updates(ad: android_device.AndroidDevice) -> None: 314 """Disable GMS auto updates on the given device.""" 315 if not ad.is_adb_root: 316 ad.log.warning( 317 'You should disable the play store auto updates manually on a' 318 'unrooted device, otherwise the test may be broken unexpected') 319 ad.log.info('try to disable GMS Auto Updates.') 320 gms_auto_updates_util.GmsAutoUpdatesUtil(ad).disable_gms_auto_updates() 321 time.sleep(_DISABLE_ENABLE_GMS_UPDATE_WAIT_TIME_SEC) 322 323 324def enable_gms_auto_updates(ad: android_device.AndroidDevice) -> None: 325 """Enable GMS auto updates on the given device.""" 326 if not ad.is_adb_root: 327 ad.log.warning( 328 'You may enable the play store auto updates manually on a' 329 'unrooted device after test.') 330 ad.log.info('try to enable GMS Auto Updates.') 331 gms_auto_updates_util.GmsAutoUpdatesUtil(ad).enable_gms_auto_updates() 332 time.sleep(_DISABLE_ENABLE_GMS_UPDATE_WAIT_TIME_SEC) 333