blob: 00e0c4ac9d245b8f92999b9c9eca5a5cf0cb7d57 [file]
#!/usr/bin/env python3
#
# Copyright (C) 2016 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 inspect
import time
from acts import asserts
from acts.controllers.buds_lib.dev_utils import apollo_sink_events
from acts_contrib.test_utils.bt.bt_constants import bt_default_timeout
def validate_controller(controller, abstract_device_class):
"""Ensure controller has all methods in abstract_device_class.
Also checks method signatures to ensure parameters are satisfied.
Args:
controller: instance of a device controller.
abstract_device_class: class definition of an abstract_device interface.
Raises:
NotImplementedError: if controller is missing one or more methods.
"""
ctlr_methods = inspect.getmembers(controller, predicate=callable)
reqd_methods = inspect.getmembers(
abstract_device_class, predicate=inspect.ismethod)
expected_func_names = {method[0] for method in reqd_methods}
controller_func_names = {method[0] for method in ctlr_methods}
if not controller_func_names.issuperset(expected_func_names):
raise NotImplementedError(
'Controller {} is missing the following functions: {}'.format(
controller.__class__.__name__,
repr(expected_func_names - controller_func_names)))
for func_name in expected_func_names:
controller_func = getattr(controller, func_name)
required_func = getattr(abstract_device_class, func_name)
required_signature = inspect.signature(required_func)
if inspect.signature(controller_func) != required_signature:
raise NotImplementedError(
'Method {} must have the signature {}{}.'.format(
controller_func.__qualname__, controller_func.__name__,
required_signature))
class BluetoothHandsfreeAbstractDevice:
"""Base class for all Bluetooth handsfree abstract devices.
Desired controller classes should have a corresponding Bluetooth handsfree
abstract device class defined in this module.
"""
@property
def mac_address(self):
raise NotImplementedError
def accept_call(self):
raise NotImplementedError()
def end_call(self):
raise NotImplementedError()
def enter_pairing_mode(self):
raise NotImplementedError()
def next_track(self):
raise NotImplementedError()
def pause(self):
raise NotImplementedError()
def play(self):
raise NotImplementedError()
def power_off(self):
raise NotImplementedError()
def power_on(self):
raise NotImplementedError()
def previous_track(self):
raise NotImplementedError()
def reject_call(self):
raise NotImplementedError()
def volume_down(self):
raise NotImplementedError()
def volume_up(self):
raise NotImplementedError()
class PixelBudsBluetoothHandsfreeAbstractDevice(
BluetoothHandsfreeAbstractDevice):
CMD_EVENT = 'EvtHex'
def __init__(self, pixel_buds_controller):
self.pixel_buds_controller = pixel_buds_controller
def format_cmd(self, cmd_name):
return self.CMD_EVENT + ' ' + apollo_sink_events.SINK_EVENTS[cmd_name]
@property
def mac_address(self):
return self.pixel_buds_controller.bluetooth_address
def accept_call(self):
return self.pixel_buds_controller.cmd(
self.format_cmd('EventUsrAnswer'))
def end_call(self):
return self.pixel_buds_controller.cmd(
self.format_cmd('EventUsrCancelEnd'))
def enter_pairing_mode(self):
return self.pixel_buds_controller.set_pairing_mode()
def next_track(self):
return self.pixel_buds_controller.cmd(
self.format_cmd('EventUsrAvrcpSkipForward'))
def pause(self):
return self.pixel_buds_controller.cmd(
self.format_cmd('EventUsrAvrcpPause'))
def play(self):
return self.pixel_buds_controller.cmd(
self.format_cmd('EventUsrAvrcpPlay'))
def power_off(self):
return self.pixel_buds_controller.power('Off')
def power_on(self):
return self.pixel_buds_controller.power('On')
def previous_track(self):
return self.pixel_buds_controller.cmd(
self.format_cmd('EventUsrAvrcpSkipBackward'))
def reject_call(self):
return self.pixel_buds_controller.cmd(
self.format_cmd('EventUsrReject'))
def volume_down(self):
return self.pixel_buds_controller.volume('Down')
def volume_up(self):
return self.pixel_buds_controller.volume('Up')
class EarstudioReceiverBluetoothHandsfreeAbstractDevice(
BluetoothHandsfreeAbstractDevice):
def __init__(self, earstudio_controller):
self.earstudio_controller = earstudio_controller
@property
def mac_address(self):
return self.earstudio_controller.mac_address
def accept_call(self):
return self.earstudio_controller.press_accept_call()
def end_call(self):
return self.earstudio_controller.press_end_call()
def enter_pairing_mode(self):
return self.earstudio_controller.enter_pairing_mode()
def next_track(self):
return self.earstudio_controller.press_next()
def pause(self):
return self.earstudio_controller.press_play_pause()
def play(self):
return self.earstudio_controller.press_play_pause()
def power_off(self):
return self.earstudio_controller.power_off()
def power_on(self):
return self.earstudio_controller.power_on()
def previous_track(self):
return self.earstudio_controller.press_previous()
def reject_call(self):
return self.earstudio_controller.press_reject_call()
def volume_down(self):
return self.earstudio_controller.press_volume_down()
def volume_up(self):
return self.earstudio_controller.press_volume_up()
class JaybirdX3EarbudsBluetoothHandsfreeAbstractDevice(
BluetoothHandsfreeAbstractDevice):
def __init__(self, jaybird_controller):
self.jaybird_controller = jaybird_controller
@property
def mac_address(self):
return self.jaybird_controller.mac_address
def accept_call(self):
return self.jaybird_controller.press_accept_call()
def end_call(self):
return self.jaybird_controller.press_reject_call()
def enter_pairing_mode(self):
return self.jaybird_controller.enter_pairing_mode()
def next_track(self):
return self.jaybird_controller.press_next()
def pause(self):
return self.jaybird_controller.press_play_pause()
def play(self):
return self.jaybird_controller.press_play_pause()
def power_off(self):
return self.jaybird_controller.power_off()
def power_on(self):
return self.jaybird_controller.power_on()
def previous_track(self):
return self.jaybird_controller.press_previous()
def reject_call(self):
return self.jaybird_controller.press_reject_call()
def volume_down(self):
return self.jaybird_controller.press_volume_down()
def volume_up(self):
return self.jaybird_controller.press_volume_up()
class AndroidHeadsetBluetoothHandsfreeAbstractDevice(
BluetoothHandsfreeAbstractDevice):
def __init__(self, ad_controller):
self.ad_controller = ad_controller
@property
def mac_address(self):
"""Getting device mac with more stability ensurance.
Sometime, getting mac address is flaky that it returns None. Adding a
loop to add more ensurance of getting correct mac address.
"""
device_mac = None
start_time = time.time()
end_time = start_time + bt_default_timeout
while not device_mac and time.time() < end_time:
device_mac = self.ad_controller.droid.bluetoothGetLocalAddress()
asserts.assert_true(device_mac, 'Can not get the MAC address')
return device_mac
def accept_call(self):
return self.ad_controller.droid.telecomAcceptRingingCall(None)
def end_call(self):
return self.ad_controller.droid.telecomEndCall()
def enter_pairing_mode(self):
self.ad_controller.droid.bluetoothStartPairingHelper(True)
return self.ad_controller.droid.bluetoothMakeDiscoverable()
def next_track(self):
return (self.ad_controller.droid.bluetoothMediaPassthrough("skipNext"))
def pause(self):
return self.ad_controller.droid.bluetoothMediaPassthrough("pause")
def play(self):
return self.ad_controller.droid.bluetoothMediaPassthrough("play")
def power_off(self):
return self.ad_controller.droid.bluetoothToggleState(False)
def power_on(self):
return self.ad_controller.droid.bluetoothToggleState(True)
def previous_track(self):
return (self.ad_controller.droid.bluetoothMediaPassthrough("skipPrev"))
def reject_call(self):
return self.ad_controller.droid.telecomCallDisconnect(
self.ad_controller.droid.telecomCallGetCallIds()[0])
def reset(self):
return self.ad_controller.droid.bluetoothFactoryReset()
def volume_down(self):
target_step = self.ad_controller.droid.getMediaVolume() - 1
target_step = max(target_step, 0)
return self.ad_controller.droid.setMediaVolume(target_step)
def volume_up(self):
target_step = self.ad_controller.droid.getMediaVolume() + 1
max_step = self.ad_controller.droid.getMaxMediaVolume()
target_step = min(target_step, max_step)
return self.ad_controller.droid.setMediaVolume(target_step)
class BluetoothHandsfreeAbstractDeviceFactory:
"""Generates a BluetoothHandsfreeAbstractDevice for any device controller.
"""
_controller_abstract_devices = {
'EarstudioReceiver': EarstudioReceiverBluetoothHandsfreeAbstractDevice,
'JaybirdX3Earbuds': JaybirdX3EarbudsBluetoothHandsfreeAbstractDevice,
'ParentDevice': PixelBudsBluetoothHandsfreeAbstractDevice,
'AndroidDevice': AndroidHeadsetBluetoothHandsfreeAbstractDevice
}
def generate(self, controller):
class_name = controller.__class__.__name__
if class_name in self._controller_abstract_devices:
return self._controller_abstract_devices[class_name](controller)
else:
validate_controller(controller, BluetoothHandsfreeAbstractDevice)
return controller