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