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