# Lint as: python2, python3
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import logging
import os
import subprocess
import tempfile
import threading
import time

from autotest_lib.client.bin import test, utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib.cros import chrome
from autotest_lib.client.cros import rtc
from autotest_lib.client.cros.audio import audio_helper
from autotest_lib.client.cros.power import power_utils

class power_AudioDetector(test.test):
    """Verifies that audio playback prevents powerd from suspending."""
    version = 1

    def initialize(self):
        self._pref_change = None


    def run_once(self, run_time_sec=60):
        if run_time_sec < 10:
            raise error.TestFail('Must run for at least 10 seconds')

        with chrome.Chrome():
            # Audio loop time should be significantly shorter than
            # |run_time_sec| time, so that the total playback time doesn't
            # exceed it by much.
            audio_loop_time_sec = min(10, run_time_sec / 10 + 0.5)

            # Set a low audio volume to avoid annoying people during tests.
            audio_helper.set_volume_levels(10, 100)

            # Start a subprocess that uses dbus-monitor to listen for suspend
            # announcements from powerd and writes the output to a log.
            dbus_log_fd, dbus_log_name = tempfile.mkstemp()
            os.unlink(dbus_log_name)
            dbus_log = os.fdopen(dbus_log_fd)
            dbus_proc = subprocess.Popen(
                'dbus-monitor --monitor --system ' +
                '"type=\'signal\',interface=\'org.chromium.PowerManager\',' +
                'member=\'SuspendImminent\'"', shell=True, stdout=dbus_log)

            # Start playing audio file.
            self._enable_audio_playback = True
            thread = threading.Thread(target=self._play_audio,
                                      args=(audio_loop_time_sec,))
            thread.start()

            # Restart powerd with timeouts for quick idle events.
            gap_ms = run_time_sec * 1000 / 4
            dim_ms = min(10000, gap_ms)
            off_ms = min(20000, gap_ms * 2)
            suspend_ms = min(30000, gap_ms * 3)
            prefs = { 'disable_idle_suspend'   : 0,
                      'ignore_external_policy' : 1,
                      'plugged_dim_ms'         : dim_ms,
                      'plugged_off_ms'         : off_ms,
                      'plugged_suspend_ms'     : suspend_ms,
                      'unplugged_dim_ms'       : dim_ms,
                      'unplugged_off_ms'       : off_ms,
                      'unplugged_suspend_ms'   : suspend_ms }
            self._pref_change = power_utils.PowerPrefChanger(prefs)

            # Set an alarm to wake up the system in case the audio detector
            # fails and the system suspends.
            alarm_time = rtc.get_seconds() + run_time_sec
            rtc.set_wake_alarm(alarm_time)

            time.sleep(run_time_sec)

            # Stop powerd to avoid suspending when the audio stops.
            utils.system_output('stop powerd')

            # Stop audio and wait for the audio thread to terminate.
            self._enable_audio_playback = False
            thread.join(timeout=(audio_loop_time_sec * 2))
            if thread.is_alive():
                logging.error('Audio thread did not terminate at end of test.')

            # Check the D-Bus log to make sure that no suspend took place.
            # dbus-monitor logs messages about its initial connection to the bus
            # in addition to the signals that we asked it for, so look for the
            # signal name in its output.
            dbus_proc.kill()
            dbus_log.seek(0)
            if 'SuspendImminent' in dbus_log.read():
                err_str = 'System suspended while audio was playing.'
                raise error.TestFail(err_str)


    def cleanup(self):
        # Restore powerd prefs.
        del self._pref_change


    def _play_audio(self, loop_time):
        """
        Repeatedly plays audio until self._audio_playback_enabled == False.
        """
        # TODO(crosbug.com/33988): Allow for pauses in audio playback to
        # simulate delays in loading the next song.
        while self._enable_audio_playback:
            audio_helper.play_sound(duration_seconds=loop_time)
        logging.info('Done playing audio.')
