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
6import os
7import subprocess
8import tempfile
9import threading
10import time
11
12from autotest_lib.client.bin import test, utils
13from autotest_lib.client.common_lib import error
14from autotest_lib.client.common_lib.cros import chrome
15from autotest_lib.client.cros import rtc
16from autotest_lib.client.cros.audio import audio_helper
17from autotest_lib.client.cros.power import power_utils
18
19class power_AudioDetector(test.test):
20    """Verifies that audio playback prevents powerd from suspending."""
21    version = 1
22
23    def initialize(self):
24        self._pref_change = None
25
26
27    def run_once(self, run_time_sec=60):
28        if run_time_sec < 10:
29            raise error.TestFail('Must run for at least 10 seconds')
30
31        with chrome.Chrome():
32            # Audio loop time should be significantly shorter than
33            # |run_time_sec| time, so that the total playback time doesn't
34            # exceed it by much.
35            audio_loop_time_sec = min(10, run_time_sec / 10 + 0.5)
36
37            # Set a low audio volume to avoid annoying people during tests.
38            audio_helper.set_volume_levels(10, 100)
39
40            # Start a subprocess that uses dbus-monitor to listen for suspend
41            # announcements from powerd and writes the output to a log.
42            dbus_log_fd, dbus_log_name = tempfile.mkstemp()
43            os.unlink(dbus_log_name)
44            dbus_log = os.fdopen(dbus_log_fd)
45            dbus_proc = subprocess.Popen(
46                'dbus-monitor --monitor --system ' +
47                '"type=\'signal\',interface=\'org.chromium.PowerManager\',' +
48                'member=\'SuspendImminent\'"', shell=True, stdout=dbus_log)
49
50            # Start playing audio file.
51            self._enable_audio_playback = True
52            thread = threading.Thread(target=self._play_audio,
53                                      args=(audio_loop_time_sec,))
54            thread.start()
55
56            # Restart powerd with timeouts for quick idle events.
57            gap_ms = run_time_sec * 1000 / 4
58            dim_ms = min(10000, gap_ms)
59            off_ms = min(20000, gap_ms * 2)
60            suspend_ms = min(30000, gap_ms * 3)
61            prefs = { 'disable_idle_suspend'   : 0,
62                      'ignore_external_policy' : 1,
63                      'plugged_dim_ms'         : dim_ms,
64                      'plugged_off_ms'         : off_ms,
65                      'plugged_suspend_ms'     : suspend_ms,
66                      'unplugged_dim_ms'       : dim_ms,
67                      'unplugged_off_ms'       : off_ms,
68                      'unplugged_suspend_ms'   : suspend_ms }
69            self._pref_change = power_utils.PowerPrefChanger(prefs)
70
71            # Set an alarm to wake up the system in case the audio detector
72            # fails and the system suspends.
73            alarm_time = rtc.get_seconds() + run_time_sec
74            rtc.set_wake_alarm(alarm_time)
75
76            time.sleep(run_time_sec)
77
78            # Stop powerd to avoid suspending when the audio stops.
79            utils.system_output('stop powerd')
80
81            # Stop audio and wait for the audio thread to terminate.
82            self._enable_audio_playback = False
83            thread.join(timeout=(audio_loop_time_sec * 2))
84            if thread.is_alive():
85                logging.error('Audio thread did not terminate at end of test.')
86
87            # Check the D-Bus log to make sure that no suspend took place.
88            # dbus-monitor logs messages about its initial connection to the bus
89            # in addition to the signals that we asked it for, so look for the
90            # signal name in its output.
91            dbus_proc.kill()
92            dbus_log.seek(0)
93            if 'SuspendImminent' in dbus_log.read():
94                err_str = 'System suspended while audio was playing.'
95                raise error.TestFail(err_str)
96
97
98    def cleanup(self):
99        # Restore powerd prefs.
100        del self._pref_change
101
102
103    def _play_audio(self, loop_time):
104        """
105        Repeatedly plays audio until self._audio_playback_enabled == False.
106        """
107        # TODO(crosbug.com/33988): Allow for pauses in audio playback to
108        # simulate delays in loading the next song.
109        while self._enable_audio_playback:
110            audio_helper.play_sound(duration_seconds=loop_time)
111        logging.info('Done playing audio.')
112