1#!/usr/bin/env python3 2# 3# Copyright (C) 2016 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may not 6# use this file except in compliance with the License. You may obtain a copy of 7# 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, WITHOUT 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14# License for the specific language governing permissions and limitations under 15# the License. 16import inspect 17 18from acts.controllers.buds_lib.dev_utils import apollo_sink_events 19 20 21def validate_controller(controller, abstract_device_class): 22 """Ensure controller has all methods in abstract_device_class. 23 Also checks method signatures to ensure parameters are satisfied. 24 25 Args: 26 controller: instance of a device controller. 27 abstract_device_class: class definition of an abstract_device interface. 28 Raises: 29 NotImplementedError: if controller is missing one or more methods. 30 """ 31 ctlr_methods = inspect.getmembers(controller, predicate=callable) 32 reqd_methods = inspect.getmembers(abstract_device_class, 33 predicate=inspect.ismethod) 34 expected_func_names = {method[0] for method in reqd_methods} 35 controller_func_names = {method[0] for method in ctlr_methods} 36 37 if not controller_func_names.issuperset(expected_func_names): 38 raise NotImplementedError( 39 'Controller {} is missing the following functions: {}'.format( 40 controller.__class__.__name__, 41 repr(expected_func_names - controller_func_names))) 42 43 for func_name in expected_func_names: 44 controller_func = getattr(controller, func_name) 45 required_func = getattr(abstract_device_class, func_name) 46 required_signature = inspect.signature(required_func) 47 if inspect.signature(controller_func) != required_signature: 48 raise NotImplementedError( 49 'Method {} must have the signature {}{}.'.format( 50 controller_func.__qualname__, 51 controller_func.__name__, 52 required_signature)) 53 54 55class BluetoothHandsfreeAbstractDevice: 56 """Base class for all Bluetooth handsfree abstract devices. 57 58 Desired controller classes should have a corresponding Bluetooth handsfree 59 abstract device class defined in this module. 60 """ 61 @property 62 def mac_address(self): 63 raise NotImplementedError 64 65 def accept_call(self): 66 raise NotImplementedError() 67 68 def end_call(self): 69 raise NotImplementedError() 70 71 def enter_pairing_mode(self): 72 raise NotImplementedError() 73 74 def next_track(self): 75 raise NotImplementedError() 76 77 def pause(self): 78 raise NotImplementedError() 79 80 def play(self): 81 raise NotImplementedError() 82 83 def power_off(self): 84 raise NotImplementedError() 85 86 def power_on(self): 87 raise NotImplementedError() 88 89 def previous_track(self): 90 raise NotImplementedError() 91 92 def reject_call(self): 93 raise NotImplementedError() 94 95 def volume_down(self): 96 raise NotImplementedError() 97 98 def volume_up(self): 99 raise NotImplementedError() 100 101 102class PixelBudsBluetoothHandsfreeAbstractDevice( 103 BluetoothHandsfreeAbstractDevice): 104 105 CMD_EVENT = 'EvtHex' 106 107 def __init__(self, pixel_buds_controller): 108 self.pixel_buds_controller = pixel_buds_controller 109 110 def format_cmd(self, cmd_name): 111 return self.CMD_EVENT + ' ' + apollo_sink_events.SINK_EVENTS[cmd_name] 112 113 @property 114 def mac_address(self): 115 return self.pixel_buds_controller.bluetooth_address 116 117 def accept_call(self): 118 return self.pixel_buds_controller.cmd(self.format_cmd('EventUsrAnswer')) 119 120 def end_call(self): 121 return self.pixel_buds_controller.cmd( 122 self.format_cmd('EventUsrCancelEnd')) 123 124 def enter_pairing_mode(self): 125 return self.pixel_buds_controller.set_pairing_mode() 126 127 def next_track(self): 128 return self.pixel_buds_controller.cmd( 129 self.format_cmd('EventUsrAvrcpSkipForward')) 130 131 def pause(self): 132 return self.pixel_buds_controller.cmd( 133 self.format_cmd('EventUsrAvrcpPause')) 134 135 def play(self): 136 return self.pixel_buds_controller.cmd( 137 self.format_cmd('EventUsrAvrcpPlay')) 138 139 def power_off(self): 140 return self.pixel_buds_controller.power('Off') 141 142 def power_on(self): 143 return self.pixel_buds_controller.power('On') 144 145 def previous_track(self): 146 return self.pixel_buds_controller.cmd( 147 self.format_cmd('EventUsrAvrcpSkipBackward')) 148 149 def reject_call(self): 150 return self.pixel_buds_controller.cmd(self.format_cmd('EventUsrReject')) 151 152 def volume_down(self): 153 return self.pixel_buds_controller.volume('Down') 154 155 def volume_up(self): 156 return self.pixel_buds_controller.volume('Up') 157 158 159class EarstudioReceiverBluetoothHandsfreeAbstractDevice( 160 BluetoothHandsfreeAbstractDevice): 161 162 def __init__(self, earstudio_controller): 163 self.earstudio_controller = earstudio_controller 164 165 @property 166 def mac_address(self): 167 return self.earstudio_controller.mac_address 168 169 def accept_call(self): 170 return self.earstudio_controller.press_accept_call() 171 172 def end_call(self): 173 return self.earstudio_controller.press_end_call() 174 175 def enter_pairing_mode(self): 176 return self.earstudio_controller.enter_pairing_mode() 177 178 def next_track(self): 179 return self.earstudio_controller.press_next() 180 181 def pause(self): 182 return self.earstudio_controller.press_play_pause() 183 184 def play(self): 185 return self.earstudio_controller.press_play_pause() 186 187 def power_off(self): 188 return self.earstudio_controller.power_off() 189 190 def power_on(self): 191 return self.earstudio_controller.power_on() 192 193 def previous_track(self): 194 return self.earstudio_controller.press_previous() 195 196 def reject_call(self): 197 return self.earstudio_controller.press_reject_call() 198 199 def volume_down(self): 200 return self.earstudio_controller.press_volume_down() 201 202 def volume_up(self): 203 return self.earstudio_controller.press_volume_up() 204 205 206class JaybirdX3EarbudsBluetoothHandsfreeAbstractDevice( 207 BluetoothHandsfreeAbstractDevice): 208 209 def __init__(self, jaybird_controller): 210 self.jaybird_controller = jaybird_controller 211 212 @property 213 def mac_address(self): 214 return self.jaybird_controller.mac_address 215 216 def accept_call(self): 217 return self.jaybird_controller.press_accept_call() 218 219 def end_call(self): 220 return self.jaybird_controller.press_reject_call() 221 222 def enter_pairing_mode(self): 223 return self.jaybird_controller.enter_pairing_mode() 224 225 def next_track(self): 226 return self.jaybird_controller.press_next() 227 228 def pause(self): 229 return self.jaybird_controller.press_play_pause() 230 231 def play(self): 232 return self.jaybird_controller.press_play_pause() 233 234 def power_off(self): 235 return self.jaybird_controller.power_off() 236 237 def power_on(self): 238 return self.jaybird_controller.power_on() 239 240 def previous_track(self): 241 return self.jaybird_controller.press_previous() 242 243 def reject_call(self): 244 return self.jaybird_controller.press_reject_call() 245 246 def volume_down(self): 247 return self.jaybird_controller.press_volume_down() 248 249 def volume_up(self): 250 return self.jaybird_controller.press_volume_up() 251 252 253class BluetoothHandsfreeAbstractDeviceFactory: 254 """Generates a BluetoothHandsfreeAbstractDevice for any device controller. 255 """ 256 257 _controller_abstract_devices = { 258 'EarstudioReceiver': EarstudioReceiverBluetoothHandsfreeAbstractDevice, 259 'JaybirdX3Earbuds': JaybirdX3EarbudsBluetoothHandsfreeAbstractDevice, 260 'ParentDevice': PixelBudsBluetoothHandsfreeAbstractDevice 261 } 262 263 def generate(self, controller): 264 class_name = controller.__class__.__name__ 265 if class_name in self._controller_abstract_devices: 266 return self._controller_abstract_devices[class_name](controller) 267 else: 268 validate_controller(controller, BluetoothHandsfreeAbstractDevice) 269 return controller 270