1#!/usr/bin/env python3
2#
3#   Copyright 2020 - Google
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
17import time
18import re
19import os
20import math
21import shutil
22import fnmatch
23import posixpath
24import tempfile
25from collections import namedtuple
26
27from acts import utils
28from acts import signals
29from acts.libs.proc import job
30from acts.controllers.android_device import list_adb_devices
31from acts.controllers.android_device import list_fastboot_devices
32from acts.controllers.android_device import DEFAULT_QXDM_LOG_PATH
33from acts.controllers.android_device import SL4A_APK_NAME
34from acts.test_utils.wifi import wifi_test_utils as wutils
35from acts.test_utils.tel import tel_test_utils as tutils
36from acts.utils import get_current_epoch_time
37from acts.utils import epoch_to_human_time
38
39WifiEnums = wutils.WifiEnums
40PULL_TIMEOUT = 300
41GNSSSTATUS_LOG_PATH = (
42    "/storage/emulated/0/Android/data/com.android.gpstool/files/")
43QXDM_MASKS = ["GPS.cfg", "GPS-general.cfg", "default.cfg"]
44TTFF_REPORT = namedtuple(
45    "TTFF_REPORT", "utc_time ttff_loop ttff_sec ttff_pe ttff_ant_cn "
46                   "ttff_base_cn")
47TRACK_REPORT = namedtuple(
48    "TRACK_REPORT", "l5flag pe ant_top4cn ant_cn base_top4cn base_cn")
49LOCAL_PROP_FILE_CONTENTS =  """\
50log.tag.LocationManagerService=VERBOSE
51log.tag.GnssLocationProvider=VERBOSE
52log.tag.GnssMeasurementsProvider=VERBOSE
53log.tag.GpsNetInitiatedHandler=VERBOSE
54log.tag.GnssNetworkConnectivityHandler=VERBOSE
55log.tag.ConnectivityService=VERBOSE
56log.tag.ConnectivityManager=VERBOSE
57log.tag.GnssVisibilityControl=VERBOSE
58log.tag.NtpTimeHelper=VERBOSE
59log.tag.NtpTrustedTime=VERBOSE"""
60TEST_PACKAGE_NAME = "com.google.android.apps.maps"
61LOCATION_PERMISSIONS = [
62    "android.permission.ACCESS_FINE_LOCATION",
63    "android.permission.ACCESS_COARSE_LOCATION"
64]
65GNSSTOOL_PACKAGE_NAME = "com.android.gpstool"
66GNSSTOOL_PERMISSIONS = [
67    "android.permission.ACCESS_FINE_LOCATION",
68    "android.permission.READ_EXTERNAL_STORAGE",
69    "android.permission.ACCESS_COARSE_LOCATION",
70    "android.permission.CALL_PHONE",
71    "android.permission.WRITE_CONTACTS",
72    "android.permission.CAMERA",
73    "android.permission.WRITE_EXTERNAL_STORAGE",
74    "android.permission.READ_CONTACTS",
75    "android.permission.ACCESS_BACKGROUND_LOCATION"
76]
77
78
79class GnssTestUtilsError(Exception):
80    pass
81
82
83def remount_device(ad):
84    """Remount device file system to read and write.
85
86    Args:
87        ad: An AndroidDevice object.
88    """
89    for retries in range(5):
90        ad.root_adb()
91        if ad.adb.getprop("ro.boot.veritymode") == "enforcing":
92            disable_verity_result = ad.adb.disable_verity()
93            reboot(ad)
94        remount_result = ad.adb.remount()
95        ad.log.info("Attempt %d - %s" % (retries + 1, remount_result))
96        if "remount succeeded" in remount_result:
97            break
98
99
100def reboot(ad):
101    """Reboot device and check if mobile data is available.
102
103    Args:
104        ad: An AndroidDevice object.
105    """
106    ad.log.info("Reboot device to make changes take effect.")
107    ad.reboot()
108    ad.unlock_screen(password=None)
109    if not int(ad.adb.shell("settings get global mobile_data")) == 1:
110        set_mobile_data(ad, True)
111    utils.sync_device_time(ad)
112
113
114def enable_gnss_verbose_logging(ad):
115    """Enable GNSS VERBOSE Logging and persistent logcat.
116
117    Args:
118        ad: An AndroidDevice object.
119    """
120    remount_device(ad)
121    ad.log.info("Enable GNSS VERBOSE Logging and persistent logcat.")
122    ad.adb.shell("echo -e '\nDEBUG_LEVEL = 5' >> /vendor/etc/gps.conf")
123    ad.adb.shell("echo %r >> /data/local.prop" % LOCAL_PROP_FILE_CONTENTS)
124    ad.adb.shell("chmod 644 /data/local.prop")
125    ad.adb.shell("setprop persist.logd.logpersistd.size 20000")
126    ad.adb.shell("setprop persist.logd.size 16777216")
127    ad.adb.shell("setprop persist.vendor.radio.adb_log_on 1")
128    ad.adb.shell("setprop persist.logd.logpersistd logcatd")
129    ad.adb.shell("setprop log.tag.copresGcore VERBOSE")
130    ad.adb.shell("sync")
131
132
133def get_am_flags(value):
134    """Returns the (value, type) flags for a given python value."""
135    if type(value) is bool:
136        return str(value).lower(), 'boolean'
137    elif type(value) is str:
138        return value, 'string'
139    raise ValueError("%s should be either 'boolean' or 'string'" % value)
140
141
142def enable_compact_and_particle_fusion_log(ad):
143    """Enable CompactLog, FLP particle fusion log and disable gms
144    location-based quake monitoring.
145
146    Args:
147        ad: An AndroidDevice object.
148    """
149    ad.root_adb()
150    ad.log.info("Enable FLP flags and Disable GMS location-based quake "
151                "monitoring.")
152    overrides = {
153        'compact_log_enabled': True,
154        'flp_use_particle_fusion': True,
155        'flp_particle_fusion_extended_bug_report': True,
156        'flp_event_log_size': '86400',
157        'proks_config': '28',
158        'flp_particle_fusion_bug_report_window_sec': '86400',
159        'flp_particle_fusion_bug_report_max_buffer_size': '86400',
160        'seismic_data_collection': False,
161        'Ealert__enable': False,
162    }
163    for flag, python_value in overrides.items():
164        value, type = get_am_flags(python_value)
165        cmd = ("am broadcast -a com.google.android.gms.phenotype.FLAG_OVERRIDE "
166               "--es package com.google.android.location --es user \* "
167               "--esa flags %s --esa values %s --esa types %s "
168               "com.google.android.gms" % (flag, value, type))
169        ad.adb.shell(cmd)
170    ad.adb.shell("am force-stop com.google.android.gms")
171    ad.adb.shell("am broadcast -a com.google.android.gms.INITIALIZE")
172
173
174def disable_xtra_throttle(ad):
175    """Disable XTRA throttle will have no limit to download XTRA data.
176
177    Args:
178        ad: An AndroidDevice object.
179    """
180    remount_device(ad)
181    ad.log.info("Disable XTRA Throttle.")
182    ad.adb.shell("echo -e '\nXTRA_TEST_ENABLED=1' >> /vendor/etc/gps.conf")
183    ad.adb.shell("echo -e '\nXTRA_THROTTLE_ENABLED=0' >> /vendor/etc/gps.conf")
184
185
186def enable_supl_mode(ad):
187    """Enable SUPL back on for next test item.
188
189    Args:
190        ad: An AndroidDevice object.
191    """
192    remount_device(ad)
193    ad.log.info("Enable SUPL mode.")
194    ad.adb.shell("echo -e '\nSUPL_MODE=1' >> /etc/gps_debug.conf")
195
196
197def disable_supl_mode(ad):
198    """Kill SUPL to test XTRA only test item.
199
200    Args:
201        ad: An AndroidDevice object.
202    """
203    remount_device(ad)
204    ad.log.info("Disable SUPL mode.")
205    ad.adb.shell("echo -e '\nSUPL_MODE=0' >> /etc/gps_debug.conf")
206    reboot(ad)
207
208
209def kill_xtra_daemon(ad):
210    """Kill XTRA daemon to test SUPL only test item.
211
212    Args:
213        ad: An AndroidDevice object.
214    """
215    ad.root_adb()
216    ad.log.info("Disable XTRA-daemon until next reboot.")
217    ad.adb.shell("killall xtra-daemon")
218
219
220def disable_private_dns_mode(ad):
221    """Due to b/118365122, it's better to disable private DNS mode while
222       testing. 8.8.8.8 private dns sever is unstable now, sometimes server
223       will not response dns query suddenly.
224
225    Args:
226        ad: An AndroidDevice object.
227    """
228    tutils.get_operator_name(ad.log, ad, subId=None)
229    if ad.adb.shell("settings get global private_dns_mode") != "off":
230        ad.log.info("Disable Private DNS mode.")
231        ad.adb.shell("settings put global private_dns_mode off")
232
233
234def _init_device(ad):
235    """Init GNSS test devices.
236
237    Args:
238        ad: An AndroidDevice object.
239    """
240    enable_gnss_verbose_logging(ad)
241    enable_compact_and_particle_fusion_log(ad)
242    disable_xtra_throttle(ad)
243    enable_supl_mode(ad)
244    ad.adb.shell("settings put system screen_off_timeout 1800000")
245    wutils.wifi_toggle_state(ad, False)
246    ad.log.info("Setting Bluetooth state to False")
247    ad.droid.bluetoothToggleState(False)
248    set_gnss_qxdm_mask(ad, QXDM_MASKS)
249    check_location_service(ad)
250    set_wifi_and_bt_scanning(ad, True)
251    disable_private_dns_mode(ad)
252    reboot(ad)
253    init_gtw_gpstool(ad)
254
255
256def connect_to_wifi_network(ad, network):
257    """Connection logic for open and psk wifi networks.
258
259    Args:
260        ad: An AndroidDevice object.
261        network: Dictionary with network info.
262    """
263    SSID = network[WifiEnums.SSID_KEY]
264    ad.ed.clear_all_events()
265    wutils.reset_wifi(ad)
266    wutils.start_wifi_connection_scan_and_ensure_network_found(ad, SSID)
267    wutils.wifi_connect(ad, network, num_of_tries=5)
268
269
270def set_wifi_and_bt_scanning(ad, state=True):
271    """Set Wi-Fi and Bluetooth scanning on/off in Settings -> Location
272
273    Args:
274        ad: An AndroidDevice object.
275        state: True to turn on "Wi-Fi and Bluetooth scanning".
276            False to turn off "Wi-Fi and Bluetooth scanning".
277    """
278    ad.root_adb()
279    if state:
280        ad.adb.shell("settings put global wifi_scan_always_enabled 1")
281        ad.adb.shell("settings put global ble_scan_always_enabled 1")
282        ad.log.info("Wi-Fi and Bluetooth scanning are enabled")
283    else:
284        ad.adb.shell("settings put global wifi_scan_always_enabled 0")
285        ad.adb.shell("settings put global ble_scan_always_enabled 0")
286        ad.log.info("Wi-Fi and Bluetooth scanning are disabled")
287
288
289def check_location_service(ad):
290    """Set location service on.
291       Verify if location service is available.
292
293    Args:
294        ad: An AndroidDevice object.
295    """
296    remount_device(ad)
297    utils.set_location_service(ad, True)
298    location_mode = int(ad.adb.shell("settings get secure location_mode"))
299    ad.log.info("Current Location Mode >> %d" % location_mode)
300    if location_mode != 3:
301        raise signals.TestError("Failed to turn Location on")
302
303
304def clear_logd_gnss_qxdm_log(ad):
305    """Clear /data/misc/logd,
306    /storage/emulated/0/Android/data/com.android.gpstool/files and
307    /data/vendor/radio/diag_logs/logs from previous test item then reboot.
308
309    Args:
310        ad: An AndroidDevice object.
311    """
312    remount_device(ad)
313    ad.log.info("Clear Logd, GNSS and QXDM Log from previous test item.")
314    ad.adb.shell("rm -rf /data/misc/logd", ignore_status=True)
315    ad.adb.shell('find %s -name "*.txt" -type f -delete' % GNSSSTATUS_LOG_PATH)
316    output_path = posixpath.join(DEFAULT_QXDM_LOG_PATH, "logs")
317    ad.adb.shell("rm -rf %s" % output_path, ignore_status=True)
318    reboot(ad)
319
320
321def get_gnss_qxdm_log(ad, qdb_path):
322    """Get /storage/emulated/0/Android/data/com.android.gpstool/files and
323    /data/vendor/radio/diag_logs/logs for test item.
324
325    Args:
326        ad: An AndroidDevice object.
327        qdb_path: The path of qdsp6m.qdb on different projects.
328    """
329    log_path = ad.device_log_path
330    os.makedirs(log_path, exist_ok=True)
331    gnss_log_name = "gnssstatus_log_%s_%s" % (ad.model, ad.serial)
332    gnss_log_path = posixpath.join(log_path, gnss_log_name)
333    os.makedirs(gnss_log_path, exist_ok=True)
334    ad.log.info("Pull GnssStatus Log to %s" % gnss_log_path)
335    ad.adb.pull("%s %s" % (GNSSSTATUS_LOG_PATH+".", gnss_log_path),
336                timeout=PULL_TIMEOUT, ignore_status=True)
337    shutil.make_archive(gnss_log_path, "zip", gnss_log_path)
338    shutil.rmtree(gnss_log_path)
339    output_path = posixpath.join(DEFAULT_QXDM_LOG_PATH, "logs/.")
340    file_count = ad.adb.shell(
341        "find %s -type f -iname *.qmdl | wc -l" % output_path)
342    if not int(file_count) == 0:
343        qxdm_log_name = "QXDM_%s_%s" % (ad.model, ad.serial)
344        qxdm_log_path = posixpath.join(log_path, qxdm_log_name)
345        os.makedirs(qxdm_log_path, exist_ok=True)
346        ad.log.info("Pull QXDM Log %s to %s" % (output_path, qxdm_log_path))
347        ad.adb.pull("%s %s" % (output_path, qxdm_log_path),
348                    timeout=PULL_TIMEOUT, ignore_status=True)
349        for path in qdb_path:
350            output = ad.adb.pull("%s %s" % (path, qxdm_log_path),
351                                 timeout=PULL_TIMEOUT, ignore_status=True)
352            if "No such file or directory" in output:
353                continue
354            break
355        shutil.make_archive(qxdm_log_path, "zip", qxdm_log_path)
356        shutil.rmtree(qxdm_log_path)
357    else:
358        ad.log.error("QXDM file count is %d. There is no QXDM log on device."
359                     % int(file_count))
360
361
362def set_mobile_data(ad, state):
363    """Set mobile data on or off and check mobile data state.
364
365    Args:
366        ad: An AndroidDevice object.
367        state: True to enable mobile data. False to disable mobile data.
368    """
369    ad.root_adb()
370    if state:
371        ad.log.info("Enable mobile data.")
372        ad.adb.shell("svc data enable")
373    else:
374        ad.log.info("Disable mobile data.")
375        ad.adb.shell("svc data disable")
376    time.sleep(5)
377    out = int(ad.adb.shell("settings get global mobile_data"))
378    if state and out == 1:
379        ad.log.info("Mobile data is enabled and set to %d" % out)
380    elif not state and out == 0:
381        ad.log.info("Mobile data is disabled and set to %d" % out)
382    else:
383        ad.log.error("Mobile data is at unknown state and set to %d" % out)
384
385
386def gnss_trigger_modem_ssr_by_adb(ad, dwelltime=60):
387    """Trigger modem SSR crash by adb and verify if modem crash and recover
388    successfully.
389
390    Args:
391        ad: An AndroidDevice object.
392        dwelltime: Waiting time for modem reset. Default is 60 seconds.
393
394    Returns:
395        True if success.
396        False if failed.
397    """
398    begin_time = get_current_epoch_time()
399    ad.root_adb()
400    cmds = ("echo restart > /sys/kernel/debug/msm_subsys/modem",
401            r"echo 'at+cfun=1,1\r' > /dev/at_mdm0")
402    for cmd in cmds:
403        ad.log.info("Triggering modem SSR crash by %s" % cmd)
404        output = ad.adb.shell(cmd, ignore_status=True)
405        if "No such file or directory" in output:
406            continue
407        break
408    time.sleep(dwelltime)
409    ad.send_keycode("HOME")
410    logcat_results = ad.search_logcat("SSRObserver", begin_time)
411    if logcat_results:
412        for ssr in logcat_results:
413            if "mSubsystem='modem', mCrashReason" in ssr["log_message"]:
414                ad.log.debug(ssr["log_message"])
415                ad.log.info("Triggering modem SSR crash successfully.")
416                return True
417        raise signals.TestError("Failed to trigger modem SSR crash")
418    raise signals.TestError("No SSRObserver found in logcat")
419
420
421def gnss_trigger_modem_ssr_by_mds(ad, dwelltime=60):
422    """Trigger modem SSR crash by mds tool and verify if modem crash and recover
423    successfully.
424
425    Args:
426        ad: An AndroidDevice object.
427        dwelltime: Waiting time for modem reset. Default is 60 seconds.
428    """
429    mds_check = ad.adb.shell("pm path com.google.mdstest")
430    if not mds_check:
431        raise signals.TestError("MDS Tool is not properly installed.")
432    ad.root_adb()
433    cmd = ('am instrument -w -e request "4b 25 03 00" '
434           '"com.google.mdstest/com.google.mdstest.instrument'
435           '.ModemCommandInstrumentation"')
436    ad.log.info("Triggering modem SSR crash by MDS")
437    output = ad.adb.shell(cmd, ignore_status=True)
438    ad.log.debug(output)
439    time.sleep(dwelltime)
440    ad.send_keycode("HOME")
441    if "SUCCESS" in output:
442        ad.log.info("Triggering modem SSR crash by MDS successfully.")
443    else:
444        raise signals.TestError(
445            "Failed to trigger modem SSR crash by MDS. \n%s" % output)
446
447
448def check_xtra_download(ad, begin_time):
449    """Verify XTRA download success log message in logcat.
450
451    Args:
452        ad: An AndroidDevice object.
453        begin_time: test begin time
454
455    Returns:
456        True: xtra_download if XTRA downloaded and injected successfully
457        otherwise return False.
458    """
459    ad.send_keycode("HOME")
460    logcat_results = ad.search_logcat("XTRA download success. "
461                                      "inject data into modem", begin_time)
462    if logcat_results:
463        ad.log.debug("%s" % logcat_results[-1]["log_message"])
464        ad.log.info("XTRA downloaded and injected successfully.")
465        return True
466    ad.log.error("XTRA downloaded FAIL.")
467    return False
468
469
470def pull_package_apk(ad, package_name):
471    """Pull apk of given package_name from device.
472
473    Args:
474        ad: An AndroidDevice object.
475        package_name: Package name of apk to pull.
476
477    Returns:
478        The temp path of pulled apk.
479    """
480    out = ad.adb.shell("pm path %s" % package_name)
481    result = re.search(r"package:(.*)", out)
482    if not result:
483        tutils.abort_all_tests(ad.log, "Couldn't find apk of %s" % package_name)
484    else:
485        apk_source = result.group(1)
486        ad.log.info("Get apk of %s from %s" % (package_name, apk_source))
487        apk_path = tempfile.mkdtemp()
488        ad.pull_files([apk_source], apk_path)
489    return apk_path
490
491
492def reinstall_package_apk(ad, package_name, apk_path):
493    """Reinstall apk of given package_name.
494
495    Args:
496        ad: An AndroidDevice object.
497        package_name: Package name of apk.
498        apk_path: The temp path of pulled apk.
499    """
500    for path_key in os.listdir(apk_path):
501        if fnmatch.fnmatch(path_key, "*.apk"):
502            apk_path = os.path.join(apk_path, path_key)
503            break
504    else:
505        raise signals.TestError("No apk is found in %s" % apk_path)
506    ad.log.info("Re-install %s with path: %s" % (package_name, apk_path))
507    ad.adb.shell("settings put global verifier_verify_adb_installs 0")
508    ad.adb.install("-r -d -g --user 0 %s" % apk_path)
509    package_check = ad.adb.shell("pm path %s" % package_name)
510    if not package_check:
511        tutils.abort_all_tests(
512            ad.log, "%s is not properly re-installed." % package_name)
513    ad.log.info("%s is re-installed successfully." % package_name)
514
515
516def init_gtw_gpstool(ad):
517    """Init GTW_GPSTool apk.
518
519    Args:
520        ad: An AndroidDevice object.
521    """
522    remount_device(ad)
523    gpstool_path = pull_package_apk(ad, "com.android.gpstool")
524    reinstall_package_apk(ad, "com.android.gpstool", gpstool_path)
525
526
527def fastboot_factory_reset(ad):
528    """Factory reset the device in fastboot mode.
529       Pull sl4a apk from device. Terminate all sl4a sessions,
530       Reboot the device to bootloader,
531       factory reset the device by fastboot.
532       Reboot the device. wait for device to complete booting
533       Re-install and start an sl4a session.
534
535    Args:
536        ad: An AndroidDevice object.
537
538    Returns:
539        True if factory reset process complete.
540    """
541    status = True
542    skip_setup_wizard = True
543    sl4a_path = pull_package_apk(ad, SL4A_APK_NAME)
544    gpstool_path = pull_package_apk(ad, "com.android.gpstool")
545    mds_path = pull_package_apk(ad, "com.google.mdstest")
546    tutils.stop_qxdm_logger(ad)
547    ad.stop_services()
548    attempts = 3
549    for i in range(1, attempts + 1):
550        try:
551            if ad.serial in list_adb_devices():
552                ad.log.info("Reboot to bootloader")
553                ad.adb.reboot("bootloader", ignore_status=True)
554                time.sleep(10)
555            if ad.serial in list_fastboot_devices():
556                ad.log.info("Factory reset in fastboot")
557                ad.fastboot._w(timeout=300, ignore_status=True)
558                time.sleep(30)
559                ad.log.info("Reboot in fastboot")
560                ad.fastboot.reboot()
561            ad.wait_for_boot_completion()
562            ad.root_adb()
563            if ad.skip_sl4a:
564                break
565            if ad.is_sl4a_installed():
566                break
567            reinstall_package_apk(ad, SL4A_APK_NAME, sl4a_path)
568            reinstall_package_apk(ad, "com.android.gpstool", gpstool_path)
569            reinstall_package_apk(ad, "com.google.mdstest", mds_path)
570            time.sleep(10)
571            break
572        except Exception as e:
573            ad.log.error(e)
574            if i == attempts:
575                tutils.abort_all_tests(ad.log, str(e))
576            time.sleep(5)
577    try:
578        ad.start_adb_logcat()
579    except Exception as e:
580        ad.log.error(e)
581    if skip_setup_wizard:
582        ad.exit_setup_wizard()
583    if ad.skip_sl4a:
584        return status
585    tutils.bring_up_sl4a(ad)
586    return status
587
588
589def clear_aiding_data_by_gtw_gpstool(ad):
590    """Launch GTW GPSTool and Clear all GNSS aiding data.
591       Wait 5 seconds for GTW GPStool to clear all GNSS aiding
592       data properly.
593
594    Args:
595        ad: An AndroidDevice object.
596    """
597    ad.log.info("Launch GTW GPSTool and Clear all GNSS aiding data")
598    ad.adb.shell("am start -S -n com.android.gpstool/.GPSTool --es mode clear")
599    time.sleep(10)
600
601
602def start_gnss_by_gtw_gpstool(ad, state, type="gnss", bgdisplay=False):
603    """Start or stop GNSS on GTW_GPSTool.
604
605    Args:
606        ad: An AndroidDevice object.
607        state: True to start GNSS. False to Stop GNSS.
608        type: Different API for location fix. Use gnss/flp/nmea
609        bgdisplay: true to run GTW when Display off.
610                   false to not run GTW when Display off.
611    """
612    if state and not bgdisplay:
613        ad.adb.shell("am start -S -n com.android.gpstool/.GPSTool "
614                     "--es mode gps --es type %s" % type)
615    elif state and bgdisplay:
616        ad.adb.shell("am start -S -n com.android.gpstool/.GPSTool --es mode "
617                     "gps --es type {} --ez BG {}".format(type, bgdisplay))
618    if not state:
619        ad.log.info("Stop %s on GTW_GPSTool." % type)
620        ad.adb.shell("am broadcast -a com.android.gpstool.stop_gps_action")
621    time.sleep(3)
622
623
624def process_gnss_by_gtw_gpstool(ad, criteria, type="gnss", clear_data=True):
625    """Launch GTW GPSTool and Clear all GNSS aiding data
626       Start GNSS tracking on GTW_GPSTool.
627
628    Args:
629        ad: An AndroidDevice object.
630        criteria: Criteria for current test item.
631        type: Different API for location fix. Use gnss/flp/nmea
632        clear_data: True to clear GNSS aiding data. False is not to. Defalt
633        set to True.
634
635    Returns:
636        True: First fix TTFF are within criteria.
637        False: First fix TTFF exceed criteria.
638    """
639    retries = 3
640    for i in range(retries):
641        if not ad.is_adb_logcat_on:
642            ad.start_adb_logcat()
643        check_adblog_functionality(ad)
644        check_location_runtime_permissions(
645            ad, GNSSTOOL_PACKAGE_NAME, GNSSTOOL_PERMISSIONS)
646        begin_time = get_current_epoch_time()
647        if clear_data:
648            clear_aiding_data_by_gtw_gpstool(ad)
649        ad.log.info("Start %s on GTW_GPSTool - attempt %d" % (type.upper(),
650                                                              i+1))
651        start_gnss_by_gtw_gpstool(ad, True, type)
652        for _ in range(10 + criteria):
653            logcat_results = ad.search_logcat("First fixed", begin_time)
654            if logcat_results:
655                ad.log.debug(logcat_results[-1]["log_message"])
656                first_fixed = int(logcat_results[-1]["log_message"].split()[-1])
657                ad.log.info("%s First fixed = %.3f seconds" %
658                            (type.upper(), first_fixed/1000))
659                if (first_fixed/1000) <= criteria:
660                    return True
661                start_gnss_by_gtw_gpstool(ad, False, type)
662                raise signals.TestFailure("Fail to get %s location fixed "
663                                          "within %d seconds criteria."
664                                          % (type.upper(), criteria))
665            time.sleep(1)
666        check_currrent_focus_app(ad)
667        start_gnss_by_gtw_gpstool(ad, False, type)
668    raise signals.TestFailure("Fail to get %s location fixed within %d "
669                              "attempts." % (type.upper(), retries))
670
671def start_ttff_by_gtw_gpstool(ad, ttff_mode, iteration, aid_data=False):
672    """Identify which TTFF mode for different test items.
673
674    Args:
675        ad: An AndroidDevice object.
676        ttff_mode: TTFF Test mode for current test item.
677        iteration: Iteration of TTFF cycles.
678        aid_data: Boolean for identify aid_data existed or not
679    """
680    begin_time = get_current_epoch_time()
681    if (ttff_mode == "hs" or ttff_mode == "ws") and not aid_data:
682        ad.log.info("Wait 5 minutes to start TTFF %s..." % ttff_mode.upper())
683        time.sleep(300)
684    if ttff_mode == "cs":
685        ad.log.info("Start TTFF Cold Start...")
686        time.sleep(3)
687    for i in range(1, 4):
688        ad.adb.shell("am broadcast -a com.android.gpstool.ttff_action "
689                     "--es ttff %s --es cycle %d" % (ttff_mode, iteration))
690        time.sleep(1)
691        if ad.search_logcat("act=com.android.gpstool.start_test_action",
692                            begin_time):
693            ad.log.info("Send TTFF start_test_action successfully.")
694            break
695    else:
696        check_currrent_focus_app(ad)
697        raise signals.TestError("Fail to send TTFF start_test_action.")
698
699
700def gnss_tracking_via_gtw_gpstool(ad, criteria, type="gnss", testtime=60):
701    """Start GNSS/FLP tracking tests for input testtime on GTW_GPSTool.
702
703    Args:
704        ad: An AndroidDevice object.
705        criteria: Criteria for current TTFF.
706        type: Different API for location fix. Use gnss/flp/nmea
707        testtime: Tracking test time for minutes. Default set to 60 minutes.
708    """
709    process_gnss_by_gtw_gpstool(ad, criteria, type)
710    ad.log.info("Start %s tracking test for %d minutes" % (type.upper(),
711                                                           testtime))
712    begin_time = get_current_epoch_time()
713    while get_current_epoch_time() - begin_time < testtime * 60 * 1000 :
714        if not ad.is_adb_logcat_on:
715            ad.start_adb_logcat()
716        crash_result = ad.search_logcat("Force finishing activity "
717                                        "com.android.gpstool/.GPSTool",
718                                        begin_time)
719        if crash_result:
720            raise signals.TestError("GPSTool crashed. Abort test.")
721    ad.log.info("Successfully tested for %d minutes" % testtime)
722    start_gnss_by_gtw_gpstool(ad, False, type)
723
724
725def parse_gtw_gpstool_log(ad, true_position, type="gnss"):
726    """Process GNSS/FLP API logs from GTW GPSTool and output track_data to
727    test_run_info for ACTS plugin to parse and display on MobileHarness as
728    Property.
729
730    Args:
731        ad: An AndroidDevice object.
732        true_position: Coordinate as [latitude, longitude] to calculate
733        position error.
734        type: Different API for location fix. Use gnss/flp/nmea
735    """
736    test_logfile = {}
737    track_data = {}
738    ant_top4_cn = 0
739    ant_cn = 0
740    base_top4_cn = 0
741    base_cn = 0
742    l5flag = "false"
743    file_count = int(ad.adb.shell("find %s -type f -iname *.txt | wc -l"
744                                  % GNSSSTATUS_LOG_PATH))
745    if file_count != 1:
746        ad.log.error("%d API logs exist." % file_count)
747    dir = ad.adb.shell("ls %s" % GNSSSTATUS_LOG_PATH).split()
748    for path_key in dir:
749        if fnmatch.fnmatch(path_key, "*.txt"):
750            logpath = posixpath.join(GNSSSTATUS_LOG_PATH, path_key)
751            out = ad.adb.shell("wc -c %s" % logpath)
752            file_size = int(out.split(" ")[0])
753            if file_size < 2000:
754                ad.log.info("Skip log %s due to log size %d bytes" %
755                            (path_key, file_size))
756                continue
757            test_logfile = logpath
758    if not test_logfile:
759        raise signals.TestError("Failed to get test log file in device.")
760    lines = ad.adb.shell("cat %s" % test_logfile).split("\n")
761    for line in lines:
762        if "Antenna_History Avg Top4" in line:
763            ant_top4_cn = float(line.split(":")[-1].strip())
764        elif "Antenna_History Avg" in line:
765            ant_cn = float(line.split(":")[-1].strip())
766        elif "Baseband_History Avg Top4" in line:
767            base_top4_cn = float(line.split(":")[-1].strip())
768        elif "Baseband_History Avg" in line:
769            base_cn = float(line.split(":")[-1].strip())
770        elif "L5 used in fix" in line:
771            l5flag = line.split(":")[-1].strip()
772        elif "Latitude" in line:
773            track_lat = float(line.split(":")[-1].strip())
774        elif "Longitude" in line:
775            track_long = float(line.split(":")[-1].strip())
776        elif "Time" in line:
777            track_utc = line.split("Time:")[-1].strip()
778            if track_utc in track_data.keys():
779                continue
780            pe = calculate_position_error(ad, track_lat, track_long,
781                                          true_position)
782            track_data[track_utc] = TRACK_REPORT(l5flag=l5flag,
783                                                 pe=pe,
784                                                 ant_top4cn=ant_top4_cn,
785                                                 ant_cn=ant_cn,
786                                                 base_top4cn=base_top4_cn,
787                                                 base_cn=base_cn)
788    ad.log.debug(track_data)
789    prop_basename = "TestResult %s_tracking_" % type.upper()
790    time_list = sorted(track_data.keys())
791    l5flag_list = [track_data[key].l5flag for key in time_list]
792    pe_list = [float(track_data[key].pe) for key in time_list]
793    ant_top4cn_list = [float(track_data[key].ant_top4cn) for key in time_list]
794    ant_cn_list = [float(track_data[key].ant_cn) for key in time_list]
795    base_top4cn_list = [float(track_data[key].base_top4cn) for key in time_list]
796    base_cn_list = [float(track_data[key].base_cn) for key in time_list]
797    ad.log.info(prop_basename+"StartTime %s" % time_list[0].replace(" ", "-"))
798    ad.log.info(prop_basename+"EndTime %s" % time_list[-1].replace(" ", "-"))
799    ad.log.info(prop_basename+"TotalFixPoints %d" % len(time_list))
800    ad.log.info(prop_basename+"L5FixRate "+'{percent:.2%}'.format(
801        percent=l5flag_list.count("true")/len(l5flag_list)))
802    ad.log.info(prop_basename+"AvgDis %.1f" % (sum(pe_list)/len(pe_list)))
803    ad.log.info(prop_basename+"MaxDis %.1f" % max(pe_list))
804    ad.log.info(prop_basename+"Ant_AvgTop4Signal %.1f" % ant_top4cn_list[-1])
805    ad.log.info(prop_basename+"Ant_AvgSignal %.1f" % ant_cn_list[-1])
806    ad.log.info(prop_basename+"Base_AvgTop4Signal %.1f" % base_top4cn_list[-1])
807    ad.log.info(prop_basename+"Base_AvgSignal %.1f" % base_cn_list[-1])
808
809
810def process_ttff_by_gtw_gpstool(ad, begin_time, true_position, type="gnss"):
811    """Process TTFF and record results in ttff_data.
812
813    Args:
814        ad: An AndroidDevice object.
815        begin_time: test begin time.
816        true_position: Coordinate as [latitude, longitude] to calculate
817        position error.
818        type: Different API for location fix. Use gnss/flp/nmea
819
820    Returns:
821        ttff_data: A dict of all TTFF data.
822    """
823    ttff_data = {}
824    ttff_loop_time = get_current_epoch_time()
825    while True:
826        if get_current_epoch_time() - ttff_loop_time >= 120000:
827            raise signals.TestError("Fail to search specific GPSService "
828                                    "message in logcat. Abort test.")
829        if not ad.is_adb_logcat_on:
830            ad.start_adb_logcat()
831        logcat_results = ad.search_logcat("write TTFF log", ttff_loop_time)
832        if logcat_results:
833            ttff_loop_time = get_current_epoch_time()
834            ttff_log = logcat_results[-1]["log_message"].split()
835            ttff_loop = int(ttff_log[8].split(":")[-1])
836            ttff_sec = float(ttff_log[11])
837            if ttff_sec != 0.0:
838                ttff_ant_cn = float(ttff_log[18].strip("]"))
839                ttff_base_cn = float(ttff_log[25].strip("]"))
840                if type == "gnss":
841                    gnss_results = ad.search_logcat("GPSService: Check item",
842                                                    begin_time)
843                    if gnss_results:
844                        ad.log.debug(gnss_results[-1]["log_message"])
845                        gnss_location_log = \
846                            gnss_results[-1]["log_message"].split()
847                        ttff_lat = float(
848                            gnss_location_log[8].split("=")[-1].strip(","))
849                        ttff_lon = float(
850                            gnss_location_log[9].split("=")[-1].strip(","))
851                        loc_time = int(
852                            gnss_location_log[10].split("=")[-1].strip(","))
853                        utc_time = epoch_to_human_time(loc_time)
854                elif type == "flp":
855                    flp_results = ad.search_logcat("GPSService: FLP Location",
856                                                   begin_time)
857                    if flp_results:
858                        ad.log.debug(flp_results[-1]["log_message"])
859                        flp_location_log = flp_results[-1][
860                            "log_message"].split()
861                        ttff_lat = float(flp_location_log[8].split(",")[0])
862                        ttff_lon = float(flp_location_log[8].split(",")[1])
863                        utc_time = epoch_to_human_time(get_current_epoch_time())
864            else:
865                ttff_ant_cn = float(ttff_log[19].strip("]"))
866                ttff_base_cn = float(ttff_log[26].strip("]"))
867                ttff_lat = 0
868                ttff_lon = 0
869                utc_time = epoch_to_human_time(get_current_epoch_time())
870            ad.log.debug("TTFF Loop %d - (Lat, Lon) = (%s, %s)" % (ttff_loop,
871                                                                   ttff_lat,
872                                                                   ttff_lon))
873            ttff_pe = calculate_position_error(ad, ttff_lat, ttff_lon,
874                                               true_position)
875            ttff_data[ttff_loop] = TTFF_REPORT(utc_time=utc_time,
876                                               ttff_loop=ttff_loop,
877                                               ttff_sec=ttff_sec,
878                                               ttff_pe=ttff_pe,
879                                               ttff_ant_cn=ttff_ant_cn,
880                                               ttff_base_cn=ttff_base_cn)
881            ad.log.info("UTC Time = %s, Loop %d = %.1f seconds, "
882                        "Position Error = %.1f meters, "
883                        "Antenna Average Signal = %.1f dbHz, "
884                        "Baseband Average Signal = %.1f dbHz" % (utc_time,
885                                                                 ttff_loop,
886                                                                 ttff_sec,
887                                                                 ttff_pe,
888                                                                 ttff_ant_cn,
889                                                                 ttff_base_cn))
890        stop_gps_results = ad.search_logcat("stop gps test", begin_time)
891        if stop_gps_results:
892            ad.send_keycode("HOME")
893            break
894        crash_result = ad.search_logcat("Force finishing activity "
895                                        "com.android.gpstool/.GPSTool",
896                                        begin_time)
897        if crash_result:
898            raise signals.TestError("GPSTool crashed. Abort test.")
899        # wait 10 seconds to avoid logs not writing into logcat yet
900        time.sleep(10)
901    return ttff_data
902
903
904def check_ttff_data(ad, ttff_data, ttff_mode, criteria):
905    """Verify all TTFF results from ttff_data.
906
907    Args:
908        ad: An AndroidDevice object.
909        ttff_data: TTFF data of secs, position error and signal strength.
910        ttff_mode: TTFF Test mode for current test item.
911        criteria: Criteria for current test item.
912
913    Returns:
914        True: All TTFF results are within criteria.
915        False: One or more TTFF results exceed criteria or Timeout.
916    """
917    ad.log.info("%d iterations of TTFF %s tests finished."
918                % (len(ttff_data.keys()), ttff_mode))
919    ad.log.info("%s PASS criteria is %d seconds" % (ttff_mode, criteria))
920    ad.log.debug("%s TTFF data: %s" % (ttff_mode, ttff_data))
921    ttff_property_key_and_value(ad, ttff_data, ttff_mode)
922    if len(ttff_data.keys()) == 0:
923        ad.log.error("GTW_GPSTool didn't process TTFF properly.")
924        return False
925    elif any(float(ttff_data[key].ttff_sec) == 0.0 for key in ttff_data.keys()):
926        ad.log.error("One or more TTFF %s Timeout" % ttff_mode)
927        return False
928    elif any(float(ttff_data[key].ttff_sec) >= criteria for key in
929             ttff_data.keys()):
930        ad.log.error("One or more TTFF %s are over test criteria %d seconds"
931                     % (ttff_mode, criteria))
932        return False
933    ad.log.info("All TTFF %s are within test criteria %d seconds."
934                % (ttff_mode, criteria))
935    return True
936
937
938def ttff_property_key_and_value(ad, ttff_data, ttff_mode):
939    """Output ttff_data to test_run_info for ACTS plugin to parse and display
940    on MobileHarness as Property.
941
942    Args:
943        ad: An AndroidDevice object.
944        ttff_data: TTFF data of secs, position error and signal strength.
945        ttff_mode: TTFF Test mode for current test item.
946    """
947    prop_basename = "TestResult "+ttff_mode.replace(" ", "_")+"_TTFF_"
948    sec_list = [float(ttff_data[key].ttff_sec) for key in ttff_data.keys()]
949    pe_list = [float(ttff_data[key].ttff_pe) for key in ttff_data.keys()]
950    ant_cn_list = [float(ttff_data[key].ttff_ant_cn) for key in
951                   ttff_data.keys()]
952    base_cn_list = [float(ttff_data[key].ttff_base_cn) for key in
953                   ttff_data.keys()]
954    timeoutcount = sec_list.count(0.0)
955    if len(sec_list) == timeoutcount:
956        avgttff = 9527
957    else:
958        avgttff = sum(sec_list)/(len(sec_list) - timeoutcount)
959    if timeoutcount != 0:
960        maxttff = 9527
961    else:
962        maxttff = max(sec_list)
963    avgdis = sum(pe_list)/len(pe_list)
964    maxdis = max(pe_list)
965    ant_avgcn = sum(ant_cn_list)/len(ant_cn_list)
966    base_avgcn = sum(base_cn_list)/len(base_cn_list)
967    ad.log.info(prop_basename+"AvgTime %.1f" % avgttff)
968    ad.log.info(prop_basename+"MaxTime %.1f" % maxttff)
969    ad.log.info(prop_basename+"TimeoutCount %d" % timeoutcount)
970    ad.log.info(prop_basename+"AvgDis %.1f" % avgdis)
971    ad.log.info(prop_basename+"MaxDis %.1f" % maxdis)
972    ad.log.info(prop_basename+"Ant_AvgSignal %.1f" % ant_avgcn)
973    ad.log.info(prop_basename+"Base_AvgSignal %.1f" % base_avgcn)
974
975
976def calculate_position_error(ad, latitude, longitude, true_position):
977    """Use haversine formula to calculate position error base on true location
978    coordinate.
979
980    Args:
981        ad: An AndroidDevice object.
982        latitude: latitude of location fixed in the present.
983        longitude: longitude of location fixed in the present.
984        true_position: [latitude, longitude] of true location coordinate.
985
986    Returns:
987        position_error of location fixed in the present.
988    """
989    radius = 6371009
990    dlat = math.radians(latitude - true_position[0])
991    dlon = math.radians(longitude - true_position[1])
992    a = math.sin(dlat/2) * math.sin(dlat/2) + \
993        math.cos(math.radians(true_position[0])) * \
994        math.cos(math.radians(latitude)) * math.sin(dlon/2) * math.sin(dlon/2)
995    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
996    return radius * c
997
998
999def launch_google_map(ad):
1000    """Launch Google Map via intent.
1001
1002    Args:
1003        ad: An AndroidDevice object.
1004    """
1005    ad.log.info("Launch Google Map.")
1006    try:
1007        ad.adb.shell("am start -S -n com.google.android.apps.maps/"
1008                     "com.google.android.maps.MapsActivity")
1009        ad.send_keycode("BACK")
1010        ad.force_stop_apk("com.google.android.apps.maps")
1011        ad.adb.shell("am start -S -n com.google.android.apps.maps/"
1012                     "com.google.android.maps.MapsActivity")
1013    except Exception as e:
1014        ad.log.error(e)
1015        raise signals.TestError("Failed to launch google map.")
1016    check_currrent_focus_app(ad)
1017
1018
1019def check_currrent_focus_app(ad):
1020    """Check to see current focused window and app.
1021
1022    Args:
1023        ad: An AndroidDevice object.
1024    """
1025    time.sleep(1)
1026    current = ad.adb.shell(
1027        "dumpsys window | grep -E 'mCurrentFocus|mFocusedApp'")
1028    ad.log.debug("\n"+current)
1029
1030
1031def check_location_api(ad, retries):
1032    """Verify if GnssLocationProvider API reports location.
1033
1034    Args:
1035        ad: An AndroidDevice object.
1036        retries: Retry time.
1037
1038    Returns:
1039        True: GnssLocationProvider API reports location.
1040        otherwise return False.
1041    """
1042    for i in range(retries):
1043        begin_time = get_current_epoch_time()
1044        ad.log.info("Try to get location report from GnssLocationProvider API "
1045                    "- attempt %d" % (i+1))
1046        while get_current_epoch_time() - begin_time <= 30000:
1047            logcat_results = ad.search_logcat("REPORT_LOCATION", begin_time)
1048            if logcat_results:
1049                ad.log.info("%s" % logcat_results[-1]["log_message"])
1050                ad.log.info("GnssLocationProvider reports location "
1051                            "successfully.")
1052                return True
1053        if not ad.is_adb_logcat_on:
1054            ad.start_adb_logcat()
1055    ad.log.error("GnssLocationProvider is unable to report location.")
1056    return False
1057
1058def check_network_location(ad, retries, location_type, criteria=30):
1059    """Verify if NLP reports location after requesting via GPSTool.
1060
1061    Args:
1062        ad: An AndroidDevice object.
1063        retries: Retry time.
1064        location_type: cell or wifi.
1065        criteria: expected nlp return time, default 30 seconds
1066
1067    Returns:
1068        True: NLP reports location.
1069        otherwise return False.
1070    """
1071    criteria = criteria * 1000
1072    search_pattern = ("GPSTool : networkLocationType = %s" % location_type)
1073    for i in range(retries):
1074        begin_time = get_current_epoch_time()
1075        ad.log.info("Try to get NLP status - attempt %d" % (i+1))
1076        ad.adb.shell(
1077            "am start -S -n com.android.gpstool/.GPSTool --es mode nlp")
1078        while get_current_epoch_time() - begin_time <= criteria:
1079            # Search pattern in 1 second interval
1080            time.sleep(1)
1081            result = ad.search_logcat(search_pattern, begin_time)
1082            if result:
1083                ad.log.info("Pattern Found: %s." % result[-1]["log_message"])
1084                ad.send_keycode("BACK")
1085                return True
1086        if not ad.is_adb_logcat_on:
1087            ad.start_adb_logcat()
1088        ad.send_keycode("BACK")
1089    ad.log.error("Unable to report network location \"%s\"." % location_type)
1090    return False
1091
1092
1093def set_attenuator_gnss_signal(ad, attenuator, atten_value):
1094    """Set attenuation value for different GNSS signal.
1095
1096    Args:
1097        ad: An AndroidDevice object.
1098        attenuator: The attenuator object.
1099        atten_value: attenuation value
1100    """
1101    try:
1102        ad.log.info(
1103            "Set attenuation value to \"%d\" for GNSS signal." % atten_value)
1104        attenuator[0].set_atten(atten_value)
1105    except Exception as e:
1106        ad.log.error(e)
1107
1108
1109def set_battery_saver_mode(ad, state):
1110    """Enable or diable battery saver mode via adb.
1111
1112    Args:
1113        ad: An AndroidDevice object.
1114        state: True is enable Battery Saver mode. False is disable.
1115    """
1116    ad.root_adb()
1117    if state:
1118        ad.log.info("Enable Battery Saver mode.")
1119        ad.adb.shell("cmd battery unplug")
1120        ad.adb.shell("settings put global low_power 1")
1121    else:
1122        ad.log.info("Disable Battery Saver mode.")
1123        ad.adb.shell("settings put global low_power 0")
1124        ad.adb.shell("cmd battery reset")
1125
1126
1127def set_gnss_qxdm_mask(ad, masks):
1128    """Find defined gnss qxdm mask and set as default logging mask.
1129
1130    Args:
1131        ad: An AndroidDevice object.
1132        masks: Defined gnss qxdm mask.
1133    """
1134    try:
1135        for mask in masks:
1136            if not tutils.find_qxdm_log_mask(ad, mask):
1137                continue
1138            tutils.set_qxdm_logger_command(ad, mask)
1139            break
1140    except Exception as e:
1141        ad.log.error(e)
1142        raise signals.TestError("Failed to set any QXDM masks.")
1143
1144
1145def start_youtube_video(ad, url=None, retries=0):
1146    """Start youtube video and verify if audio is in music state.
1147
1148    Args:
1149        ad: An AndroidDevice object.
1150        url: Youtube video url.
1151        retries: Retry times if audio is not in music state.
1152
1153    Returns:
1154        True if youtube video is playing normally.
1155        False if youtube video is not playing properly.
1156    """
1157    for i in range(retries):
1158        ad.log.info("Open an youtube video - attempt %d" % (i+1))
1159        ad.adb.shell("am start -a android.intent.action.VIEW -d \"%s\"" % url)
1160        time.sleep(2)
1161        out = ad.adb.shell(
1162            "dumpsys activity | grep NewVersionAvailableActivity")
1163        if out:
1164            ad.log.info("Skip Youtube New Version Update.")
1165            ad.send_keycode("BACK")
1166        if tutils.wait_for_state(ad.droid.audioIsMusicActive, True, 15, 1):
1167            ad.log.info("Started a video in youtube, audio is in MUSIC state")
1168            return True
1169        ad.log.info("Force-Stop youtube and reopen youtube again.")
1170        ad.force_stop_apk("com.google.android.youtube")
1171    check_currrent_focus_app(ad)
1172    raise signals.TestError("Started a video in youtube, "
1173                            "but audio is not in MUSIC state")
1174
1175
1176def get_baseband_and_gms_version(ad, extra_msg=""):
1177    """Get current radio baseband and GMSCore version of AndroidDevice object.
1178
1179    Args:
1180        ad: An AndroidDevice object.
1181    """
1182    try:
1183        build_version = ad.adb.getprop("ro.build.id")
1184        baseband_version = ad.adb.getprop("gsm.version.baseband")
1185        gms_version = ad.adb.shell(
1186            "dumpsys package com.google.android.gms | grep versionName"
1187        ).split("\n")[0].split("=")[1]
1188        mpss_version = ad.adb.shell("cat /sys/devices/soc0/images | grep MPSS "
1189                                    "| cut -d ':' -f 3")
1190        if not extra_msg:
1191            ad.log.info("TestResult Build_Version %s" % build_version)
1192            ad.log.info("TestResult Baseband_Version %s" % baseband_version)
1193            ad.log.info(
1194                "TestResult GMS_Version %s" % gms_version.replace(" ", ""))
1195            ad.log.info("TestResult MPSS_Version %s" % mpss_version)
1196        else:
1197            ad.log.info(
1198                "%s, Baseband_Version = %s" % (extra_msg, baseband_version))
1199    except Exception as e:
1200        ad.log.error(e)
1201
1202
1203def start_toggle_gnss_by_gtw_gpstool(ad, iteration):
1204    """Send toggle gnss off/on start_test_action
1205
1206    Args:
1207        ad: An AndroidDevice object.
1208        iteration: Iteration of toggle gnss off/on cycles.
1209    """
1210    msg_list = []
1211    begin_time = get_current_epoch_time()
1212    try:
1213        for i in range(1, 4):
1214            ad.adb.shell("am start -S -n com.android.gpstool/.GPSTool "
1215                         "--es mode toggle --es cycle %d" % iteration)
1216            time.sleep(1)
1217            if ad.search_logcat("cmp=com.android.gpstool/.ToggleGPS",
1218                                begin_time):
1219                ad.log.info("Send ToggleGPS start_test_action successfully.")
1220                break
1221        else:
1222            check_currrent_focus_app(ad)
1223            raise signals.TestError("Fail to send ToggleGPS "
1224                                    "start_test_action within 3 attempts.")
1225        time.sleep(2)
1226        test_start = ad.search_logcat("GPSTool_ToggleGPS: startService",
1227                                      begin_time)
1228        if test_start:
1229            ad.log.info(test_start[-1]["log_message"].split(":")[-1].strip())
1230        else:
1231            raise signals.TestError("Fail to start toggle GPS off/on test.")
1232        # Every iteration is expected to finish within 4 minutes.
1233        while get_current_epoch_time() - begin_time <= iteration * 240000:
1234            crash_end = ad.search_logcat("Force finishing activity "
1235                                         "com.android.gpstool/.GPSTool",
1236                                         begin_time)
1237            if crash_end:
1238                raise signals.TestError("GPSTool crashed. Abort test.")
1239            toggle_results = ad.search_logcat("GPSTool : msg", begin_time)
1240            if toggle_results:
1241                for toggle_result in toggle_results:
1242                    msg = toggle_result["log_message"]
1243                    if not msg in msg_list:
1244                        ad.log.info(msg.split(":")[-1].strip())
1245                        msg_list.append(msg)
1246                    if "timeout" in msg:
1247                        raise signals.TestFailure("Fail to get location fixed "
1248                                                  "within 60 seconds.")
1249                    if "Test end" in msg:
1250                        raise signals.TestPass("Completed quick toggle GNSS "
1251                                               "off/on test.")
1252        raise signals.TestFailure("Fail to finish toggle GPS off/on test "
1253                                  "within %d minutes" % (iteration * 4))
1254    finally:
1255        ad.send_keycode("HOME")
1256
1257
1258def grant_location_permission(ad, option):
1259    """Grant or revoke location related permission.
1260
1261    Args:
1262        ad: An AndroidDevice object.
1263        option: Boolean to grant or revoke location related permissions.
1264    """
1265    action = "grant" if option else "revoke"
1266    for permission in LOCATION_PERMISSIONS:
1267        ad.log.info(
1268            "%s permission:%s on %s" % (action, permission, TEST_PACKAGE_NAME))
1269        ad.adb.shell("pm %s %s %s" % (action, TEST_PACKAGE_NAME, permission))
1270
1271
1272def check_location_runtime_permissions(ad, package, permissions):
1273    """Check if runtime permissions are granted on selected package.
1274
1275    Args:
1276        ad: An AndroidDevice object.
1277        package: Apk package name to check.
1278        permissions: A list of permissions to be granted.
1279    """
1280    for _ in range(3):
1281        location_runtime_permission = ad.adb.shell(
1282            "dumpsys package %s | grep ACCESS_FINE_LOCATION" % package)
1283        if "true" not in location_runtime_permission:
1284            ad.log.info("ACCESS_FINE_LOCATION is NOT granted on %s" % package)
1285            for permission in permissions:
1286                ad.log.debug("Grant %s on %s" % (permission, package))
1287                ad.adb.shell("pm grant %s %s" % (package, permission))
1288        else:
1289            ad.log.info("ACCESS_FINE_LOCATION is granted on %s" % package)
1290            break
1291    else:
1292        raise signals.TestError(
1293            "Fail to grant ACCESS_FINE_LOCATION on %s" % package)
1294
1295
1296def install_mdstest_app(ad, mdsapp):
1297    """
1298        Install MDS test app in DUT
1299
1300        Args:
1301            ad: An Android Device Object
1302            mdsapp: Installation path of MDSTest app
1303    """
1304    if not ad.is_apk_installed("com.google.mdstest"):
1305        ad.adb.install("-r %s" % mdsapp, timeout=300, ignore_status=True)
1306
1307
1308def write_modemconfig(ad, mdsapp, nvitem_dict, modemparfile):
1309    """
1310        Modify the NV items using modem_tool.par
1311        Note: modem_tool.par
1312
1313        Args:
1314            ad:  An Android Device Object
1315            mdsapp: Installation path of MDSTest app
1316            nvitem_dict: dictionary of NV items and values.
1317            modemparfile: modem_tool.par path.
1318    """
1319    ad.log.info("Verify MDSTest app installed in DUT")
1320    install_mdstest_app(ad, mdsapp)
1321    os.system("chmod 777 %s" % modemparfile)
1322    for key, value in nvitem_dict.items():
1323        if key.isdigit():
1324            op_name = "WriteEFS"
1325        else:
1326            op_name = "WriteNV"
1327        ad.log.info("Modifying the NV{!r} using {}".format(key, op_name))
1328        job.run("{} --op {} --item {} --data '{}'".
1329                format(modemparfile, op_name, key, value))
1330        time.sleep(2)
1331
1332
1333def verify_modemconfig(ad, nvitem_dict, modemparfile):
1334    """
1335        Verify the NV items using modem_tool.par
1336        Note: modem_tool.par
1337
1338        Args:
1339            ad:  An Android Device Object
1340            nvitem_dict: dictionary of NV items and values
1341            modemparfile: modem_tool.par path.
1342    """
1343    os.system("chmod 777 %s" % modemparfile)
1344    for key, value in nvitem_dict.items():
1345        if key.isdigit():
1346            op_name = "ReadEFS"
1347        else:
1348            op_name = "ReadNV"
1349        # Sleeptime to avoid Modem communication error
1350        time.sleep(5)
1351        result = job.run(
1352            "{} --op {} --item {}".format(modemparfile, op_name, key))
1353        output = str(result.stdout)
1354        ad.log.info("Actual Value for NV{!r} is {!r}".format(key, output))
1355        if not value.casefold() in output:
1356            ad.log.error("NV Value is wrong {!r} in {!r}".format(value, result))
1357            raise ValueError(
1358                "could not find {!r} in {!r}".format(value, result))
1359
1360
1361def check_ttff_pe(ad, ttff_data, ttff_mode, pecriteria):
1362    """Verify all TTFF results from ttff_data.
1363
1364    Args:
1365        ad: An AndroidDevice object.
1366        ttff_data: TTFF data of secs, position error and signal strength.
1367        ttff_mode: TTFF Test mode for current test item.
1368        criteria: Criteria for current test item.
1369
1370    """
1371    ad.log.info("%d iterations of TTFF %s tests finished.",
1372                (len(ttff_data.keys()), ttff_mode))
1373    ad.log.info("%s PASS criteria is %f meters", (ttff_mode, pecriteria))
1374    ad.log.debug("%s TTFF data: %s", (ttff_mode, ttff_data))
1375
1376    if len(ttff_data.keys()) == 0:
1377        ad.log.error("GTW_GPSTool didn't process TTFF properly.")
1378        raise signals.TestFailure("GTW_GPSTool didn't process TTFF properly.")
1379
1380    elif any(float(ttff_data[key].ttff_pe) >= pecriteria for key in
1381             ttff_data.keys()):
1382        ad.log.error("One or more TTFF %s are over test criteria %f meters",
1383                     (ttff_mode, pecriteria))
1384        raise signals.TestFailure("GTW_GPSTool didn't process TTFF properly.")
1385    ad.log.info("All TTFF %s are within test criteria %f meters.",
1386                (ttff_mode, pecriteria))
1387
1388
1389def check_adblog_functionality(ad):
1390    """Restart adb logcat if system can't write logs into file after checking
1391    adblog file size.
1392
1393    Args:
1394        ad: An AndroidDevice object.
1395    """
1396    logcat_path = os.path.join(ad.device_log_path, "adblog_%s_debug.txt" %
1397                               ad.serial)
1398    if not os.path.exists(logcat_path):
1399        raise signals.TestError("Logcat file %s does not exist." % logcat_path)
1400    original_log_size = os.path.getsize(logcat_path)
1401    ad.log.debug("Original adblog size is %d" % original_log_size)
1402    time.sleep(.5)
1403    current_log_size = os.path.getsize(logcat_path)
1404    ad.log.debug("Current adblog size is %d" % current_log_size)
1405    if current_log_size == original_log_size:
1406        ad.log.warn("System can't write logs into file. Restart adb "
1407                    "logcat process now.")
1408        ad.stop_adb_logcat()
1409        ad.start_adb_logcat()
1410