Merge "Add AcqiredInfo.RETRYING_CAPTURE to IFingerprint" into sc-dev am: 904b4f8a1e

Original change: https://googleplex-android-review.googlesource.com/c/platform/hardware/interfaces/+/13997438

Change-Id: I9c91625789cb56591b8046f3f5bc8ed9d50ddef8
diff --git a/automotive/can/1.0/default/libnl++/Android.bp b/automotive/can/1.0/default/libnl++/Android.bp
index 790adcd..2ebd1b4 100644
--- a/automotive/can/1.0/default/libnl++/Android.bp
+++ b/automotive/can/1.0/default/libnl++/Android.bp
@@ -35,6 +35,7 @@
         "protocols/generic/Generic.cpp",
         "protocols/generic/GenericMessageBase.cpp",
         "protocols/generic/Unknown.cpp",
+        "protocols/generic/families/Mac80211hwsim.cpp",
         "protocols/generic/families/Nl80211.cpp",
         "protocols/route/Link.cpp",
         "protocols/route/Route.cpp",
@@ -42,6 +43,7 @@
         "protocols/MessageDefinition.cpp",
         "protocols/NetlinkProtocol.cpp",
         "protocols/all.cpp",
+        "protocols/structs.cpp",
         "Attributes.cpp",
         "MessageFactory.cpp",
         "MessageMutator.cpp",
diff --git a/automotive/can/1.0/default/libnl++/MessageMutator.cpp b/automotive/can/1.0/default/libnl++/MessageMutator.cpp
index 00b48a6..de2a2b1 100644
--- a/automotive/can/1.0/default/libnl++/MessageMutator.cpp
+++ b/automotive/can/1.0/default/libnl++/MessageMutator.cpp
@@ -19,7 +19,7 @@
 namespace android::nl {
 
 MessageMutator::MessageMutator(nlmsghdr* buffer, size_t totalLen)
-    : mConstBuffer(buffer, totalLen), mMutableBuffer(buffer) {
+    : mMutableBuffer(buffer), mTotalLen(totalLen) {
     CHECK(totalLen >= sizeof(nlmsghdr));
 }
 
@@ -27,8 +27,12 @@
     return mMutableBuffer;
 }
 
+Buffer<nlmsghdr> MessageMutator::constBuffer() const {
+    return {mMutableBuffer, mTotalLen};
+}
+
 MessageMutator::operator Buffer<nlmsghdr>() const {
-    return mConstBuffer;
+    return constBuffer();
 }
 
 uint64_t MessageMutator::read(Buffer<nlattr> attr) const {
@@ -37,7 +41,8 @@
 
 void MessageMutator::write(Buffer<nlattr> attr, uint64_t val) const {
     const auto attrData = attr.data<uint64_t>();
-    const auto offset = mConstBuffer.getOffset(attrData);
+    // TODO(b/177251183): deduplicate this code against fragment()
+    const auto offset = constBuffer().getOffset(attrData);
     CHECK(offset.has_value()) << "Trying to write attribute that's not a member of this message";
 
     const auto writeableBuffer = reinterpret_cast<uint8_t*>(mMutableBuffer) + *offset;
@@ -47,4 +52,40 @@
     memcpy(writeableBuffer, &val, std::min(sizeof(val), attrSize));
 }
 
+MessageMutator MessageMutator::fragment(Buffer<nlmsghdr> buf) const {
+    const auto offset = constBuffer().getOffset(buf);
+    CHECK(offset.has_value()) << "Trying to modify a fragment outside of buffer range";
+
+    const auto writeableBuffer = reinterpret_cast<nlmsghdr*>(uintptr_t(mMutableBuffer) + *offset);
+    const auto len = buf.getRaw().len();
+    CHECK(len <= mTotalLen - *offset);
+
+    return {writeableBuffer, len};
+}
+
+MessageMutator::iterator MessageMutator::begin() const {
+    return {*this, constBuffer().begin()};
+}
+
+MessageMutator::iterator MessageMutator::end() const {
+    return {*this, constBuffer().end()};
+}
+
+MessageMutator::iterator::iterator(const MessageMutator& container,
+                                   Buffer<nlmsghdr>::iterator current)
+    : mContainer(container), mCurrent(current) {}
+
+MessageMutator::iterator MessageMutator::iterator::operator++() {
+    ++mCurrent;
+    return *this;
+}
+
+bool MessageMutator::iterator::operator==(const iterator& other) const {
+    return other.mCurrent == mCurrent;
+}
+
+const MessageMutator MessageMutator::iterator::operator*() const {
+    return mContainer.fragment(*mCurrent);
+}
+
 }  // namespace android::nl
diff --git a/automotive/can/1.0/default/libnl++/include/libnl++/Buffer.h b/automotive/can/1.0/default/libnl++/include/libnl++/Buffer.h
index d759a0a..4cabb9a 100644
--- a/automotive/can/1.0/default/libnl++/include/libnl++/Buffer.h
+++ b/automotive/can/1.0/default/libnl++/include/libnl++/Buffer.h
@@ -138,7 +138,7 @@
     class raw_iterator : public iterator {
       public:
         iterator operator++() {
-            this->mCurrent.mData++;  // ignore alignment
+            ++this->mCurrent.mData;  // ignore alignment
             return *this;
         }
         const T& operator*() const { return *this->mCurrent.mData; }
diff --git a/automotive/can/1.0/default/libnl++/include/libnl++/Message.h b/automotive/can/1.0/default/libnl++/include/libnl++/Message.h
index 50b3c4b..29f397d 100644
--- a/automotive/can/1.0/default/libnl++/include/libnl++/Message.h
+++ b/automotive/can/1.0/default/libnl++/include/libnl++/Message.h
@@ -34,7 +34,7 @@
  * a single instance can only be used by a single thread - the one owning the underlying buffer).
  */
 template <typename T>
-class Message {
+class Message : public Buffer<nlmsghdr> {
   public:
     /**
      * Validate buffer contents as a message carrying T data and create instance of parsed message.
@@ -51,7 +51,7 @@
 
         const auto attributes = buf.data<nlattr>(sizeof(T));
 
-        return Message<T>(nlHeader, dataHeader, attributes);
+        return Message<T>(buf, nlHeader, dataHeader, attributes);
     }
 
     /**
@@ -94,8 +94,9 @@
     const T* operator->() const { return &data; }
 
   private:
-    Message(const nlmsghdr& nlHeader, const T& dataHeader, Attributes attributes)
-        : header(nlHeader), data(dataHeader), attributes(attributes) {}
+    Message(Buffer<nlmsghdr> buffer, const nlmsghdr& nlHeader, const T& dataHeader,
+            Attributes attributes)
+        : Buffer<nlmsghdr>(buffer), header(nlHeader), data(dataHeader), attributes(attributes) {}
 };
 
 }  // namespace android::nl
diff --git a/automotive/can/1.0/default/libnl++/include/libnl++/MessageMutator.h b/automotive/can/1.0/default/libnl++/include/libnl++/MessageMutator.h
index 7d495e9..baadc44 100644
--- a/automotive/can/1.0/default/libnl++/include/libnl++/MessageMutator.h
+++ b/automotive/can/1.0/default/libnl++/include/libnl++/MessageMutator.h
@@ -53,9 +53,27 @@
      */
     void write(Buffer<nlattr> attr, uint64_t val) const;
 
+    class iterator {
+      public:
+        iterator(const MessageMutator& container, Buffer<nlmsghdr>::iterator current);
+
+        iterator operator++();
+        bool operator==(const iterator& other) const;
+        const MessageMutator operator*() const;
+
+      protected:
+        const MessageMutator& mContainer;
+        Buffer<nlmsghdr>::iterator mCurrent;
+    };
+    iterator begin() const;
+    iterator end() const;
+
   private:
-    const Buffer<nlmsghdr> mConstBuffer;
     nlmsghdr* mMutableBuffer;
+    size_t mTotalLen;
+
+    Buffer<nlmsghdr> constBuffer() const;
+    MessageMutator fragment(Buffer<nlmsghdr> buf) const;
 };
 
 }  // namespace android::nl
diff --git a/automotive/can/1.0/default/libnl++/include/libnl++/generic/families/mac80211_hwsim.h b/automotive/can/1.0/default/libnl++/include/libnl++/generic/families/mac80211_hwsim.h
new file mode 100644
index 0000000..9b811f8
--- /dev/null
+++ b/automotive/can/1.0/default/libnl++/include/libnl++/generic/families/mac80211_hwsim.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+// API definitions from kernel drivers/net/wireless/mac80211_hwsim.h
+
+#define BIT(n) (1 << (n))
+
+enum hwsim_tx_control_flags {
+    HWSIM_TX_CTL_REQ_TX_STATUS = BIT(0),
+    HWSIM_TX_CTL_NO_ACK = BIT(1),
+    HWSIM_TX_STAT_ACK = BIT(2),
+};
+
+enum {
+    HWSIM_CMD_UNSPEC,
+    HWSIM_CMD_REGISTER,
+    HWSIM_CMD_FRAME,
+    HWSIM_CMD_TX_INFO_FRAME,
+    HWSIM_CMD_NEW_RADIO,
+    HWSIM_CMD_DEL_RADIO,
+    HWSIM_CMD_GET_RADIO,
+    HWSIM_CMD_ADD_MAC_ADDR,
+    HWSIM_CMD_DEL_MAC_ADDR,
+};
+
+enum {
+    HWSIM_ATTR_UNSPEC,
+    HWSIM_ATTR_ADDR_RECEIVER,
+    HWSIM_ATTR_ADDR_TRANSMITTER,
+    HWSIM_ATTR_FRAME,
+    HWSIM_ATTR_FLAGS,
+    HWSIM_ATTR_RX_RATE,
+    HWSIM_ATTR_SIGNAL,
+    HWSIM_ATTR_TX_INFO,
+    HWSIM_ATTR_COOKIE,
+    HWSIM_ATTR_CHANNELS,
+    HWSIM_ATTR_RADIO_ID,
+    HWSIM_ATTR_REG_HINT_ALPHA2,
+    HWSIM_ATTR_REG_CUSTOM_REG,
+    HWSIM_ATTR_REG_STRICT_REG,
+    HWSIM_ATTR_SUPPORT_P2P_DEVICE,
+    HWSIM_ATTR_USE_CHANCTX,
+    HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE,
+    HWSIM_ATTR_RADIO_NAME,
+    HWSIM_ATTR_NO_VIF,
+    HWSIM_ATTR_FREQ,
+    HWSIM_ATTR_PAD,
+    HWSIM_ATTR_TX_INFO_FLAGS,
+    HWSIM_ATTR_PERM_ADDR,
+    HWSIM_ATTR_IFTYPE_SUPPORT,
+    HWSIM_ATTR_CIPHER_SUPPORT,
+};
+
+struct hwsim_tx_rate {
+    int8_t idx;
+    uint8_t count;
+} __packed;
+static_assert(sizeof(hwsim_tx_rate) == 2);
+
+#undef BIT
diff --git a/automotive/can/1.0/default/libnl++/printer.cpp b/automotive/can/1.0/default/libnl++/printer.cpp
index f08897e..d540482 100644
--- a/automotive/can/1.0/default/libnl++/printer.cpp
+++ b/automotive/can/1.0/default/libnl++/printer.cpp
@@ -154,16 +154,19 @@
     }
 }
 
-std::string toString(const Buffer<nlmsghdr> hdr, int protocol, bool printPayload) {
-    if (!hdr.firstOk()) return "nlmsg{buffer overflow}";
+static void toStream(std::stringstream& ss, const Buffer<nlmsghdr> hdr, int protocol,
+                     bool printPayload) {
+    if (!hdr.firstOk()) {
+        ss << "nlmsg{buffer overflow}";
+        return;
+    }
 
-    std::stringstream ss;
     ss << std::setfill('0');
 
     auto protocolMaybe = protocols::get(protocol);
     if (!protocolMaybe.has_value()) {
         ss << "nlmsg{protocol=" << protocol << "}";
-        return ss.str();
+        return;
     }
     protocols::NetlinkProtocol& protocolDescr = *protocolMaybe;
 
@@ -187,7 +190,7 @@
     ss << ", crc=" << std::hex << std::setw(4) << crc16(hdr.data<uint8_t>()) << std::dec;
     ss << '}';
 
-    if (!printPayload) return ss.str();
+    if (!printPayload) return;
     ss << ' ';
 
     if (!msgDescMaybe.has_value()) {
@@ -210,6 +213,17 @@
     }
 
     ss << "}";
+}
+
+std::string toString(const Buffer<nlmsghdr> hdrs, int protocol, bool printPayload) {
+    std::stringstream ss;
+    bool first = true;
+    for (const auto hdr : hdrs) {
+        if (!first) ss << std::endl;
+        first = false;
+
+        toStream(ss, hdr, protocol, printPayload);
+    }
 
     return ss.str();
 }
diff --git a/automotive/can/1.0/default/libnl++/protocols/generic/Ctrl.cpp b/automotive/can/1.0/default/libnl++/protocols/generic/Ctrl.cpp
index 1e1ad12..478c383 100644
--- a/automotive/can/1.0/default/libnl++/protocols/generic/Ctrl.cpp
+++ b/automotive/can/1.0/default/libnl++/protocols/generic/Ctrl.cpp
@@ -16,6 +16,7 @@
 
 #include "Ctrl.h"
 
+#include "families/Mac80211hwsim.h"
 #include "families/Nl80211.h"
 
 #include <libnl++/Message.h>
@@ -68,12 +69,15 @@
     const auto familyId = msg.attributes.get<uint16_t>(CTRL_ATTR_FAMILY_ID);
     const auto familyName = msg.attributes.get<std::string>(CTRL_ATTR_FAMILY_NAME);
 
-    /* For now, we support just a single family. But if you add more, please define proper
+    /* For now, we support just two families. But if you add more, please define proper
      * abstraction and not hardcode every name and class here.
      */
     if (familyName == "nl80211") {
         mFamilyRegister[familyId] = std::make_shared<families::Nl80211>(familyId);
     }
+    if (familyName == "MAC80211_HWSIM") {
+        mFamilyRegister[familyId] = std::make_shared<families::Mac80211hwsim>(familyId);
+    }
 }
 
 }  // namespace android::nl::protocols::generic
diff --git a/automotive/can/1.0/default/libnl++/protocols/generic/GenericMessageBase.cpp b/automotive/can/1.0/default/libnl++/protocols/generic/GenericMessageBase.cpp
index b7b811b..f92d6c0 100644
--- a/automotive/can/1.0/default/libnl++/protocols/generic/GenericMessageBase.cpp
+++ b/automotive/can/1.0/default/libnl++/protocols/generic/GenericMessageBase.cpp
@@ -40,9 +40,9 @@
 
     ss << "genlmsghdr{";
     if (commandName.has_value()) {
-        ss << "cmd=" << unsigned(data.cmd);
-    } else {
         ss << "cmd=" << *commandName;
+    } else {
+        ss << "cmd=" << unsigned(data.cmd);
     }
     ss << ", version=" << unsigned(data.version);
     if (data.reserved != 0) ss << ", reserved=" << data.reserved;
diff --git a/automotive/can/1.0/default/libnl++/protocols/generic/families/Mac80211hwsim.cpp b/automotive/can/1.0/default/libnl++/protocols/generic/families/Mac80211hwsim.cpp
new file mode 100644
index 0000000..f85309e
--- /dev/null
+++ b/automotive/can/1.0/default/libnl++/protocols/generic/families/Mac80211hwsim.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 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 "Mac80211hwsim.h"
+
+#include "../../structs.h"
+#include "common.h"
+
+#include <libnl++/generic/families/mac80211_hwsim.h>
+
+namespace android::nl::protocols::generic::families {
+
+using DataType = AttributeDefinition::DataType;
+using Flags = AttributeDefinition::Flags;
+
+static void hwsim_tx_rateToStream(std::stringstream& ss, const Buffer<nlattr> attr);
+
+static const FlagsMap txControlFlags{
+        {HWSIM_TX_CTL_REQ_TX_STATUS, "REQ_TX"},
+        {HWSIM_TX_CTL_NO_ACK, "NO_ACK"},
+        {HWSIM_TX_STAT_ACK, "ACK"},
+};
+
+// clang-format off
+Mac80211hwsim::Mac80211hwsim(nlmsgtype_t familyId) : GenericMessageBase(familyId, "hwsim", {
+    {HWSIM_CMD_UNSPEC, "UNSPEC"},
+    {HWSIM_CMD_REGISTER, "REGISTER"},
+    {HWSIM_CMD_FRAME, "FRAME"},
+    {HWSIM_CMD_TX_INFO_FRAME, "TX_INFO_FRAME"},
+    {HWSIM_CMD_NEW_RADIO, "NEW_RADIO"},
+    {HWSIM_CMD_DEL_RADIO, "DEL_RADIO"},
+    {HWSIM_CMD_GET_RADIO, "GET_RADIO"},
+    {HWSIM_CMD_ADD_MAC_ADDR, "ADD_MAC_ADDR"},
+    {HWSIM_CMD_DEL_MAC_ADDR, "DEL_MAC_ADDR"},
+}, {
+    {HWSIM_ATTR_UNSPEC, {"UNSPEC"}},
+    {HWSIM_ATTR_ADDR_RECEIVER, {"ADDR_RECEIVER", DataType::Struct, hwaddrToStream}},
+    {HWSIM_ATTR_ADDR_TRANSMITTER, {"ADDR_TRANSMITTER", DataType::Struct, hwaddrToStream}},
+    {HWSIM_ATTR_FRAME, {"FRAME", DataType::Raw, AttributeMap{}, Flags::Verbose}},
+    {HWSIM_ATTR_FLAGS, {"FLAGS", DataType::Struct, flagsToStream(txControlFlags)}},
+    {HWSIM_ATTR_RX_RATE, {"RX_RATE", DataType::Uint}},
+    {HWSIM_ATTR_SIGNAL, {"SIGNAL", DataType::Uint}},
+    {HWSIM_ATTR_TX_INFO, {"TX_INFO", DataType::Struct, hwsim_tx_rateToStream}},
+    {HWSIM_ATTR_COOKIE, {"COOKIE", DataType::Uint}},
+    {HWSIM_ATTR_CHANNELS, {"CHANNELS", DataType::Uint}},
+    {HWSIM_ATTR_RADIO_ID, {"RADIO_ID", DataType::Uint}},
+    {HWSIM_ATTR_REG_HINT_ALPHA2, {"REG_HINT_ALPHA2", DataType::String}},
+    {HWSIM_ATTR_REG_CUSTOM_REG, {"REG_CUSTOM_REG", DataType::Uint}},
+    {HWSIM_ATTR_REG_STRICT_REG, {"REG_STRICT_REG", DataType::Flag}},
+    {HWSIM_ATTR_SUPPORT_P2P_DEVICE, {"SUPPORT_P2P_DEVICE", DataType::Flag}},
+    {HWSIM_ATTR_USE_CHANCTX, {"USE_CHANCTX", DataType::Flag}},
+    {HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE, {"DESTROY_RADIO_ON_CLOSE", DataType::Flag}},
+    {HWSIM_ATTR_RADIO_NAME, {"RADIO_NAME", DataType::String}},
+    {HWSIM_ATTR_NO_VIF, {"NO_VIF", DataType::Flag}},
+    {HWSIM_ATTR_FREQ, {"FREQ", DataType::Uint}},
+    {HWSIM_ATTR_PAD, {"PAD", DataType::Uint}},
+    {HWSIM_ATTR_TX_INFO_FLAGS, {"TX_INFO_FLAGS"}},  // hwsim_tx_rate_flag
+    {HWSIM_ATTR_PERM_ADDR, {"PERM_ADDR"}},
+    {HWSIM_ATTR_IFTYPE_SUPPORT, {"IFTYPE_SUPPORT", DataType::Uint}},  // NL80211_IFTYPE_STATION etc
+    {HWSIM_ATTR_CIPHER_SUPPORT, {"CIPHER_SUPPORT", DataType::Struct, arrayToStream<int32_t>}},
+}) {}
+// clang-format on
+
+static void hwsim_tx_rateToStream(std::stringstream& ss, const Buffer<nlattr> attr) {
+    ss << '{';
+    bool first = true;
+    for (const auto rate : attr.data<hwsim_tx_rate>().getRaw()) {
+        if (rate.idx == -1) continue;
+
+        ss << (int)rate.idx << ": " << (unsigned)rate.count;
+
+        if (!first) ss << ", ";
+        first = false;
+    }
+    ss << '}';
+}
+
+}  // namespace android::nl::protocols::generic::families
diff --git a/automotive/can/1.0/default/libnl++/protocols/generic/families/Mac80211hwsim.h b/automotive/can/1.0/default/libnl++/protocols/generic/families/Mac80211hwsim.h
new file mode 100644
index 0000000..c01eb93
--- /dev/null
+++ b/automotive/can/1.0/default/libnl++/protocols/generic/families/Mac80211hwsim.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#pragma once
+
+#include "../GenericMessageBase.h"
+
+namespace android::nl::protocols::generic::families {
+
+class Mac80211hwsim : public GenericMessageBase {
+  public:
+    Mac80211hwsim(nlmsgtype_t familyId);
+};
+
+}  // namespace android::nl::protocols::generic::families
diff --git a/automotive/can/1.0/default/libnl++/protocols/structs.cpp b/automotive/can/1.0/default/libnl++/protocols/structs.cpp
new file mode 100644
index 0000000..8ff71f0
--- /dev/null
+++ b/automotive/can/1.0/default/libnl++/protocols/structs.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 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 "structs.h"
+
+#include <iomanip>
+
+namespace android::nl::protocols {
+
+AttributeDefinition::ToStream flagsToStream(FlagsMap flags) {
+    return [flags](std::stringstream& ss, const Buffer<nlattr> attr) {
+        auto val = attr.data<uint64_t>().copyFirst();
+
+        bool first = true;
+        for (const auto& [flag, name] : flags) {
+            if ((val & flag) != flag) continue;
+            val &= ~flag;
+
+            if (!first) ss << '|';
+            first = false;
+
+            ss << name;
+        }
+
+        if (val == 0) return;
+
+        if (!first) ss << '|';
+        ss << std::hex << val << std::dec;
+    };
+}
+
+void hwaddrToStream(std::stringstream& ss, const Buffer<nlattr> attr) {
+    ss << std::hex;
+    bool first = true;
+    for (const auto byte : attr.data<uint8_t>().getRaw()) {
+        if (!first) ss << ':';
+        first = false;
+
+        ss << std::setw(2) << unsigned(byte);
+    }
+    ss << std::dec;
+}
+
+}  // namespace android::nl::protocols
diff --git a/automotive/can/1.0/default/libnl++/protocols/structs.h b/automotive/can/1.0/default/libnl++/protocols/structs.h
index 44c17b8..f3a8c44 100644
--- a/automotive/can/1.0/default/libnl++/protocols/structs.h
+++ b/automotive/can/1.0/default/libnl++/protocols/structs.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include "MessageDefinition.h"
+
 #include <sstream>
 
 namespace android::nl::protocols {
@@ -30,4 +32,9 @@
     ss << '}';
 }
 
+typedef std::map<uint64_t, std::string> FlagsMap;
+AttributeDefinition::ToStream flagsToStream(FlagsMap flags);
+
+void hwaddrToStream(std::stringstream& ss, const Buffer<nlattr> attr);
+
 }  // namespace android::nl::protocols
diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml
index c656af2..bb53654 100644
--- a/compatibility_matrices/compatibility_matrix.current.xml
+++ b/compatibility_matrices/compatibility_matrix.current.xml
@@ -521,7 +521,7 @@
     </hal>
     <hal format="hidl" optional="true">
         <name>android.hardware.soundtrigger</name>
-        <version>2.3</version>
+        <version>2.3-4</version>
         <interface>
             <name>ISoundTriggerHw</name>
             <instance>default</instance>
diff --git a/drm/1.0/default/CryptoPlugin.cpp b/drm/1.0/default/CryptoPlugin.cpp
index e6d4e84..17a2f24 100644
--- a/drm/1.0/default/CryptoPlugin.cpp
+++ b/drm/1.0/default/CryptoPlugin.cpp
@@ -79,7 +79,7 @@
             }
         }
 
-        android::CryptoPlugin::Mode legacyMode;
+        android::CryptoPlugin::Mode legacyMode = android::CryptoPlugin::kMode_Unencrypted;
         switch(mode) {
         case Mode::UNENCRYPTED:
             legacyMode = android::CryptoPlugin::kMode_Unencrypted;
@@ -146,7 +146,10 @@
                 return Void();
             }
 
-            if (destBuffer.offset + destBuffer.size > destBase->getSize()) {
+            size_t totalSize = 0;
+            if (__builtin_add_overflow(destBuffer.offset, destBuffer.size, &totalSize) ||
+                totalSize > destBase->getSize()) {
+                android_errorWriteLog(0x534e4554, "176496353");
                 _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "invalid buffer size");
                 return Void();
             }
@@ -157,7 +160,7 @@
             }
 
             base = static_cast<uint8_t *>(static_cast<void *>(destBase->getPointer()));
-            destPtr = static_cast<void *>(base + destination.nonsecureMemory.offset);
+            destPtr = static_cast<void*>(base + destination.nonsecureMemory.offset);
         } else if (destination.type == BufferType::NATIVE_HANDLE) {
             if (!secure) {
                 _hidl_cb(Status::BAD_VALUE, 0, "native handle destination must be secure");
diff --git a/soundtrigger/2.4/Android.bp b/soundtrigger/2.4/Android.bp
new file mode 100644
index 0000000..44befc3
--- /dev/null
+++ b/soundtrigger/2.4/Android.bp
@@ -0,0 +1,30 @@
+// This file is autogenerated by hidl-gen -Landroidbp.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "hardware_interfaces_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+hidl_interface {
+    name: "android.hardware.soundtrigger@2.4",
+    root: "android.hardware",
+    srcs: [
+        "ISoundTriggerHw.hal",
+        "ISoundTriggerHwCallback.hal",
+        "ISoundTriggerHwGlobalCallback.hal",
+    ],
+    interfaces: [
+        "android.hardware.audio.common@2.0",
+        "android.hardware.soundtrigger@2.0",
+        "android.hardware.soundtrigger@2.1",
+        "android.hardware.soundtrigger@2.2",
+        "android.hardware.soundtrigger@2.3",
+        "android.hidl.base@1.0",
+        "android.hidl.safe_union@1.0",
+    ],
+    gen_java: true,
+}
diff --git a/soundtrigger/2.4/ISoundTriggerHw.hal b/soundtrigger/2.4/ISoundTriggerHw.hal
new file mode 100644
index 0000000..fd39303
--- /dev/null
+++ b/soundtrigger/2.4/ISoundTriggerHw.hal
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2021 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.
+ */
+
+package android.hardware.soundtrigger@2.4;
+
+import @2.0::SoundModelHandle;
+import @2.1::ISoundTriggerHw.SoundModel;
+import @2.1::ISoundTriggerHw.PhraseSoundModel;
+import @2.3::ISoundTriggerHw;
+import @2.3::RecognitionConfig;
+import ISoundTriggerHwCallback;
+import ISoundTriggerHwGlobalCallback;
+
+/**
+ * SoundTrigger HAL interface. Used for hardware recognition of hotwords
+ * and other sounds.
+ *
+ * Important notes about the threading model:
+ * ==========================================
+ * Both this interface and the corresponding callback interface use a synchronized calling
+ * convention. This model comes with some advantages, but also with some risks of deadlocks if the
+ * implementation does not handle this correctly. Please consider the following:
+ * - After stopRecognition() returns no more recognition events for that model may be sent. This
+ *   implies that any queues holding such events must be flushed before the call returns and that
+ *   may imply that callback from the HAL to the client are done while stopRecognition() is blocked.
+ *   This is OK, and supported by the framework.
+ * - Similarly, the same relationship applies between unloadModel() and subsequent callbacks to
+ *   modelUnloaded().
+ * - Other than these two cases, calls into the HAL *MAY NOT* block on callbacks from the HAL, or
+ *   else deadlock conditions may result, which may be handled by rebooting of the HAL process and
+ *   cause service outages.
+ *
+ * Similarly, it is expected that a single call to startRecognition() generates at most one event
+ * (the model automatically becomes stopped when the event occurs, until explicitly started again)
+ * and that after a modelUnloaded() event no more events would be sent regarding the model.
+ * Note that a getModelState() call may generate a recognition event, but this event DOES NOT modify
+ * the model state - the model remains started.
+ *
+ * The HAL is expected to correctly handle a stopRecognition() call even after it sent an event
+ * indicating that recognition is stopped and an unloadModel() call even after it sent an event
+ * indicating that it has been unloaded. This is required in order to prevent race conditions
+ * between these calls. This also implies that model handles should generally not be reused until
+ * explicitly unloaded. To avoid the rare possibility of running out of handles, the framework will
+ * call unloadModel() on models that have been preemptively unloaded by the HAL.
+ *
+ * Due to the asynchronous nature of recognition events and preemptive model unloading, the HAL must
+ * correctly handle requests that would have been valid before an event has been delivered, but
+ * became moot as result of the event. Namely:
+ * - stopRecognition() may be called on a model that has already delivered an event and became
+ *   inactive as a result. The HAL must return a successful return code in this case.
+ * - Furthermore, if a model is preemptively unloaded after it triggers (typically, this would
+ *   happen when it is first aborted and immediately preemptively unloaded), stopRecognition() may
+ *   be called on it. The HAL must return a successful return code in this case.
+ * - startRecognition() may be called on a model that has been preemptively unloaded. In this case,
+ *   the HAL must return -EBUSY to indicate that the operation is temporarily unsuccessful.
+ * - unloadSoundModel() may be called on a model that has been preemptively unloaded. The HAL must
+ *   return a successful return code in this case.
+ *
+ * Important notes about resource constraints and concurrency
+ * =========================================================
+ * Up until this version, the framework would enforce concurrency constraints expressed by the
+ * Properties presented by the soundtrigger instance. These include constraints on the maximum
+ * amount of models that can be loaded at the same time and on running recognition while capturing
+ * from the microphone.
+ * This version changes the approach for how these constraints are modeled, both offering the HAL
+ * implementation more flexibility and simplifying the framework's job in enforcing these
+ * limitations. Note that there is no change for how the framework behaves with earlier versions,
+ * everything described below only applies to this version and onward.
+ * The way this is achieved is as following:
+ * - The framework will no longer enforce constraints on concurrent loading of models, as expressed
+ *   in the Properties.maxSoundModels field (this property is merely a hint at this point and may be
+ *   deprecated in the future.
+ * - The framework will no longer enforce constraints on concurrency of audio recording and
+ *   soundtrigger operation, as expressed in the Properties.concurrentCapture field (this property
+ *   is merely a hint at this point and may be deprecated in the future).
+ * - The framework will no longer enforce constraints on concurrent loading of models, as expressed
+ *   in the Properties (these properties are merely hints at this point and may be deprecated in the
+ *   future.
+ * - The HAL implementation is free to reject starting of any model at any time by having the
+ *   respective start*() method return -EBUSY.
+ * - The HAL implementation is free to reject loading of any model at any time by having the
+ *   respective load*() method return -EBUSY.
+ * - The HAL implementation is free to preemptively stop a previously started model at its own
+ *   discretion (for example, if a higher priority use-case which cannot coexist with detection
+ *   has been requested). The HAL must notify the framework of the preemption by sending a
+ *   recognition event with an `ABORT` status. The implementation must NOT attempt to restart the
+ *   recognition automatically when conditions change.
+ * - The HAL implementation is free to preemptively unload a previously loaded model at its own
+ *   discretion (for example, if a higher-priority model is being loaded and the two cannot
+ *   coexist). When doing so, it must first abort the detection if active (as per above) and then
+ *   notify the framework of the unload using the newly added modelUnloaded callback.
+ * - When conditions change, such that a model that couldn't previously load or start or that had
+ *   previously been preemptively stopped or unloaded, the HAL must notify the framework via the
+ *   newly added tryAgain() callback. This callback is not a guarantee that any operation would now
+ *   succeed, but merely a hint that retrying something that had previously failed, now MAY succeed.
+ *   Until this callback arrives, the framework may assume that any operation that had previously
+ *   failed or aborted would still fail if retried, so the implementation should not forget to
+ *   deliver it. There are no guarantees regarding how the framework may respond to this event and
+ *   the order in which it may choose to reload/restart its models. Typically, as result of this
+ *   event the framework will make a single attempt per model to bring this model to its desired
+ *   state (loaded, started).
+ */
+interface ISoundTriggerHw extends @2.3::ISoundTriggerHw {
+    /**
+     * This will get called at most once per every attachment to the service.
+     *
+     * All events not tied to a specific model should go through this callback.
+     */
+    registerGlobalCallback(ISoundTriggerHwGlobalCallback callback);
+
+    /**
+     * Load a sound model. Once loaded, recognition of this model can be
+     * started and stopped.
+     * The implementation returns a unique handle used by other functions
+     * (unloadSoundModel(), startRecognition*(), etc...
+     *
+     * Must have the exact same semantics as loadSoundModel from ISoundTriggerHw@2.3 except that the
+     * return values have changed and that there is no cookie provided (the implementation may pass
+     * any value to the callback, as it is ignored).
+     *
+     * @param soundModel A SoundModel structure describing the sound model
+     *     to load.
+     * @param callback The callback interface on which the soundModelCallback*()
+     *     method must be called upon completion and modelUnloaded() upon preempted unload.
+     * @return retval Operation completion status: 0 in case of success,
+     *     -EBUSY in case the operation is temporarily unavailable (but possible in general).
+     * @return modelHandle A unique handle assigned by the HAL for use by the
+     *     framework when controlling activity for this sound model.
+     */
+    loadSoundModel_2_4(SoundModel soundModel, ISoundTriggerHwCallback callback)
+            generates (int32_t retval, SoundModelHandle modelHandle);
+
+    /**
+     * Load a key phrase sound model. Once loaded, recognition of this model can
+     * be started and stopped. Only one active recognition per model at a time.
+     * The SoundTrigger service must handle concurrent recognition requests by
+     * different users/applications on the same model.
+     * The implementation returns a unique handle used by other functions
+     * (unloadSoundModel(), startRecognition*(), etc...
+     *
+     * Must have the exact same semantics as loadPhraseSoundModel from ISoundTriggerHw@2.3 except
+     * that the return values have changed and that there is no cookie provided (the implementation
+     * may pass any value to the callback, as it is ignored).
+     *
+     * @param soundModel A PhraseSoundModel structure describing the sound model
+     *     to load.
+     * @param callback The callback interface on which the soundModelCallback*()
+     *     method must be called upon completion and modelUnloaded() upon preempted unload.
+     * @return retval Operation completion status: 0 in case of success,
+     *     -EBUSY in case the operation is temporarily unavailable (but possible in general).
+     * @return modelHandle A unique handle assigned by the HAL for use by the
+     *     framework when controlling activity for this sound model.
+     */
+    loadPhraseSoundModel_2_4(PhraseSoundModel soundModel, ISoundTriggerHwCallback callback)
+            generates (int32_t retval, SoundModelHandle modelHandle);
+
+    /**
+     * Start recognition on a given model. Only one recognition active
+     * at a time per model. Once recognition succeeds or fails, the callback
+     * associated with the model handle is called.
+     *
+     * Must have the exact same semantics as startRecognition from ISoundTriggerHw@2.3 except that
+     * there are different expectations of the return value and that there is no cookie provided
+     * (the implementation may pass any value to the callback, as it is ignored).
+     *
+     * @param modelHandle the handle of the sound model to use for recognition
+     * @param config A RecognitionConfig structure containing attributes of the
+     *     recognition to perform
+     * @param callback The callback interface on which the recognitionCallback()
+     *     method must be called upon recognition.
+     * @return retval Operation completion status: 0 in case of success,
+     *     -EBUSY in case the operation is temporarily unavailable (but possible in general), or in
+     *            case model has been preemtively unloaded.
+     */
+    startRecognition_2_4(SoundModelHandle modelHandle, RecognitionConfig config)
+            generates (int32_t retval);
+};
diff --git a/soundtrigger/2.4/ISoundTriggerHwCallback.hal b/soundtrigger/2.4/ISoundTriggerHwCallback.hal
new file mode 100644
index 0000000..594deb0
--- /dev/null
+++ b/soundtrigger/2.4/ISoundTriggerHwCallback.hal
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2021 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.
+ */
+
+package android.hardware.soundtrigger@2.4;
+
+import @2.0::SoundModelHandle;
+import @2.1::ISoundTriggerHwCallback;
+
+/**
+ * SoundTrigger HAL per-model Callback interface.
+ */
+interface ISoundTriggerHwCallback extends @2.1::ISoundTriggerHwCallback {
+    /**
+     * Callback method called by the HAL when a model has been unloaded at the HAL implementation's
+     * discretion. Only a stopped model may be unloaded.
+     * This event is NOT sent as part of an unload sequence initiated by the client.
+     *
+     * @param model The model handle.
+     */
+    modelUnloaded(SoundModelHandle model);
+};
diff --git a/soundtrigger/2.4/ISoundTriggerHwGlobalCallback.hal b/soundtrigger/2.4/ISoundTriggerHwGlobalCallback.hal
new file mode 100644
index 0000000..2f1a977
--- /dev/null
+++ b/soundtrigger/2.4/ISoundTriggerHwGlobalCallback.hal
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 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.
+ */
+
+package android.hardware.soundtrigger@2.4;
+
+/**
+ * SoundTrigger HAL callback interface for events not associated with a particular model.
+ */
+interface ISoundTriggerHwGlobalCallback {
+    /**
+     * Callback method called by the HAL whenever internal conditions have been made available, such
+     * that a call that would previously have failed with an -EBUSY status may now succeed.
+     * There is no guarantee that any call would succeed following this event. It is merely a hint
+     * to the client that it may retry.
+     * Conversely, any call that have failed previously with -EBUSY is guaranteed to fail again if
+     * retried, until this callback is delivered.
+     */
+    onResourcesAvailable();
+};
diff --git a/soundtrigger/2.4/cli/Android.bp b/soundtrigger/2.4/cli/Android.bp
new file mode 100644
index 0000000..8d0979b
--- /dev/null
+++ b/soundtrigger/2.4/cli/Android.bp
@@ -0,0 +1,17 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "hardware_interfaces_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+java_binary {
+    name: "sthal_cli_2.4",
+    wrapper: "sthal_cli_2.4",
+    srcs: ["java/**/*.java"],
+    static_libs: [
+        "android.hardware.soundtrigger-V2.4-java",
+    ],
+}
diff --git a/soundtrigger/2.4/cli/OWNERS b/soundtrigger/2.4/cli/OWNERS
new file mode 100644
index 0000000..e21b66e
--- /dev/null
+++ b/soundtrigger/2.4/cli/OWNERS
@@ -0,0 +1 @@
+include /media/java/android/media/soundtrigger_middleware/OWNERS
diff --git a/soundtrigger/2.4/cli/java/android/hardware/soundtrigger/V2_4/cli/SthalCli.java b/soundtrigger/2.4/cli/java/android/hardware/soundtrigger/V2_4/cli/SthalCli.java
new file mode 100644
index 0000000..e6870aa
--- /dev/null
+++ b/soundtrigger/2.4/cli/java/android/hardware/soundtrigger/V2_4/cli/SthalCli.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright 2021 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.
+ */
+package android.hardware.soundtrigger.V2_4.cli;
+
+import android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra;
+import android.hardware.soundtrigger.V2_0.RecognitionMode;
+import android.hardware.soundtrigger.V2_0.SoundModelType;
+import android.hardware.soundtrigger.V2_3.OptionalModelParameterRange;
+import android.hardware.soundtrigger.V2_4.ISoundTriggerHw;
+import android.hardware.soundtrigger.V2_4.ISoundTriggerHwCallback;
+import android.hardware.soundtrigger.V2_4.ISoundTriggerHwGlobalCallback;
+import android.os.HidlMemoryUtil;
+import android.os.HwBinder;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+
+import java.util.Scanner;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * This is a quick-and-dirty sound trigger HAL console mock.
+ *
+ * It would only work on userdebug builds.
+ *
+ * When this app is started, it will initially:
+ * - Register a ISoundTriggerHw HAL with an instance name "mock".
+ * - Set a sysprop that tells SoundTriggerMiddlewareService to try to connect to the mock instance
+ * rather than the default one.
+ * - Reboot the real (default) HAL.
+ *
+ * In response to that, SoundTriggerMiddlewareService is going to connect to the mock HAL and resume
+ * normal operation.
+ *
+ * Our mock HAL will print to stdout every call it receives as well as expose a basic set of
+ * operations for sending event callbacks to the client. This allows us to simulate the frameworks
+ * behavior in response to different HAL behaviors.
+ */
+public class SthalCli {
+    private static SoundTriggerImpl mService;
+    private static final Scanner scanner = new Scanner(System.in);
+
+    public static void main(String[] args) {
+        try {
+            System.out.println("Registering mock STHAL");
+            HwBinder.setTrebleTestingOverride(true);
+            mService = new SoundTriggerImpl();
+            mService.registerAsService("mock");
+
+            System.out.println("Rebooting STHAL");
+            SystemProperties.set("debug.soundtrigger_middleware.use_mock_hal", "true");
+            SystemProperties.set("sys.audio.restart.hal", "1");
+
+            while (processCommand()) ;
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            cleanup();
+        }
+    }
+
+    private static void cleanup() {
+        System.out.println("Cleaning up.");
+        SystemProperties.set("debug.soundtrigger_middleware.use_mock_hal", "false");
+        HwBinder.setTrebleTestingOverride(false);
+    }
+
+    private static boolean processCommand() {
+        String line = scanner.nextLine();
+        String[] tokens = line.split("\\s+");
+        if (tokens.length < 1) {
+            return false;
+        }
+        switch (tokens[0]) {
+            case "q":
+                return false;
+
+            case "a":
+                mService.sendOnResourcesAvailable();
+                return true;
+
+            case "u":
+                mService.sendModelUnloaded(Integer.parseInt(tokens[1]));
+                return true;
+
+            case "r":
+                mService.sendRecognitionEvent(Integer.parseInt(tokens[1]),
+                        Integer.parseInt(tokens[2]));
+                return true;
+
+            case "p":
+                mService.sendPhraseRecognitionEvent(Integer.parseInt(tokens[1]),
+                        Integer.parseInt(tokens[2]));
+                return true;
+
+            case "d":
+                mService.dumpModels();
+                return true;
+
+            case "h":
+                System.out.print("Available commands:\n" + "h - help\n" + "q - quit\n"
+                        + "a - send onResourcesAvailable event\n"
+                        + "u <model> - send modelUnloaded event\n"
+                        + "r <model> <status> - send recognitionEvent\n"
+                        + "p <model> <status> - send phraseRecognitionEvent\n"
+                        + "d - dump models\n");
+
+            default:
+                return true;
+        }
+    }
+
+    private static class SoundTriggerImpl extends ISoundTriggerHw.Stub {
+        static class Model {
+            final ISoundTriggerHwCallback callback;
+            final SoundModel model;
+            final PhraseSoundModel phraseModel;
+            public android.hardware.soundtrigger.V2_3.RecognitionConfig config = null;
+
+            Model(ISoundTriggerHwCallback callback, SoundModel model) {
+                this.callback = callback;
+                this.model = model;
+                this.phraseModel = null;
+            }
+
+            Model(ISoundTriggerHwCallback callback, PhraseSoundModel model) {
+                this.callback = callback;
+                this.model = null;
+                this.phraseModel = model;
+            }
+        }
+
+        private ISoundTriggerHwGlobalCallback mGlobalCallback;
+        private final ConcurrentMap<Integer, Model> mLoadedModels = new ConcurrentHashMap<>();
+        private int mHandleCounter = 1;
+
+        public void dumpModels() {
+            mLoadedModels.forEach((handle, model) -> {
+                System.out.println("+++ Model " + handle);
+                System.out.println("    config = " + model.config);
+                android.hardware.soundtrigger.V2_3.RecognitionConfig recognitionConfig =
+                        model.config;
+                if (recognitionConfig != null) {
+                    System.out.println("    ACTIVE recognitionConfig = " + recognitionConfig);
+                } else {
+                    System.out.println("    INACTIVE");
+                }
+            });
+        }
+
+        public void sendOnResourcesAvailable() {
+            if (mGlobalCallback != null) {
+                try {
+                    mGlobalCallback.onResourcesAvailable();
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        public void sendRecognitionEvent(int modelHandle, int status) {
+            Model model = mLoadedModels.get(modelHandle);
+            if (model != null && model.config != null) {
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event =
+                        new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent();
+                event.header.model = modelHandle;
+                event.header.type = SoundModelType.GENERIC;
+                event.header.status = status;
+                event.header.captureSession = model.config.base.header.captureHandle;
+                event.header.captureAvailable = true;
+                event.header.audioConfig.channelMask = 16;
+                event.header.audioConfig.format = 1;
+                event.header.audioConfig.sampleRateHz = 16000;
+                event.data = HidlMemoryUtil.byteArrayToHidlMemory(new byte[0]);
+                try {
+                    model.callback.recognitionCallback_2_1(event, 0);
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+                model.config = null;
+            }
+        }
+
+        public void sendPhraseRecognitionEvent(int modelHandle, int status) {
+            Model model = mLoadedModels.get(modelHandle);
+            if (model != null && model.config != null) {
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent
+                        event =
+                        new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent();
+                event.common.header.model = modelHandle;
+                event.common.header.type = SoundModelType.KEYPHRASE;
+                event.common.header.status = status;
+                event.common.header.captureSession = model.config.base.header.captureHandle;
+                event.common.header.captureAvailable = true;
+                event.common.header.audioConfig.channelMask = 16;
+                event.common.header.audioConfig.format = 1;
+                event.common.header.audioConfig.sampleRateHz = 16000;
+                event.common.data = HidlMemoryUtil.byteArrayToHidlMemory(new byte[0]);
+                if (!model.phraseModel.phrases.isEmpty()) {
+                    PhraseRecognitionExtra extra = new PhraseRecognitionExtra();
+                    extra.id = model.phraseModel.phrases.get(0).id;
+                    extra.confidenceLevel = 100;
+                    extra.recognitionModes = model.phraseModel.phrases.get(0).recognitionModes;
+                    event.phraseExtras.add(extra);
+                }
+                try {
+                    model.callback.phraseRecognitionCallback_2_1(event, 0);
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+                model.config = null;
+            }
+        }
+
+        public void sendModelUnloaded(int modelHandle) {
+            Model model = mLoadedModels.remove(modelHandle);
+            if (model != null) {
+                try {
+                    model.callback.modelUnloaded(modelHandle);
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        @Override
+        public void registerGlobalCallback(ISoundTriggerHwGlobalCallback callback) {
+            System.out.println("registerGlobalCallback()");
+            mGlobalCallback = callback;
+        }
+
+        @Override
+        public void loadSoundModel_2_4(SoundModel soundModel, ISoundTriggerHwCallback callback,
+                loadSoundModel_2_4Callback _hidl_cb) {
+            int handle = mHandleCounter++;
+            System.out.printf("loadSoundModel_2_4(soundModel=%s) -> %d%n", soundModel, handle);
+            mLoadedModels.put(handle, new Model(callback, soundModel));
+            _hidl_cb.onValues(0, handle);
+        }
+
+        @Override
+        public void loadPhraseSoundModel_2_4(PhraseSoundModel soundModel,
+                ISoundTriggerHwCallback callback, loadPhraseSoundModel_2_4Callback _hidl_cb) {
+            int handle = mHandleCounter++;
+            System.out.printf("loadPhraseSoundModel_2_4(soundModel=%s) -> %d%n", soundModel,
+                    handle);
+            mLoadedModels.put(handle, new Model(callback, soundModel));
+            _hidl_cb.onValues(0, handle);
+        }
+
+        @Override
+        public int startRecognition_2_4(int modelHandle,
+                android.hardware.soundtrigger.V2_3.RecognitionConfig config) {
+            System.out.printf("startRecognition_2_4(modelHandle=%d)%n", modelHandle);
+            Model model = mLoadedModels.get(modelHandle);
+            if (model != null) {
+                model.config = config;
+            }
+            return 0;
+        }
+
+        @Override
+        public void getProperties_2_3(getProperties_2_3Callback _hidl_cb) {
+            System.out.println("getProperties_2_3()");
+            android.hardware.soundtrigger.V2_3.Properties properties =
+                    new android.hardware.soundtrigger.V2_3.Properties();
+            properties.base.implementor = "Android";
+            properties.base.description = "Mock STHAL";
+            properties.base.maxSoundModels = 2;
+            properties.base.maxKeyPhrases = 1;
+            properties.base.recognitionModes =
+                    RecognitionMode.VOICE_TRIGGER | RecognitionMode.GENERIC_TRIGGER;
+            _hidl_cb.onValues(0, properties);
+        }
+
+        @Override
+        public void queryParameter(int modelHandle, int modelParam,
+                queryParameterCallback _hidl_cb) {
+            _hidl_cb.onValues(0, new OptionalModelParameterRange());
+        }
+
+        @Override
+        public int getModelState(int modelHandle) {
+            System.out.printf("getModelState(modelHandle=%d)%n", modelHandle);
+            return 0;
+        }
+
+        @Override
+        public int unloadSoundModel(int modelHandle) {
+            System.out.printf("unloadSoundModel(modelHandle=%d)%n", modelHandle);
+            return 0;
+        }
+
+        @Override
+        public int stopRecognition(int modelHandle) {
+            System.out.printf("stopRecognition(modelHandle=%d)%n", modelHandle);
+            Model model = mLoadedModels.get(modelHandle);
+            if (model != null) {
+                model.config = null;
+            }
+            return 0;
+        }
+
+        @Override
+        public void debug(android.os.NativeHandle fd, java.util.ArrayList<String> options) {
+            if (!options.isEmpty()) {
+                switch (options.get(0)) {
+                    case "reboot":
+                        System.out.println("Received a reboot request. Exiting.");
+                        cleanup();
+                        System.exit(1);
+                }
+            }
+        }
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // Everything below is not implemented and not expected to be called.
+
+        @Override
+        public int startRecognition_2_3(int modelHandle,
+                android.hardware.soundtrigger.V2_3.RecognitionConfig config) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int setParameter(int modelHandle, int modelParam, int value) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void getParameter(int modelHandle, int modelParam, getParameterCallback _hidl_cb) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void loadSoundModel_2_1(SoundModel soundModel,
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback, int cookie,
+                loadSoundModel_2_1Callback _hidl_cb) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void loadPhraseSoundModel_2_1(PhraseSoundModel soundModel,
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback, int cookie,
+                loadPhraseSoundModel_2_1Callback _hidl_cb) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int startRecognition_2_1(int modelHandle, RecognitionConfig config,
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback, int cookie) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void getProperties(getPropertiesCallback _hidl_cb) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void loadSoundModel(
+                android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel soundModel,
+                android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback, int cookie,
+                loadSoundModelCallback _hidl_cb) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void loadPhraseSoundModel(
+                android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel soundModel,
+                android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback, int cookie,
+                loadPhraseSoundModelCallback _hidl_cb) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int startRecognition(int modelHandle,
+                android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config,
+                android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback, int cookie) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int stopAllRecognitions() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/soundtrigger/2.4/cli/sthal_cli_2.4 b/soundtrigger/2.4/cli/sthal_cli_2.4
new file mode 100644
index 0000000..0801464
--- /dev/null
+++ b/soundtrigger/2.4/cli/sthal_cli_2.4
@@ -0,0 +1,7 @@
+#!/system/bin/sh
+# Script to start "sthal_cli_2.4" on the device
+#
+base=/system
+export CLASSPATH=$base/framework/sthal_cli_2.4.jar
+exec app_process $base/bin android.hardware.soundtrigger.V2_4.cli.SthalCli "$@"
+
diff --git a/soundtrigger/2.4/vts/functional/Android.bp b/soundtrigger/2.4/vts/functional/Android.bp
new file mode 100644
index 0000000..4b7ae91
--- /dev/null
+++ b/soundtrigger/2.4/vts/functional/Android.bp
@@ -0,0 +1,41 @@
+//
+// Copyright (C) 2021 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.
+//
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "hardware_interfaces_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_test {
+    name: "VtsHalSoundtriggerV2_4TargetTest",
+    defaults: ["VtsHalTargetTestDefaults"],
+    srcs: ["VtsHalSoundtriggerV2_4TargetTest.cpp"],
+    static_libs: [
+        "android.hardware.soundtrigger@2.0",
+        "android.hardware.soundtrigger@2.1",
+        "android.hardware.soundtrigger@2.2",
+        "android.hardware.soundtrigger@2.3",
+        "android.hardware.soundtrigger@2.4",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/soundtrigger/2.4/vts/functional/VtsHalSoundtriggerV2_4TargetTest.cpp b/soundtrigger/2.4/vts/functional/VtsHalSoundtriggerV2_4TargetTest.cpp
new file mode 100644
index 0000000..13d7005
--- /dev/null
+++ b/soundtrigger/2.4/vts/functional/VtsHalSoundtriggerV2_4TargetTest.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 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 "SoundTriggerHidlHalTest"
+
+#include <android-base/logging.h>
+#include <android/hardware/audio/common/2.0/types.h>
+#include <android/hardware/soundtrigger/2.4/ISoundTriggerHwGlobalCallback.h>
+#include <android/hardware/soundtrigger/2.4/ISoundTriggerHw.h>
+#include <gtest/gtest.h>
+#include <hidl/GtestPrinter.h>
+#include <hidl/ServiceManagement.h>
+
+using ::android::sp;
+using ::android::hardware::Return;
+using ::android::hardware::Status;
+using ::android::hardware::soundtrigger::V2_4::ISoundTriggerHw;
+using ::android::hardware::soundtrigger::V2_4::ISoundTriggerHwGlobalCallback;
+
+/**
+ * Test class holding the instance of the SoundTriggerHW service to test.
+ * The passed parameter is the registered name of the implementing service
+ * supplied by INSTANTIATE_TEST_SUITE_P() call.
+ */
+class SoundTriggerHidlTest : public testing::TestWithParam<std::string> {
+public:
+    void SetUp() override {
+        mSoundtrigger = ISoundTriggerHw::getService(GetParam());
+
+        ASSERT_NE(mSoundtrigger, nullptr);
+        LOG(INFO) << "Test is remote " << mSoundtrigger->isRemote();
+    }
+
+protected:
+    sp<ISoundTriggerHw> mSoundtrigger;
+};
+
+/**
+ * Empty test is in place to ensure service is initialized.
+ * Due to the nature of SoundTrigger HAL providing an interface for
+ * proprietary or vendor specific implementations, limited testing on
+ * individual APIs is possible.
+ */
+TEST_P(SoundTriggerHidlTest, ServiceIsInstantiated) {}
+
+class GlobalCallback : public ISoundTriggerHwGlobalCallback {
+    Return<void> onResourcesAvailable() override {
+        return Status::ok();
+    }
+};
+
+/**
+ * Test ISoundTriggerHw::registerGlobalCallback method
+ *
+ * Verifies that:
+ * - the implementation implements the method
+ * - the method returns no error
+ */
+TEST_P(SoundTriggerHidlTest, RegisterGlobalCallback) {
+    Return<void> hidlReturn;
+    sp<ISoundTriggerHwGlobalCallback> callback = new GlobalCallback();
+    hidlReturn = mSoundtrigger->registerGlobalCallback(callback);
+    EXPECT_TRUE(hidlReturn.isOk());
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SoundTriggerHidlTest);
+
+INSTANTIATE_TEST_SUITE_P(
+        PerInstance, SoundTriggerHidlTest,
+        testing::ValuesIn(android::hardware::getAllHalInstanceNames(ISoundTriggerHw::descriptor)),
+        android::hardware::PrintInstanceNameToString);