1# Copyright 2015 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
6import threading
7
8import dbus
9import dbus.mainloop.glib
10import gobject
11import os
12
13from autotest_lib.client.cros.input_playback import keyboard
14
15DARK_SUSPEND_DELAY_MILLISECONDS = 50000
16
17class DarkResumeListener(object):
18    """Server which listens for dark resume-related DBus signals to count how
19    many dark resumes we have seen since instantiation."""
20
21    SIGNAL_NAME = 'DarkSuspendImminent'
22
23
24    def __init__(self):
25        dbus.mainloop.glib.threads_init()
26        gobject.threads_init()
27
28        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
29        self._bus = dbus.SystemBus()
30        self._count = 0
31        self._stop_resuspend = False
32
33        self._bus.add_signal_receiver(handler_function=self._saw_dark_resume,
34                                      signal_name=self.SIGNAL_NAME)
35
36        def loop_runner():
37            """Handles DBus events on the system bus using the mainloop."""
38            # If we just call run on this loop, the listener will hang and the test
39            # will never finish. Instead, we process events as they come in. This
40            # thread is set to daemon below, which means that the program will exit
41            # when the main thread exits.
42            loop = gobject.MainLoop()
43            context = loop.get_context()
44            while True:
45                context.iteration(True)
46        thread = threading.Thread(None, loop_runner)
47        thread.daemon = True
48        thread.start()
49        logging.debug('Dark resume listener started')
50
51        def register_dark_delay():
52            """Register a new client with powerd to delay dark suspend."""
53            # Powerd resuspends on dark resume once all clients are ready. Once
54            # resuspended the device might not be reachable. Thus this test on
55            # seeing a dark resume injects an input event by creating a virtual
56            # input device so that powerd bails out of resuspend process. But we
57            # need to delay the resuspend long enough for powerd to detect new
58            # input device and read the input event. This needs to run on a
59            # seperate thread as powerd will automatically unregister this
60            # client's suspend delay when it disconnects from D-Bus. Thus this
61            # thread is set to daemon below, which means that the program will
62            # exit when the main thread exits.
63            command = ('/usr/bin/suspend_delay_sample --delay_ms=%d '
64                       '--timeout_ms=%d --dark_suspend_delay' %
65                       (DARK_SUSPEND_DELAY_MILLISECONDS,
66                        DARK_SUSPEND_DELAY_MILLISECONDS))
67            logging.info("Running '%s'", command)
68            os.system(command)
69
70        suspend_delay_thread = threading.Thread(None, register_dark_delay)
71        suspend_delay_thread.daemon = True
72        suspend_delay_thread.start()
73        logging.debug('Dark suspend delay registered')
74
75
76    @property
77    def count(self):
78        """Number of DarkSuspendImminent events this listener has seen since its
79        creation."""
80        return self._count
81
82
83    def _saw_dark_resume(self, unused):
84        self._count += 1
85        if self._stop_resuspend:
86            # Inject input event to stop re-suspend.
87            with keyboard.Keyboard() as keys:
88                    keys.press_key('f4')
89
90
91
92    def stop_resuspend(self, should_stop):
93        """
94        Whether to stop suspend after seeing a dark resume.
95
96        @param should_stop: Whether to stop system from re-suspending.
97        """
98        self._stop_resuspend = should_stop
99
100