1# Copyright 2023 Google LLC
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Client class to access the Floss telephony interface."""
15import logging
16
17from floss.pandora.floss import observer_base
18from floss.pandora.floss import utils
19
20
21class BluetoothTelephonyCallbacks:
22    """Callbacks for the telephony interface.
23
24    Implement this to observe these callbacks when exporting callbacks via register_callback.
25    """
26
27    def on_telephony_use(self, addr, state):
28        """Called when telephony is in use.
29
30        Args:
31            addr: The address of the telephony device.
32            state: The boolean value indicating the telephony state.
33        """
34        pass
35
36
37class FlossTelephonyClient:
38    """Handles method calls and callbacks from the telephony interface."""
39
40    TELEPHONY_SERVICE = 'org.chromium.bluetooth'
41    TELEPHONY_INTERFACE = 'org.chromium.bluetooth.BluetoothTelephony'
42    TELEPHONY_OBJECT_PATTERN = '/org/chromium/bluetooth/hci{}/telephony'
43    TELEPHONY_CB_INTF = 'org.chromium.bluetooth.BluetoothTelephonyCallback'
44    TELEPHONY_CB_OBJ_NAME = 'test_telephony_client'
45
46    class ExportedTelephonyCallbacks(observer_base.ObserverBase):
47        """
48        <node>
49            <interface name="org.chromium.bluetooth.BluetoothTelephonyCallback">
50                <method name="OnTelephonyUse">
51                    <arg type="s" name="add" direction="in" />
52                    <arg type="b" name="state" direction="in" />
53                </method>
54            </interface>
55        </node>
56        """
57
58        def __init__(self):
59            """Constructs exported callbacks object."""
60            observer_base.ObserverBase.__init__(self)
61
62        def OnTelephonyUse(self, addr, state):
63            """Handles telephony use callback.
64
65            Args:
66                addr: The address of the telephony device.
67                state: The boolean value indicating the telephony state.
68            """
69
70            for observer in self.observers.values():
71                observer.on_telephony_use(addr, state)
72
73    def __init__(self, bus, hci):
74        """Constructs the client.
75
76        Args:
77            bus: D-Bus bus over which we'll establish connections.
78            hci: HCI adapter index. Get this value from `get_default_adapter` on FlossManagerClient.
79        """
80        self.bus = bus
81        self.hci = hci
82        self.objpath = self.TELEPHONY_OBJECT_PATTERN.format(hci)
83
84        # We don't register callbacks by default.
85        self.callbacks = None
86
87    def __del__(self):
88        """Destructor."""
89        del self.callbacks
90
91    @utils.glib_callback()
92    def on_telephony_use(self, addr, state):
93        """Handles telephony use callback.
94
95        Args:
96            addr: The address of the telephony device.
97            state: The boolean value indicating the telephony state.
98        """
99        logging.debug('on_telephony_use: addr: %s, state: %s', addr, state)
100
101    def _make_dbus_phone_number(self, number):
102        """Makes struct for phone number D-Bus.
103
104        Args:
105            number : The phone number to use.
106
107        Returns:
108            Dictionary of phone number.
109        """
110        return utils.dbus_optional_value('s', number)
111
112    @utils.glib_call(False)
113    def has_proxy(self):
114        """Checks whether telephony proxy can be acquired."""
115        return bool(self.proxy())
116
117    def proxy(self):
118        """Gets proxy object to telephony interface for method calls."""
119        return self.bus.get(self.TELEPHONY_SERVICE, self.objpath)[self.TELEPHONY_INTERFACE]
120
121    @utils.glib_call(None)
122    def register_telephony_callback(self):
123        """Registers telephony callback for this client if one doesn't already exist.
124
125        Returns:
126            True on success, False on failure, None on DBus error.
127        """
128        if self.callbacks:
129            return True
130
131        # Create and publish callbacks
132        self.callbacks = self.ExportedTelephonyCallbacks()
133        self.callbacks.add_observer('telephony_client', self)
134        objpath = utils.generate_dbus_cb_objpath(self.TELEPHONY_CB_OBJ_NAME, self.hci)
135        self.bus.register_object(objpath, self.callbacks, None)
136
137        # Register published callbacks with manager daemon
138        return self.proxy().RegisterTelephonyCallback(objpath)
139
140    @utils.glib_call(False)
141    def set_network_available(self, network_available):
142        """Sets network availability status.
143
144        Args:
145            network_available: A boolean value indicating whether the device is connected to the cellular network.
146
147        Returns:
148            True on success, False otherwise.
149        """
150        self.proxy().SetNetworkAvailable(network_available)
151        return True
152
153    @utils.glib_call(False)
154    def set_roaming(self, roaming):
155        """Sets roaming mode.
156
157        Args:
158            roaming: A boolean value indicating whether the device is in roaming mode.
159
160        Returns:
161            True on success, False otherwise.
162        """
163        self.proxy().SetRoaming(roaming)
164        return True
165
166    @utils.glib_call(None)
167    def set_signal_strength(self, signal_strength):
168        """Sets signal strength.
169
170        Args:
171            signal_strength: The signal strength value to be set, ranging from 0 to 5.
172
173        Returns:
174            True on success, False on failure, None on DBus error.
175        """
176        return self.proxy().SetSignalStrength(signal_strength)
177
178    @utils.glib_call(None)
179    def set_battery_level(self, battery_level):
180        """Sets battery level.
181
182        Args:
183            battery_level: The battery level value to be set, ranging from 0 to 5.
184
185        Returns:
186            True on success, False on failure, None on DBus error.
187        """
188        return self.proxy().SetBatteryLevel(battery_level)
189
190    @utils.glib_call(False)
191    def set_phone_ops_enabled(self, enable):
192        """Sets phone operations status.
193
194        Args:
195            enable: A boolean value indicating whether phone operations are enabled.
196
197        Returns:
198            True on success, False otherwise.
199        """
200        self.proxy().SetPhoneOpsEnabled(enable)
201        return True
202
203    @utils.glib_call(False)
204    def set_mps_qualification_enabled(self, enable):
205        """Sets MPS qualification status.
206
207        Args:
208            enable: A boolean value indicating whether MPS qualification is enabled.
209
210        Returns:
211            True on success, False otherwise.
212        """
213        self.proxy().SetMpsQualificationEnabled(enable)
214        return True
215
216    @utils.glib_call(None)
217    def incoming_call(self, number):
218        """Initiates an incoming call with the specified phone number.
219
220        Args:
221            number: The phone number of the incoming call.
222
223        Returns:
224            True on success, False on failure, None on DBus error.
225        """
226
227        return self.proxy().IncomingCall(number)
228
229    @utils.glib_call(None)
230    def dialing_call(self, number):
231        """Initiates a dialing call with the specified phone number.
232
233        Args:
234            number: The phone number to dial.
235
236        Returns:
237            True on success, False on failure, None on DBus error.
238        """
239        return self.proxy().DialingCall(number)
240
241    @utils.glib_call(None)
242    def answer_call(self):
243        """Answers an incoming or dialing call.
244
245        Returns:
246            True on success, False on failure, None on DBus error.
247        """
248        return self.proxy().AnswerCall()
249
250    @utils.glib_call(None)
251    def hangup_call(self):
252        """Hangs up an active, incoming, or dialing call.
253
254        Returns:
255            True on success, False on failure, None on DBus error.
256        """
257        return self.proxy().HangupCall()
258
259    @utils.glib_call(None)
260    def set_last_call(self, number=None):
261        """Sets last call with the specified phone number.
262
263        Args:
264            number: Optional phone number value to be set as the last call, Defaults to None if not provided.
265        Returns:
266            True on success, False on failure, None on DBus error.
267        """
268        number = self._make_dbus_phone_number(number)
269        return self.proxy().SetLastCall(number)
270
271    @utils.glib_call(None)
272    def set_memory_call(self, number=None):
273        """Sets memory call with the specified phone number.
274
275        Args:
276            number: Optional phone number value to be set as the last call, Defaults to None if not provided.
277
278        Returns:
279            True on success, False on failure, None on DBus error.
280        """
281        number = self._make_dbus_phone_number(number)
282        return self.proxy().SetMemoryCall(number)
283
284    @utils.glib_call(None)
285    def release_held(self):
286        """Releases all of the held calls.
287
288        Returns:
289            True on success, False on failure, None on DBus error.
290        """
291        return self.proxy().ReleaseHeld()
292
293    @utils.glib_call(None)
294    def release_active_accept_held(self):
295        """Releases the active call and accepts a held call.
296
297        Returns:
298            True on success, False on failure, None on DBus error.
299        """
300        return self.proxy().ReleaseActiveAcceptHeld()
301
302    @utils.glib_call(None)
303    def hold_active_accept_held(self):
304        """Holds the active call and accepts a held call.
305
306        Returns:
307            True on success, False on failure, None on DBus error.
308        """
309        return self.proxy().HoldActiveAcceptHeld()
310
311    @utils.glib_call(None)
312    def audio_connect(self, address):
313        """Initiates an audio connection to the remote device.
314
315        Args:
316            address: The address of the remote device for audio connection.
317
318        Returns:
319            True on success, False on failure, None on DBus error.
320        """
321        return self.proxy().AudioConnect(address)
322
323    @utils.glib_call(False)
324    def audio_disconnect(self, address):
325        """Disconnects the audio connection to the remote device.
326
327        Args:
328            address: The address of the remote device for audio disconnection.
329
330        Returns:
331            True on success, False otherwise.
332        """
333        self.proxy().AudioDisconnect(address)
334        return True
335