audio: Implement compressed offload simulation

Add 'StreamOffloadStub' to 'ModulePrimary' which simulates
offloaded playback ('COMPRESS_OFFLOAD') and can send asynchronous
callbacks. The simulation only keeps track of the playback
time, it does not actually decode compressed audio. This
approach was chosen to avoid linking against any decoder
implementation.

For the same reason, the only supported format is 'APE' ("Monkey
audio")  which was chosen due to the simplicity of its header
structure.

As 'StreamOffloadStub' provides a proper implementation of
'EARLY_NOTIFY' drain callbacks, there is no more need to use
'aosp.forceDrainToDraining' property in VTS, and support for
it has been removed.

Bug: 373872271
Bug: 384431822
Test: atest CtsMediaAudioTestCases
Test: atest VtsHalAudioCoreTargetTest
Change-Id: I74ae867227c768d4afe9314ed93168da58362131
diff --git a/audio/aidl/vts/Android.bp b/audio/aidl/vts/Android.bp
index 14e70ef..f855038 100644
--- a/audio/aidl/vts/Android.bp
+++ b/audio/aidl/vts/Android.bp
@@ -41,7 +41,6 @@
         "-Wthread-safety",
         "-Wno-error=unused-parameter",
     ],
-    test_config_template: "VtsHalAudioTargetTestTemplate.xml",
     test_suites: [
         "general-tests",
         "vts",
@@ -60,6 +59,7 @@
     srcs: [
         ":effectCommonFile",
     ],
+    test_config_template: "VtsHalAudioEffectTargetTestTemplate.xml",
 }
 
 cc_test {
@@ -77,6 +77,11 @@
         "VtsHalAudioCoreConfigTargetTest.cpp",
         "VtsHalAudioCoreModuleTargetTest.cpp",
     ],
+    data: [
+        "data/sine882hz_44100_3s.ape",
+        "data/sine960hz_48000_3s.ape",
+    ],
+    test_config_template: "VtsHalAudioCoreTargetTestTemplate.xml",
 }
 
 cc_test {
diff --git a/audio/aidl/vts/ModuleConfig.cpp b/audio/aidl/vts/ModuleConfig.cpp
index d24c4c8..7d4cc70 100644
--- a/audio/aidl/vts/ModuleConfig.cpp
+++ b/audio/aidl/vts/ModuleConfig.cpp
@@ -36,12 +36,10 @@
 using aidl::android::media::audio::common::AudioChannelLayout;
 using aidl::android::media::audio::common::AudioDeviceDescription;
 using aidl::android::media::audio::common::AudioDeviceType;
-using aidl::android::media::audio::common::AudioEncapsulationMode;
 using aidl::android::media::audio::common::AudioFormatDescription;
 using aidl::android::media::audio::common::AudioFormatType;
 using aidl::android::media::audio::common::AudioInputFlags;
 using aidl::android::media::audio::common::AudioIoFlags;
-using aidl::android::media::audio::common::AudioOffloadInfo;
 using aidl::android::media::audio::common::AudioOutputFlags;
 using aidl::android::media::audio::common::AudioPort;
 using aidl::android::media::audio::common::AudioPortConfig;
@@ -51,26 +49,6 @@
 using aidl::android::media::audio::common::Int;
 
 // static
-std::optional<AudioOffloadInfo> ModuleConfig::generateOffloadInfoIfNeeded(
-        const AudioPortConfig& portConfig) {
-    if (portConfig.flags.has_value() &&
-        portConfig.flags.value().getTag() == AudioIoFlags::Tag::output &&
-        isBitPositionFlagSet(portConfig.flags.value().get<AudioIoFlags::Tag::output>(),
-                             AudioOutputFlags::COMPRESS_OFFLOAD)) {
-        AudioOffloadInfo offloadInfo;
-        offloadInfo.base.sampleRate = portConfig.sampleRate.value().value;
-        offloadInfo.base.channelMask = portConfig.channelMask.value();
-        offloadInfo.base.format = portConfig.format.value();
-        offloadInfo.bitRatePerSecond = 256000;                             // Arbitrary value.
-        offloadInfo.durationUs = std::chrono::microseconds(1min).count();  // Arbitrary value.
-        offloadInfo.usage = AudioUsage::MEDIA;
-        offloadInfo.encapsulationMode = AudioEncapsulationMode::NONE;
-        return offloadInfo;
-    }
-    return {};
-}
-
-// static
 std::vector<aidl::android::media::audio::common::AudioPort>
 ModuleConfig::getAudioPortsForDeviceTypes(
         const std::vector<aidl::android::media::audio::common::AudioPort>& ports,
diff --git a/audio/aidl/vts/ModuleConfig.h b/audio/aidl/vts/ModuleConfig.h
index 27286e5..d45ccda 100644
--- a/audio/aidl/vts/ModuleConfig.h
+++ b/audio/aidl/vts/ModuleConfig.h
@@ -34,10 +34,6 @@
     using SrcSinkGroup =
             std::pair<aidl::android::hardware::audio::core::AudioRoute, std::vector<SrcSinkPair>>;
 
-    static std::optional<aidl::android::media::audio::common::AudioOffloadInfo>
-    generateOffloadInfoIfNeeded(
-            const aidl::android::media::audio::common::AudioPortConfig& portConfig);
-
     static std::vector<aidl::android::media::audio::common::AudioPort> getAudioPortsForDeviceTypes(
             const std::vector<aidl::android::media::audio::common::AudioPort>& ports,
             const std::vector<aidl::android::media::audio::common::AudioDeviceType>& deviceTypes,
diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
index 750e54d..8bbb60b 100644
--- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
@@ -19,6 +19,7 @@
 #include <cmath>
 #include <condition_variable>
 #include <forward_list>
+#include <fstream>
 #include <limits>
 #include <memory>
 #include <mutex>
@@ -81,12 +82,15 @@
 using aidl::android::hardware::audio::core::sounddose::ISoundDose;
 using aidl::android::hardware::common::fmq::SynchronizedReadWrite;
 using aidl::android::media::audio::common::AudioChannelLayout;
+using aidl::android::media::audio::common::AudioConfigBase;
 using aidl::android::media::audio::common::AudioContentType;
 using aidl::android::media::audio::common::AudioDevice;
 using aidl::android::media::audio::common::AudioDeviceAddress;
 using aidl::android::media::audio::common::AudioDeviceDescription;
 using aidl::android::media::audio::common::AudioDeviceType;
 using aidl::android::media::audio::common::AudioDualMonoMode;
+using aidl::android::media::audio::common::AudioEncapsulationMode;
+using aidl::android::media::audio::common::AudioFormatDescription;
 using aidl::android::media::audio::common::AudioFormatType;
 using aidl::android::media::audio::common::AudioGainConfig;
 using aidl::android::media::audio::common::AudioInputFlags;
@@ -96,6 +100,7 @@
 using aidl::android::media::audio::common::AudioMMapPolicyInfo;
 using aidl::android::media::audio::common::AudioMMapPolicyType;
 using aidl::android::media::audio::common::AudioMode;
+using aidl::android::media::audio::common::AudioOffloadInfo;
 using aidl::android::media::audio::common::AudioOutputFlags;
 using aidl::android::media::audio::common::AudioPlaybackRate;
 using aidl::android::media::audio::common::AudioPort;
@@ -217,6 +222,59 @@
     return result;
 }
 
+static const AudioFormatDescription kApeFileAudioFormat = {.encoding = "audio/x-ape"};
+static const AudioChannelLayout kApeFileChannelMask =
+        AudioChannelLayout::make<AudioChannelLayout::layoutMask>(AudioChannelLayout::LAYOUT_MONO);
+struct MediaFileInfo {
+    std::string path;
+    int32_t bps;
+    int32_t durationMs;
+};
+static const std::map<AudioConfigBase, MediaFileInfo> kMediaFileDataInfos = {
+        {{44100, kApeFileChannelMask, kApeFileAudioFormat},
+         {"/data/local/tmp/sine882hz_44100_3s.ape", 217704, 3000}},
+        {{48000, kApeFileChannelMask, kApeFileAudioFormat},
+         {"/data/local/tmp/sine960hz_48000_3s.ape", 236256, 3000}},
+};
+
+std::optional<MediaFileInfo> getMediaFileInfoForConfig(const AudioConfigBase& config) {
+    const auto it = kMediaFileDataInfos.find(config);
+    if (it != kMediaFileDataInfos.end()) return it->second;
+    return std::nullopt;
+}
+
+std::optional<MediaFileInfo> getMediaFileInfoForConfig(const AudioPortConfig& config) {
+    if (!config.sampleRate.has_value() || !config.format.has_value() ||
+        !config.channelMask.has_value()) {
+        return std::nullopt;
+    }
+    return getMediaFileInfoForConfig(AudioConfigBase{
+            config.sampleRate->value, config.channelMask.value(), config.format.value()});
+}
+
+std::optional<AudioOffloadInfo> generateOffloadInfoIfNeeded(const AudioPortConfig& portConfig) {
+    if (portConfig.flags.has_value() &&
+        portConfig.flags.value().getTag() == AudioIoFlags::Tag::output &&
+        isBitPositionFlagSet(portConfig.flags.value().get<AudioIoFlags::Tag::output>(),
+                             AudioOutputFlags::COMPRESS_OFFLOAD)) {
+        AudioOffloadInfo offloadInfo;
+        offloadInfo.base.sampleRate = portConfig.sampleRate.value().value;
+        offloadInfo.base.channelMask = portConfig.channelMask.value();
+        offloadInfo.base.format = portConfig.format.value();
+        if (auto info = getMediaFileInfoForConfig(portConfig); info.has_value()) {
+            offloadInfo.bitRatePerSecond = info->bps;
+            offloadInfo.durationUs = info->durationMs * 1000LL;
+        } else {
+            offloadInfo.bitRatePerSecond = 256000;                             // Arbitrary value.
+            offloadInfo.durationUs = std::chrono::microseconds(1min).count();  // Arbitrary value.
+        }
+        offloadInfo.usage = AudioUsage::MEDIA;
+        offloadInfo.encapsulationMode = AudioEncapsulationMode::NONE;
+        return offloadInfo;
+    }
+    return {};
+}
+
 // All 'With*' classes are move-only because they are associated with some
 // resource or state of a HAL module.
 class WithDebugFlags {
@@ -652,11 +710,14 @@
     typedef AidlMessageQueue<StreamDescriptor::Reply, SynchronizedReadWrite> ReplyMQ;
     typedef AidlMessageQueue<int8_t, SynchronizedReadWrite> DataMQ;
 
-    explicit StreamContext(const StreamDescriptor& descriptor)
+    explicit StreamContext(const StreamDescriptor& descriptor, const AudioConfigBase& config,
+                           AudioIoFlags flags)
         : mFrameSizeBytes(descriptor.frameSizeBytes),
+          mConfig(config),
           mCommandMQ(new CommandMQ(descriptor.command)),
           mReplyMQ(new ReplyMQ(descriptor.reply)),
           mBufferSizeFrames(descriptor.bufferSizeFrames),
+          mFlags(flags),
           mDataMQ(maybeCreateDataMQ(descriptor)),
           mIsMmapped(isMmapped(descriptor)),
           mSharedMemoryFd(maybeGetMmapFd(descriptor)) {
@@ -695,9 +756,12 @@
     size_t getBufferSizeBytes() const { return mFrameSizeBytes * mBufferSizeFrames; }
     size_t getBufferSizeFrames() const { return mBufferSizeFrames; }
     CommandMQ* getCommandMQ() const { return mCommandMQ.get(); }
+    const AudioConfigBase& getConfig() const { return mConfig; }
     DataMQ* getDataMQ() const { return mDataMQ.get(); }
+    AudioIoFlags getFlags() const { return mFlags; }
     size_t getFrameSizeBytes() const { return mFrameSizeBytes; }
     ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); }
+    int getSampleRate() const { return mConfig.sampleRate; }
     bool isMmapped() const { return mIsMmapped; }
     int8_t* getMmapMemory() const { return mSharedMemory; }
 
@@ -722,9 +786,11 @@
     }
 
     const size_t mFrameSizeBytes;
+    const AudioConfigBase mConfig;
     std::unique_ptr<CommandMQ> mCommandMQ;
     std::unique_ptr<ReplyMQ> mReplyMQ;
     const size_t mBufferSizeFrames;
+    const AudioIoFlags mFlags;
     std::unique_ptr<DataMQ> mDataMQ;
     const bool mIsMmapped;
     const int32_t mSharedMemoryFd;
@@ -926,12 +992,19 @@
           mDriver(driver),
           mEventReceiver(eventReceiver),
           mIsMmapped(context.isMmapped()),
-          mSharedMemory(context.getMmapMemory()) {}
+          mSharedMemory(context.getMmapMemory()),
+          mIsCompressOffload(context.getFlags().getTag() == AudioIoFlags::output &&
+                             isBitPositionFlagSet(context.getFlags().get<AudioIoFlags::output>(),
+                                                  AudioOutputFlags::COMPRESS_OFFLOAD)),
+          mConfig(context.getConfig()) {}
     StreamContext::CommandMQ* getCommandMQ() const { return mCommandMQ; }
+    const AudioConfigBase& getConfig() const { return mConfig; }
     StreamContext::ReplyMQ* getReplyMQ() const { return mReplyMQ; }
     StreamContext::DataMQ* getDataMQ() const { return mDataMQ; }
     StreamLogicDriver* getDriver() const { return mDriver; }
     StreamEventReceiver* getEventReceiver() const { return mEventReceiver; }
+    int getSampleRate() const { return mConfig.sampleRate; }
+    bool isCompressOffload() const { return mIsCompressOffload; }
     bool isMmapped() const { return mIsMmapped; }
 
     std::string init() override {
@@ -940,6 +1013,10 @@
     }
     const std::vector<int8_t>& getData() const { return mData; }
     void fillData(int8_t filler) { std::fill(mData.begin(), mData.end(), filler); }
+    void loadData(std::ifstream& is, size_t* size) {
+        *size = std::min(*size, mData.size());
+        is.read(reinterpret_cast<char*>(mData.data()), *size);
+    }
     std::optional<StreamDescriptor::Command> maybeGetNextCommand(int* actualSize = nullptr) {
         TransitionTrigger trigger = mDriver->getNextTrigger(mData.size(), actualSize);
         if (StreamEventReceiver::Event* expEvent =
@@ -1002,6 +1079,8 @@
     int mLastEventSeq = StreamEventReceiver::kEventSeqInit;
     const bool mIsMmapped;
     int8_t* mSharedMemory = nullptr;
+    const bool mIsCompressOffload;
+    const AudioConfigBase mConfig;
 };
 
 class StreamReaderLogic : public StreamCommonLogic {
@@ -1102,6 +1181,24 @@
     const std::vector<int8_t>& getData() const { return StreamCommonLogic::getData(); }
 
   protected:
+    std::string init() override {
+        if (auto status = StreamCommonLogic::init(); !status.empty()) return status;
+        if (isCompressOffload()) {
+            const auto info = getMediaFileInfoForConfig(getConfig());
+            if (info) {
+                mCompressedMedia.open(info->path, std::ios::in | std::ios::binary);
+                if (!mCompressedMedia.is_open()) {
+                    return std::string("failed to open media file \"") + info->path + "\"";
+                }
+                mCompressedMedia.seekg(0, mCompressedMedia.end);
+                mCompressedMediaSize = mCompressedMedia.tellg();
+                mCompressedMedia.seekg(0, mCompressedMedia.beg);
+                LOG(DEBUG) << __func__ << ": using media file \"" << info->path << "\", size "
+                           << mCompressedMediaSize << " bytes";
+            }
+        }
+        return "";
+    }
     Status cycle() override {
         if (getDriver()->done()) {
             LOG(DEBUG) << __func__ << ": clean exit";
@@ -1115,13 +1212,31 @@
             LOG(ERROR) << __func__ << ": no next command";
             return Status::ABORT;
         }
-        if (actualSize != 0) {
+        if (actualSize > 0) {
             if (command.getTag() == StreamDescriptor::Command::burst) {
-                fillData(mBurstIteration);
-                if (mBurstIteration < std::numeric_limits<int8_t>::max()) {
-                    mBurstIteration++;
+                if (!isCompressOffload()) {
+                    fillData(mBurstIteration);
+                    if (mBurstIteration < std::numeric_limits<int8_t>::max()) {
+                        mBurstIteration++;
+                    } else {
+                        mBurstIteration = 0;
+                    }
                 } else {
-                    mBurstIteration = 0;
+                    fillData(0);
+                    size_t size = std::min(static_cast<size_t>(actualSize),
+                                           mCompressedMediaSize - mCompressedMediaPos);
+                    loadData(mCompressedMedia, &size);
+                    if (!mCompressedMedia.good()) {
+                        LOG(ERROR) << __func__ << ": read failed";
+                        return Status::ABORT;
+                    }
+                    LOG(DEBUG) << __func__ << ": read from file " << size << " bytes";
+                    mCompressedMediaPos += size;
+                    if (mCompressedMediaPos >= mCompressedMediaSize) {
+                        mCompressedMedia.seekg(0, mCompressedMedia.beg);
+                        mCompressedMediaPos = 0;
+                        LOG(DEBUG) << __func__ << ": rewound to the beginning of the file";
+                    }
                 }
             }
             if (isMmapped() ? !writeDataToMmap() : !writeDataToMQ()) {
@@ -1185,6 +1300,9 @@
 
   private:
     int8_t mBurstIteration = 1;
+    std::ifstream mCompressedMedia;
+    size_t mCompressedMediaSize = 0;
+    size_t mCompressedMediaPos = 0;
 };
 using StreamWriter = StreamWorker<StreamWriterLogic>;
 
@@ -1293,7 +1411,13 @@
         ASSERT_NE(nullptr, mStream) << "port config id " << getPortId();
         EXPECT_GE(mDescriptor.bufferSizeFrames, bufferSizeFrames)
                 << "actual buffer size must be no less than requested";
-        mContext.emplace(mDescriptor);
+        const auto& config = mPortConfig.get();
+        ASSERT_TRUE(config.channelMask.has_value());
+        ASSERT_TRUE(config.format.has_value());
+        ASSERT_TRUE(config.sampleRate.has_value());
+        ASSERT_TRUE(config.flags.has_value());
+        const AudioConfigBase cfg{config.sampleRate->value, *config.channelMask, *config.format};
+        mContext.emplace(mDescriptor, cfg, config.flags.value());
         ASSERT_NO_FATAL_FAILURE(mContext.value().checkIsValid());
     }
     void SetUp(IModule* module, long bufferSizeFrames) {
@@ -1364,7 +1488,7 @@
     aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
     args.portConfigId = portConfig.id;
     args.sourceMetadata = GenerateSourceMetadata(portConfig);
-    args.offloadInfo = ModuleConfig::generateOffloadInfoIfNeeded(portConfig);
+    args.offloadInfo = generateOffloadInfoIfNeeded(portConfig);
     args.bufferSizeFrames = bufferSizeFrames;
     auto callback = ndk::SharedRefBase::make<DefaultStreamCallback>();
     args.callback = callback;
@@ -3192,10 +3316,12 @@
                                     {AudioInputFlags::MMAP_NOIRQ, AudioInputFlags::VOIP_TX,
                                      AudioInputFlags::HW_HOTWORD, AudioInputFlags::HOTWORD_TAP})) ||
            (portConfig.flags.value().getTag() == AudioIoFlags::output &&
-            isAnyBitPositionFlagSet(
-                    portConfig.flags.value().template get<AudioIoFlags::output>(),
-                    {AudioOutputFlags::MMAP_NOIRQ, AudioOutputFlags::VOIP_RX,
-                     AudioOutputFlags::COMPRESS_OFFLOAD, AudioOutputFlags::INCALL_MUSIC}));
+            (isAnyBitPositionFlagSet(portConfig.flags.value().template get<AudioIoFlags::output>(),
+                                     {AudioOutputFlags::MMAP_NOIRQ, AudioOutputFlags::VOIP_RX,
+                                      AudioOutputFlags::INCALL_MUSIC}) ||
+             (isBitPositionFlagSet(portConfig.flags.value().template get<AudioIoFlags::output>(),
+                                   AudioOutputFlags::COMPRESS_OFFLOAD) &&
+              !getMediaFileInfoForConfig(portConfig))));
 }
 
 // Certain types of devices can not be used without special preconditions.
@@ -3863,7 +3989,7 @@
     aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
     args.portConfigId = portConfig.id;
     args.sourceMetadata = GenerateSourceMetadata(portConfig);
-    args.offloadInfo = ModuleConfig::generateOffloadInfoIfNeeded(portConfig);
+    args.offloadInfo = generateOffloadInfoIfNeeded(portConfig);
     args.bufferSizeFrames = stream.getPatch().minimumStreamBufferSizeFrames;
     aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
     EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openOutputStream(args, &ret))
@@ -4185,18 +4311,6 @@
                     std::get<NAMED_CMD_DELAY_MS>(std::get<PARAM_CMD_SEQ>(GetParam()));
             ASSERT_NO_FATAL_FAILURE(delayTransientStates.SetUp(module.get()));
             ASSERT_NO_FATAL_FAILURE(runStreamIoCommands(portConfig));
-            if (aidlVersion >= kAidlVersion3 && isNonBlocking && !IOTraits<Stream>::is_input) {
-                // Also try running the same sequence with "aosp.forceDrainToDraining" set.
-                // This will only work with the default implementation. When it works, the stream
-                // tries always to move to the 'DRAINING' state after an "early notify" drain.
-                // This helps to check more paths for our test scenarios.
-                WithModuleParameter forceDrainToDraining("aosp.forceDrainToDraining",
-                                                         Boolean{true});
-                if (forceDrainToDraining.SetUpNoChecks(module.get(), true /*failureExpected*/)
-                            .isOk()) {
-                    ASSERT_NO_FATAL_FAILURE(runStreamIoCommands(portConfig));
-                }
-            }
             if (isNonBlocking) {
                 // Also try running the same sequence with "aosp.forceTransientBurst" set.
                 // This will only work with the default implementation. When it works, the stream
@@ -4744,9 +4858,14 @@
 std::shared_ptr<StateSequence> makeDrainEarlyOutCommands() {
     using State = StreamDescriptor::State;
     auto d = std::make_unique<StateDag>();
-    StateDag::Node last = d->makeFinalNode(State::IDLE);
-    StateDag::Node draining = d->makeNode(State::DRAINING, kDrainReadyEvent, last);
-    draining.children().push_back(d->makeNode(State::DRAINING, kGetStatusCommand, last));
+    // In the "early notify" case, the transition to the `IDLE` state following
+    // the 'onDrainReady' event can take some time. Waiting for an arbitrary amount
+    // of time may make the test fragile. Instead, for successful completion
+    // is registered if the stream has entered `IDLE` or `DRAINING` state.
+    StateDag::Node lastIdle = d->makeFinalNode(State::IDLE);
+    StateDag::Node lastDraining = d->makeFinalNode(State::DRAINING);
+    StateDag::Node draining =
+            d->makeNode(State::DRAINING, kDrainReadyEvent, lastIdle, lastDraining);
     StateDag::Node active = d->makeNode(State::ACTIVE, kDrainOutEarlyCommand, draining);
     StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
     idle.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, active));
diff --git a/audio/aidl/vts/VtsHalAudioCoreTargetTestTemplate.xml b/audio/aidl/vts/VtsHalAudioCoreTargetTestTemplate.xml
new file mode 100644
index 0000000..94db58d
--- /dev/null
+++ b/audio/aidl/vts/VtsHalAudioCoreTargetTestTemplate.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2025 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.
+-->
+<configuration description="Runs {MODULE}.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-native" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.StopServicesSetup"/>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="setprop vts.native_server.on 1"/>
+        <option name="teardown-command" value="setprop vts.native_server.on 0"/>
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}" />
+        <option name="push" value="sine882hz_44100_3s.ape->/data/local/tmp/sine882hz_44100_3s.ape" />
+        <option name="push" value="sine960hz_48000_3s.ape->/data/local/tmp/sine960hz_48000_3s.ape" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="{MODULE}" />
+        <option name="native-test-timeout" value="30m" />
+    </test>
+</configuration>
diff --git a/audio/aidl/vts/VtsHalAudioTargetTestTemplate.xml b/audio/aidl/vts/VtsHalAudioEffectTargetTestTemplate.xml
similarity index 100%
rename from audio/aidl/vts/VtsHalAudioTargetTestTemplate.xml
rename to audio/aidl/vts/VtsHalAudioEffectTargetTestTemplate.xml
diff --git a/audio/aidl/vts/data/sine882hz_44100_3s.ape b/audio/aidl/vts/data/sine882hz_44100_3s.ape
new file mode 100644
index 0000000..1cefb15
--- /dev/null
+++ b/audio/aidl/vts/data/sine882hz_44100_3s.ape
Binary files differ
diff --git a/audio/aidl/vts/data/sine960hz_48000_3s.ape b/audio/aidl/vts/data/sine960hz_48000_3s.ape
new file mode 100644
index 0000000..149c42a
--- /dev/null
+++ b/audio/aidl/vts/data/sine960hz_48000_3s.ape
Binary files differ