Merge "Add haydennix@ to OWNERS in controllers"
diff --git a/acts/.gitignore b/acts/.gitignore
index 5cd10e7..3f46a3b 100644
--- a/acts/.gitignore
+++ b/acts/.gitignore
@@ -68,4 +68,6 @@
# PyCharm
.idea/
+
+# IntelliJ
*.iml
diff --git a/acts/framework/acts/controllers/android_lib/tel/__init__.py b/acts/framework/acts/controllers/android_lib/tel/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts/framework/acts/controllers/android_lib/tel/__init__.py
diff --git a/acts/framework/acts/controllers/android_lib/tel/tel_utils.py b/acts/framework/acts/controllers/android_lib/tel/tel_utils.py
new file mode 100644
index 0000000..53a59ea
--- /dev/null
+++ b/acts/framework/acts/controllers/android_lib/tel/tel_utils.py
@@ -0,0 +1,667 @@
+#!/usr/bin/env python3
+#
+# Copyright 2020 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Generic telephony utility functions. Cloned from test_utils.tel."""
+
+import re
+import struct
+import time
+from queue import Empty
+
+from acts.logger import epoch_to_log_line_timestamp
+
+
+INCALL_UI_DISPLAY_FOREGROUND = "foreground"
+INCALL_UI_DISPLAY_BACKGROUND = "background"
+INCALL_UI_DISPLAY_DEFAULT = "default"
+
+# Max time to wait after caller make a call and before
+# callee start ringing
+MAX_WAIT_TIME_ACCEPT_CALL_TO_OFFHOOK_EVENT = 30
+
+# Max time to wait after toggle airplane mode and before
+# get expected event
+MAX_WAIT_TIME_AIRPLANEMODE_EVENT = 90
+
+# Wait time between state check retry
+WAIT_TIME_BETWEEN_STATE_CHECK = 5
+
+# Constant for Data Roaming State
+DATA_ROAMING_ENABLE = 1
+DATA_ROAMING_DISABLE = 0
+
+# Constant for Telephony Manager Call State
+TELEPHONY_STATE_RINGING = "RINGING"
+TELEPHONY_STATE_IDLE = "IDLE"
+TELEPHONY_STATE_OFFHOOK = "OFFHOOK"
+TELEPHONY_STATE_UNKNOWN = "UNKNOWN"
+
+# Constant for Service State
+SERVICE_STATE_EMERGENCY_ONLY = "EMERGENCY_ONLY"
+SERVICE_STATE_IN_SERVICE = "IN_SERVICE"
+SERVICE_STATE_OUT_OF_SERVICE = "OUT_OF_SERVICE"
+SERVICE_STATE_POWER_OFF = "POWER_OFF"
+SERVICE_STATE_UNKNOWN = "UNKNOWN"
+
+# Constant for Network Mode
+NETWORK_MODE_GSM_ONLY = "NETWORK_MODE_GSM_ONLY"
+NETWORK_MODE_WCDMA_ONLY = "NETWORK_MODE_WCDMA_ONLY"
+NETWORK_MODE_LTE_ONLY = "NETWORK_MODE_LTE_ONLY"
+
+# Constant for Events
+EVENT_CALL_STATE_CHANGED = "CallStateChanged"
+EVENT_SERVICE_STATE_CHANGED = "ServiceStateChanged"
+
+
+class CallStateContainer:
+ INCOMING_NUMBER = "incomingNumber"
+ SUBSCRIPTION_ID = "subscriptionId"
+ CALL_STATE = "callState"
+
+
+class ServiceStateContainer:
+ VOICE_REG_STATE = "voiceRegState"
+ VOICE_NETWORK_TYPE = "voiceNetworkType"
+ DATA_REG_STATE = "dataRegState"
+ DATA_NETWORK_TYPE = "dataNetworkType"
+ OPERATOR_NAME = "operatorName"
+ OPERATOR_ID = "operatorId"
+ IS_MANUAL_NW_SELECTION = "isManualNwSelection"
+ ROAMING = "roaming"
+ IS_EMERGENCY_ONLY = "isEmergencyOnly"
+ NETWORK_ID = "networkId"
+ SYSTEM_ID = "systemId"
+ SUBSCRIPTION_ID = "subscriptionId"
+ SERVICE_STATE = "serviceState"
+
+
+def dumpsys_last_call_info(ad):
+ """ Get call information by dumpsys telecom. """
+ num = dumpsys_last_call_number(ad)
+ output = ad.adb.shell("dumpsys telecom")
+ result = re.search(r"Call TC@%s: {(.*?)}" % num, output, re.DOTALL)
+ call_info = {"TC": num}
+ if result:
+ result = result.group(1)
+ for attr in ("startTime", "endTime", "direction", "isInterrupted",
+ "callTechnologies", "callTerminationsReason",
+ "isVideoCall", "callProperties"):
+ match = re.search(r"%s: (.*)" % attr, result)
+ if match:
+ if attr in ("startTime", "endTime"):
+ call_info[attr] = epoch_to_log_line_timestamp(
+ int(match.group(1)))
+ else:
+ call_info[attr] = match.group(1)
+ ad.log.debug("call_info = %s", call_info)
+ return call_info
+
+
+def dumpsys_last_call_number(ad):
+ output = ad.adb.shell("dumpsys telecom")
+ call_nums = re.findall("Call TC@(\d+):", output)
+ if not call_nums:
+ return 0
+ else:
+ return int(call_nums[-1])
+
+
+def get_device_epoch_time(ad):
+ return int(1000 * float(ad.adb.shell("date +%s.%N")))
+
+
+def get_outgoing_voice_sub_id(ad):
+ """ Get outgoing voice subscription id
+ """
+ if hasattr(ad, "outgoing_voice_sub_id"):
+ return ad.outgoing_voice_sub_id
+ else:
+ return ad.droid.subscriptionGetDefaultVoiceSubId()
+
+
+def get_rx_tx_power_levels(log, ad):
+ """ Obtains Rx and Tx power levels from the MDS application.
+
+ The method requires the MDS app to be installed in the DUT.
+
+ Args:
+ log: logger object
+ ad: an android device
+
+ Return:
+ A tuple where the first element is an array array with the RSRP value
+ in Rx chain, and the second element is the transmitted power in dBm.
+ Values for invalid Rx / Tx chains are set to None.
+ """
+ cmd = ('am instrument -w -e request "80 00 e8 03 00 08 00 00 00" -e '
+ 'response wait "com.google.mdstest/com.google.mdstest.instrument.'
+ 'ModemCommandInstrumentation"')
+ output = ad.adb.shell(cmd)
+
+ if 'result=SUCCESS' not in output:
+ raise RuntimeError('Could not obtain Tx/Rx power levels from MDS. Is '
+ 'the MDS app installed?')
+
+ response = re.search(r"(?<=response=).+", output)
+
+ if not response:
+ raise RuntimeError('Invalid response from the MDS app:\n' + output)
+
+ # Obtain a list of bytes in hex format from the response string
+ response_hex = response.group(0).split(' ')
+
+ def get_bool(pos):
+ """ Obtain a boolean variable from the byte array. """
+ return response_hex[pos] == '01'
+
+ def get_int32(pos):
+ """ Obtain an int from the byte array. Bytes are printed in
+ little endian format."""
+ return struct.unpack(
+ '<i', bytearray.fromhex(''.join(response_hex[pos:pos + 4])))[0]
+
+ rx_power = []
+ RX_CHAINS = 4
+
+ for i in range(RX_CHAINS):
+ # Calculate starting position for the Rx chain data structure
+ start = 12 + i * 22
+
+ # The first byte in the data structure indicates if the rx chain is
+ # valid.
+ if get_bool(start):
+ rx_power.append(get_int32(start + 2) / 10)
+ else:
+ rx_power.append(None)
+
+ # Calculate the position for the tx chain data structure
+ tx_pos = 12 + RX_CHAINS * 22
+
+ tx_valid = get_bool(tx_pos)
+ if tx_valid:
+ tx_power = get_int32(tx_pos + 2) / -10
+ else:
+ tx_power = None
+
+ return rx_power, tx_power
+
+
+def get_telephony_signal_strength(ad):
+ #{'evdoEcio': -1, 'asuLevel': 28, 'lteSignalStrength': 14, 'gsmLevel': 0,
+ # 'cdmaAsuLevel': 99, 'evdoDbm': -120, 'gsmDbm': -1, 'cdmaEcio': -160,
+ # 'level': 2, 'lteLevel': 2, 'cdmaDbm': -120, 'dbm': -112, 'cdmaLevel': 0,
+ # 'lteAsuLevel': 28, 'gsmAsuLevel': 99, 'gsmBitErrorRate': 0,
+ # 'lteDbm': -112, 'gsmSignalStrength': 99}
+ try:
+ signal_strength = ad.droid.telephonyGetSignalStrength()
+ if not signal_strength:
+ signal_strength = {}
+ except Exception as e:
+ ad.log.error(e)
+ signal_strength = {}
+ return signal_strength
+
+
+def initiate_call(log,
+ ad,
+ callee_number,
+ emergency=False,
+ incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
+ video=False):
+ """Make phone call from caller to callee.
+
+ Args:
+ log: log object.
+ ad: Caller android device object.
+ callee_number: Callee phone number.
+ emergency : specify the call is emergency.
+ Optional. Default value is False.
+ incall_ui_display: show the dialer UI foreground or background
+ video: whether to initiate as video call
+
+ Returns:
+ result: if phone call is placed successfully.
+ """
+ ad.ed.clear_events(EVENT_CALL_STATE_CHANGED)
+ sub_id = get_outgoing_voice_sub_id(ad)
+ begin_time = get_device_epoch_time(ad)
+ ad.droid.telephonyStartTrackingCallStateForSubscription(sub_id)
+ try:
+ # Make a Call
+ ad.log.info("Make a phone call to %s", callee_number)
+ if emergency:
+ ad.droid.telecomCallEmergencyNumber(callee_number)
+ else:
+ ad.droid.telecomCallNumber(callee_number, video)
+
+ # Verify OFFHOOK state
+ if not wait_for_call_offhook_for_subscription(
+ log, ad, sub_id, event_tracking_started=True):
+ ad.log.info("sub_id %s not in call offhook state", sub_id)
+ last_call_drop_reason(ad, begin_time=begin_time)
+ return False
+ else:
+ return True
+ finally:
+ if hasattr(ad, "sdm_log") and getattr(ad, "sdm_log"):
+ ad.adb.shell("i2cset -fy 3 64 6 1 b", ignore_status=True)
+ ad.adb.shell("i2cset -fy 3 65 6 1 b", ignore_status=True)
+ ad.droid.telephonyStopTrackingCallStateChangeForSubscription(sub_id)
+ if incall_ui_display == INCALL_UI_DISPLAY_FOREGROUND:
+ ad.droid.telecomShowInCallScreen()
+ elif incall_ui_display == INCALL_UI_DISPLAY_BACKGROUND:
+ ad.droid.showHomeScreen()
+
+
+def is_event_match(event, field, value):
+ """Return if <field> in "event" match <value> or not.
+
+ Args:
+ event: event to test. This event need to have <field>.
+ field: field to match.
+ value: value to match.
+
+ Returns:
+ True if <field> in "event" match <value>.
+ False otherwise.
+ """
+ return is_event_match_for_list(event, field, [value])
+
+
+def is_event_match_for_list(event, field, value_list):
+ """Return if <field> in "event" match any one of the value
+ in "value_list" or not.
+
+ Args:
+ event: event to test. This event need to have <field>.
+ field: field to match.
+ value_list: a list of value to match.
+
+ Returns:
+ True if <field> in "event" match one of the value in "value_list".
+ False otherwise.
+ """
+ try:
+ value_in_event = event['data'][field]
+ except KeyError:
+ return False
+ for value in value_list:
+ if value_in_event == value:
+ return True
+ return False
+
+
+def is_phone_in_call(log, ad):
+ """Return True if phone in call.
+
+ Args:
+ log: log object.
+ ad: android device.
+ """
+ try:
+ return ad.droid.telecomIsInCall()
+ except:
+ return "mCallState=2" in ad.adb.shell(
+ "dumpsys telephony.registry | grep mCallState")
+
+
+def last_call_drop_reason(ad, begin_time=None):
+ reasons = ad.search_logcat(
+ "qcril_qmi_voice_map_qmi_to_ril_last_call_failure_cause", begin_time)
+ reason_string = ""
+ if reasons:
+ log_msg = "Logcat call drop reasons:"
+ for reason in reasons:
+ log_msg = "%s\n\t%s" % (log_msg, reason["log_message"])
+ if "ril reason str" in reason["log_message"]:
+ reason_string = reason["log_message"].split(":")[-1].strip()
+ ad.log.info(log_msg)
+ reasons = ad.search_logcat("ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION",
+ begin_time)
+ if reasons:
+ ad.log.warning("ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION is seen")
+ ad.log.info("last call dumpsys: %s",
+ sorted(dumpsys_last_call_info(ad).items()))
+ return reason_string
+
+
+def toggle_airplane_mode(log, ad, new_state=None, strict_checking=True):
+ """ Toggle the state of airplane mode.
+
+ Args:
+ log: log handler.
+ ad: android_device object.
+ new_state: Airplane mode state to set to.
+ If None, opposite of the current state.
+ strict_checking: Whether to turn on strict checking that checks all features.
+
+ Returns:
+ result: True if operation succeed. False if error happens.
+ """
+ if ad.skip_sl4a:
+ return toggle_airplane_mode_by_adb(log, ad, new_state)
+ else:
+ return toggle_airplane_mode_msim(
+ log, ad, new_state, strict_checking=strict_checking)
+
+
+def toggle_airplane_mode_by_adb(log, ad, new_state=None):
+ """ Toggle the state of airplane mode.
+
+ Args:
+ log: log handler.
+ ad: android_device object.
+ new_state: Airplane mode state to set to.
+ If None, opposite of the current state.
+
+ Returns:
+ result: True if operation succeed. False if error happens.
+ """
+ cur_state = bool(int(ad.adb.shell("settings get global airplane_mode_on")))
+ if new_state == cur_state:
+ ad.log.info("Airplane mode already in %s", new_state)
+ return True
+ elif new_state is None:
+ new_state = not cur_state
+ ad.log.info("Change airplane mode from %s to %s", cur_state, new_state)
+ try:
+ ad.adb.shell("settings put global airplane_mode_on %s" % int(new_state))
+ ad.adb.shell("am broadcast -a android.intent.action.AIRPLANE_MODE")
+ except Exception as e:
+ ad.log.error(e)
+ return False
+ changed_state = bool(int(ad.adb.shell("settings get global airplane_mode_on")))
+ return changed_state == new_state
+
+
+def toggle_airplane_mode_msim(log, ad, new_state=None, strict_checking=True):
+ """ Toggle the state of airplane mode.
+
+ Args:
+ log: log handler.
+ ad: android_device object.
+ new_state: Airplane mode state to set to.
+ If None, opposite of the current state.
+ strict_checking: Whether to turn on strict checking that checks all features.
+
+ Returns:
+ result: True if operation succeed. False if error happens.
+ """
+
+ cur_state = ad.droid.connectivityCheckAirplaneMode()
+ if cur_state == new_state:
+ ad.log.info("Airplane mode already in %s", new_state)
+ return True
+ elif new_state is None:
+ new_state = not cur_state
+ ad.log.info("Toggle APM mode, from current tate %s to %s", cur_state,
+ new_state)
+ sub_id_list = []
+ active_sub_info = ad.droid.subscriptionGetAllSubInfoList()
+ if active_sub_info:
+ for info in active_sub_info:
+ sub_id_list.append(info['subscriptionId'])
+
+ ad.ed.clear_all_events()
+ time.sleep(0.1)
+ service_state_list = []
+ if new_state:
+ service_state_list.append(SERVICE_STATE_POWER_OFF)
+ ad.log.info("Turn on airplane mode")
+
+ else:
+ # If either one of these 3 events show up, it should be OK.
+ # Normal SIM, phone in service
+ service_state_list.append(SERVICE_STATE_IN_SERVICE)
+ # NO SIM, or Dead SIM, or no Roaming coverage.
+ service_state_list.append(SERVICE_STATE_OUT_OF_SERVICE)
+ service_state_list.append(SERVICE_STATE_EMERGENCY_ONLY)
+ ad.log.info("Turn off airplane mode")
+
+ for sub_id in sub_id_list:
+ ad.droid.telephonyStartTrackingServiceStateChangeForSubscription(
+ sub_id)
+
+ timeout_time = time.time() + MAX_WAIT_TIME_AIRPLANEMODE_EVENT
+ ad.droid.connectivityToggleAirplaneMode(new_state)
+
+ try:
+ try:
+ event = ad.ed.wait_for_event(
+ EVENT_SERVICE_STATE_CHANGED,
+ is_event_match_for_list,
+ timeout=MAX_WAIT_TIME_AIRPLANEMODE_EVENT,
+ field=ServiceStateContainer.SERVICE_STATE,
+ value_list=service_state_list)
+ ad.log.info("Got event %s", event)
+ except Empty:
+ ad.log.warning("Did not get expected service state change to %s",
+ service_state_list)
+ finally:
+ for sub_id in sub_id_list:
+ ad.droid.telephonyStopTrackingServiceStateChangeForSubscription(
+ sub_id)
+ except Exception as e:
+ ad.log.error(e)
+
+ # APM on (new_state=True) will turn off bluetooth but may not turn it on
+ try:
+ if new_state and not _wait_for_bluetooth_in_state(
+ log, ad, False, timeout_time - time.time()):
+ ad.log.error(
+ "Failed waiting for bluetooth during airplane mode toggle")
+ if strict_checking: return False
+ except Exception as e:
+ ad.log.error("Failed to check bluetooth state due to %s", e)
+ if strict_checking:
+ raise
+
+ # APM on (new_state=True) will turn off wifi but may not turn it on
+ if new_state and not _wait_for_wifi_in_state(log, ad, False,
+ timeout_time - time.time()):
+ ad.log.error("Failed waiting for wifi during airplane mode toggle on")
+ if strict_checking: return False
+
+ if ad.droid.connectivityCheckAirplaneMode() != new_state:
+ ad.log.error("Set airplane mode to %s failed", new_state)
+ return False
+ return True
+
+
+def toggle_cell_data_roaming(ad, state):
+ """Enable cell data roaming for default data subscription.
+
+ Wait for the data roaming status to be DATA_STATE_CONNECTED
+ or DATA_STATE_DISCONNECTED.
+
+ Args:
+ ad: Android Device Object.
+ state: True or False for enable or disable cell data roaming.
+
+ Returns:
+ True if success.
+ False if failed.
+ """
+ state_int = {True: DATA_ROAMING_ENABLE, False: DATA_ROAMING_DISABLE}[state]
+ action_str = {True: "Enable", False: "Disable"}[state]
+ if ad.droid.connectivityCheckDataRoamingMode() == state:
+ ad.log.info("Data roaming is already in state %s", state)
+ return True
+ if not ad.droid.connectivitySetDataRoaming(state_int):
+ ad.error.info("Fail to config data roaming into state %s", state)
+ return False
+ if ad.droid.connectivityCheckDataRoamingMode() == state:
+ ad.log.info("Data roaming is configured into state %s", state)
+ return True
+ else:
+ ad.log.error("Data roaming is not configured into state %s", state)
+ return False
+
+
+def wait_for_call_offhook_event(
+ log,
+ ad,
+ sub_id,
+ event_tracking_started=False,
+ timeout=MAX_WAIT_TIME_ACCEPT_CALL_TO_OFFHOOK_EVENT):
+ """Wait for an incoming call on specified subscription.
+
+ Args:
+ log: log object.
+ ad: android device object.
+ event_tracking_started: True if event tracking already state outside
+ timeout: time to wait for event
+
+ Returns:
+ True: if call offhook event is received.
+ False: if call offhook event is not received.
+ """
+ if not event_tracking_started:
+ ad.ed.clear_events(EVENT_CALL_STATE_CHANGED)
+ ad.droid.telephonyStartTrackingCallStateForSubscription(sub_id)
+ try:
+ ad.ed.wait_for_event(
+ EVENT_CALL_STATE_CHANGED,
+ is_event_match,
+ timeout=timeout,
+ field=CallStateContainer.CALL_STATE,
+ value=TELEPHONY_STATE_OFFHOOK)
+ ad.log.info("Got event %s", TELEPHONY_STATE_OFFHOOK)
+ except Empty:
+ ad.log.info("No event for call state change to OFFHOOK")
+ return False
+ finally:
+ if not event_tracking_started:
+ ad.droid.telephonyStopTrackingCallStateChangeForSubscription(
+ sub_id)
+ return True
+
+
+def wait_for_call_offhook_for_subscription(
+ log,
+ ad,
+ sub_id,
+ event_tracking_started=False,
+ timeout=MAX_WAIT_TIME_ACCEPT_CALL_TO_OFFHOOK_EVENT,
+ interval=WAIT_TIME_BETWEEN_STATE_CHECK):
+ """Wait for an incoming call on specified subscription.
+
+ Args:
+ log: log object.
+ ad: android device object.
+ sub_id: subscription ID
+ timeout: time to wait for ring
+ interval: checking interval
+
+ Returns:
+ True: if incoming call is received and answered successfully.
+ False: for errors
+ """
+ if not event_tracking_started:
+ ad.ed.clear_events(EVENT_CALL_STATE_CHANGED)
+ ad.droid.telephonyStartTrackingCallStateForSubscription(sub_id)
+ offhook_event_received = False
+ end_time = time.time() + timeout
+ try:
+ while time.time() < end_time:
+ if not offhook_event_received:
+ if wait_for_call_offhook_event(log, ad, sub_id, True,
+ interval):
+ offhook_event_received = True
+ telephony_state = ad.droid.telephonyGetCallStateForSubscription(
+ sub_id)
+ telecom_state = ad.droid.telecomGetCallState()
+ if telephony_state == TELEPHONY_STATE_OFFHOOK and (
+ telecom_state == TELEPHONY_STATE_OFFHOOK):
+ ad.log.info("telephony and telecom are in OFFHOOK state")
+ return True
+ else:
+ ad.log.info(
+ "telephony in %s, telecom in %s, expecting OFFHOOK state",
+ telephony_state, telecom_state)
+ if offhook_event_received:
+ time.sleep(interval)
+ finally:
+ if not event_tracking_started:
+ ad.droid.telephonyStopTrackingCallStateChangeForSubscription(
+ sub_id)
+
+
+def _wait_for_bluetooth_in_state(log, ad, state, max_wait):
+ # FIXME: These event names should be defined in a common location
+ _BLUETOOTH_STATE_ON_EVENT = 'BluetoothStateChangedOn'
+ _BLUETOOTH_STATE_OFF_EVENT = 'BluetoothStateChangedOff'
+ ad.ed.clear_events(_BLUETOOTH_STATE_ON_EVENT)
+ ad.ed.clear_events(_BLUETOOTH_STATE_OFF_EVENT)
+
+ ad.droid.bluetoothStartListeningForAdapterStateChange()
+ try:
+ bt_state = ad.droid.bluetoothCheckState()
+ if bt_state == state:
+ return True
+ if max_wait <= 0:
+ ad.log.error("Time out: bluetooth state still %s, expecting %s",
+ bt_state, state)
+ return False
+
+ event = {
+ False: _BLUETOOTH_STATE_OFF_EVENT,
+ True: _BLUETOOTH_STATE_ON_EVENT
+ }[state]
+ event = ad.ed.pop_event(event, max_wait)
+ ad.log.info("Got event %s", event['name'])
+ return True
+ except Empty:
+ ad.log.error("Time out: bluetooth state still in %s, expecting %s",
+ bt_state, state)
+ return False
+ finally:
+ ad.droid.bluetoothStopListeningForAdapterStateChange()
+
+
+def wait_for_droid_in_call(log, ad, max_time):
+ """Wait for android to be in call state.
+
+ Args:
+ log: log object.
+ ad: android device.
+ max_time: maximal wait time.
+
+ Returns:
+ If phone become in call state within max_time, return True.
+ Return False if timeout.
+ """
+ return _wait_for_droid_in_state(log, ad, max_time, is_phone_in_call)
+
+
+def _wait_for_droid_in_state(log, ad, max_time, state_check_func, *args,
+ **kwargs):
+ while max_time >= 0:
+ if state_check_func(log, ad, *args, **kwargs):
+ return True
+
+ time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+ max_time -= WAIT_TIME_BETWEEN_STATE_CHECK
+
+ return False
+
+
+# TODO: replace this with an event-based function
+def _wait_for_wifi_in_state(log, ad, state, max_wait):
+ return _wait_for_droid_in_state(log, ad, max_wait,
+ lambda log, ad, state: \
+ (True if ad.droid.wifiCheckState() == state else False),
+ state)
diff --git a/acts/framework/acts/controllers/buds_lib/test_actions/apollo_acts.py b/acts/framework/acts/controllers/buds_lib/test_actions/apollo_acts.py
index 4b2160b..67937bd 100644
--- a/acts/framework/acts/controllers/buds_lib/test_actions/apollo_acts.py
+++ b/acts/framework/acts/controllers/buds_lib/test_actions/apollo_acts.py
@@ -19,14 +19,14 @@
import time
+from acts.controllers.android_lib.tel.tel_utils import initiate_call
+from acts.controllers.android_lib.tel.tel_utils import wait_for_droid_in_call
from acts.controllers.buds_lib.apollo_lib import DeviceError
from acts.controllers.buds_lib.test_actions.agsa_acts import AgsaOTAError
from acts.controllers.buds_lib.test_actions.base_test_actions import BaseTestAction
from acts.controllers.buds_lib.test_actions.base_test_actions import timed_action
from acts.controllers.buds_lib.test_actions.bt_utils import BTUtils
from acts.libs.utils.timer import TimeRecorder
-from acts.test_utils.tel.tel_test_utils import initiate_call
-from acts.test_utils.tel.tel_test_utils import wait_for_droid_in_call
from acts.utils import wait_until
PACKAGE_NAME_AGSA = 'com.google.android.googlequicksearchbox'
diff --git a/acts/framework/acts/controllers/cellular_lib/AndroidCellularDut.py b/acts/framework/acts/controllers/cellular_lib/AndroidCellularDut.py
index aaef6f5..94bc4df 100644
--- a/acts/framework/acts/controllers/cellular_lib/AndroidCellularDut.py
+++ b/acts/framework/acts/controllers/cellular_lib/AndroidCellularDut.py
@@ -14,9 +14,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from acts.controllers.android_lib.tel import tel_utils
from acts.controllers.cellular_lib import BaseCellularDut
-from acts.test_utils.tel import tel_test_utils as tel_utils
-from acts.test_utils.tel import tel_defines
class AndroidCellularDut(BaseCellularDut.BaseCellularDut):
@@ -74,11 +73,11 @@
type: an instance of class PreferredNetworkType
"""
if type == BaseCellularDut.PreferredNetworkType.LTE_ONLY:
- formatted_type = tel_defines.NETWORK_MODE_LTE_ONLY
+ formatted_type = tel_utils.NETWORK_MODE_LTE_ONLY
elif type == BaseCellularDut.PreferredNetworkType.WCDMA_ONLY:
- formatted_type = tel_defines.NETWORK_MODE_WCDMA_ONLY
+ formatted_type = tel_utils.NETWORK_MODE_WCDMA_ONLY
elif type == BaseCellularDut.PreferredNetworkType.GSM_ONLY:
- formatted_type = tel_defines.NETWORK_MODE_GSM_ONLY
+ formatted_type = tel_utils.NETWORK_MODE_GSM_ONLY
else:
raise ValueError('Invalid RAT type.')
diff --git a/acts/framework/acts/test_utils/abstract_devices/wmm_transceiver.py b/acts/framework/acts/test_utils/abstract_devices/wmm_transceiver.py
new file mode 100644
index 0000000..f1aad98
--- /dev/null
+++ b/acts/framework/acts/test_utils/abstract_devices/wmm_transceiver.py
@@ -0,0 +1,665 @@
+#!/usr/bin/env python3
+#
+# Copyright 2020 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import multiprocessing
+import time
+
+from datetime import datetime
+from uuid import uuid4
+
+from acts import signals
+from acts import tracelogger
+from acts import utils
+from acts.controllers import iperf_client
+from acts.controllers import iperf_server
+
+AC_VO = 'AC_VO'
+AC_VI = 'AC_VI'
+AC_BE = 'AC_BE'
+AC_BK = 'AC_BK'
+
+# TODO(fxb/61421): Add tests to check all DSCP classes are mapped to the correct
+# AC (there are many that aren't included here). Requires implementation of
+# sniffer.
+DEFAULT_AC_TO_TOS_TAG_MAP = {
+ AC_VO: '0xC0',
+ AC_VI: '0x80',
+ AC_BE: '0x0',
+ AC_BK: '0x20'
+}
+UDP = 'udp'
+TCP = 'tcp'
+DEFAULT_IPERF_PORT = 5201
+DEFAULT_STREAM_TIME = 10
+DEFAULT_IP_ADDR_TIMEOUT = 15
+PROCESS_JOIN_TIMEOUT = 60
+AVAILABLE = True
+UNAVAILABLE = False
+
+
+class WmmTransceiverError(signals.ControllerError):
+ pass
+
+
+def create(config, identifier=None, wlan_devices=None, access_points=None):
+ """Creates a WmmTransceiver from a config.
+
+ Args:
+ config: dict, config parameters for the transceiver. Contains:
+ - iperf_config: dict, the config to use for creating IPerfClients
+ and IPerfServers (excluding port).
+ - port_range_start: int, the lower bound of the port range to use
+ for creating IPerfServers. Defaults to 5201.
+ - wlan_device: string, the identifier of the wlan_device used for
+ this WmmTransceiver (optional)
+
+ identifier: string, identifier for the WmmTransceiver. Must be provided
+ either as arg or in the config.
+ wlan_devices: list of WlanDevice objects from which to get the
+ wlan_device, if any, used as this transceiver
+ access_points: list of AccessPoint objects from which to get the
+ access_point, if any, used as this transceiver
+ """
+ try:
+ # If identifier is not provided as func arg, it must be provided via
+ # config file.
+ if not identifier:
+ identifier = config['identifier']
+ iperf_config = config['iperf_config']
+
+ except KeyError as err:
+ raise WmmTransceiverError(
+ 'Parameter not provided as func arg, nor found in config: %s' %
+ err)
+
+ if wlan_devices is None:
+ wlan_devices = []
+
+ if access_points is None:
+ access_points = []
+
+ port_range_start = config.get('port_range_start', DEFAULT_IPERF_PORT)
+
+ wd = None
+ ap = None
+ if 'wlan_device' in config:
+ wd = _find_wlan_device(config['wlan_device'], wlan_devices)
+ elif 'access_point' in config:
+ ap = _find_access_point(config['access_point'], access_points)
+
+ return WmmTransceiver(iperf_config,
+ identifier,
+ wlan_device=wd,
+ access_point=ap,
+ port_range_start=port_range_start)
+
+
+def _find_wlan_device(wlan_device_identifier, wlan_devices):
+ """Returns WlanDevice based on string identifier (e.g. ip, serial, etc.)
+
+ Args:
+ wlan_device_identifier: string, identifier for the desired WlanDevice
+ wlan_devices: list, WlanDevices to search through
+
+ Returns:
+ WlanDevice, with identifier matching wlan_device_identifier
+
+ Raises:
+ WmmTransceiverError, if no WlanDevice matches identifier
+ """
+ for wd in wlan_devices:
+ if wlan_device_identifier == wd.identifier:
+ return wd
+ raise WmmTransceiverError('No WlanDevice with identifier: %s' %
+ wlan_device_identifier)
+
+
+def _find_access_point(access_point_ip, access_points):
+ """Returns AccessPoint based on string ip address
+
+ Args:
+ access_point_ip: string, control plane ip addr of the desired AP,
+ access_points: list, AccessPoints to search through
+
+ Returns:
+ AccessPoint, with hostname matching access_point_ip
+
+ Raises:
+ WmmTransceiverError, if no AccessPoint matches ip"""
+ for ap in access_points:
+ if ap.ssh_settings.hostname == access_point_ip:
+ return ap
+ raise WmmTransceiverError('No AccessPoint with ip: %s' % access_point_ip)
+
+
+class WmmTransceiver(object):
+ """Object for handling WMM tagged streams between devices"""
+ def __init__(self,
+ iperf_config,
+ identifier,
+ wlan_device=None,
+ access_point=None,
+ port_range_start=5201):
+
+ self.identifier = identifier
+ self.log = tracelogger.TraceLogger(
+ WmmTransceiverLoggerAdapter(logging.getLogger(),
+ {'identifier': self.identifier}))
+ # WlanDevice or AccessPoint, that is used as the transceiver. Only one
+ # will be set. This helps consolodate association, setup, teardown, etc.
+ self.wlan_device = wlan_device
+ self.access_point = access_point
+
+ # Parameters used to create IPerfClient and IPerfServer objects on
+ # device
+ self._iperf_config = iperf_config
+ self._test_interface = self._iperf_config.get('test_interface')
+ self._port_range_start = port_range_start
+ self._next_server_port = port_range_start
+
+ # Maps IPerfClients, used for streams from this device, to True if
+ # available, False if reserved
+ self._iperf_clients = {}
+
+ # Maps IPerfServers, used to receive streams from other devices, to True
+ # if available, False if reserved
+ self._iperf_servers = {}
+
+ # Maps ports of servers, which are provided to other transceivers, to
+ # the actual IPerfServer objects
+ self._iperf_server_ports = {}
+
+ # Maps stream UUIDs to IPerfClients reserved for that streams use
+ self._reserved_clients = {}
+
+ # Maps stream UUIDs to (WmmTransceiver, IPerfServer) tuples, where the
+ # server is reserved on the transceiver for that streams use
+ self._reserved_servers = {}
+
+ # Maps with shared memory functionality to be used across the parallel
+ # streams. active_streams holds UUIDs of streams that are currently
+ # running on this device (mapped to True, since there is no
+ # multiprocessing set). stream_results maps UUIDs of streams completed
+ # on this device to IPerfResult results for that stream.
+ self._manager = multiprocessing.Manager()
+ self._active_streams = self._manager.dict()
+ self._stream_results = self._manager.dict()
+
+ # Holds parameters for streams that are prepared to run asynchronously
+ # (i.e. resources have been allocated). Maps UUIDs of the future streams
+ # to a dict, containing the stream parameters.
+ self._pending_async_streams = {}
+
+ # Set of UUIDs of asynchronous streams that have at least started, but
+ # have not had their resources reclaimed yet
+ self._ran_async_streams = set()
+
+ # Set of stream parallel process, which can be joined if completed
+ # successfully, or terminated and joined in the event of an error
+ self._running_processes = set()
+
+ def run_synchronous_traffic_stream(self, stream_parameters, subnet):
+ """Runs a traffic stream with IPerf3 between two WmmTransceivers and
+ saves the results.
+
+ Args:
+ stream_parameters: dict, containing parameters to used for the
+ stream. See _parse_stream_parameters for details.
+ subnet: string, the subnet of the network to use for the stream
+
+ Returns:
+ uuid: UUID object, identifier of the stream
+ """
+ (receiver, access_category, bandwidth,
+ stream_time) = self._parse_stream_parameters(stream_parameters)
+ uuid = uuid4()
+
+ (client, server_ip,
+ server_port) = self._get_stream_resources(uuid, receiver, subnet)
+
+ self._validate_server_address(server_ip, uuid)
+
+ self.log.info('Running synchronous stream to %s WmmTransceiver' %
+ receiver.identifier)
+ self._run_traffic(uuid,
+ client,
+ server_ip,
+ server_port,
+ self._active_streams,
+ self._stream_results,
+ access_category=access_category,
+ bandwidth=bandwidth,
+ stream_time=stream_time)
+
+ self._return_stream_resources(uuid)
+ return uuid
+
+ def prepare_asynchronous_stream(self, stream_parameters, subnet):
+ """Reserves resources and saves configs for upcoming asynchronous
+ traffic streams, so they can be started more simultaneously.
+
+ Args:
+ stream_parameters: dict, containing parameters to used for the
+ stream. See _parse_stream_parameters for details.
+ subnet: string, the subnet of the network to use for the stream
+
+ Returns:
+ uuid: UUID object, identifier of the stream
+ """
+ (receiver, access_category, bandwidth,
+ time) = self._parse_stream_parameters(stream_parameters)
+ uuid = uuid4()
+
+ (client, server_ip,
+ server_port) = self._get_stream_resources(uuid, receiver, subnet)
+
+ self._validate_server_address(server_ip, uuid)
+
+ pending_stream_config = {
+ 'client': client,
+ 'server_ip': server_ip,
+ 'server_port': server_port,
+ 'access_category': access_category,
+ 'bandwidth': bandwidth,
+ 'time': time
+ }
+
+ self._pending_async_streams[uuid] = pending_stream_config
+ self.log.info('Stream to %s WmmTransceiver prepared.' %
+ receiver.identifier)
+ return uuid
+
+ def start_asynchronous_streams(self, start_time=None):
+ """Starts pending asynchronous streams between two WmmTransceivers as
+ parallel processes.
+
+ Args:
+ start_time: float, time, seconds since epoch, at which to start the
+ stream (for better synchronicity). If None, start immediately.
+ """
+ for uuid in self._pending_async_streams:
+ pending_stream_config = self._pending_async_streams[uuid]
+ client = pending_stream_config['client']
+ server_ip = pending_stream_config['server_ip']
+ server_port = pending_stream_config['server_port']
+ access_category = pending_stream_config['access_category']
+ bandwidth = pending_stream_config['bandwidth']
+ time = pending_stream_config['time']
+
+ process = multiprocessing.Process(target=self._run_traffic,
+ args=[
+ uuid, client, server_ip,
+ server_port,
+ self._active_streams,
+ self._stream_results
+ ],
+ kwargs={
+ 'access_category':
+ access_category,
+ 'bandwidth': bandwidth,
+ 'stream_time': time,
+ 'start_time': start_time
+ })
+
+ # This needs to be set here to ensure its marked active before
+ # it even starts.
+ self._active_streams[uuid] = True
+ process.start()
+ self._ran_async_streams.add(uuid)
+ self._running_processes.add(process)
+
+ self._pending_async_streams.clear()
+
+ def cleanup_asynchronous_streams(self, timeout=PROCESS_JOIN_TIMEOUT):
+ """Releases reservations on resources (IPerfClients and IPerfServers)
+ that were held for asynchronous streams, both pending and finished.
+ Attempts to join any running processes, logging an error if timeout is
+ exceeded.
+
+ Args:
+ timeout: time, in seconds, to wait for each running process, if any,
+ to join
+ """
+ self.log.info('Cleaning up any asynchronous streams.')
+
+ # Releases resources for any streams that were prepared, but no run
+ for uuid in self._pending_async_streams:
+ self.log.error(
+ 'Pending asynchronous stream %s never ran. Cleaning.' % uuid)
+ self._return_stream_resources(uuid)
+ self._pending_async_streams.clear()
+
+ # Attempts to join any running streams, terminating them after timeout
+ # if necessary.
+ while self._running_processes:
+ process = self._running_processes.pop()
+ process.join(timeout)
+ if process.is_alive():
+ self.log.error(
+ 'Stream process failed to join in %s seconds. Terminating.'
+ % timeout)
+ process.terminate()
+ process.join()
+ self._active_streams.clear()
+
+ # Release resources for any finished streams
+ while self._ran_async_streams:
+ uuid = self._ran_async_streams.pop()
+ self._return_stream_resources(uuid)
+
+ def get_results(self, uuid):
+ """Retrieves a streams IPerfResults from stream_results
+
+ Args:
+ uuid: UUID object, identifier of the stream
+ """
+ return self._stream_results.get(uuid, None)
+
+ def destroy_resources(self):
+ for server in self._iperf_servers:
+ server.stop()
+ self._iperf_servers.clear()
+ self._iperf_server_ports.clear()
+ self._iperf_clients.clear()
+ self._next_server_port = self._port_range_start
+ self._stream_results.clear()
+
+ @property
+ def has_active_streams(self):
+ return bool(self._active_streams)
+
+ # Helper Functions
+
+ def _run_traffic(self,
+ uuid,
+ client,
+ server_ip,
+ server_port,
+ active_streams,
+ stream_results,
+ access_category=None,
+ bandwidth=None,
+ stream_time=DEFAULT_STREAM_TIME,
+ start_time=None):
+ """Runs an iperf3 stream.
+
+ 1. Adds stream UUID to active_streams
+ 2. Runs stream
+ 3. Saves results to stream_results
+ 4. Removes stream UUID from active_streams
+
+ Args:
+ uuid: UUID object, identifier for stream
+ client: IPerfClient object on device
+ server_ip: string, ip address of IPerfServer for stream
+ server_port: int, port of the IPerfServer for stream
+ active_streams: multiprocessing.Manager.dict, which holds stream
+ UUIDs of active streams on the device
+ stream_results: multiprocessing.Manager.dict, which maps stream
+ UUIDs of streams to IPerfResult objects
+ access_category: string, WMM access category to use with iperf
+ (AC_BK, AC_BE, AC_VI, AC_VO). Unset if None.
+ bandwidth: int, bandwidth in mbps to use with iperf. Implies UDP.
+ Unlimited if None.
+ stream_time: int, time in seconds, to run iperf stream
+ start_time: float, time, seconds since epoch, at which to start the
+ stream (for better synchronicity). If None, start immediately.
+ """
+ active_streams[uuid] = True
+ # SSH sessions must be started within the process that is going to
+ # use it.
+ if type(client) == iperf_client.IPerfClientOverSsh:
+ with utils.SuppressLogOutput():
+ client.start_ssh()
+
+ ac_flag = ''
+ bandwidth_flag = ''
+ time_flag = '-t %s' % stream_time
+
+ if access_category:
+ ac_flag = ' -S %s' % DEFAULT_AC_TO_TOS_TAG_MAP[access_category]
+
+ if bandwidth:
+ bandwidth_flag = ' -u -b %sM' % bandwidth
+
+ iperf_flags = '-p %s -i 1 %s%s%s -J' % (server_port, time_flag,
+ ac_flag, bandwidth_flag)
+ if not start_time:
+ start_time = time.time()
+ time_str = datetime.fromtimestamp(start_time).strftime('%H:%M:%S.%f')
+ self.log.info(
+ 'At %s, starting %s second stream to %s:%s with (AC: %s, Bandwidth: %s)'
+ % (time_str, stream_time, server_ip, server_port, access_category,
+ bandwidth if bandwidth else 'Unlimited'))
+
+ # If present, wait for stream start time
+ if start_time:
+ current_time = time.time()
+ while current_time < start_time:
+ current_time = time.time()
+ path = client.start(server_ip, iperf_flags, '%s' % uuid)
+ stream_results[uuid] = iperf_server.IPerfResult(
+ path, reporting_speed_units='mbps')
+
+ if type(client) == iperf_client.IPerfClientOverSsh:
+ client.close_ssh()
+ active_streams.pop(uuid)
+
+ def _get_stream_resources(self, uuid, receiver, subnet):
+ """Reserves an IPerfClient and IPerfServer for a stream.
+
+ Args:
+ uuid: UUID object, identifier of the stream
+ receiver: WmmTransceiver object, which will be the streams receiver
+ subnet: string, subnet of test network, to retrieve the appropriate
+ server address
+
+ Returns:
+ (IPerfClient, string, int) representing the client, server address,
+ and server port to use for the stream
+ """
+ client = self._get_client(uuid)
+ server_ip, server_port = self._get_server(receiver, uuid, subnet)
+ return (client, server_ip, server_port)
+
+ def _return_stream_resources(self, uuid):
+ """Releases reservations on a streams IPerfClient and IPerfServer, so
+ they can be used by a future stream.
+
+ Args:
+ uuid: UUID object, identifier of the stream
+ """
+ if uuid in self._active_streams:
+ raise EnvironmentError('Resource still being used by stream %s' %
+ uuid)
+ (receiver, server_port) = self._reserved_servers.pop(uuid)
+ receiver._release_server(server_port)
+ client = self._reserved_clients.pop(uuid)
+ self._iperf_clients[client] = AVAILABLE
+
+ def _get_client(self, uuid):
+ """Retrieves and reserves IPerfClient for use in a stream. If none are
+ available, a new one is created.
+
+ Args:
+ uuid: UUID object, identifier for stream, used to link client to
+ stream for teardown
+
+ Returns:
+ IPerfClient on device
+ """
+ reserved_client = None
+ for client in self._iperf_clients:
+ if self._iperf_clients[client] == AVAILABLE:
+ reserved_client = client
+ break
+ else:
+ reserved_client = iperf_client.create([self._iperf_config])[0]
+ # Due to the nature of multiprocessing, ssh connections must
+ # be started inside the parallel processes, so it must be closed
+ # here.
+ if type(reserved_client) == iperf_client.IPerfClientOverSsh:
+ reserved_client.close_ssh()
+
+ self._iperf_clients[reserved_client] = UNAVAILABLE
+ self._reserved_clients[uuid] = reserved_client
+ return reserved_client
+
+ def _get_server(self, receiver, uuid, subnet):
+ """Retrieves the address and port of a reserved IPerfServer object from
+ the receiver object for use in a stream.
+
+ Args:
+ receiver: WmmTransceiver, to get an IPerfServer from
+ uuid: UUID, identifier for stream, used to link server to stream
+ for teardown
+ subnet: string, subnet of test network, to retrieve the appropriate
+ server address
+
+ Returns:
+ (string, int) representing the IPerfServer address and port
+ """
+ (server_ip, server_port) = receiver._reserve_server(subnet)
+ self._reserved_servers[uuid] = (receiver, server_port)
+ return (server_ip, server_port)
+
+ def _reserve_server(self, subnet):
+ """Reserves an available IPerfServer for use in a stream from another
+ WmmTransceiver. If none are available, a new one is created.
+
+ Args:
+ subnet: string, subnet of test network, to retrieve the appropriate
+ server address
+
+ Returns:
+ (string, int) representing the IPerfServer address and port
+ """
+ reserved_server = None
+ for server in self._iperf_servers:
+ if self._iperf_servers[server] == AVAILABLE:
+ reserved_server = server
+ break
+ else:
+ iperf_server_config = self._iperf_config
+ iperf_server_config.update({'port': self._next_server_port})
+ self._next_server_port += 1
+ reserved_server = iperf_server.create([iperf_server_config])[0]
+ self._iperf_server_ports[reserved_server.port] = reserved_server
+
+ self._iperf_servers[reserved_server] = UNAVAILABLE
+ reserved_server.start()
+ end_time = time.time() + DEFAULT_IP_ADDR_TIMEOUT
+ while time.time() < end_time:
+ if self.wlan_device:
+ addresses = utils.get_interface_ip_addresses(
+ self.wlan_device.device, self._test_interface)
+ else:
+ addresses = reserved_server.get_interface_ip_addresses(
+ self._test_interface)
+ for addr in addresses['ipv4_private']:
+ if utils.ip_in_subnet(addr, subnet):
+ return (addr, reserved_server.port)
+ raise AttributeError(
+ 'Reserved server has no ipv4 address in the %s subnet' % subnet)
+
+ def _release_server(self, server_port):
+ """Releases reservation on IPerfServer, which was held for a stream
+ from another WmmTransceiver.
+
+ Args:
+ server_port: int, the port of the IPerfServer being returned (since)
+ it is the identifying characteristic
+ """
+ server = self._iperf_server_ports[server_port]
+ server.stop()
+ self._iperf_servers[server] = AVAILABLE
+
+ def _validate_server_address(self, server_ip, uuid, timeout=60):
+ """ Verifies server address can be pinged before attempting to run
+ traffic, since iperf is unforgiving when the server is unreachable.
+
+ Args:
+ server_ip: string, ip address of the iperf server
+ uuid: string, uuid of the stream to use this server
+ timeout: int, time in seconds to wait for server to respond to pings
+
+ Raises:
+ WmmTransceiverError, if, after timeout, server ip is unreachable.
+ """
+ self.log.info('Verifying server address (%s) is reachable.' %
+ server_ip)
+ end_time = time.time() + timeout
+ while time.time() < end_time:
+ if self.can_ping(server_ip):
+ break
+ else:
+ self.log.debug(
+ 'Could not ping server address (%s). Retrying in 1 second.'
+ % (server_ip))
+ time.sleep(1)
+ else:
+ self._return_stream_resources(uuid)
+ raise WmmTransceiverError('IPerfServer address (%s) unreachable.' %
+ server_ip)
+
+ def can_ping(self, dest_ip):
+ """ Utilizes can_ping function in wlan_device or access_point device to
+ ping dest_ip
+
+ Args:
+ dest_ip: string, ip address to ping
+
+ Returns:
+ True, if dest address is reachable
+ False, otherwise
+ """
+ if self.wlan_device:
+ return self.wlan_device.can_ping(dest_ip)
+ else:
+ return self.access_point.can_ping(dest_ip)
+
+ def _parse_stream_parameters(self, stream_parameters):
+ """Parses stream_parameters from dictionary.
+
+ Args:
+ stream_parameters: dict of stream parameters
+ 'receiver': WmmTransceiver, the receiver for the stream
+ 'access_category': String, the access category to use for the
+ stream. Unset if None.
+ 'bandwidth': int, bandwidth in mbps for the stream. If set,
+ implies UDP. If unset, implies TCP and unlimited bandwidth.
+ 'time': int, time in seconds to run stream.
+
+ Returns:
+ (receiver, access_category, bandwidth, time) as
+ (WmmTransceiver, String, int, int)
+ """
+ receiver = stream_parameters['receiver']
+ access_category = stream_parameters.get('access_category', None)
+ bandwidth = stream_parameters.get('bandwidth', None)
+ time = stream_parameters.get('time', DEFAULT_STREAM_TIME)
+ return (receiver, access_category, bandwidth, time)
+
+
+class WmmTransceiverLoggerAdapter(logging.LoggerAdapter):
+ def process(self, msg, kwargs):
+ if self.extra['identifier']:
+ log_identifier = ' | %s' % self.extra['identifier']
+ else:
+ log_identifier = ''
+ msg = "[WmmTransceiver%s] %s" % (log_identifier, msg)
+ return (msg, kwargs)
diff --git a/acts_tests/.gitignore b/acts_tests/.gitignore
index 5cd10e7..3f46a3b 100644
--- a/acts_tests/.gitignore
+++ b/acts_tests/.gitignore
@@ -68,4 +68,6 @@
# PyCharm
.idea/
+
+# IntelliJ
*.iml