xref: /aosp_15_r20/external/autotest/client/cros/cellular/pseudomodem/modem_3gpp.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1# Lint as: python2, python3
2# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import dbus
7import dbus.types
8import logging
9
10from autotest_lib.client.cros.cellular.pseudomodem import modem
11from autotest_lib.client.cros.cellular.pseudomodem import pm_constants
12from autotest_lib.client.cros.cellular.pseudomodem import pm_errors
13from autotest_lib.client.cros.cellular.pseudomodem import utils
14
15from autotest_lib.client.cros.cellular import mm1_constants
16
17def SubscriptionStateToPco(state):
18    """
19    Takes an old SubscriptionState enum and returns a Pco that will be
20    interpreted as that subscription state.
21
22    @param state: see mm1_constants.MM_MODEM_3GPP_SUBSCRIPTION_STATE_*
23    """
24
25    pco_data = '\x27\x08\x00\xFF\x00\x04\x13\x01\x84'
26    if state == mm1_constants.MM_MODEM_3GPP_SUBSCRIPTION_STATE_UNKNOWN:
27        pco_data += '\xFF'
28    elif state == mm1_constants.MM_MODEM_3GPP_SUBSCRIPTION_STATE_UNPROVISIONED:
29        pco_data += '\x05'
30    elif state == mm1_constants.MM_MODEM_3GPP_SUBSCRIPTION_STATE_PROVISIONED:
31        pco_data += '\x00'
32    elif state == mm1_constants.MM_MODEM_3GPP_SUBSCRIPTION_STATE_OUT_OF_DATA:
33        pco_data += '\x03'
34
35    return dbus.types.Struct(
36            [dbus.types.UInt32(1),
37             dbus.types.Boolean(True),
38             dbus.types.ByteArray(pco_data)],
39            signature='ubay')
40
41
42class Modem3gpp(modem.Modem):
43    """
44    Pseudomodem implementation of the
45    org.freedesktop.ModemManager1.Modem.Modem3gpp and
46    org.freedesktop.ModemManager1.Modem.Simple interfaces. This class provides
47    access to specific actions that may be performed in modems with 3GPP
48    capabilities.
49
50    """
51
52    IMEI = '00112342342123'
53
54    class GsmNetwork(object):
55        """
56        GsmNetwork stores the properties of a 3GPP network that can be
57        discovered during a network scan.
58
59        """
60        def __init__(self,
61                     operator_long,
62                     operator_short,
63                     operator_code,
64                     status,
65                     access_technology):
66            self.status = status
67            self.operator_long = operator_long
68            self.operator_short = operator_short
69            self.operator_code = operator_code
70            self.access_technology = access_technology
71
72
73        def ToScanDictionary(self):
74            """
75            @returns: Dictionary containing operator data as defined by
76                    org.freedesktop.ModemManager1.Modem.Modem3gpp.Scan.
77
78            """
79            return {
80              'status': dbus.types.UInt32(self.status),
81              'operator-long': self.operator_long,
82              'operator-short': self.operator_short,
83              'operator-code': self.operator_code,
84              'access-technology': dbus.types.UInt32(self.access_technology),
85            }
86
87
88    def __init__(self,
89                 state_machine_factory=None,
90                 bus=None,
91                 device='pseudomodem0',
92                 index=0,
93                 roaming_networks=None,
94                 config=None):
95        modem.Modem.__init__(self,
96                             state_machine_factory,
97                             bus=bus,
98                             device=device,
99                             roaming_networks=roaming_networks,
100                             config=config)
101
102        self._scanned_networks = {}
103        self._cached_pco = dbus.types.Array([], "(ubay)")
104
105
106    def _InitializeProperties(self):
107        ip = modem.Modem._InitializeProperties(self)
108        props = ip[mm1_constants.I_MODEM]
109        props3gpp = self._GetDefault3GPPProperties()
110        if props3gpp:
111            ip[mm1_constants.I_MODEM_3GPP] = props3gpp
112        props['SupportedCapabilities'] = [
113                dbus.types.UInt32(mm1_constants.MM_MODEM_CAPABILITY_GSM_UMTS),
114                dbus.types.UInt32(mm1_constants.MM_MODEM_CAPABILITY_LTE),
115                dbus.types.UInt32(
116                        mm1_constants.MM_MODEM_CAPABILITY_GSM_UMTS |
117                        mm1_constants.MM_MODEM_CAPABILITY_LTE)
118        ]
119        props['CurrentCapabilities'] = dbus.types.UInt32(
120                mm1_constants.MM_MODEM_CAPABILITY_GSM_UMTS |
121                mm1_constants.MM_MODEM_CAPABILITY_LTE)
122        props['MaxBearers'] = dbus.types.UInt32(3)
123        props['MaxActiveBearers'] = dbus.types.UInt32(2)
124        props['EquipmentIdentifier'] = self.IMEI
125        props['AccessTechnologies'] = dbus.types.UInt32((
126                mm1_constants.MM_MODEM_ACCESS_TECHNOLOGY_GSM |
127                mm1_constants.MM_MODEM_ACCESS_TECHNOLOGY_UMTS))
128        props['SupportedModes'] = [
129                dbus.types.Struct(
130                        [dbus.types.UInt32(mm1_constants.MM_MODEM_MODE_3G |
131                                           mm1_constants.MM_MODEM_MODE_4G),
132                         dbus.types.UInt32(mm1_constants.MM_MODEM_MODE_4G)],
133                        signature='uu')
134        ]
135        props['CurrentModes'] = props['SupportedModes'][0]
136        props['SupportedBands'] = [
137            dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_EGSM),
138            dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_DCS),
139            dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_PCS),
140            dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_G850),
141            dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_U2100),
142            dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_U1800),
143            dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_U17IV),
144            dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_U800),
145            dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_U850)
146        ]
147        props['CurrentBands'] = [
148            dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_EGSM),
149            dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_DCS),
150            dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_PCS),
151            dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_G850),
152            dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_U2100),
153            dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_U800),
154            dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_U850)
155        ]
156        return ip
157
158
159    def _GetDefault3GPPProperties(self):
160        if not self.sim or self.sim.locked:
161            return None
162        return {
163            'Imei' : self.IMEI,
164            'RegistrationState' : (
165                    dbus.types.UInt32(
166                        mm1_constants.MM_MODEM_3GPP_REGISTRATION_STATE_IDLE)),
167            'OperatorCode' : '',
168            'OperatorName' : '',
169            'EnabledFacilityLocks' : (
170                    dbus.types.UInt32(self.sim.enabled_locks)),
171            'Pco': dbus.types.Array([], "(ubay)"),
172        }
173
174
175    def SyncScan(self):
176        """ The synchronous implementation of |Scan| for this class. """
177        state = self.Get(mm1_constants.I_MODEM, 'State')
178        if state < mm1_constants.MM_MODEM_STATE_ENABLED:
179            raise pm_errors.MMCoreError(
180                    pm_errors.MMCoreError.WRONG_STATE,
181                    'Modem not enabled, cannot scan for networks.')
182
183        sim_path = self.Get(mm1_constants.I_MODEM, 'Sim')
184        if not self.sim:
185            assert sim_path == mm1_constants.ROOT_PATH
186            raise pm_errors.MMMobileEquipmentError(
187                pm_errors.MMMobileEquipmentError.SIM_NOT_INSERTED,
188                'Cannot scan for networks because no SIM is inserted.')
189        assert sim_path != mm1_constants.ROOT_PATH
190
191        # TODO(armansito): check here for SIM lock?
192
193        scanned = [network.ToScanDictionary()
194                   for network in self.roaming_networks]
195
196        # get home network
197        sim_props = self.sim.GetAll(mm1_constants.I_SIM)
198        scanned.append({
199            'status': dbus.types.UInt32(
200                    mm1_constants.MM_MODEM_3GPP_NETWORK_AVAILABILITY_AVAILABLE),
201            'operator-long': sim_props['OperatorName'],
202            'operator-short': sim_props['OperatorName'],
203            'operator-code': sim_props['OperatorIdentifier'],
204            'access-technology': dbus.types.UInt32(self.sim.access_technology)
205        })
206
207        self._scanned_networks = (
208                {network['operator-code']: network for network in scanned})
209        return scanned
210
211
212    def AssignPco(self, pco):
213        """
214        Stores the given value so that it is shown as the value of Pco when
215        the modem is in a registered state.
216
217        Always prefer this method over calling "Set" directly if the PCO value
218        should be cached.
219
220        Note: See testing.Testing.UpdatePco, which allows calling this method
221        over D-Bus.
222
223        @param pco_value: D-Bus struct containing the PCO value to remember.
224
225        """
226        self._cached_pco = pco
227        self.UpdatePco()
228
229
230    def UpdatePco(self):
231        """
232        Updates the current PCO value based on the registration state.
233
234        """
235        if not mm1_constants.I_MODEM_3GPP in self._properties:
236            return
237        state = self.Get(mm1_constants.I_MODEM_3GPP, 'RegistrationState')
238        if (state == mm1_constants.MM_MODEM_3GPP_REGISTRATION_STATE_HOME or
239            state == mm1_constants.MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING):
240            new_pco_value = self._cached_pco
241        else:
242            new_pco_value = dbus.types.Array([], "(ubay)")
243        self.Set(mm1_constants.I_MODEM_3GPP, 'Pco', new_pco_value)
244
245
246    def AssignSubscriptionState(self,
247                                registered_subscription_state):
248        """
249        Caches the given subscription states and updates the actual
250        |SubscriptionState| property depending on the |RegistrationState|.
251
252        @param unregistered_subscription_state: This subscription state is
253                returned when the modem is not registered on a network.
254        @param registered_subscription_state: This subscription state is
255                returned when the modem is registered on a network.
256
257        """
258        new_pco = SubscriptionStateToPco(registered_subscription_state)
259        self.AssignPco([new_pco])
260        self.UpdatePco()
261
262
263    def UpdateLockStatus(self):
264        """
265        Overloads superclass implementation. Also updates
266        'EnabledFacilityLocks' if 3GPP properties are exposed.
267
268        """
269        modem.Modem.UpdateLockStatus(self)
270        if mm1_constants.I_MODEM_3GPP in self._properties:
271            self.SetUInt32(mm1_constants.I_MODEM_3GPP,
272                     'EnabledFacilityLocks',
273                     self.sim.enabled_locks)
274
275
276    def SetSIM(self, sim):
277        """
278        Overrides modem.Modem.SetSIM. Once the SIM has been assigned, attempts
279        to expose 3GPP properties if SIM readable.
280
281        @param sim: An instance of sim.SIM
282        Emits:
283            PropertiesChanged
284
285        """
286        modem.Modem.SetSIM(self, sim)
287        self.Expose3GPPProperties()
288
289
290    def Expose3GPPProperties(self):
291        """
292        A call to this method will attempt to expose 3GPP properties if there
293        is a current SIM and is unlocked.
294
295        """
296        props = self._GetDefault3GPPProperties()
297        if props:
298            self.SetAll(mm1_constants.I_MODEM_3GPP, props)
299
300
301    def SetRegistrationState(self, state):
302        """
303        Sets the 'RegistrationState' property.
304
305        @param state: An MMModem3gppRegistrationState value.
306        Emits:
307            PropertiesChanged
308
309        """
310        self.SetUInt32(mm1_constants.I_MODEM_3GPP, 'RegistrationState', state)
311        self.UpdatePco()
312
313
314    @property
315    def scanned_networks(self):
316        """
317        @returns: Dictionary containing the result of the most recent network
318                scan, where the keys are the operator code.
319
320        """
321        return self._scanned_networks
322
323
324    @utils.log_dbus_method(return_cb_arg='return_cb', raise_cb_arg='raise_cb')
325    @dbus.service.method(mm1_constants.I_MODEM_3GPP, in_signature='s',
326                         async_callbacks=('return_cb', 'raise_cb'))
327    def Register(self, operator_id, return_cb=None, raise_cb=None):
328        """
329        Request registration with a given modem network.
330
331        @param operator_id: The operator ID to register. An empty string can be
332                used to register to the home network.
333        @param return_cb: Async success callback.
334        @param raise_cb: Async error callback.
335
336        """
337        logging.info('Modem3gpp.Register: %s', operator_id)
338
339        # Check if we're already registered with the given network.
340        if (self.Get(mm1_constants.I_MODEM_3GPP, 'OperatorCode') ==
341            operator_id or
342            ((not operator_id and self.Get(mm1_constants.I_MODEM, 'State') >=
343                    mm1_constants.MM_MODEM_STATE_REGISTERED))):
344            message = 'Already registered.'
345            logging.info(message)
346            raise pm_errors.MMCoreError(pm_errors.MMCoreError.FAILED, message)
347
348        if (self.Get(mm1_constants.I_MODEM, 'State') <
349            mm1_constants.MM_MODEM_STATE_ENABLED):
350            message = 'Cannot register the modem if not enabled.'
351            logging.info(message)
352            raise pm_errors.MMCoreError(pm_errors.MMCoreError.FAILED, message)
353
354        self.CancelAllStateMachines()
355
356        def _Reregister():
357            if (self.Get(mm1_constants.I_MODEM, 'State') ==
358                mm1_constants.MM_MODEM_STATE_REGISTERED):
359                self.UnregisterWithNetwork()
360            self.RegisterWithNetwork(operator_id, return_cb, raise_cb)
361
362        if (self.Get(mm1_constants.I_MODEM, 'State') ==
363            mm1_constants.MM_MODEM_STATE_CONNECTED):
364            self.Disconnect(mm1_constants.ROOT_PATH, _Reregister, raise_cb)
365        else:
366            _Reregister()
367
368
369    def SetRegistered(self, operator_code, operator_name):
370        """
371        Sets the modem to be registered with the give network. Sets the Modem
372        and Modem3gpp registration states.
373
374        @param operator_code: The operator code that should be displayed by
375                the modem.
376        @param operator_name: The operator name that should be displayed by
377                the modem.
378
379        """
380        if operator_code:
381            assert self.sim
382            assert (self.Get(mm1_constants.I_MODEM, 'Sim') !=
383                    mm1_constants.ROOT_PATH)
384            if (operator_code ==
385                self.sim.Get(mm1_constants.I_SIM, 'OperatorIdentifier')):
386                state = mm1_constants.MM_MODEM_3GPP_REGISTRATION_STATE_HOME
387            else:
388                state = mm1_constants.MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING
389        else:
390            state = mm1_constants.MM_MODEM_3GPP_REGISTRATION_STATE_HOME
391
392        logging.info('Modem3gpp.Register: Setting registration state to %s.',
393            mm1_constants.RegistrationStateToString(state))
394        self.SetRegistrationState(state)
395        logging.info('Modem3gpp.Register: Setting state to REGISTERED.')
396        self.ChangeState(mm1_constants.MM_MODEM_STATE_REGISTERED,
397            mm1_constants.MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED)
398        self.Set(mm1_constants.I_MODEM_3GPP, 'OperatorCode', operator_code)
399        self.Set(mm1_constants.I_MODEM_3GPP, 'OperatorName', operator_name)
400
401
402    @utils.log_dbus_method(return_cb_arg='return_cb', raise_cb_arg='raise_cb')
403    @dbus.service.method(mm1_constants.I_MODEM_3GPP, out_signature='aa{sv}',
404                         async_callbacks=('return_cb', 'raise_cb'))
405    def Scan(self, return_cb, raise_cb):
406        """
407        Scan for available networks.
408
409        @param return_cb: This function is called with the result.
410        @param raise_cb: This function may be called with error.
411        @returns: An array of dictionaries with each array element describing a
412                mobile network found in the scan. See the ModemManager reference
413                manual for the list of keys that may be included in the returned
414                dictionary.
415
416        """
417        scan_result = self.SyncScan()
418        return_cb(scan_result)
419
420
421    def RegisterWithNetwork(
422            self, operator_id="", return_cb=None, raise_cb=None):
423        """
424        Overridden from superclass.
425
426        @param operator_id: See superclass documentation.
427        @param return_cb: See superclass documentation.
428        @param raise_cb: See superclass documentation.
429
430        """
431        machine = self._state_machine_factory.CreateMachine(
432                pm_constants.STATE_MACHINE_REGISTER,
433                self,
434                operator_id,
435                return_cb,
436                raise_cb)
437        machine.Start()
438
439
440    def UnregisterWithNetwork(self):
441        """
442        Overridden from superclass.
443
444        """
445        logging.info('Modem3gpp.UnregisterWithHomeNetwork')
446        logging.info('Setting registration state to IDLE.')
447        self.SetRegistrationState(
448                mm1_constants.MM_MODEM_3GPP_REGISTRATION_STATE_IDLE)
449        logging.info('Setting state to ENABLED.')
450        self.ChangeState(mm1_constants.MM_MODEM_STATE_ENABLED,
451            mm1_constants.MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED)
452        self.Set(mm1_constants.I_MODEM_3GPP, 'OperatorName', '')
453        self.Set(mm1_constants.I_MODEM_3GPP, 'OperatorCode', '')
454
455
456    # Inherited from modem_simple.ModemSimple.
457    @utils.log_dbus_method(return_cb_arg='return_cb', raise_cb_arg='raise_cb')
458    def Connect(self, properties, return_cb, raise_cb):
459        """
460        Overriden from superclass.
461
462        @param properties
463        @param return_cb
464        @param raise_cb
465
466        """
467        logging.info('Connect')
468        machine = self._state_machine_factory.CreateMachine(
469                pm_constants.STATE_MACHINE_CONNECT,
470                self,
471                properties,
472                return_cb,
473                raise_cb)
474        machine.Start()
475
476
477    # Inherited from modem_simple.ModemSimple.
478    @utils.log_dbus_method(return_cb_arg='return_cb', raise_cb_arg='raise_cb')
479    def Disconnect(self, bearer_path, return_cb, raise_cb, *return_cb_args):
480        """
481        Overriden from superclass.
482
483        @param bearer_path
484        @param return_cb
485        @param raise_cb
486        @param return_cb_args
487
488        """
489        logging.info('Disconnect: %s', bearer_path)
490        machine = self._state_machine_factory.CreateMachine(
491                pm_constants.STATE_MACHINE_DISCONNECT,
492                self,
493                bearer_path,
494                return_cb,
495                raise_cb,
496                return_cb_args)
497        machine.Start()
498
499
500    # Inherited from modem_simple.ModemSimple.
501    @utils.log_dbus_method()
502    def GetStatus(self):
503        """
504        Overriden from superclass.
505
506        """
507        modem_props = self.GetAll(mm1_constants.I_MODEM)
508        m3gpp_props = self.GetAll(mm1_constants.I_MODEM_3GPP)
509        retval = {}
510        retval['state'] = modem_props['State']
511        if retval['state'] >= mm1_constants.MM_MODEM_STATE_REGISTERED:
512            retval['signal-quality'] = modem_props['SignalQuality'][0]
513            retval['bands'] = modem_props['CurrentBands']
514            retval['access-technology'] = self.sim.access_technology
515            retval['m3gpp-registration-state'] = \
516                m3gpp_props['RegistrationState']
517            retval['m3gpp-operator-code'] = m3gpp_props['OperatorCode']
518            retval['m3gpp-operator-name'] = m3gpp_props['OperatorName']
519        return retval
520    # TODO(armansito): implement
521    # org.freedesktop.ModemManager1.Modem.Modem3gpp.Ussd, if needed
522    # (in a separate class?)
523