1# Copyright (c) 2013 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
6
7from autotest_lib.client.bin import utils
8from autotest_lib.client.cros import constants
9
10def connect(bus_loop):
11    """Create and return a DBus connection to session_manager.
12
13    Connects to the session manager over the DBus system bus.  Returns
14    appropriately configured DBus interface object.
15
16    @param bus_loop: An externally-owned DBusGMainLoop.
17
18    @return a dbus.Interface object connection to the session_manager.
19    """
20    bus = dbus.SystemBus(mainloop=bus_loop)
21    proxy = bus.get_object('org.chromium.SessionManager',
22                           '/org/chromium/SessionManager')
23    return dbus.Interface(proxy, 'org.chromium.SessionManagerInterface')
24
25
26class SignalListener(object):
27    """A class to listen for DBus signals from the session manager.
28
29    The session_manager emits several DBus signals when different events
30    of interest occur. This class provides a framework for derived classes
31    to use to listen for certain signals.
32    """
33
34    def __init__(self, g_main_loop):
35        """Constructor
36
37        @param g_mail_loop: glib main loop object.
38        """
39        self._main_loop = g_main_loop
40
41
42    def wait_for_signals(self, desc,
43                         timeout=constants.DEFAULT_OWNERSHIP_TIMEOUT):
44        """Block for |timeout| seconds waiting for the signals to come in.
45
46        @param desc: string describing the high-level reason you're waiting
47                     for the signals.
48        @param timeout: maximum seconds to wait for the signals.
49
50        @raises TimeoutError if the timeout is hit.
51        """
52        utils.poll_for_condition(
53            condition=lambda: self.__received_signals(),
54            desc=desc,
55            timeout=timeout)
56        self.reset_signal_state()
57
58
59    def __received_signals(self):
60        """Run main loop until all pending events are done, checks for signals.
61
62        Runs self._main_loop until it says it has no more events pending,
63        then returns the state of the internal variables tracking whether
64        desired signals have been received.
65
66        @return True if both signals have been handled, False otherwise.
67        """
68        self.__flush()
69        return self.all_signals_received()
70
71
72    def __flush(self):
73        """Runs the main loop until pending events are done."""
74        context = self._main_loop.get_context()
75        while context.iteration(False):
76            pass
77
78
79    def reset(self):
80        """Prepares the listener to receive a new signal.
81
82        This resets the signal state and flushes any pending signals in order to
83        avoid picking up stale signals still lingering in the process' input
84        queues.
85        """
86        self.__flush()
87        self.reset_signal_state()
88
89
90    def reset_signal_state(self):
91        """Resets internal signal tracking state."""
92        raise NotImplementedError()
93
94
95    def all_signals_received(self):
96        """Resets internal signal tracking state."""
97        raise NotImplementedError()
98
99
100    def listen_to_signal(self, callback, signal):
101        """Connect a callback to a given session_manager dbus signal.
102
103        Sets up a signal receiver for signal, and calls the provided callback
104        when it comes in.
105
106        @param callback: a callable to call when signal is received.
107        @param signal: the signal to listen for.
108        """
109        bus = dbus.SystemBus()
110        bus.add_signal_receiver(
111            handler_function=callback,
112            signal_name=signal,
113            dbus_interface='org.chromium.SessionManagerInterface',
114            bus_name=None,
115            path='/org/chromium/SessionManager')
116
117
118
119class OwnershipSignalListener(SignalListener):
120    """A class to listen for ownership-related DBus signals.
121
122    The session_manager emits a couple of DBus signals when certain events
123    related to device ownership occur.  This class provides a way to
124    listen for them and check on their status.
125    """
126
127    def __init__(self, g_main_loop):
128        """Constructor
129
130        @param g_mail_loop: glib main loop object.
131        """
132        super(OwnershipSignalListener, self).__init__(g_main_loop)
133        self._listen_for_new_key = False
134        self._got_new_key = False
135        self._listen_for_new_policy = False
136        self._got_new_policy = False
137
138
139    def listen_for_new_key_and_policy(self):
140        """Set to listen for signals indicating new owner key and device policy.
141        """
142        self._listen_for_new_key = self._listen_for_new_policy = True
143        self.listen_to_signal(self.__handle_new_key, 'SetOwnerKeyComplete')
144        self.listen_to_signal(self.__handle_new_policy,
145                              'PropertyChangeComplete')
146        self.reset()
147
148
149    def listen_for_new_policy(self):
150        """Set to listen for signal indicating new device policy.
151        """
152        self._listen_for_new_key = False
153        self._listen_for_new_policy = True
154        self.listen_to_signal(self.__handle_new_policy,
155                              'PropertyChangeComplete')
156        self.reset()
157
158
159    def reset_signal_state(self):
160        """Resets internal signal tracking state."""
161        self._got_new_key = not self._listen_for_new_key
162        self._got_new_policy = not self._listen_for_new_policy
163
164
165    def all_signals_received(self):
166        """Returns true when expected signals are all receieved."""
167        return self._got_new_key and self._got_new_policy
168
169
170    def __handle_new_key(self, success):
171        """Callback to be used when a new key signal is received.
172
173        @param success: the string 'success' if the key was generated.
174        """
175        self._got_new_key = (success == 'success')
176
177
178    def __handle_new_policy(self, success):
179        """Callback to be used when a new policy signal is received.
180
181        @param success: the string 'success' if the policy was stored.
182        """
183        self._got_new_policy = (success == 'success')
184
185
186
187class SessionSignalListener(SignalListener):
188    """A class to listen for SessionStateChanged DBus signals.
189
190    The session_manager emits a DBus signal whenever a user signs in, when
191    the user session begins termination, and when the session is terminated.
192    This class allows this signal to be polled for
193    """
194
195    def __init__(self, g_main_loop):
196        """Constructor
197
198        @param g_mail_loop: glib main loop object.
199        """
200        super(SessionSignalListener, self).__init__(g_main_loop)
201        self._new_state = None
202        self._expected_state = None
203
204
205    def listen_for_session_state_change(self, expected):
206        """Set to listen for state changed signal with payload == |expected|.
207
208        @param expected: string representing the state transition we expect.
209                         One of 'started', 'stopping', or 'stopped'.
210        """
211        if expected not in {'started', 'stopping', 'stopped'}:
212            raise ValueError("expected must be one of 'started', 'stopping'," +
213                             " or 'stopped'.")
214        self.listen_to_signal(self.__handle_signal, 'SessionStateChanged')
215        self._expected_state = expected
216
217
218    def reset_signal_state(self):
219        """Resets internal signal tracking state."""
220        self._new_state = None
221
222
223    def all_signals_received(self):
224        """Returns true when expected signals are all receieved."""
225        return self._new_state == self._expected_state
226
227
228    def __handle_signal(self, state):
229        """Callback to be used when a new state-change signal is received.
230
231        @param state: the state transition being signaled.
232        """
233        self._new_state = state
234
235
236
237class ScreenIsLockedSignalListener(SignalListener):
238    """A class to listen for ScreenIsLocked DBus signal.
239
240    The session_manager emits a DBus signal when screen lock operation is
241    completed.
242    """
243
244    def __init__(self, g_main_loop):
245        """Constructor
246
247        @param g_main_loop: glib main loop object.
248        """
249        super(ScreenIsLockedSignalListener, self).__init__(g_main_loop)
250        self._screen_is_locked_received = False
251        self.listen_to_signal(self.__handle_signal, 'ScreenIsLocked')
252
253
254    def reset_signal_state(self):
255        """Resets internal signal tracking state."""
256        self._screen_is_locked_received = False
257
258
259    def all_signals_received(self):
260        """Returns true when expected signals are all receieved."""
261        return self._screen_is_locked_received
262
263
264    def __handle_signal(self):
265        """Callback to be used when ScreenIsLocked signal is received.
266        """
267        self._screen_is_locked_received = True
268