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