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