diff --git a/audio/6.0/IDevice.hal b/audio/6.0/IDevice.hal index 520e776c21..122c550ee7 100644 --- a/audio/6.0/IDevice.hal +++ b/audio/6.0/IDevice.hal @@ -286,8 +286,12 @@ interface IDevice { * all currently allocated resources. It is recommended to close * the device on the client side as soon as it is becomes unused. * + * Note that all streams must be closed by the client before + * attempting to close the device they belong to. + * * @return retval OK in case the success. - * INVALID_STATE if the device was already closed. + * INVALID_STATE if the device was already closed + * or there are streams currently opened. */ @exit close() generates (Result retval); diff --git a/audio/core/all-versions/default/Device.cpp b/audio/core/all-versions/default/Device.cpp index 5ea4c8df59..21dab00387 100644 --- a/audio/core/all-versions/default/Device.cpp +++ b/audio/core/all-versions/default/Device.cpp @@ -53,10 +53,14 @@ Result Device::analyzeStatus(const char* funcName, int status, void Device::closeInputStream(audio_stream_in_t* stream) { mDevice->close_input_stream(mDevice, stream); + LOG_ALWAYS_FATAL_IF(mOpenedStreamsCount == 0, "mOpenedStreamsCount is already 0"); + --mOpenedStreamsCount; } void Device::closeOutputStream(audio_stream_out_t* stream) { mDevice->close_output_stream(mDevice, stream); + LOG_ALWAYS_FATAL_IF(mOpenedStreamsCount == 0, "mOpenedStreamsCount is already 0"); + --mOpenedStreamsCount; } char* Device::halGetParameters(const char* keys) { @@ -158,6 +162,7 @@ std::tuple> Device::openOutputStreamImpl(int32_t ioHandle sp streamOut; if (status == OK) { streamOut = new StreamOut(this, halStream); + ++mOpenedStreamsCount; } HidlUtils::audioConfigFromHal(halConfig, suggestedConfig); return {analyzeStatus("open_output_stream", status, {EINVAL} /*ignore*/), streamOut}; @@ -184,6 +189,7 @@ std::tuple> Device::openInputStreamImpl( sp streamIn; if (status == OK) { streamIn = new StreamIn(this, halStream); + ++mOpenedStreamsCount; } HidlUtils::audioConfigFromHal(halConfig, suggestedConfig); return {analyzeStatus("open_input_stream", status, {EINVAL} /*ignore*/), streamIn}; @@ -383,7 +389,7 @@ Return Device::setConnectedState(const DeviceAddress& address, bool conn #endif Result Device::doClose() { - if (mIsClosed) return Result::INVALID_STATE; + if (mIsClosed || mOpenedStreamsCount != 0) return Result::INVALID_STATE; mIsClosed = true; return analyzeStatus("close", audio_hw_device_close(mDevice)); } diff --git a/audio/core/all-versions/default/include/core/default/Device.h b/audio/core/all-versions/default/include/core/default/Device.h index dc53a3961a..11ab6077ab 100644 --- a/audio/core/all-versions/default/include/core/default/Device.h +++ b/audio/core/all-versions/default/include/core/default/Device.h @@ -130,6 +130,7 @@ struct Device : public IDevice, public ParametersUtil { private: bool mIsClosed; audio_hw_device_t* mDevice; + int mOpenedStreamsCount = 0; virtual ~Device(); diff --git a/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp index 7e931ff1b1..22e60be380 100644 --- a/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp +++ b/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp @@ -145,3 +145,49 @@ const std::vector& getInputDeviceConfigParameters() { }(); return parameters; } + +TEST_P(AudioHidlDeviceTest, CloseDeviceWithOpenedOutputStreams) { + doc::test("Verify that a device can't be closed if there are streams opened"); + DeviceAddress address{.device = AudioDevice::OUT_DEFAULT}; + AudioConfig config{}; + auto flags = hidl_bitfield(AudioOutputFlag::NONE); + SourceMetadata initMetadata = {{{AudioUsage::MEDIA, AudioContentType::MUSIC, 1 /* gain */}}}; + sp stream; + StreamHelper helper(stream); + AudioConfig suggestedConfig{}; + ASSERT_NO_FATAL_FAILURE(helper.open( + [&](AudioIoHandle handle, AudioConfig config, auto cb) { + return getDevice()->openOutputStream(handle, address, config, flags, initMetadata, + cb); + }, + config, &res, &suggestedConfig)); + ASSERT_RESULT(Result::INVALID_STATE, getDevice()->close()); + ASSERT_NO_FATAL_FAILURE(helper.close(true /*clear*/, &res)); + ASSERT_OK(getDevice()->close()); + ASSERT_TRUE(resetDevice()); +} + +TEST_P(AudioHidlDeviceTest, CloseDeviceWithOpenedInputStreams) { + doc::test("Verify that a device can't be closed if there are streams opened"); + auto module = getCachedPolicyConfig().getModuleFromName(getDeviceName()); + if (module->getInputProfiles().empty()) { + GTEST_SKIP() << "Device doesn't have input profiles"; + } + DeviceAddress address{.device = AudioDevice::IN_DEFAULT}; + AudioConfig config{}; + auto flags = hidl_bitfield(AudioInputFlag::NONE); + SinkMetadata initMetadata = {{{.source = AudioSource::MIC, .gain = 1}}}; + sp stream; + StreamHelper helper(stream); + AudioConfig suggestedConfig{}; + ASSERT_NO_FATAL_FAILURE(helper.open( + [&](AudioIoHandle handle, AudioConfig config, auto cb) { + return getDevice()->openInputStream(handle, address, config, flags, initMetadata, + cb); + }, + config, &res, &suggestedConfig)); + ASSERT_RESULT(Result::INVALID_STATE, getDevice()->close()); + ASSERT_NO_FATAL_FAILURE(helper.close(true /*clear*/, &res)); + ASSERT_OK(getDevice()->close()); + ASSERT_TRUE(resetDevice()); +}