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 DisableMachine(state_machine.StateMachine):
13    """
14    DisableMachine handles the state transitions involved in bringing the modem
15    to the DISABLED state.
16
17    """
18    def __init__(self, modem, return_cb, raise_cb):
19        super(DisableMachine, self).__init__(modem)
20        self.return_cb = return_cb
21        self.raise_cb = raise_cb
22
23
24    def _HandleConnectedState(self):
25        logging.info('DisableMachine: Modem is CONNECTED.')
26        assert self._modem.connect_step is None
27        # TODO(armansito): Pass a different raise_cb here to handle
28        # disconnect failure
29        logging.info('DisableMachine: Starting Disconnect.')
30        self._modem.Disconnect(mm1_constants.ROOT_PATH, DisableMachine.Step,
31                               DisableMachine.Step, self)
32        return True
33
34
35    def _HandleConnectingState(self):
36        logging.info('DisableMachine: Modem is CONNECTING.')
37        assert self._modem.connect_step
38        logging.info('DisableMachine: Canceling connect.')
39        self._modem.connect_step.Cancel()
40        return True
41
42
43    def _HandleDisconnectingState(self):
44        logging.info('DisableMachine: Modem is DISCONNECTING.')
45        assert self._modem.disconnect_step
46        logging.info('DisableMachine: Waiting for disconnect.')
47        # wait until disconnect ends
48        return True
49
50
51    def _HandleRegisteredState(self):
52        logging.info('DisableMachine: Modem is REGISTERED.')
53        assert not self._modem.IsPendingRegister()
54        assert not self._modem.IsPendingEnable()
55        assert not self._modem.IsPendingConnect()
56        assert not self._modem.IsPendingDisconnect()
57        self._modem.UnregisterWithNetwork()
58        logging.info('DisableMachine: Setting state to DISABLING.')
59        reason = mm1_constants.MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED
60        self._modem.ChangeState(mm1_constants.MM_MODEM_STATE_DISABLING, reason)
61        return True
62
63
64    def _HandleSearchingState(self):
65        logging.info('DisableMachine: Modem is SEARCHING.')
66        assert self._modem.register_step
67        assert not self._modem.IsPendingEnable()
68        assert not self._modem.IsPendingConnect()
69        logging.info('DisableMachine: Canceling register.')
70        self._modem.register_step.Cancel()
71        return True
72
73
74    def _HandleEnabledState(self):
75        logging.info('DisableMachine: Modem is ENABLED.')
76        assert not self._modem.IsPendingRegister()
77        assert not self._modem.IsPendingEnable()
78        assert not self._modem.IsPendingConnect()
79        logging.info('DisableMachine: Setting state to DISABLING.')
80        reason = mm1_constants.MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED
81        self._modem.ChangeState(mm1_constants.MM_MODEM_STATE_DISABLING, reason)
82        return True
83
84
85    def _HandleDisablingState(self):
86        logging.info('DisableMachine: Modem is DISABLING.')
87        assert not self._modem.IsPendingRegister()
88        assert not self._modem.IsPendingEnable()
89        assert not self._modem.IsPendingConnect()
90        assert not self._modem.IsPendingDisconnect()
91        logging.info('DisableMachine: Setting state to DISABLED.')
92        reason = mm1_constants.MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED
93        self._modem.ChangeState(mm1_constants.MM_MODEM_STATE_DISABLED, reason)
94        self._modem.disable_step = None
95        if self.return_cb:
96            self.return_cb()
97        return False
98
99
100    def _GetModemStateFunctionMap(self):
101        return {
102            mm1_constants.MM_MODEM_STATE_CONNECTED:
103                    DisableMachine._HandleConnectedState,
104            mm1_constants.MM_MODEM_STATE_CONNECTING:
105                    DisableMachine._HandleConnectingState,
106            mm1_constants.MM_MODEM_STATE_DISCONNECTING:
107                    DisableMachine._HandleDisconnectingState,
108            mm1_constants.MM_MODEM_STATE_REGISTERED:
109                    DisableMachine._HandleRegisteredState,
110            mm1_constants.MM_MODEM_STATE_SEARCHING:
111                    DisableMachine._HandleSearchingState,
112            mm1_constants.MM_MODEM_STATE_ENABLED:
113                    DisableMachine._HandleEnabledState,
114            mm1_constants.MM_MODEM_STATE_DISABLING:
115                    DisableMachine._HandleDisablingState
116        }
117
118
119    def _ShouldStartStateMachine(self):
120        if self._modem.disable_step and self._modem.disable_step != self:
121            # There is already a disable operation in progress.
122            message = 'Modem disable already in progress.'
123            logging.info(message)
124            raise pm_errors.MMCoreError(pm_errors.MMCoreError.IN_PROGRESS,
125                                        message)
126        elif self._modem.disable_step is None:
127            # There is no disable operation going in, cancelled or otherwise.
128            state = self._modem.Get(mm1_constants.I_MODEM, 'State')
129            if state == mm1_constants.MM_MODEM_STATE_DISABLED:
130                # The reason we're not raising an error here is that
131                # shill will make multiple successive calls to disable
132                # but WON'T check for raised errors, which causes
133                # problems. Treat this particular case as success.
134                logging.info('Already in a disabled state. Ignoring.')
135                if self.return_cb:
136                    self.return_cb()
137                return False
138
139            invalid_states = [
140                mm1_constants.MM_MODEM_STATE_FAILED,
141                mm1_constants.MM_MODEM_STATE_UNKNOWN,
142                mm1_constants.MM_MODEM_STATE_INITIALIZING,
143                mm1_constants.MM_MODEM_STATE_LOCKED
144            ]
145            if state in invalid_states:
146                raise pm_errors.MMCoreError(
147                        pm_errors.MMCoreError.WRONG_STATE,
148                        ('Modem disable cannot be initiated while in state'
149                         ' %u.') % state)
150            if self._modem.connect_step:
151                logging.info('There is an ongoing Connect, canceling it.')
152                self._modem.connect_step.Cancel()
153            if self._modem.register_step:
154                logging.info('There is an ongoing Register, canceling it.')
155                self._modem.register_step.Cancel()
156            if self._modem.enable_step:
157                # This needs to be done here, because the case where an enable
158                # cycle has been initiated but it hasn't triggered any state
159                # transitions yet would not be detected in a state handler.
160                logging.info('There is an ongoing Enable, canceling it.')
161                logging.info('This should bring the modem to a disabled state.'
162                             ' DisableMachine will not start.')
163                self._modem.enable_step.Cancel()
164                assert self._modem.Get(mm1_constants.I_MODEM, 'State') == \
165                    mm1_constants.MM_MODEM_STATE_DISABLED
166            if self._modem.Get(mm1_constants.I_MODEM, 'State') == \
167                    mm1_constants.MM_MODEM_STATE_DISABLED:
168                if self.return_cb:
169                    self.return_cb()
170                return False
171
172            logging.info('Starting Disable.')
173            self._modem.disable_step = self
174        return True
175