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 logging
6
7import pm_errors
8import state_machine
9
10from autotest_lib.client.cros.cellular import mm1_constants
11
12class DisconnectMachine(state_machine.StateMachine):
13    """
14    DisconnectMachine handles the state transitions involved in bringing the
15    modem to the DISCONNECTED state.
16
17    """
18    def __init__(self, modem, bearer_path, return_cb, raise_cb,
19        return_cb_args=[]):
20        super(DisconnectMachine, self).__init__(modem)
21        self.bearer_path = bearer_path
22        self.return_cb = return_cb
23        self.raise_cb = raise_cb
24        self.return_cb_args = return_cb_args
25
26
27    def _HandleConnectedState(self):
28        logging.info('DisconnectMachine: Modem state is CONNECTED.')
29        logging.info('DisconnectMachine: Setting state to DISCONNECTING.')
30        reason = mm1_constants.MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED
31        self._modem.ChangeState(mm1_constants.MM_MODEM_STATE_DISCONNECTING,
32                                reason)
33        return True
34
35
36    def _HandleDisconnectingState(self):
37        logging.info('DisconnectMachine: Modem state is DISCONNECTING.')
38        assert not self._modem.IsPendingConnect()
39        assert not self._modem.IsPendingEnable()
40        assert not self._modem.IsPendingRegister()
41
42        dc_reason = mm1_constants.MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED
43        try:
44            if self.bearer_path == mm1_constants.ROOT_PATH:
45                for bearer in self._modem.active_bearers.keys():
46                    self._modem.DeactivateBearer(bearer)
47            else:
48                self._modem.DeactivateBearer(self.bearer_path)
49        except pm_errors.MMError as e:
50            logging.error('DisconnectMachine: Failed to disconnect: ' + str(e))
51            dc_reason = mm1_constants.MM_MODEM_STATE_CHANGE_REASON_UNKNOWN
52            self.raise_cb(e)
53        finally:
54            # TODO(armansito): What should happen in a disconnect
55            # failure? Should we stay connected or become REGISTERED?
56            logging.info('DisconnectMachine: Setting state to REGISTERED.')
57            self._modem.ChangeState(mm1_constants.MM_MODEM_STATE_REGISTERED,
58                dc_reason)
59            self._modem.disconnect_step = None
60            logging.info('DisconnectMachine: Calling return callback.')
61            self.return_cb(*self.return_cb_args)
62            return False
63
64
65    def _GetModemStateFunctionMap(self):
66        return {
67            mm1_constants.MM_MODEM_STATE_CONNECTED:
68                    DisconnectMachine._HandleConnectedState,
69            mm1_constants.MM_MODEM_STATE_DISCONNECTING:
70                    DisconnectMachine._HandleDisconnectingState
71        }
72
73
74    def _ShouldStartStateMachine(self):
75        if (self._modem.disconnect_step and
76            # There is already a disconnect operation in progress.
77            self._modem.disconnect_step != self):
78            message = 'There is already an ongoing disconnect operation.'
79            logging.error(message)
80            self.raise_cb(
81                pm_errors.MMCoreError(pm_errors.MMCoreError.IN_PROGRESS,
82                                      message))
83            return False
84        elif self._modem.disconnect_step is None:
85            # There is no disconnect operation going on, canceled or otherwise.
86            state = self._modem.Get(mm1_constants.I_MODEM, 'State')
87            if state != mm1_constants.MM_MODEM_STATE_CONNECTED:
88                message = 'Modem cannot be disconnected when not connected.'
89                logging.error(message)
90                self.raise_cb(
91                    pm_errors.MMCoreError(pm_errors.MMCoreError.WRONG_STATE,
92                                          message))
93                return False
94
95            if self.bearer_path == mm1_constants.ROOT_PATH:
96                logging.info('All bearers will be disconnected.')
97            elif not (self.bearer_path in self._modem.bearers):
98                message = ('Bearer with path "%s" not found' %
99                           self.bearer_path)
100                logging.error(message)
101                self.raise_cb(
102                    pm_errors.MMCoreError(pm_errors.MMCoreError.NOT_FOUND,
103                                          message))
104                return False
105            elif not (self.bearer_path in self._modem.active_bearers):
106                message = ('No active bearer with path ' +
107                    self.bearer_path +
108                    ' found, current active bearers are ' +
109                    str(self._modem.active_bearers))
110                logging.error(message)
111                self.raise_cb(pm_errors.MMCoreError(
112                        pm_errors.MMCoreError.NOT_FOUND, message))
113                return False
114
115            assert not self._modem.IsPendingConnect()
116            assert not self._modem.IsPendingEnable()
117            assert not self._modem.IsPendingRegister()
118
119            logging.info('Starting Disconnect.')
120            self._modem.disconnect_step = self
121        return True
122