Add support for test mode enable/disable

Bug: 247124878
Test: manual test
Change-Id: Ia888bd4e20fd77793f044f056cd48288b9485c5a
diff --git a/host/hal_generic/common/multi_client_context_hub_base.cc b/host/hal_generic/common/multi_client_context_hub_base.cc
index f486c67..cd74c60 100644
--- a/host/hal_generic/common/multi_client_context_hub_base.cc
+++ b/host/hal_generic/common/multi_client_context_hub_base.cc
@@ -36,6 +36,8 @@
 
 // timeout for calling getContextHubs(), which is synchronous
 constexpr auto kHubInfoQueryTimeout = std::chrono::seconds(5);
+// timeout for enable/disable test mode, which is synchronous
+constexpr std::chrono::duration ktestModeTimeOut = std::chrono::seconds(5);
 
 enum class HalErrorCode : int32_t {
   OPERATION_FAILED = -1,
@@ -380,9 +382,58 @@
   return ndk::ScopedAStatus::ok();
 }
 
-ScopedAStatus MultiClientContextHubBase::setTestMode(bool /*enable*/) {
-  // To be implemented.
-  return ScopedAStatus::ok();
+ScopedAStatus MultiClientContextHubBase::setTestMode(bool enable) {
+  return fromResult(enable ? enableTestMode() : disableTestMode());
+}
+
+bool MultiClientContextHubBase::enableTestMode() {
+  std::unique_lock<std::mutex> lock(mTestModeMutex);
+  if (mIsTestModeEnabled) {
+    return true;
+  }
+  mTestModeNanoapps.reset();
+  if (!queryNanoapps(kDefaultHubId).isOk()) {
+    LOGE("Failed to get a list of loaded nanoapps.");
+    mTestModeNanoapps.emplace();
+    return false;
+  }
+  mEnableTestModeCv.wait_for(lock, ktestModeTimeOut,
+                             [&]() { return mTestModeNanoapps.has_value(); });
+  for (const auto &appId : *mTestModeNanoapps) {
+    if (!unloadNanoapp(kDefaultHubId, appId, mTestModeTransactionId).isOk()) {
+      LOGE("Failed to unload nanoapp 0x%" PRIx64 " to enable the test mode.",
+           appId);
+      return false;
+    }
+    mTestModeSyncUnloadResult.reset();
+    mEnableTestModeCv.wait_for(lock, ktestModeTimeOut, [&]() {
+      return mTestModeSyncUnloadResult.has_value();
+    });
+    if (!*mTestModeSyncUnloadResult) {
+      LOGE("Failed to unload nanoapp 0x%" PRIx64 " to enable the test mode.",
+           appId);
+      return false;
+    }
+  }
+  mIsTestModeEnabled = true;
+  return true;
+}
+
+bool MultiClientContextHubBase::disableTestMode() {
+  std::unique_lock<std::mutex> lock(mTestModeMutex);
+  if (!mIsTestModeEnabled) {
+    return true;
+  }
+  if (mTestModeNanoapps.has_value() && !mTestModeNanoapps->empty()) {
+    if (!mPreloadedNanoappLoader->loadPreloadedNanoapps(*mTestModeNanoapps)) {
+      LOGE("Failed to reload the nanoapps to disable the test mode.");
+      return false;
+    }
+  }
+  mTestModeNanoapps.emplace();
+  mTestModeTransactionId = static_cast<int32_t>(kDefaultTestModeTransactionId);
+  mIsTestModeEnabled = false;
+  return true;
 }
 
 void MultiClientContextHubBase::handleMessageFromChre(
@@ -498,6 +549,17 @@
     appInfo.rpcServices = rpcServices;
     appInfoList.push_back(appInfo);
   }
+  {
+    std::unique_lock<std::mutex> lock(mTestModeMutex);
+    if (!mTestModeNanoapps.has_value()) {
+      mTestModeNanoapps.emplace();
+      for (const auto &appInfo : appInfoList) {
+        mTestModeNanoapps->insert(appInfo.nanoappId);
+      }
+      mEnableTestModeCv.notify_all();
+    }
+  }
+
   callback->handleNanoappInfo(appInfoList);
 }
 
@@ -552,6 +614,14 @@
     const fbs::UnloadNanoappResponseT &response, HalClientId clientId) {
   if (mHalClientManager->resetPendingUnloadTransaction(
           clientId, response.transaction_id)) {
+    {
+      std::unique_lock<std::mutex> lock(mTestModeMutex);
+      if (response.transaction_id == mTestModeTransactionId) {
+        mTestModeSyncUnloadResult.emplace(response.success);
+        mEnableTestModeCv.notify_all();
+        return;
+      }
+    }
     if (auto callback = mHalClientManager->getCallback(clientId);
         callback != nullptr) {
       callback->handleTransactionResult(response.transaction_id,
diff --git a/host/hal_generic/common/multi_client_context_hub_base.h b/host/hal_generic/common/multi_client_context_hub_base.h
index fa15996..c5ac533 100644
--- a/host/hal_generic/common/multi_client_context_hub_base.h
+++ b/host/hal_generic/common/multi_client_context_hub_base.h
@@ -43,8 +43,6 @@
  *
  * A subclass should initiate mConnection, mHalClientManager and
  * mPreloadedNanoappLoader in its constructor.
- *
- * TODO(b/247124878): setTestMode is not implemented yet.
  */
 class MultiClientContextHubBase
     : public BnContextHub,
@@ -102,6 +100,9 @@
       this->clientPid = pid;
     }
   };
+
+  static constexpr uint32_t kDefaultTestModeTransactionId = 0x80000000;
+
   MultiClientContextHubBase() = default;
 
   bool sendFragmentedLoadRequest(HalClientId clientId,
@@ -122,6 +123,9 @@
       const ::chre::fbs::DebugDumpResponseT & /* response */);
   void handleClientDeath(pid_t pid);
 
+  bool enableTestMode();
+  bool disableTestMode();
+
   inline bool isSettingEnabled(Setting setting) {
     return mSettingEnabled.find(setting) != mSettingEnabled.end() &&
            mSettingEnabled[setting];
@@ -154,6 +158,16 @@
   std::mutex mPreloadedNanoappIdsMutex;
   std::optional<std::vector<uint64_t>> mPreloadedNanoappIds{};
 
+  // test mode settings
+  std::mutex mTestModeMutex;
+  std::condition_variable mEnableTestModeCv;
+  bool mIsTestModeEnabled = false;
+  std::optional<bool> mTestModeSyncUnloadResult = std::nullopt;
+  std::optional<std::unordered_set<uint64_t>> mTestModeNanoapps =
+      std::unordered_set<uint64_t>{};
+  int32_t mTestModeTransactionId =
+      static_cast<int32_t>(kDefaultTestModeTransactionId);
+
   EventLogger mEventLogger;
 };
 }  // namespace android::hardware::contexthub::common::implementation