diff --git a/audio/common/all-versions/test/utility/include/utility/ReturnIn.h b/audio/common/all-versions/test/utility/include/utility/ReturnIn.h index 2b92a216fe..7fd0d4a437 100644 --- a/audio/common/all-versions/test/utility/include/utility/ReturnIn.h +++ b/audio/common/all-versions/test/utility/include/utility/ReturnIn.h @@ -45,7 +45,7 @@ class ReturnIn { template void set(Head&& head, Tail&&... tail) { std::get(results) = std::forward(head); - set(tail...); + set(std::forward(tail)...); } // Trivial case void set() {} @@ -56,7 +56,7 @@ class ReturnIn { } // namespace detail // Generate the HIDL synchronous callback with a copy policy -// Input: the variables (lvalue reference) where to save the return values +// Input: the variables (lvalue references) where to copy the return values // Output: the callback to provide to a HIDL call with a synchronous callback // The output parameters *will be copied* do not use this function if you have // a zero copy policy diff --git a/audio/common/all-versions/util/include/common/all-versions/VersionUtils.h b/audio/common/all-versions/util/include/common/all-versions/VersionUtils.h index a998b0675c..70c3d56a42 100644 --- a/audio/common/all-versions/util/include/common/all-versions/VersionUtils.h +++ b/audio/common/all-versions/util/include/common/all-versions/VersionUtils.h @@ -52,6 +52,12 @@ auto mkEnumConverter(Source source) { return EnumConverter{source}; } +/** Allows converting an enum to its bitfield or itself. */ +template +EnumConverter mkBitfield(Enum value) { + return EnumConverter{value}; +} + } // namespace utils } // namespace common } // namespace audio diff --git a/audio/core/4.0/vts/OWNERS b/audio/core/4.0/vts/OWNERS new file mode 100644 index 0000000000..8711a9ff6a --- /dev/null +++ b/audio/core/4.0/vts/OWNERS @@ -0,0 +1,5 @@ +elaurent@google.com +krocard@google.com +mnaganov@google.com +yim@google.com +zhuoyao@google.com \ No newline at end of file diff --git a/audio/core/4.0/vts/functional/Android.bp b/audio/core/4.0/vts/functional/Android.bp new file mode 100644 index 0000000000..dde3e7b036 --- /dev/null +++ b/audio/core/4.0/vts/functional/Android.bp @@ -0,0 +1,36 @@ +// +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +cc_test { + name: "VtsHalAudioV4_0TargetTest", + defaults: ["VtsHalTargetTestDefaults"], + srcs: [ + "AudioPrimaryHidlHalTest.cpp", + "ValidateAudioConfiguration.cpp" + ], + static_libs: [ + "android.hardware.audio.common.test.utility", + "android.hardware.audio@4.0", + "android.hardware.audio.common@4.0", + "libxml2", + ], + shared_libs: [ + "libicuuc", + ], + header_libs: [ + "android.hardware.audio.common.util@all-versions", + ], +} diff --git a/audio/core/4.0/vts/functional/AudioPrimaryHidlHalTest.cpp b/audio/core/4.0/vts/functional/AudioPrimaryHidlHalTest.cpp new file mode 100644 index 0000000000..a568a3cebe --- /dev/null +++ b/audio/core/4.0/vts/functional/AudioPrimaryHidlHalTest.cpp @@ -0,0 +1,1324 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "VtsHalAudioV4_0TargetTest" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include "utility/AssertOk.h" +#include "utility/Documentation.h" +#include "utility/EnvironmentTearDown.h" +#define AUDIO_HAL_VERSION V4_0 +#include "utility/PrettyPrintAudioTypes.h" +#include "utility/ReturnIn.h" + +using std::initializer_list; +using std::string; +using std::to_string; +using std::vector; + +using ::android::sp; +using ::android::hardware::Return; +using ::android::hardware::hidl_bitfield; +using ::android::hardware::hidl_handle; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::MQDescriptorSync; +using ::android::hardware::audio::V4_0::AudioDrain; +using ::android::hardware::audio::V4_0::DeviceAddress; +using ::android::hardware::audio::V4_0::IDevice; +using ::android::hardware::audio::V4_0::IPrimaryDevice; +using TtyMode = ::android::hardware::audio::V4_0::IPrimaryDevice::TtyMode; +using ::android::hardware::audio::V4_0::IDevicesFactory; +using ::android::hardware::audio::V4_0::IStream; +using ::android::hardware::audio::V4_0::IStreamIn; +using ::android::hardware::audio::V4_0::TimeSpec; +using ReadParameters = ::android::hardware::audio::V4_0::IStreamIn::ReadParameters; +using ReadStatus = ::android::hardware::audio::V4_0::IStreamIn::ReadStatus; +using ::android::hardware::audio::V4_0::IStreamOut; +using ::android::hardware::audio::V4_0::IStreamOutCallback; +using ::android::hardware::audio::V4_0::MmapBufferInfo; +using ::android::hardware::audio::V4_0::MmapPosition; +using ::android::hardware::audio::V4_0::ParameterValue; +using ::android::hardware::audio::V4_0::Result; +using ::android::hardware::audio::V4_0::SourceMetadata; +using ::android::hardware::audio::V4_0::SinkMetadata; +using ::android::hardware::audio::common::V4_0::AudioChannelMask; +using ::android::hardware::audio::common::V4_0::AudioConfig; +using ::android::hardware::audio::common::V4_0::AudioDevice; +using ::android::hardware::audio::common::V4_0::AudioFormat; +using ::android::hardware::audio::common::V4_0::AudioHandleConsts; +using ::android::hardware::audio::common::V4_0::AudioHwSync; +using ::android::hardware::audio::common::V4_0::AudioInputFlag; +using ::android::hardware::audio::common::V4_0::AudioIoHandle; +using ::android::hardware::audio::common::V4_0::AudioMode; +using ::android::hardware::audio::common::V4_0::AudioOffloadInfo; +using ::android::hardware::audio::common::V4_0::AudioOutputFlag; +using ::android::hardware::audio::common::V4_0::AudioSource; +using ::android::hardware::audio::common::V4_0::ThreadInfo; +using ::android::hardware::audio::common::utils::mkBitfield; + +using namespace ::android::hardware::audio::common::test::utility; + +class AudioHidlTestEnvironment : public ::Environment { + public: + virtual void registerTestServices() override { registerTestService(); } +}; + +// Instance to register global tearDown +static AudioHidlTestEnvironment* environment; + +class HidlTest : public ::testing::VtsHalHidlTargetTestBase { + protected: + // Convenient member to store results + Result res; +}; + +////////////////////////////////////////////////////////////////////////////// +////////////////////// getService audio_devices_factory ////////////////////// +////////////////////////////////////////////////////////////////////////////// + +// Test all audio devices +class AudioHidlTest : public HidlTest { + public: + void SetUp() override { + ASSERT_NO_FATAL_FAILURE(HidlTest::SetUp()); // setup base + + if (devicesFactory == nullptr) { + environment->registerTearDown([] { devicesFactory.clear(); }); + devicesFactory = ::testing::VtsHalHidlTargetTestBase::getService( + environment->getServiceName("default")); + } + ASSERT_TRUE(devicesFactory != nullptr); + } + + protected: + // Cache the devicesFactory retrieval to speed up each test by ~0.5s + static sp devicesFactory; +}; +sp AudioHidlTest::devicesFactory; + +TEST_F(AudioHidlTest, GetAudioDevicesFactoryService) { + doc::test("test the getService (called in SetUp)"); +} + +TEST_F(AudioHidlTest, OpenDeviceInvalidParameter) { + doc::test("test passing an invalid parameter to openDevice"); + Result result; + sp device; + ASSERT_OK(devicesFactory->openDevice("Non existing device", returnIn(result, device))); + ASSERT_EQ(Result::INVALID_ARGUMENTS, result); + ASSERT_TRUE(device == nullptr); +} + +////////////////////////////////////////////////////////////////////////////// +/////////////////////////////// openDevice primary /////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +// Test the primary device +class AudioPrimaryHidlTest : public AudioHidlTest { + public: + /** Primary HAL test are NOT thread safe. */ + void SetUp() override { + ASSERT_NO_FATAL_FAILURE(AudioHidlTest::SetUp()); // setup base + + if (device == nullptr) { + Result result; + sp baseDevice; + ASSERT_OK(devicesFactory->openDevice("primary", returnIn(result, baseDevice))); + ASSERT_OK(result); + ASSERT_TRUE(baseDevice != nullptr); + + environment->registerTearDown([] { device.clear(); }); + device = IPrimaryDevice::castFrom(baseDevice); + ASSERT_TRUE(device != nullptr); + } + } + + protected: + // Cache the device opening to speed up each test by ~0.5s + static sp device; +}; +sp AudioPrimaryHidlTest::device; + +TEST_F(AudioPrimaryHidlTest, OpenPrimaryDevice) { + doc::test("Test the openDevice (called in SetUp)"); +} + +TEST_F(AudioPrimaryHidlTest, Init) { + doc::test("Test that the audio primary hal initialized correctly"); + ASSERT_OK(device->initCheck()); +} + +////////////////////////////////////////////////////////////////////////////// +///////////////////// {set,get}{Master,Mic}{Mute,Volume} ///////////////////// +////////////////////////////////////////////////////////////////////////////// + +template +class AccessorPrimaryHidlTest : public AudioPrimaryHidlTest { + protected: + /** Test a property getter and setter. */ + template + void testAccessors(const string& propertyName, const vector& valuesToTest, + Setter setter, Getter getter, const vector& invalidValues = {}) { + Property initialValue; // Save initial value to restore it at the end + // of the test + ASSERT_OK((device.get()->*getter)(returnIn(res, initialValue))); + ASSERT_OK(res); + + for (Property setValue : valuesToTest) { + SCOPED_TRACE("Test " + propertyName + " getter and setter for " + + testing::PrintToString(setValue)); + ASSERT_OK((device.get()->*setter)(setValue)); + Property getValue; + // Make sure the getter returns the same value just set + ASSERT_OK((device.get()->*getter)(returnIn(res, getValue))); + ASSERT_OK(res); + EXPECT_EQ(setValue, getValue); + } + + for (Property invalidValue : invalidValues) { + SCOPED_TRACE("Try to set " + propertyName + " with the invalid value " + + testing::PrintToString(invalidValue)); + EXPECT_RESULT(Result::INVALID_ARGUMENTS, (device.get()->*setter)(invalidValue)); + } + + ASSERT_OK((device.get()->*setter)(initialValue)); // restore initial value + } + + /** Test the getter and setter of an optional feature. */ + template + void testOptionalAccessors(const string& propertyName, const vector& valuesToTest, + Setter setter, Getter getter, + const vector& invalidValues = {}) { + doc::test("Test the optional " + propertyName + " getters and setter"); + { + SCOPED_TRACE("Test feature support by calling the getter"); + Property initialValue; + ASSERT_OK((device.get()->*getter)(returnIn(res, initialValue))); + if (res == Result::NOT_SUPPORTED) { + doc::partialTest(propertyName + " getter is not supported"); + return; + } + ASSERT_OK(res); // If it is supported it must succeed + } + // The feature is supported, test it + testAccessors(propertyName, valuesToTest, setter, getter, invalidValues); + } +}; + +using BoolAccessorPrimaryHidlTest = AccessorPrimaryHidlTest; + +TEST_F(BoolAccessorPrimaryHidlTest, MicMuteTest) { + doc::test("Check that the mic can be muted and unmuted"); + testAccessors("mic mute", {true, false, true}, &IDevice::setMicMute, &IDevice::getMicMute); + // TODO: check that the mic is really muted (all sample are 0) +} + +TEST_F(BoolAccessorPrimaryHidlTest, MasterMuteTest) { + doc::test( + "If master mute is supported, try to mute and unmute the master " + "output"); + testOptionalAccessors("master mute", {true, false, true}, &IDevice::setMasterMute, + &IDevice::getMasterMute); + // TODO: check that the master volume is really muted +} + +using FloatAccessorPrimaryHidlTest = AccessorPrimaryHidlTest; +TEST_F(FloatAccessorPrimaryHidlTest, MasterVolumeTest) { + doc::test("Test the master volume if supported"); + testOptionalAccessors( + "master volume", {0, 0.5, 1}, &IDevice::setMasterVolume, &IDevice::getMasterVolume, + {-0.1, 1.1, NAN, INFINITY, -INFINITY, 1 + std::numeric_limits::epsilon()}); + // TODO: check that the master volume is really changed +} + +////////////////////////////////////////////////////////////////////////////// +//////////////////////////////// AudioPatches //////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +class AudioPatchPrimaryHidlTest : public AudioPrimaryHidlTest { + protected: + bool areAudioPatchesSupported() { + auto result = device->supportsAudioPatches(); + EXPECT_IS_OK(result); + return result; + } +}; + +TEST_F(AudioPatchPrimaryHidlTest, AudioPatches) { + doc::test("Test if audio patches are supported"); + if (!areAudioPatchesSupported()) { + doc::partialTest("Audio patches are not supported"); + return; + } + // TODO: test audio patches +} + +////////////////////////////////////////////////////////////////////////////// +//////////////// Required and recommended audio format support /////////////// +// From: +// https://source.android.com/compatibility/android-cdd.html#5_4_audio_recording +// From: +// https://source.android.com/compatibility/android-cdd.html#5_5_audio_playback +/////////// TODO: move to the beginning of the file for easier update //////// +////////////////////////////////////////////////////////////////////////////// + +class AudioConfigPrimaryTest : public AudioPatchPrimaryHidlTest { + public: + // Cache result ? + static const vector getRequiredSupportPlaybackAudioConfig() { + return combineAudioConfig({AudioChannelMask::OUT_STEREO, AudioChannelMask::OUT_MONO}, + {8000, 11025, 16000, 22050, 32000, 44100}, + {AudioFormat::PCM_16_BIT}); + } + + static const vector getRecommendedSupportPlaybackAudioConfig() { + return combineAudioConfig({AudioChannelMask::OUT_STEREO, AudioChannelMask::OUT_MONO}, + {24000, 48000}, {AudioFormat::PCM_16_BIT}); + } + + static const vector getSupportedPlaybackAudioConfig() { + // TODO: retrieve audio config supported by the platform + // as declared in the policy configuration + return {}; + } + + static const vector getRequiredSupportCaptureAudioConfig() { + return combineAudioConfig({AudioChannelMask::IN_MONO}, {8000, 11025, 16000, 44100}, + {AudioFormat::PCM_16_BIT}); + } + static const vector getRecommendedSupportCaptureAudioConfig() { + return combineAudioConfig({AudioChannelMask::IN_STEREO}, {22050, 48000}, + {AudioFormat::PCM_16_BIT}); + } + static const vector getSupportedCaptureAudioConfig() { + // TODO: retrieve audio config supported by the platform + // as declared in the policy configuration + return {}; + } + + private: + static const vector combineAudioConfig(vector channelMasks, + vector sampleRates, + vector formats) { + vector configs; + for (auto channelMask : channelMasks) { + for (auto sampleRate : sampleRates) { + for (auto format : formats) { + AudioConfig config{}; + // leave offloadInfo to 0 + config.channelMask = mkBitfield(channelMask); + config.sampleRateHz = sampleRate; + config.format = format; + // FIXME: leave frameCount to 0 ? + configs.push_back(config); + } + } + } + return configs; + } +}; + +/** Generate a test name based on an audio config. + * + * As the only parameter changing are channel mask and sample rate, + * only print those ones in the test name. + */ +static string generateTestName(const testing::TestParamInfo& info) { + const AudioConfig& config = info.param; + return to_string(info.index) + "__" + to_string(config.sampleRateHz) + "_" + + // "MONO" is more clear than "FRONT_LEFT" + ((config.channelMask == mkBitfield(AudioChannelMask::OUT_MONO) || + config.channelMask == mkBitfield(AudioChannelMask::IN_MONO)) + ? "MONO" + : ::testing::PrintToString(config.channelMask)); +} + +////////////////////////////////////////////////////////////////////////////// +///////////////////////////// getInputBufferSize ///////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +// FIXME: execute input test only if platform declares +// android.hardware.microphone +// how to get this value ? is it a property ??? + +class AudioCaptureConfigPrimaryTest : public AudioConfigPrimaryTest, + public ::testing::WithParamInterface { + protected: + void inputBufferSizeTest(const AudioConfig& audioConfig, bool supportRequired) { + uint64_t bufferSize; + ASSERT_OK(device->getInputBufferSize(audioConfig, returnIn(res, bufferSize))); + + switch (res) { + case Result::INVALID_ARGUMENTS: + EXPECT_FALSE(supportRequired); + break; + case Result::OK: + // Check that the buffer is of a sane size + // For now only that it is > 0 + EXPECT_GT(bufferSize, uint64_t(0)); + break; + default: + FAIL() << "Invalid return status: " << ::testing::PrintToString(res); + } + } +}; + +// Test that the required capture config and those declared in the policy are +// indeed supported +class RequiredInputBufferSizeTest : public AudioCaptureConfigPrimaryTest {}; +TEST_P(RequiredInputBufferSizeTest, RequiredInputBufferSizeTest) { + doc::test( + "Input buffer size must be retrievable for a format with required " + "support."); + inputBufferSizeTest(GetParam(), true); +} +INSTANTIATE_TEST_CASE_P( + RequiredInputBufferSize, RequiredInputBufferSizeTest, + ::testing::ValuesIn(AudioConfigPrimaryTest::getRequiredSupportCaptureAudioConfig()), + &generateTestName); +INSTANTIATE_TEST_CASE_P( + SupportedInputBufferSize, RequiredInputBufferSizeTest, + ::testing::ValuesIn(AudioConfigPrimaryTest::getSupportedCaptureAudioConfig()), + &generateTestName); + +// Test that the recommended capture config are supported or lead to a +// INVALID_ARGUMENTS return +class OptionalInputBufferSizeTest : public AudioCaptureConfigPrimaryTest {}; +TEST_P(OptionalInputBufferSizeTest, OptionalInputBufferSizeTest) { + doc::test( + "Input buffer size should be retrievable for a format with recommended " + "support."); + inputBufferSizeTest(GetParam(), false); +} +INSTANTIATE_TEST_CASE_P( + RecommendedCaptureAudioConfigSupport, OptionalInputBufferSizeTest, + ::testing::ValuesIn(AudioConfigPrimaryTest::getRecommendedSupportCaptureAudioConfig()), + &generateTestName); + +////////////////////////////////////////////////////////////////////////////// +/////////////////////////////// setScreenState /////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +TEST_F(AudioPrimaryHidlTest, setScreenState) { + doc::test("Check that the hal can receive the screen state"); + for (bool turnedOn : {false, true, true, false, false}) { + auto ret = device->setScreenState(turnedOn); + ASSERT_IS_OK(ret); + Result result = ret; + auto okOrNotSupported = {Result::OK, Result::NOT_SUPPORTED}; + ASSERT_RESULT(okOrNotSupported, result); + } +} + +////////////////////////////////////////////////////////////////////////////// +//////////////////////////// {get,set}Parameters ///////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +TEST_F(AudioPrimaryHidlTest, getParameters) { + doc::test("Check that the hal can set and get parameters"); + hidl_vec context; + hidl_vec keys; + hidl_vec values; + ASSERT_OK(device->getParameters(context, keys, returnIn(res, values))); + ASSERT_OK(device->setParameters(context, values)); + values.resize(0); + ASSERT_OK(device->setParameters(context, values)); +} + +////////////////////////////////////////////////////////////////////////////// +//////////////////////////////// debugDebug ////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +template +static void testDebugDump(DebugDump debugDump) { + // File descriptors to our pipe. fds[0] corresponds to the read end and + // fds[1] to the write end. + int fds[2]; + ASSERT_EQ(0, pipe2(fds, O_NONBLOCK)) << errno; + + // Make sure that the pipe is at least 1 MB in size. The test process runs + // in su domain, so it should be safe to make this call. + fcntl(fds[0], F_SETPIPE_SZ, 1 << 20); + + // Wrap the temporary file file descriptor in a native handle + auto* nativeHandle = native_handle_create(1, 0); + ASSERT_NE(nullptr, nativeHandle); + nativeHandle->data[0] = fds[1]; + + // Wrap this native handle in a hidl handle + hidl_handle handle; + handle.setTo(nativeHandle, false /*take ownership*/); + + ASSERT_OK(debugDump(handle)); + + // Check that at least one bit was written by the hal + // TODO: debugDump does not return a Result. + // This mean that the hal can not report that it not implementing the + // function. + char buff; + if (read(fds[0], &buff, 1) != 1) { + doc::note("debugDump does not seem implemented"); + } + EXPECT_EQ(0, close(fds[0])) << errno; + EXPECT_EQ(0, close(fds[1])) << errno; +} + +TEST_F(AudioPrimaryHidlTest, DebugDump) { + doc::test("Check that the hal can dump its state without error"); + testDebugDump([](const auto& handle) { return device->debug(handle, {/* options */}); }); +} + +TEST_F(AudioPrimaryHidlTest, DebugDumpInvalidArguments) { + doc::test("Check that the hal dump doesn't crash on invalid arguments"); + ASSERT_OK(device->debug(hidl_handle(), {/* options */})); +} + +TEST_F(AudioPrimaryHidlTest, SetConnectedState) { + doc::test("Check that the HAL can be notified of device connection and deconnection"); + using AD = AudioDevice; + for (auto deviceType : {AD::OUT_HDMI, AD::OUT_WIRED_HEADPHONE, AD::IN_USB_HEADSET}) { + SCOPED_TRACE("device=" + ::testing::PrintToString(deviceType)); + for (bool state : {true, false}) { + SCOPED_TRACE("state=" + ::testing::PrintToString(state)); + DeviceAddress address = {}; + address.device = deviceType; + auto ret = device->setConnectedState(address, state); + ASSERT_TRUE(ret.isOk()); + if (res == Result::NOT_SUPPORTED) { + doc::partialTest("setConnectedState is not supported"); + return; + } + ASSERT_OK(res); + } + } +} + +////////////////////////////////////////////////////////////////////////////// +////////////////////////// open{Output,Input}Stream ////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +template +class OpenStreamTest : public AudioConfigPrimaryTest, + public ::testing::WithParamInterface { + protected: + template + void testOpen(Open openStream, const AudioConfig& config) { + // FIXME: Open a stream without an IOHandle + // This is not required to be accepted by hal implementations + AudioIoHandle ioHandle = (AudioIoHandle)AudioHandleConsts::AUDIO_IO_HANDLE_NONE; + AudioConfig suggestedConfig{}; + ASSERT_OK(openStream(ioHandle, config, returnIn(res, stream, suggestedConfig))); + + // TODO: only allow failure for RecommendedPlaybackAudioConfig + switch (res) { + case Result::OK: + ASSERT_TRUE(stream != nullptr); + audioConfig = config; + break; + case Result::INVALID_ARGUMENTS: + ASSERT_TRUE(stream == nullptr); + AudioConfig suggestedConfigRetry; + // Could not open stream with config, try again with the + // suggested one + ASSERT_OK(openStream(ioHandle, suggestedConfig, + returnIn(res, stream, suggestedConfigRetry))); + // This time it must succeed + ASSERT_OK(res); + ASSERT_TRUE(stream != nullptr); + audioConfig = suggestedConfig; + break; + default: + FAIL() << "Invalid return status: " << ::testing::PrintToString(res); + } + open = true; + } + + Return closeStream() { + open = false; + return stream->close(); + } + + private: + void TearDown() override { + if (open) { + ASSERT_OK(stream->close()); + } + } + + protected: + AudioConfig audioConfig; + DeviceAddress address = {}; + sp stream; + bool open = false; +}; + +////////////////////////////// openOutputStream ////////////////////////////// + +class OutputStreamTest : public OpenStreamTest { + virtual void SetUp() override { + ASSERT_NO_FATAL_FAILURE(OpenStreamTest::SetUp()); // setup base + address.device = AudioDevice::OUT_DEFAULT; + const AudioConfig& config = GetParam(); + // TODO: test all flag combination + auto flags = hidl_bitfield(AudioOutputFlag::NONE); + SourceMetadata metadata = {{{}}}; // create on track metadata + testOpen( + [&](AudioIoHandle handle, AudioConfig config, auto cb) { + return device->openOutputStream(handle, address, config, flags, metadata, cb); + }, + config); + } +}; +TEST_P(OutputStreamTest, OpenOutputStreamTest) { + doc::test( + "Check that output streams can be open with the required and " + "recommended config"); + // Open done in SetUp +} +INSTANTIATE_TEST_CASE_P( + RequiredOutputStreamConfigSupport, OutputStreamTest, + ::testing::ValuesIn(AudioConfigPrimaryTest::getRequiredSupportPlaybackAudioConfig()), + &generateTestName); +INSTANTIATE_TEST_CASE_P( + SupportedOutputStreamConfig, OutputStreamTest, + ::testing::ValuesIn(AudioConfigPrimaryTest::getSupportedPlaybackAudioConfig()), + &generateTestName); + +INSTANTIATE_TEST_CASE_P( + RecommendedOutputStreamConfigSupport, OutputStreamTest, + ::testing::ValuesIn(AudioConfigPrimaryTest::getRecommendedSupportPlaybackAudioConfig()), + &generateTestName); + +////////////////////////////// openInputStream ////////////////////////////// + +class InputStreamTest : public OpenStreamTest { + virtual void SetUp() override { + ASSERT_NO_FATAL_FAILURE(OpenStreamTest::SetUp()); // setup base + address.device = AudioDevice::IN_DEFAULT; + const AudioConfig& config = GetParam(); + // TODO: test all supported flags and source + auto flags = hidl_bitfield(AudioInputFlag::NONE); + SinkMetadata metadata = {{{AudioSource::DEFAULT, 1}}}; + testOpen( + [&](AudioIoHandle handle, AudioConfig config, auto cb) { + return device->openInputStream(handle, address, config, flags, metadata, cb); + }, + config); + } +}; + +TEST_P(InputStreamTest, OpenInputStreamTest) { + doc::test( + "Check that input streams can be open with the required and " + "recommended config"); + // Open done in setup +} +INSTANTIATE_TEST_CASE_P( + RequiredInputStreamConfigSupport, InputStreamTest, + ::testing::ValuesIn(AudioConfigPrimaryTest::getRequiredSupportCaptureAudioConfig()), + &generateTestName); +INSTANTIATE_TEST_CASE_P( + SupportedInputStreamConfig, InputStreamTest, + ::testing::ValuesIn(AudioConfigPrimaryTest::getSupportedCaptureAudioConfig()), + &generateTestName); + +INSTANTIATE_TEST_CASE_P( + RecommendedInputStreamConfigSupport, InputStreamTest, + ::testing::ValuesIn(AudioConfigPrimaryTest::getRecommendedSupportCaptureAudioConfig()), + &generateTestName); + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////// IStream getters /////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +/** Unpack the provided result. + * If the result is not OK, register a failure and return an undefined value. */ +template +static R extract(Return ret) { + if (!ret.isOk()) { + EXPECT_IS_OK(ret); + return R{}; + } + return ret; +} + +/* Could not find a way to write a test for two parametrized class fixure + * thus use this macro do duplicate tests for Input and Output stream */ +#define TEST_IO_STREAM(test_name, documentation, code) \ + TEST_P(InputStreamTest, test_name) { \ + doc::test(documentation); \ + code; \ + } \ + TEST_P(OutputStreamTest, test_name) { \ + doc::test(documentation); \ + code; \ + } + +TEST_IO_STREAM(GetFrameCount, "Check that the stream frame count == the one it was opened with", + ASSERT_EQ(audioConfig.frameCount, extract(stream->getFrameCount()))) + +TEST_IO_STREAM(GetSampleRate, "Check that the stream sample rate == the one it was opened with", + ASSERT_EQ(audioConfig.sampleRateHz, extract(stream->getSampleRate()))) + +TEST_IO_STREAM(GetChannelMask, "Check that the stream channel mask == the one it was opened with", + ASSERT_EQ(audioConfig.channelMask, extract(stream->getChannelMask()))) + +TEST_IO_STREAM(GetFormat, "Check that the stream format == the one it was opened with", + ASSERT_EQ(audioConfig.format, extract(stream->getFormat()))) + +// TODO: for now only check that the framesize is not incoherent +TEST_IO_STREAM(GetFrameSize, "Check that the stream frame size == the one it was opened with", + ASSERT_GT(extract(stream->getFrameSize()), 0U)) + +TEST_IO_STREAM(GetBufferSize, "Check that the stream buffer size== the one it was opened with", + ASSERT_GE(extract(stream->getBufferSize()), extract(stream->getFrameSize()))); + +template +static void testCapabilityGetter(const string& name, IStream* stream, + CapablityGetter capablityGetter, + Return (IStream::*getter)(), + Return (IStream::*setter)(Property), + bool currentMustBeSupported = true) { + hidl_vec capabilities; + auto ret = capablityGetter(stream, capabilities); + if (ret == Result::NOT_SUPPORTED) { + doc::partialTest(name + " is not supported"); + return; + }; + ASSERT_OK(ret); + + if (currentMustBeSupported) { + Property currentValue = extract((stream->*getter)()); + EXPECT_NE(std::find(capabilities.begin(), capabilities.end(), currentValue), + capabilities.end()) + << "current " << name << " is not in the list of the supported ones " + << toString(capabilities); + } + + // Check that all declared supported values are indeed supported + for (auto capability : capabilities) { + auto ret = (stream->*setter)(capability); + ASSERT_TRUE(ret.isOk()); + if (ret == Result::NOT_SUPPORTED) { + doc::partialTest("Setter is not supported"); + return; + } + ASSERT_OK(ret); + ASSERT_EQ(capability, extract((stream->*getter)())); + } +} + +Result getSupportedSampleRates(IStream* stream, hidl_vec& rates) { + Result res; + EXPECT_OK(stream->getSupportedSampleRates(extract(stream->getFormat()), returnIn(res, rates))); + return res; +} + +Result getSupportedChannelMasks(IStream* stream, + hidl_vec>& channels) { + Result res; + EXPECT_OK( + stream->getSupportedSampleRates(extract(stream->getFormat()), returnIn(res, channels))); + return res; +} + +Result getSupportedFormats(IStream* stream, hidl_vec& capabilities) { + EXPECT_OK(stream->getSupportedFormats(returnIn(capabilities))); + // TODO: this should be an optional function + return Result::OK; +} + +TEST_IO_STREAM(SupportedSampleRate, "Check that the stream sample rate is declared as supported", + testCapabilityGetter("getSupportedSampleRate", stream.get(), + &getSupportedSampleRates, &IStream::getSampleRate, + &IStream::setSampleRate, + // getSupportedSampleRate returns the native sampling rates, + // (the sampling rates that can be played without resampling) + // but other sampling rates can be supported by the HAL. + false)) + +TEST_IO_STREAM(SupportedChannelMask, "Check that the stream channel mask is declared as supported", + testCapabilityGetter("getSupportedChannelMask", stream.get(), + &getSupportedChannelMasks, &IStream::getChannelMask, + &IStream::setChannelMask)) + +TEST_IO_STREAM(SupportedFormat, "Check that the stream format is declared as supported", + testCapabilityGetter("getSupportedFormat", stream.get(), &getSupportedFormats, + &IStream::getFormat, &IStream::setFormat)) + +static void testGetDevice(IStream* stream, AudioDevice expectedDevice) { + hidl_vec devices; + Result res; + ASSERT_OK(stream->getDevices(returnIn(res, devices))); + if (res == Result::NOT_SUPPORTED) { + return doc::partialTest("GetDevices is not supported"); + } + // The stream was constructed with one device, thus getDevices must only return one + ASSERT_EQ(1U, devices.size()); + AudioDevice device = devices[0].device; + ASSERT_TRUE(device == expectedDevice) + << "Expected: " << ::testing::PrintToString(expectedDevice) + << "\n Actual: " << ::testing::PrintToString(device); +} + +TEST_IO_STREAM(GetDevice, "Check that the stream device == the one it was opened with", + areAudioPatchesSupported() ? doc::partialTest("Audio patches are supported") + : testGetDevice(stream.get(), address.device)) + +static void testSetDevice(IStream* stream, const DeviceAddress& address) { + DeviceAddress otherAddress = address; + otherAddress.device = (address.device & AudioDevice::BIT_IN) == 0 ? AudioDevice::OUT_SPEAKER + : AudioDevice::IN_BUILTIN_MIC; + EXPECT_OK(stream->setDevices({otherAddress})); + + ASSERT_OK(stream->setDevices({address})); // Go back to the original value +} + +TEST_IO_STREAM(SetDevice, "Check that the stream can be rerouted to SPEAKER or BUILTIN_MIC", + areAudioPatchesSupported() ? doc::partialTest("Audio patches are supported") + : testSetDevice(stream.get(), address)) + +static void testGetAudioProperties(IStream* stream, AudioConfig expectedConfig) { + uint32_t sampleRateHz; + hidl_bitfield mask; + AudioFormat format; + + stream->getAudioProperties(returnIn(sampleRateHz, mask, format)); + + // FIXME: the qcom hal it does not currently negotiate the sampleRate & + // channel mask + EXPECT_EQ(expectedConfig.sampleRateHz, sampleRateHz); + EXPECT_EQ(expectedConfig.channelMask, mask); + EXPECT_EQ(expectedConfig.format, format); +} + +TEST_IO_STREAM(GetAudioProperties, + "Check that the stream audio properties == the ones it was opened with", + testGetAudioProperties(stream.get(), audioConfig)) + +static auto invalidArgsOrNotSupportedOrOK = {Result::INVALID_ARGUMENTS, Result::NOT_SUPPORTED, + Result::OK}; +TEST_IO_STREAM(SetHwAvSync, "Try to set hardware sync to an invalid value", + ASSERT_RESULT(invalidArgsOrNotSupportedOrOK, stream->setHwAvSync(666))) + +static void checkGetHwAVSync(IDevice* device) { + Result res; + AudioHwSync sync; + ASSERT_OK(device->getHwAvSync(returnIn(res, sync))); + if (res == Result::NOT_SUPPORTED) { + return doc::partialTest("getHwAvSync is not supported"); + } + ASSERT_OK(res); +} +TEST_IO_STREAM(GetHwAvSync, "Get hardware sync can not fail", checkGetHwAVSync(device.get())); + +static void checkGetNoParameter(IStream* stream, hidl_vec keys, + initializer_list expectedResults) { + hidl_vec context; + hidl_vec parameters; + Result res; + ASSERT_OK(stream->getParameters(context, keys, returnIn(res, parameters))); + ASSERT_RESULT(expectedResults, res); + if (res == Result::OK) { + for (auto& parameter : parameters) { + ASSERT_EQ(0U, parameter.value.size()) << toString(parameter); + } + } +} + +/* Get/Set parameter is intended to be an opaque channel between vendors app and + * their HALs. + * Thus can not be meaningfully tested. + */ +TEST_IO_STREAM(getEmptySetParameter, "Retrieve the values of an empty set", + checkGetNoParameter(stream.get(), {} /* keys */, {Result::OK})) + +TEST_IO_STREAM(getNonExistingParameter, "Retrieve the values of an non existing parameter", + checkGetNoParameter(stream.get(), {"Non existing key"} /* keys */, + {Result::NOT_SUPPORTED})) + +TEST_IO_STREAM(setEmptySetParameter, "Set the values of an empty set of parameters", + ASSERT_RESULT(Result::OK, stream->setParameters({}, {}))) + +TEST_IO_STREAM(setNonExistingParameter, "Set the values of an non existing parameter", + // Unfortunately, the set_parameter legacy interface did not return any + // error code when a key is not supported. + // To allow implementation to just wrapped the legacy one, consider OK as a + // valid result for setting a non existing parameter. + ASSERT_RESULT(invalidArgsOrNotSupportedOrOK, + stream->setParameters({}, {{"non existing key", "0"}}))) + +TEST_IO_STREAM(DebugDump, "Check that a stream can dump its state without error", + testDebugDump([this](const auto& handle) { return stream->debug(handle, {}); })) + +TEST_IO_STREAM(DebugDumpInvalidArguments, + "Check that the stream dump doesn't crash on invalid arguments", + ASSERT_OK(stream->debug(hidl_handle(), {}))) + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////// addRemoveEffect /////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +TEST_IO_STREAM(AddNonExistingEffect, "Adding a non existing effect should fail", + ASSERT_RESULT(Result::INVALID_ARGUMENTS, stream->addEffect(666))) +TEST_IO_STREAM(RemoveNonExistingEffect, "Removing a non existing effect should fail", + ASSERT_RESULT(Result::INVALID_ARGUMENTS, stream->removeEffect(666))) + +// TODO: positive tests + +////////////////////////////////////////////////////////////////////////////// +/////////////////////////////// Control //////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +TEST_IO_STREAM(standby, "Make sure the stream can be put in stanby", + ASSERT_OK(stream->standby())) // can not fail + +static constexpr auto invalidStateOrNotSupported = {Result::INVALID_STATE, Result::NOT_SUPPORTED}; + +TEST_IO_STREAM(startNoMmap, "Starting a mmaped stream before mapping it should fail", + ASSERT_RESULT(invalidStateOrNotSupported, stream->start())) + +TEST_IO_STREAM(stopNoMmap, "Stopping a mmaped stream before mapping it should fail", + ASSERT_RESULT(invalidStateOrNotSupported, stream->stop())) + +TEST_IO_STREAM(getMmapPositionNoMmap, "Get a stream Mmap position before mapping it should fail", + ASSERT_RESULT(invalidStateOrNotSupported, stream->stop())) + +TEST_IO_STREAM(close, "Make sure a stream can be closed", ASSERT_OK(closeStream())) +TEST_IO_STREAM(closeTwice, "Make sure a stream can not be closed twice", ASSERT_OK(closeStream()); + ASSERT_RESULT(Result::INVALID_STATE, closeStream())) + +static auto invalidArgsOrNotSupported = {Result::INVALID_ARGUMENTS, Result::NOT_SUPPORTED}; +static void testCreateTooBigMmapBuffer(IStream* stream) { + MmapBufferInfo info; + Result res; + // Assume that int max is a value too big to be allocated + // This is true currently with a 32bit media server, but might not when it + // will run in 64 bit + auto minSizeFrames = std::numeric_limits::max(); + ASSERT_OK(stream->createMmapBuffer(minSizeFrames, returnIn(res, info))); + ASSERT_RESULT(invalidArgsOrNotSupported, res); +} + +TEST_IO_STREAM(CreateTooBigMmapBuffer, "Create mmap buffer too big should fail", + testCreateTooBigMmapBuffer(stream.get())) + +static void testGetMmapPositionOfNonMmapedStream(IStream* stream) { + Result res; + MmapPosition position; + ASSERT_OK(stream->getMmapPosition(returnIn(res, position))); + ASSERT_RESULT(invalidArgsOrNotSupported, res); +} + +TEST_IO_STREAM(GetMmapPositionOfNonMmapedStream, + "Retrieving the mmap position of a non mmaped stream should fail", + testGetMmapPositionOfNonMmapedStream(stream.get())) + +////////////////////////////////////////////////////////////////////////////// +///////////////////////////////// StreamIn /////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +TEST_P(InputStreamTest, GetAudioSource) { + doc::test("Retrieving the audio source of an input stream should always succeed"); + AudioSource source; + ASSERT_OK(stream->getAudioSource(returnIn(res, source))); + if (res == Result::NOT_SUPPORTED) { + doc::partialTest("getAudioSource is not supported"); + return; + } + ASSERT_OK(res); + ASSERT_EQ(AudioSource::DEFAULT, source); +} + +static void testUnitaryGain(std::function(float)> setGain) { + for (float value : (float[]){-INFINITY, -1.0, 1.0 + std::numeric_limits::epsilon(), 2.0, + INFINITY, NAN}) { + EXPECT_RESULT(Result::INVALID_ARGUMENTS, setGain(value)) << "value=" << value; + } + // Do not consider -0.0 as an invalid value as it is == with 0.0 + for (float value : {-0.0, 0.0, 0.01, 0.5, 0.09, 1.0 /* Restore volume*/}) { + EXPECT_OK(setGain(value)) << "value=" << value; + } +} + +static void testOptionalUnitaryGain(std::function(float)> setGain, + string debugName) { + auto result = setGain(1); + ASSERT_IS_OK(result); + if (result == Result::NOT_SUPPORTED) { + doc::partialTest(debugName + " is not supported"); + return; + } + testUnitaryGain(setGain); +} + +TEST_P(InputStreamTest, SetGain) { + doc::test("The gain of an input stream should only be set between [0,1]"); + testOptionalUnitaryGain([this](float volume) { return stream->setGain(volume); }, + "InputStream::setGain"); +} + +static void testPrepareForReading(IStreamIn* stream, uint32_t frameSize, uint32_t framesCount) { + Result res; + // Ignore output parameters as the call should fail + ASSERT_OK(stream->prepareForReading(frameSize, framesCount, + [&res](auto r, auto&, auto&, auto&, auto&) { res = r; })); + EXPECT_RESULT(Result::INVALID_ARGUMENTS, res); +} + +TEST_P(InputStreamTest, PrepareForReadingWithZeroBuffer) { + doc::test("Preparing a stream for reading with a 0 sized buffer should fail"); + testPrepareForReading(stream.get(), 0, 0); +} + +TEST_P(InputStreamTest, PrepareForReadingWithHugeBuffer) { + doc::test("Preparing a stream for reading with a 2^32 sized buffer should fail"); + testPrepareForReading(stream.get(), 1, std::numeric_limits::max()); +} + +TEST_P(InputStreamTest, PrepareForReadingCheckOverflow) { + doc::test( + "Preparing a stream for reading with a overflowing sized buffer should " + "fail"); + auto uintMax = std::numeric_limits::max(); + testPrepareForReading(stream.get(), uintMax, uintMax); +} + +TEST_P(InputStreamTest, GetInputFramesLost) { + doc::test("The number of frames lost on a never started stream should be 0"); + auto ret = stream->getInputFramesLost(); + ASSERT_IS_OK(ret); + uint32_t framesLost{ret}; + ASSERT_EQ(0U, framesLost); +} + +TEST_P(InputStreamTest, getCapturePosition) { + doc::test( + "The capture position of a non prepared stream should not be " + "retrievable"); + uint64_t frames; + uint64_t time; + ASSERT_OK(stream->getCapturePosition(returnIn(res, frames, time))); + ASSERT_RESULT(invalidStateOrNotSupported, res); +} + +////////////////////////////////////////////////////////////////////////////// +///////////////////////////////// StreamIn /////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +TEST_P(OutputStreamTest, getLatency) { + doc::test("Make sure latency is over 0"); + auto result = stream->getLatency(); + ASSERT_IS_OK(result); + ASSERT_GT(result, 0U); +} + +TEST_P(OutputStreamTest, setVolume) { + doc::test("Try to set the output volume"); + testOptionalUnitaryGain([this](float volume) { return stream->setVolume(volume, volume); }, + "setVolume"); +} + +static void testPrepareForWriting(IStreamOut* stream, uint32_t frameSize, uint32_t framesCount) { + Result res; + // Ignore output parameters as the call should fail + ASSERT_OK(stream->prepareForWriting(frameSize, framesCount, + [&res](auto r, auto&, auto&, auto&, auto&) { res = r; })); + EXPECT_RESULT(Result::INVALID_ARGUMENTS, res); +} + +TEST_P(OutputStreamTest, PrepareForWriteWithZeroBuffer) { + doc::test("Preparing a stream for writing with a 0 sized buffer should fail"); + testPrepareForWriting(stream.get(), 0, 0); +} + +TEST_P(OutputStreamTest, PrepareForWriteWithHugeBuffer) { + doc::test("Preparing a stream for writing with a 2^32 sized buffer should fail"); + testPrepareForWriting(stream.get(), 1, std::numeric_limits::max()); +} + +TEST_P(OutputStreamTest, PrepareForWritingCheckOverflow) { + doc::test( + "Preparing a stream for writing with a overflowing sized buffer should " + "fail"); + auto uintMax = std::numeric_limits::max(); + testPrepareForWriting(stream.get(), uintMax, uintMax); +} + +struct Capability { + Capability(IStreamOut* stream) { + EXPECT_OK(stream->supportsPauseAndResume(returnIn(pause, resume))); + auto ret = stream->supportsDrain(); + EXPECT_IS_OK(ret); + if (ret.isOk()) { + drain = ret; + } + } + bool pause = false; + bool resume = false; + bool drain = false; +}; + +TEST_P(OutputStreamTest, SupportsPauseAndResumeAndDrain) { + doc::test("Implementation must expose pause, resume and drain capabilities"); + Capability(stream.get()); +} + +template +static void checkInvalidStateOr0(Result res, Value value) { + switch (res) { + case Result::INVALID_STATE: + break; + case Result::OK: + ASSERT_EQ(0U, value); + break; + default: + FAIL() << "Unexpected result " << toString(res); + } +} + +TEST_P(OutputStreamTest, GetRenderPosition) { + doc::test("A new stream render position should be 0 or INVALID_STATE"); + uint32_t dspFrames; + ASSERT_OK(stream->getRenderPosition(returnIn(res, dspFrames))); + if (res == Result::NOT_SUPPORTED) { + doc::partialTest("getRenderPosition is not supported"); + return; + } + checkInvalidStateOr0(res, dspFrames); +} + +TEST_P(OutputStreamTest, GetNextWriteTimestamp) { + doc::test("A new stream next write timestamp should be 0 or INVALID_STATE"); + uint64_t timestampUs; + ASSERT_OK(stream->getNextWriteTimestamp(returnIn(res, timestampUs))); + if (res == Result::NOT_SUPPORTED) { + doc::partialTest("getNextWriteTimestamp is not supported"); + return; + } + checkInvalidStateOr0(res, timestampUs); +} + +/** Stub implementation of out stream callback. */ +class MockOutCallbacks : public IStreamOutCallback { + Return onWriteReady() override { return {}; } + Return onDrainReady() override { return {}; } + Return onError() override { return {}; } +}; + +static bool isAsyncModeSupported(IStreamOut* stream) { + auto res = stream->setCallback(new MockOutCallbacks); + stream->clearCallback(); // try to restore the no callback state, ignore + // any error + auto okOrNotSupported = {Result::OK, Result::NOT_SUPPORTED}; + EXPECT_RESULT(okOrNotSupported, res); + return res.isOk() ? res == Result::OK : false; +} + +TEST_P(OutputStreamTest, SetCallback) { + doc::test( + "If supported, registering callback for async operation should never " + "fail"); + if (!isAsyncModeSupported(stream.get())) { + doc::partialTest("The stream does not support async operations"); + return; + } + ASSERT_OK(stream->setCallback(new MockOutCallbacks)); + ASSERT_OK(stream->setCallback(new MockOutCallbacks)); +} + +TEST_P(OutputStreamTest, clearCallback) { + doc::test( + "If supported, clearing a callback to go back to sync operation should " + "not fail"); + if (!isAsyncModeSupported(stream.get())) { + doc::partialTest("The stream does not support async operations"); + return; + } + // TODO: Clarify if clearing a non existing callback should fail + ASSERT_OK(stream->setCallback(new MockOutCallbacks)); + ASSERT_OK(stream->clearCallback()); +} + +TEST_P(OutputStreamTest, Resume) { + doc::test( + "If supported, a stream should fail to resume if not previously " + "paused"); + if (!Capability(stream.get()).resume) { + doc::partialTest("The output stream does not support resume"); + return; + } + ASSERT_RESULT(Result::INVALID_STATE, stream->resume()); +} + +TEST_P(OutputStreamTest, Pause) { + doc::test( + "If supported, a stream should fail to pause if not previously " + "started"); + if (!Capability(stream.get()).pause) { + doc::partialTest("The output stream does not support pause"); + return; + } + ASSERT_RESULT(Result::INVALID_STATE, stream->resume()); +} + +static void testDrain(IStreamOut* stream, AudioDrain type) { + if (!Capability(stream).drain) { + doc::partialTest("The output stream does not support drain"); + return; + } + ASSERT_RESULT(Result::OK, stream->drain(type)); +} + +TEST_P(OutputStreamTest, DrainAll) { + doc::test("If supported, a stream should always succeed to drain"); + testDrain(stream.get(), AudioDrain::ALL); +} + +TEST_P(OutputStreamTest, DrainEarlyNotify) { + doc::test("If supported, a stream should always succeed to drain"); + testDrain(stream.get(), AudioDrain::EARLY_NOTIFY); +} + +TEST_P(OutputStreamTest, FlushStop) { + doc::test("If supported, a stream should always succeed to flush"); + auto ret = stream->flush(); + ASSERT_IS_OK(ret); + if (ret == Result::NOT_SUPPORTED) { + doc::partialTest("Flush is not supported"); + return; + } + ASSERT_OK(ret); +} + +TEST_P(OutputStreamTest, GetPresentationPositionStop) { + doc::test( + "If supported, a stream should always succeed to retrieve the " + "presentation position"); + uint64_t frames; + TimeSpec mesureTS; + ASSERT_OK(stream->getPresentationPosition(returnIn(res, frames, mesureTS))); + if (res == Result::NOT_SUPPORTED) { + doc::partialTest("getpresentationPosition is not supported"); + return; + } + ASSERT_EQ(0U, frames); + + if (mesureTS.tvNSec == 0 && mesureTS.tvSec == 0) { + // As the stream has never written a frame yet, + // the timestamp does not really have a meaning, allow to return 0 + return; + } + + // Make sure the return measure is not more than 1s old. + struct timespec currentTS; + ASSERT_EQ(0, clock_gettime(CLOCK_MONOTONIC, ¤tTS)) << errno; + + auto toMicroSec = [](uint64_t sec, auto nsec) { return sec * 1e+6 + nsec / 1e+3; }; + auto currentTime = toMicroSec(currentTS.tv_sec, currentTS.tv_nsec); + auto mesureTime = toMicroSec(mesureTS.tvSec, mesureTS.tvNSec); + ASSERT_PRED2([](auto c, auto m) { return c - m < 1e+6; }, currentTime, mesureTime); +} + +////////////////////////////////////////////////////////////////////////////// +/////////////////////////////// PrimaryDevice //////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +TEST_F(AudioPrimaryHidlTest, setVoiceVolume) { + doc::test("Make sure setVoiceVolume only succeed if volume is in [0,1]"); + testUnitaryGain([](float volume) { return device->setVoiceVolume(volume); }); +} + +TEST_F(AudioPrimaryHidlTest, setMode) { + doc::test( + "Make sure setMode always succeeds if mode is valid " + "and fails otherwise"); + // Test Invalid values + for (int mode : {-1, 0, int(AudioMode::IN_COMMUNICATION) + 1}) { + SCOPED_TRACE("mode=" + to_string(mode)); + ASSERT_RESULT(Result::INVALID_ARGUMENTS, device->setMode(AudioMode(mode))); + } + // Test valid values + for (AudioMode mode : {AudioMode::IN_CALL, AudioMode::IN_COMMUNICATION, AudioMode::RINGTONE, + AudioMode::NORMAL /* Make sure to leave the test in normal mode */}) { + SCOPED_TRACE("mode=" + toString(mode)); + ASSERT_OK(device->setMode(mode)); + } +} + +TEST_F(BoolAccessorPrimaryHidlTest, BtScoNrecEnabled) { + doc::test("Query and set the BT SCO NR&EC state"); + testOptionalAccessors("BtScoNrecEnabled", {true, false, true}, + &IPrimaryDevice::setBtScoNrecEnabled, + &IPrimaryDevice::getBtScoNrecEnabled); +} + +TEST_F(BoolAccessorPrimaryHidlTest, setGetBtScoWidebandEnabled) { + doc::test("Query and set the SCO whideband state"); + testOptionalAccessors("BtScoWideband", {true, false, true}, + &IPrimaryDevice::setBtScoWidebandEnabled, + &IPrimaryDevice::getBtScoWidebandEnabled); +} + +using TtyModeAccessorPrimaryHidlTest = AccessorPrimaryHidlTest; +TEST_F(TtyModeAccessorPrimaryHidlTest, setGetTtyMode) { + doc::test("Query and set the TTY mode state"); + testOptionalAccessors("TTY mode", {TtyMode::OFF, TtyMode::HCO, TtyMode::VCO, TtyMode::FULL}, + &IPrimaryDevice::setTtyMode, &IPrimaryDevice::getTtyMode); +} + +TEST_F(BoolAccessorPrimaryHidlTest, setGetHac) { + doc::test("Query and set the HAC state"); + testOptionalAccessors("HAC", {true, false, true}, &IPrimaryDevice::setHacEnabled, + &IPrimaryDevice::getHacEnabled); +} + +////////////////////////////////////////////////////////////////////////////// +//////////////////// Clean caches on global tear down //////////////////////// +////////////////////////////////////////////////////////////////////////////// + +int main(int argc, char** argv) { + environment = new AudioHidlTestEnvironment; + ::testing::AddGlobalTestEnvironment(environment); + ::testing::InitGoogleTest(&argc, argv); + environment->init(&argc, argv); + int status = RUN_ALL_TESTS(); + return status; +} diff --git a/audio/core/4.0/vts/functional/ValidateAudioConfiguration.cpp b/audio/core/4.0/vts/functional/ValidateAudioConfiguration.cpp new file mode 100644 index 0000000000..bef0e8276c --- /dev/null +++ b/audio/core/4.0/vts/functional/ValidateAudioConfiguration.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "utility/ValidateXml.h" + +TEST(CheckConfig, audioPolicyConfigurationValidation) { + RecordProperty("description", + "Verify that the audio policy configuration file " + "is valid according to the schema"); + + std::vector locations = {"/odm/etc", "/vendor/etc", "/system/etc"}; + EXPECT_ONE_VALID_XML_MULTIPLE_LOCATIONS("audio_policy_configuration.xml", locations, + "/data/local/tmp/audio_policy_configuration.xsd"); +} diff --git a/audio/effect/4.0/vts/OWNERS b/audio/effect/4.0/vts/OWNERS new file mode 100644 index 0000000000..8711a9ff6a --- /dev/null +++ b/audio/effect/4.0/vts/OWNERS @@ -0,0 +1,5 @@ +elaurent@google.com +krocard@google.com +mnaganov@google.com +yim@google.com +zhuoyao@google.com \ No newline at end of file diff --git a/audio/effect/4.0/vts/functional/Android.bp b/audio/effect/4.0/vts/functional/Android.bp new file mode 100644 index 0000000000..92b5db748d --- /dev/null +++ b/audio/effect/4.0/vts/functional/Android.bp @@ -0,0 +1,39 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +cc_test { + name: "VtsHalAudioEffectV4_0TargetTest", + defaults: ["VtsHalTargetTestDefaults"], + srcs: [ + "VtsHalAudioEffectV4_0TargetTest.cpp", + "ValidateAudioEffectsConfiguration.cpp" + ], + static_libs: [ + "android.hardware.audio.common.test.utility", + "android.hardware.audio.common@4.0", + "android.hardware.audio.effect@4.0", + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + "libxml2", + ], + shared_libs: [ + "libeffectsconfig", + "libicuuc", + ], + header_libs: [ + "android.hardware.audio.common.util@all-versions", + ], +} diff --git a/audio/effect/4.0/vts/functional/ValidateAudioEffectsConfiguration.cpp b/audio/effect/4.0/vts/functional/ValidateAudioEffectsConfiguration.cpp new file mode 100644 index 0000000000..6338563c2e --- /dev/null +++ b/audio/effect/4.0/vts/functional/ValidateAudioEffectsConfiguration.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include + +#include "utility/ValidateXml.h" + +TEST(CheckConfig, audioEffectsConfigurationValidation) { + RecordProperty("description", + "Verify that the effects configuration file is valid according to the schema"); + using namespace android::effectsConfig; + + std::vector locations(std::begin(DEFAULT_LOCATIONS), std::end(DEFAULT_LOCATIONS)); + EXPECT_ONE_VALID_XML_MULTIPLE_LOCATIONS(DEFAULT_NAME, locations, + "/data/local/tmp/audio_effects_conf_V4_0.xsd"); +} diff --git a/audio/effect/4.0/vts/functional/VtsHalAudioEffectV4_0TargetTest.cpp b/audio/effect/4.0/vts/functional/VtsHalAudioEffectV4_0TargetTest.cpp new file mode 100644 index 0000000000..ec783c4bfa --- /dev/null +++ b/audio/effect/4.0/vts/functional/VtsHalAudioEffectV4_0TargetTest.cpp @@ -0,0 +1,852 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "AudioEffectHidlHalTest" +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +using android::hardware::audio::common::V4_0::AudioDevice; +using android::hardware::audio::common::V4_0::AudioHandleConsts; +using android::hardware::audio::common::V4_0::AudioMode; +using android::hardware::audio::common::V4_0::AudioSource; +using android::hardware::audio::common::V4_0::Uuid; +using android::hardware::audio::common::utils::mkBitfield; +using android::hardware::audio::effect::V4_0::AudioBuffer; +using android::hardware::audio::effect::V4_0::EffectAuxChannelsConfig; +using android::hardware::audio::effect::V4_0::EffectBufferConfig; +using android::hardware::audio::effect::V4_0::EffectConfig; +using android::hardware::audio::effect::V4_0::EffectDescriptor; +using android::hardware::audio::effect::V4_0::EffectOffloadParameter; +using android::hardware::audio::effect::V4_0::IEffect; +using android::hardware::audio::effect::V4_0::IEffectsFactory; +using android::hardware::audio::effect::V4_0::IEqualizerEffect; +using android::hardware::audio::effect::V4_0::ILoudnessEnhancerEffect; +using android::hardware::audio::effect::V4_0::Result; +using android::hardware::MQDescriptorSync; +using android::hardware::Return; +using android::hardware::Void; +using android::hardware::hidl_handle; +using android::hardware::hidl_memory; +using android::hardware::hidl_string; +using android::hardware::hidl_vec; +using android::hidl::allocator::V1_0::IAllocator; +using android::hidl::memory::V1_0::IMemory; +using android::sp; + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a))) +#endif + +// Test environment for Audio Effects Factory HIDL HAL. +class AudioEffectsFactoryHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { + public: + // get the test environment singleton + static AudioEffectsFactoryHidlEnvironment* Instance() { + static AudioEffectsFactoryHidlEnvironment* instance = + new AudioEffectsFactoryHidlEnvironment; + return instance; + } + + virtual void registerTestServices() override { registerTestService(); } +}; + +// The main test class for Audio Effects Factory HIDL HAL. +class AudioEffectsFactoryHidlTest : public ::testing::VtsHalHidlTargetTestBase { + public: + void SetUp() override { + effectsFactory = ::testing::VtsHalHidlTargetTestBase::getService( + AudioEffectsFactoryHidlEnvironment::Instance()->getServiceName()); + ASSERT_NE(effectsFactory, nullptr); + } + + void TearDown() override { effectsFactory.clear(); } + + protected: + static void description(const std::string& description) { + RecordProperty("description", description); + } + + sp effectsFactory; +}; + +TEST_F(AudioEffectsFactoryHidlTest, EnumerateEffects) { + description("Verify that EnumerateEffects returns at least one effect"); + Result retval = Result::NOT_INITIALIZED; + size_t effectCount = 0; + Return ret = effectsFactory->getAllDescriptors( + [&](Result r, const hidl_vec& result) { + retval = r; + effectCount = result.size(); + }); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, retval); + EXPECT_GT(effectCount, 0u); +} + +TEST_F(AudioEffectsFactoryHidlTest, CreateEffect) { + description("Verify that an effect can be created via CreateEffect"); + bool gotEffect = false; + Uuid effectUuid; + Return ret = effectsFactory->getAllDescriptors( + [&](Result r, const hidl_vec& result) { + if (r == Result::OK && result.size() > 0) { + gotEffect = true; + effectUuid = result[0].uuid; + } + }); + ASSERT_TRUE(ret.isOk()); + ASSERT_TRUE(gotEffect); + Result retval = Result::NOT_INITIALIZED; + sp effect; + ret = effectsFactory->createEffect( + effectUuid, 1 /*session*/, 1 /*ioHandle*/, + [&](Result r, const sp& result, uint64_t /*effectId*/) { + retval = r; + if (r == Result::OK) { + effect = result; + } + }); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, retval); + EXPECT_NE(nullptr, effect.get()); +} + +TEST_F(AudioEffectsFactoryHidlTest, GetDescriptor) { + description( + "Verify that effects factory can provide an effect descriptor via " + "GetDescriptor"); + hidl_vec allDescriptors; + Return ret = effectsFactory->getAllDescriptors( + [&](Result r, const hidl_vec& result) { + if (r == Result::OK) { + allDescriptors = result; + } + }); + ASSERT_TRUE(ret.isOk()); + ASSERT_GT(allDescriptors.size(), 0u); + for (size_t i = 0; i < allDescriptors.size(); ++i) { + ret = effectsFactory->getDescriptor( + allDescriptors[i].uuid, [&](Result r, const EffectDescriptor& result) { + EXPECT_EQ(r, Result::OK); + EXPECT_EQ(result, allDescriptors[i]); + }); + } + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(AudioEffectsFactoryHidlTest, DebugDumpInvalidArgument) { + description("Verify that debugDump doesn't crash on invalid arguments"); + Return ret = effectsFactory->debug(hidl_handle(), {}); + ASSERT_TRUE(ret.isOk()); +} + +// Equalizer effect is required by CDD, but only the type is fixed. +// This is the same UUID as AudioEffect.EFFECT_TYPE_EQUALIZER in Java. +static const Uuid EQUALIZER_EFFECT_TYPE = { + 0x0bed4300, 0xddd6, 0x11db, 0x8f34, + std::array{{0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}}}; +// Loudness Enhancer effect is required by CDD, but only the type is fixed. +// This is the same UUID as AudioEffect.EFFECT_TYPE_LOUDNESS_ENHANCER in Java. +static const Uuid LOUDNESS_ENHANCER_EFFECT_TYPE = { + 0xfe3199be, 0xaed0, 0x413f, 0x87bb, + std::array{{0x11, 0x26, 0x0e, 0xb6, 0x3c, 0xf1}}}; + +// The main test class for Audio Effect HIDL HAL. +class AudioEffectHidlTest : public ::testing::VtsHalHidlTargetTestBase { + public: + void SetUp() override { + effectsFactory = + ::testing::VtsHalHidlTargetTestBase::getService(); + ASSERT_NE(nullptr, effectsFactory.get()); + + findAndCreateEffect(getEffectType()); + ASSERT_NE(nullptr, effect.get()); + + Return ret = effect->init(); + ASSERT_TRUE(ret.isOk()); + ASSERT_EQ(Result::OK, ret); + } + + void TearDown() override { + effect.clear(); + effectsFactory.clear(); + } + + protected: + static void description(const std::string& description) { + RecordProperty("description", description); + } + + virtual Uuid getEffectType() { return EQUALIZER_EFFECT_TYPE; } + + void findAndCreateEffect(const Uuid& type); + void findEffectInstance(const Uuid& type, Uuid* uuid); + void getChannelCount(uint32_t* channelCount); + + sp effectsFactory; + sp effect; +}; + +void AudioEffectHidlTest::findAndCreateEffect(const Uuid& type) { + Uuid effectUuid; + findEffectInstance(type, &effectUuid); + Return ret = effectsFactory->createEffect( + effectUuid, 1 /*session*/, 1 /*ioHandle*/, + [&](Result r, const sp& result, uint64_t /*effectId*/) { + if (r == Result::OK) { + effect = result; + } + }); + ASSERT_TRUE(ret.isOk()); +} + +void AudioEffectHidlTest::findEffectInstance(const Uuid& type, Uuid* uuid) { + bool effectFound = false; + Return ret = effectsFactory->getAllDescriptors( + [&](Result r, const hidl_vec& result) { + if (r == Result::OK) { + for (const auto& desc : result) { + if (desc.type == type) { + effectFound = true; + *uuid = desc.uuid; + break; + } + } + } + }); + ASSERT_TRUE(ret.isOk()); + ASSERT_TRUE(effectFound); +} + +void AudioEffectHidlTest::getChannelCount(uint32_t* channelCount) { + Result retval; + EffectConfig currentConfig; + Return ret = effect->getConfig([&](Result r, const EffectConfig& conf) { + retval = r; + if (r == Result::OK) { + currentConfig = conf; + } + }); + ASSERT_TRUE(ret.isOk()); + ASSERT_EQ(Result::OK, retval); + ASSERT_TRUE(audio_channel_mask_is_valid( + static_cast(currentConfig.outputCfg.channels))); + *channelCount = audio_channel_count_from_out_mask( + static_cast(currentConfig.outputCfg.channels)); +} + +TEST_F(AudioEffectHidlTest, Close) { + description("Verify that an effect can be closed"); + Return ret = effect->close(); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, ret); +} + +TEST_F(AudioEffectHidlTest, GetDescriptor) { + description( + "Verify that an effect can return its own descriptor via GetDescriptor"); + Result retval = Result::NOT_INITIALIZED; + Uuid actualType; + Return ret = + effect->getDescriptor([&](Result r, const EffectDescriptor& desc) { + retval = r; + if (r == Result::OK) { + actualType = desc.type; + } + }); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, retval); + EXPECT_EQ(getEffectType(), actualType); +} + +TEST_F(AudioEffectHidlTest, GetSetConfig) { + description( + "Verify that it is possible to manipulate effect config via Get / " + "SetConfig"); + Result retval = Result::NOT_INITIALIZED; + EffectConfig currentConfig; + Return ret = effect->getConfig([&](Result r, const EffectConfig& conf) { + retval = r; + if (r == Result::OK) { + currentConfig = conf; + } + }); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, retval); + Return ret2 = effect->setConfig(currentConfig, nullptr, nullptr); + EXPECT_TRUE(ret2.isOk()); + EXPECT_EQ(Result::OK, ret2); +} + +TEST_F(AudioEffectHidlTest, GetConfigReverse) { + description("Verify that GetConfigReverse does not crash"); + Return ret = + effect->getConfigReverse([&](Result, const EffectConfig&) {}); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(AudioEffectHidlTest, GetSupportedAuxChannelsConfigs) { + description("Verify that GetSupportedAuxChannelsConfigs does not crash"); + Return ret = effect->getSupportedAuxChannelsConfigs( + 0, [&](Result, const hidl_vec&) {}); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(AudioEffectHidlTest, GetAuxChannelsConfig) { + description("Verify that GetAuxChannelsConfig does not crash"); + Return ret = effect->getAuxChannelsConfig( + [&](Result, const EffectAuxChannelsConfig&) {}); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(AudioEffectHidlTest, SetAuxChannelsConfig) { + description("Verify that SetAuxChannelsConfig does not crash"); + Return ret = effect->setAuxChannelsConfig(EffectAuxChannelsConfig()); + EXPECT_TRUE(ret.isOk()); +} + +// Not generated automatically because AudioBuffer contains +// instances of hidl_memory which can't be compared properly +// in general case due to presence of handles. +// +// However, in this particular case, handles must not present +// thus comparison is possible. +// +// operator== must be defined in the same namespace as the structures. +namespace android { +namespace hardware { +namespace audio { +namespace effect { +namespace V4_0 { +inline bool operator==(const AudioBuffer& lhs, const AudioBuffer& rhs) { + return lhs.id == rhs.id && lhs.frameCount == rhs.frameCount && + lhs.data.handle() == nullptr && rhs.data.handle() == nullptr; +} + +inline bool operator==(const EffectBufferConfig& lhs, + const EffectBufferConfig& rhs) { + return lhs.buffer == rhs.buffer && lhs.samplingRateHz == rhs.samplingRateHz && + lhs.channels == rhs.channels && lhs.format == rhs.format && + lhs.accessMode == rhs.accessMode && lhs.mask == rhs.mask; +} + +inline bool operator==(const EffectConfig& lhs, const EffectConfig& rhs) { + return lhs.inputCfg == rhs.inputCfg && lhs.outputCfg == rhs.outputCfg; +} +} // namespace V4_0 +} // namespace effect +} // namespace audio +} // namespace hardware +} // namespace android + +TEST_F(AudioEffectHidlTest, Reset) { + description("Verify that Reset preserves effect configuration"); + Result retval = Result::NOT_INITIALIZED; + EffectConfig originalConfig; + Return ret = effect->getConfig([&](Result r, const EffectConfig& conf) { + retval = r; + if (r == Result::OK) { + originalConfig = conf; + } + }); + ASSERT_TRUE(ret.isOk()); + ASSERT_EQ(Result::OK, retval); + Return ret2 = effect->reset(); + EXPECT_TRUE(ret2.isOk()); + EXPECT_EQ(Result::OK, ret2); + EffectConfig configAfterReset; + ret = effect->getConfig([&](Result r, const EffectConfig& conf) { + retval = r; + if (r == Result::OK) { + configAfterReset = conf; + } + }); + EXPECT_EQ(originalConfig, configAfterReset); +} + +TEST_F(AudioEffectHidlTest, DisableEnableDisable) { + description("Verify Disable -> Enable -> Disable sequence for an effect"); + Return ret = effect->disable(); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::INVALID_ARGUMENTS, ret); + ret = effect->enable(); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, ret); + ret = effect->disable(); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, ret); +} + +TEST_F(AudioEffectHidlTest, SetDevice) { + description("Verify that SetDevice works for an output chain effect"); + Return ret = effect->setDevice(mkBitfield(AudioDevice::OUT_SPEAKER)); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, ret); +} + +TEST_F(AudioEffectHidlTest, SetAndGetVolume) { + description("Verify that SetAndGetVolume method works for an effect"); + uint32_t channelCount; + getChannelCount(&channelCount); + hidl_vec volumes; + volumes.resize(channelCount); + for (uint32_t i = 0; i < channelCount; ++i) { + volumes[i] = 0; + } + Result retval = Result::NOT_INITIALIZED; + Return ret = effect->setAndGetVolume( + volumes, [&](Result r, const hidl_vec&) { retval = r; }); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, retval); +} + +TEST_F(AudioEffectHidlTest, VolumeChangeNotification) { + description("Verify that effect accepts VolumeChangeNotification"); + uint32_t channelCount; + getChannelCount(&channelCount); + hidl_vec volumes; + volumes.resize(channelCount); + for (uint32_t i = 0; i < channelCount; ++i) { + volumes[i] = 0; + } + Return ret = effect->volumeChangeNotification(volumes); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, ret); +} + +TEST_F(AudioEffectHidlTest, SetAudioMode) { + description("Verify that SetAudioMode works for an effect"); + Return ret = effect->setAudioMode(AudioMode::NORMAL); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, ret); +} + +TEST_F(AudioEffectHidlTest, SetConfigReverse) { + description("Verify that SetConfigReverse does not crash"); + Return ret = + effect->setConfigReverse(EffectConfig(), nullptr, nullptr); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(AudioEffectHidlTest, SetInputDevice) { + description("Verify that SetInputDevice does not crash"); + Return ret = effect->setInputDevice(mkBitfield(AudioDevice::IN_BUILTIN_MIC)); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(AudioEffectHidlTest, SetAudioSource) { + description("Verify that SetAudioSource does not crash"); + Return ret = effect->setAudioSource(AudioSource::MIC); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(AudioEffectHidlTest, Offload) { + description("Verify that calling Offload method does not crash"); + EffectOffloadParameter offloadParam; + offloadParam.isOffload = false; + offloadParam.ioHandle = + static_cast(AudioHandleConsts::AUDIO_IO_HANDLE_NONE); + Return ret = effect->offload(offloadParam); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(AudioEffectHidlTest, PrepareForProcessing) { + description("Verify that PrepareForProcessing method works for an effect"); + Result retval = Result::NOT_INITIALIZED; + Return ret = effect->prepareForProcessing( + [&](Result r, const MQDescriptorSync&) { retval = r; }); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, retval); +} + +TEST_F(AudioEffectHidlTest, SetProcessBuffers) { + description("Verify that SetProcessBuffers works for an effect"); + sp ashmem = IAllocator::getService("ashmem"); + ASSERT_NE(nullptr, ashmem.get()); + bool success = false; + AudioBuffer buffer; + Return ret = + ashmem->allocate(1024, [&](bool s, const hidl_memory& memory) { + success = s; + if (s) { + buffer.data = memory; + } + }); + ASSERT_TRUE(ret.isOk()); + ASSERT_TRUE(success); + Return ret2 = effect->setProcessBuffers(buffer, buffer); + EXPECT_TRUE(ret2.isOk()); + EXPECT_EQ(Result::OK, ret2); +} + +TEST_F(AudioEffectHidlTest, Command) { + description("Verify that Command does not crash"); + Return ret = effect->command(0, hidl_vec(), 0, + [&](int32_t, const hidl_vec&) {}); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(AudioEffectHidlTest, SetParameter) { + description("Verify that SetParameter does not crash"); + Return ret = + effect->setParameter(hidl_vec(), hidl_vec()); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(AudioEffectHidlTest, GetParameter) { + description("Verify that GetParameter does not crash"); + Return ret = effect->getParameter( + hidl_vec(), 0, [&](Result, const hidl_vec&) {}); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(AudioEffectHidlTest, GetSupportedConfigsForFeature) { + description("Verify that GetSupportedConfigsForFeature does not crash"); + Return ret = effect->getSupportedConfigsForFeature( + 0, 0, 0, [&](Result, uint32_t, const hidl_vec&) {}); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(AudioEffectHidlTest, GetCurrentConfigForFeature) { + description("Verify that GetCurrentConfigForFeature does not crash"); + Return ret = effect->getCurrentConfigForFeature( + 0, 0, [&](Result, const hidl_vec&) {}); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(AudioEffectHidlTest, SetCurrentConfigForFeature) { + description("Verify that SetCurrentConfigForFeature does not crash"); + Return ret = + effect->setCurrentConfigForFeature(0, hidl_vec()); + EXPECT_TRUE(ret.isOk()); +} + + +// The main test class for Equalizer Audio Effect HIDL HAL. +class EqualizerAudioEffectHidlTest : public AudioEffectHidlTest { + public: + void SetUp() override { + AudioEffectHidlTest::SetUp(); + equalizer = IEqualizerEffect::castFrom(effect); + ASSERT_NE(nullptr, equalizer.get()); + } + + protected: + Uuid getEffectType() override { return EQUALIZER_EFFECT_TYPE; } + void getNumBands(uint16_t* numBands); + void getLevelRange(int16_t* minLevel, int16_t* maxLevel); + void getBandFrequencyRange(uint16_t band, uint32_t* minFreq, + uint32_t* centerFreq, uint32_t* maxFreq); + void getPresetCount(size_t* count); + + sp equalizer; +}; + +void EqualizerAudioEffectHidlTest::getNumBands(uint16_t* numBands) { + Result retval = Result::NOT_INITIALIZED; + Return ret = equalizer->getNumBands([&](Result r, uint16_t b) { + retval = r; + if (retval == Result::OK) { + *numBands = b; + } + }); + ASSERT_TRUE(ret.isOk()); + ASSERT_EQ(Result::OK, retval); +} + +void EqualizerAudioEffectHidlTest::getLevelRange(int16_t* minLevel, + int16_t* maxLevel) { + Result retval = Result::NOT_INITIALIZED; + Return ret = + equalizer->getLevelRange([&](Result r, int16_t min, int16_t max) { + retval = r; + if (retval == Result::OK) { + *minLevel = min; + *maxLevel = max; + } + }); + ASSERT_TRUE(ret.isOk()); + ASSERT_EQ(Result::OK, retval); +} + +void EqualizerAudioEffectHidlTest::getBandFrequencyRange(uint16_t band, + uint32_t* minFreq, + uint32_t* centerFreq, + uint32_t* maxFreq) { + Result retval = Result::NOT_INITIALIZED; + Return ret = equalizer->getBandFrequencyRange( + band, [&](Result r, uint32_t min, uint32_t max) { + retval = r; + if (retval == Result::OK) { + *minFreq = min; + *maxFreq = max; + } + }); + ASSERT_TRUE(ret.isOk()); + ASSERT_EQ(Result::OK, retval); + ret = equalizer->getBandCenterFrequency(band, [&](Result r, uint32_t center) { + retval = r; + if (retval == Result::OK) { + *centerFreq = center; + } + }); + ASSERT_TRUE(ret.isOk()); + ASSERT_EQ(Result::OK, retval); +} + +void EqualizerAudioEffectHidlTest::getPresetCount(size_t* count) { + Result retval = Result::NOT_INITIALIZED; + Return ret = equalizer->getPresetNames( + [&](Result r, const hidl_vec& names) { + retval = r; + if (retval == Result::OK) { + *count = names.size(); + } + }); + ASSERT_TRUE(ret.isOk()); + ASSERT_EQ(Result::OK, retval); +} + +TEST_F(EqualizerAudioEffectHidlTest, GetNumBands) { + description("Verify that Equalizer effect reports at least one band"); + uint16_t numBands = 0; + getNumBands(&numBands); + EXPECT_GT(numBands, 0); +} + +TEST_F(EqualizerAudioEffectHidlTest, GetLevelRange) { + description("Verify that Equalizer effect reports adequate band level range"); + int16_t minLevel = 0x7fff, maxLevel = 0; + getLevelRange(&minLevel, &maxLevel); + EXPECT_GT(maxLevel, minLevel); +} + +TEST_F(EqualizerAudioEffectHidlTest, GetSetBandLevel) { + description( + "Verify that manipulating band levels works for Equalizer effect"); + uint16_t numBands = 0; + getNumBands(&numBands); + ASSERT_GT(numBands, 0); + int16_t levels[3]{0x7fff, 0, 0}; + getLevelRange(&levels[0], &levels[2]); + ASSERT_GT(levels[2], levels[0]); + levels[1] = (levels[2] + levels[0]) / 2; + for (uint16_t i = 0; i < numBands; ++i) { + for (size_t j = 0; j < ARRAY_SIZE(levels); ++j) { + Return ret = equalizer->setBandLevel(i, levels[j]); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, ret); + Result retval = Result::NOT_INITIALIZED; + int16_t actualLevel; + Return ret2 = equalizer->getBandLevel(i, [&](Result r, int16_t l) { + retval = r; + if (retval == Result::OK) { + actualLevel = l; + } + }); + EXPECT_TRUE(ret2.isOk()); + EXPECT_EQ(Result::OK, retval); + EXPECT_EQ(levels[j], actualLevel); + } + } +} + +TEST_F(EqualizerAudioEffectHidlTest, GetBandCenterFrequencyAndRange) { + description( + "Verify that Equalizer effect reports adequate band frequency range"); + uint16_t numBands = 0; + getNumBands(&numBands); + ASSERT_GT(numBands, 0); + for (uint16_t i = 0; i < numBands; ++i) { + uint32_t minFreq = 0xffffffff, centerFreq = 0xffffffff, + maxFreq = 0xffffffff; + getBandFrequencyRange(i, &minFreq, ¢erFreq, &maxFreq); + // Note: NXP legacy implementation reports "1" as upper bound for last band, + // so this check fails. + EXPECT_GE(maxFreq, centerFreq); + EXPECT_GE(centerFreq, minFreq); + } +} + +TEST_F(EqualizerAudioEffectHidlTest, GetBandForFrequency) { + description( + "Verify that Equalizer effect supports GetBandForFrequency correctly"); + uint16_t numBands = 0; + getNumBands(&numBands); + ASSERT_GT(numBands, 0); + for (uint16_t i = 0; i < numBands; ++i) { + uint32_t freqs[3]{0, 0, 0}; + getBandFrequencyRange(i, &freqs[0], &freqs[1], &freqs[2]); + // NXP legacy implementation reports "1" as upper bound for last band, some + // of the checks fail. + for (size_t j = 0; j < ARRAY_SIZE(freqs); ++j) { + if (j == 0) { + freqs[j]++; + } // Min frequency is an open interval. + Result retval = Result::NOT_INITIALIZED; + uint16_t actualBand = numBands + 1; + Return ret = + equalizer->getBandForFrequency(freqs[j], [&](Result r, uint16_t b) { + retval = r; + if (retval == Result::OK) { + actualBand = b; + } + }); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, retval); + EXPECT_EQ(i, actualBand) << "Frequency: " << freqs[j]; + } + } +} + +TEST_F(EqualizerAudioEffectHidlTest, GetPresetNames) { + description("Verify that Equalizer effect reports at least one preset"); + size_t presetCount; + getPresetCount(&presetCount); + EXPECT_GT(presetCount, 0u); +} + +TEST_F(EqualizerAudioEffectHidlTest, GetSetCurrentPreset) { + description( + "Verify that manipulating the current preset for Equalizer effect"); + size_t presetCount; + getPresetCount(&presetCount); + ASSERT_GT(presetCount, 0u); + for (uint16_t i = 0; i < presetCount; ++i) { + Return ret = equalizer->setCurrentPreset(i); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, ret); + Result retval = Result::NOT_INITIALIZED; + uint16_t actualPreset = 0xffff; + Return ret2 = equalizer->getCurrentPreset([&](Result r, uint16_t p) { + retval = r; + if (retval == Result::OK) { + actualPreset = p; + } + }); + EXPECT_TRUE(ret2.isOk()); + EXPECT_EQ(Result::OK, retval); + EXPECT_EQ(i, actualPreset); + } +} + +TEST_F(EqualizerAudioEffectHidlTest, GetSetAllProperties) { + description( + "Verify that setting band levels and presets works via Get / " + "SetAllProperties for Equalizer effect"); + using AllProperties = + android::hardware::audio::effect::V4_0::IEqualizerEffect::AllProperties; + uint16_t numBands = 0; + getNumBands(&numBands); + ASSERT_GT(numBands, 0); + AllProperties props; + props.bandLevels.resize(numBands); + for (size_t i = 0; i < numBands; ++i) { + props.bandLevels[i] = 0; + } + + AllProperties actualProps; + Result retval = Result::NOT_INITIALIZED; + + // Verify setting of the band levels via properties. + props.curPreset = -1; + Return ret = equalizer->setAllProperties(props); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, ret); + Return ret2 = + equalizer->getAllProperties([&](Result r, AllProperties p) { + retval = r; + if (retval == Result::OK) { + actualProps = p; + } + }); + EXPECT_TRUE(ret2.isOk()); + EXPECT_EQ(Result::OK, retval); + EXPECT_EQ(props.bandLevels, actualProps.bandLevels); + + // Verify setting of the current preset via properties. + props.curPreset = 0; // Assuming there is at least one preset. + ret = equalizer->setAllProperties(props); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, ret); + ret2 = equalizer->getAllProperties([&](Result r, AllProperties p) { + retval = r; + if (retval == Result::OK) { + actualProps = p; + } + }); + EXPECT_TRUE(ret2.isOk()); + EXPECT_EQ(Result::OK, retval); + EXPECT_EQ(props.curPreset, actualProps.curPreset); +} + +// The main test class for Equalizer Audio Effect HIDL HAL. +class LoudnessEnhancerAudioEffectHidlTest : public AudioEffectHidlTest { + public: + void SetUp() override { + AudioEffectHidlTest::SetUp(); + enhancer = ILoudnessEnhancerEffect::castFrom(effect); + ASSERT_NE(nullptr, enhancer.get()); + } + + protected: + Uuid getEffectType() override { return LOUDNESS_ENHANCER_EFFECT_TYPE; } + + sp enhancer; +}; + +TEST_F(LoudnessEnhancerAudioEffectHidlTest, GetSetTargetGain) { + description( + "Verify that manipulating the target gain works for Loudness Enhancer " + "effect"); + const int32_t gain = 100; + Return ret = enhancer->setTargetGain(gain); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, ret); + int32_t actualGain = 0; + Result retval; + Return ret2 = enhancer->getTargetGain([&](Result r, int32_t g) { + retval = r; + if (retval == Result::OK) { + actualGain = g; + } + }); + EXPECT_TRUE(ret2.isOk()); + EXPECT_EQ(Result::OK, retval); + EXPECT_EQ(gain, actualGain); +} + +int main(int argc, char** argv) { + ::testing::AddGlobalTestEnvironment(AudioEffectsFactoryHidlEnvironment::Instance()); + ::testing::InitGoogleTest(&argc, argv); + AudioEffectsFactoryHidlEnvironment::Instance()->init(&argc, argv); + int status = RUN_ALL_TESTS(); + LOG(INFO) << "Test result = " << status; + return status; +}