Add VTS test for NFC observe mode
Test: this is only a test; manual run on a device
Bug: 305979303 326470047
Merged-In: Idf4953e942bb5db8c2ee72779dfdf80ed4e224b2
Change-Id: Idf4953e942bb5db8c2ee72779dfdf80ed4e224b2
diff --git a/nfc/aidl/vts/functional/Android.bp b/nfc/aidl/vts/functional/Android.bp
index 0200860..b865255 100644
--- a/nfc/aidl/vts/functional/Android.bp
+++ b/nfc/aidl/vts/functional/Android.bp
@@ -45,3 +45,45 @@
"vts",
],
}
+
+cc_test {
+ name: "VtsNfcBehaviorChangesTest",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ srcs: [
+ "VtsNfcBehaviorChangesTest.cpp",
+ "CondVar.cpp",
+ ],
+ include_dirs: [
+ "system/nfc/src/gki/common",
+ "system/nfc/src/gki/ulinux",
+ "system/nfc/src/include",
+ "system/nfc/src/nfa/include",
+ "system/nfc/src/nfc/include",
+ "system/nfc/utils/include",
+ ],
+ shared_libs: [
+ "libbinder",
+ "libbinder_ndk",
+ "libnativehelper",
+ "libstatssocket",
+ ],
+ static_libs: [
+ "android.hardware.nfc-V2-ndk",
+ "android.hardware.nfc@1.0",
+ "android.hardware.nfc@1.1",
+ "android.hardware.nfc@1.2",
+ "android_nfc_flags_aconfig_c_lib",
+ "libnfc-nci",
+ "libnfc-nci_flags",
+ "libnfcutils",
+ "libstatslog_nfc",
+ "server_configurable_flags",
+ ],
+ require_root: true,
+ test_suites: [
+ "vts",
+ ],
+}
diff --git a/nfc/aidl/vts/functional/CondVar.cpp b/nfc/aidl/vts/functional/CondVar.cpp
new file mode 100644
index 0000000..59e5b51
--- /dev/null
+++ b/nfc/aidl/vts/functional/CondVar.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * Encapsulate a condition variable for thread synchronization.
+ */
+
+#include "CondVar.h"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <errno.h>
+#include <string.h>
+
+using android::base::StringPrintf;
+
+/*******************************************************************************
+**
+** Function: CondVar
+**
+** Description: Initialize member variables.
+**
+** Returns: None.
+**
+*******************************************************************************/
+CondVar::CondVar() {
+ pthread_condattr_t attr;
+ pthread_condattr_init(&attr);
+ pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
+ memset(&mCondition, 0, sizeof(mCondition));
+ int const res = pthread_cond_init(&mCondition, &attr);
+ if (res) {
+ LOG(ERROR) << StringPrintf("CondVar::CondVar: fail init; error=0x%X", res);
+ }
+}
+
+/*******************************************************************************
+**
+** Function: ~CondVar
+**
+** Description: Cleanup all resources.
+**
+** Returns: None.
+**
+*******************************************************************************/
+CondVar::~CondVar() {
+ int const res = pthread_cond_destroy(&mCondition);
+ if (res) {
+ LOG(ERROR) << StringPrintf("CondVar::~CondVar: fail destroy; error=0x%X", res);
+ }
+}
+
+/*******************************************************************************
+**
+** Function: wait
+**
+** Description: Block the caller and wait for a condition.
+**
+** Returns: None.
+**
+*******************************************************************************/
+void CondVar::wait(std::mutex& mutex) {
+ int const res = pthread_cond_wait(&mCondition, mutex.native_handle());
+ if (res) {
+ LOG(ERROR) << StringPrintf("CondVar::wait: fail wait; error=0x%X", res);
+ }
+}
+
+/*******************************************************************************
+**
+** Function: wait
+**
+** Description: Block the caller and wait for a condition.
+** millisec: Timeout in milliseconds.
+**
+** Returns: True if wait is successful; false if timeout occurs.
+**
+*******************************************************************************/
+bool CondVar::wait(std::mutex& mutex, long millisec) {
+ bool retVal = false;
+ struct timespec absoluteTime;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &absoluteTime) == -1) {
+ LOG(ERROR) << StringPrintf("CondVar::wait: fail get time; errno=0x%X", errno);
+ } else {
+ absoluteTime.tv_sec += millisec / 1000;
+ long ns = absoluteTime.tv_nsec + ((millisec % 1000) * 1000000);
+ if (ns > 1000000000) {
+ absoluteTime.tv_sec++;
+ absoluteTime.tv_nsec = ns - 1000000000;
+ } else
+ absoluteTime.tv_nsec = ns;
+ }
+
+ int waitResult = pthread_cond_timedwait(&mCondition, mutex.native_handle(), &absoluteTime);
+ if ((waitResult != 0) && (waitResult != ETIMEDOUT))
+ LOG(ERROR) << StringPrintf("CondVar::wait: fail timed wait; error=0x%X", waitResult);
+ retVal = (waitResult == 0); // waited successfully
+ return retVal;
+}
+
+/*******************************************************************************
+**
+** Function: notifyOne
+**
+** Description: Unblock the waiting thread.
+**
+** Returns: None.
+**
+*******************************************************************************/
+void CondVar::notifyOne() {
+ int const res = pthread_cond_signal(&mCondition);
+ if (res) {
+ LOG(ERROR) << StringPrintf("CondVar::notifyOne: fail signal; error=0x%X", res);
+ }
+}
diff --git a/nfc/aidl/vts/functional/CondVar.h b/nfc/aidl/vts/functional/CondVar.h
new file mode 100644
index 0000000..5e0dcf7
--- /dev/null
+++ b/nfc/aidl/vts/functional/CondVar.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * Encapsulate a condition variable for thread synchronization.
+ */
+
+#pragma once
+#include <pthread.h>
+
+#include <mutex>
+
+class CondVar {
+ public:
+ /*******************************************************************************
+ **
+ ** Function: CondVar
+ **
+ ** Description: Initialize member variables.
+ **
+ ** Returns: None.
+ **
+ *******************************************************************************/
+ CondVar();
+
+ /*******************************************************************************
+ **
+ ** Function: ~CondVar
+ **
+ ** Description: Cleanup all resources.
+ **
+ ** Returns: None.
+ **
+ *******************************************************************************/
+ ~CondVar();
+
+ /*******************************************************************************
+ **
+ ** Function: wait
+ **
+ ** Description: Block the caller and wait for a condition.
+ **
+ ** Returns: None.
+ **
+ *******************************************************************************/
+ void wait(std::mutex& mutex);
+
+ /*******************************************************************************
+ **
+ ** Function: wait
+ **
+ ** Description: Block the caller and wait for a condition.
+ ** millisec: Timeout in milliseconds.
+ **
+ ** Returns: True if wait is successful; false if timeout occurs.
+ **
+ *******************************************************************************/
+ bool wait(std::mutex& mutex, long millisec);
+
+ /*******************************************************************************
+ **
+ ** Function: notifyOne
+ **
+ ** Description: Unblock the waiting thread.
+ **
+ ** Returns: None.
+ **
+ *******************************************************************************/
+ void notifyOne();
+
+ private:
+ pthread_cond_t mCondition;
+};
diff --git a/nfc/aidl/vts/functional/SyncEvent.h b/nfc/aidl/vts/functional/SyncEvent.h
new file mode 100644
index 0000000..352a549
--- /dev/null
+++ b/nfc/aidl/vts/functional/SyncEvent.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * Synchronize two or more threads using a condition variable and a mutex.
+ */
+#pragma once
+#include <mutex>
+
+#include "CondVar.h"
+
+class SyncEvent {
+ public:
+ /*******************************************************************************
+ **
+ ** Function: ~SyncEvent
+ **
+ ** Description: Cleanup all resources.
+ **
+ ** Returns: None.
+ **
+ *******************************************************************************/
+ ~SyncEvent() {}
+
+ /*******************************************************************************
+ **
+ ** Function: start
+ **
+ ** Description: Start a synchronization operation.
+ **
+ ** Returns: None.
+ **
+ *******************************************************************************/
+ void start() { mMutex.lock(); }
+
+ /*******************************************************************************
+ **
+ ** Function: wait
+ **
+ ** Description: Block the thread and wait for the event to occur.
+ **
+ ** Returns: None.
+ **
+ *******************************************************************************/
+ void wait() { mCondVar.wait(mMutex); }
+
+ /*******************************************************************************
+ **
+ ** Function: wait
+ **
+ ** Description: Block the thread and wait for the event to occur.
+ ** millisec: Timeout in milliseconds.
+ **
+ ** Returns: True if wait is successful; false if timeout occurs.
+ **
+ *******************************************************************************/
+ bool wait(long millisec) {
+ bool retVal = mCondVar.wait(mMutex, millisec);
+ return retVal;
+ }
+
+ /*******************************************************************************
+ **
+ ** Function: notifyOne
+ **
+ ** Description: Notify a blocked thread that the event has occurred.
+ *Unblocks it.
+ **
+ ** Returns: None.
+ **
+ *******************************************************************************/
+ void notifyOne() { mCondVar.notifyOne(); }
+
+ /*******************************************************************************
+ **
+ ** Function: end
+ **
+ ** Description: End a synchronization operation.
+ **
+ ** Returns: None.
+ **
+ *******************************************************************************/
+ void end() { mMutex.unlock(); }
+
+ private:
+ CondVar mCondVar;
+ std::mutex mMutex;
+};
+
+/*****************************************************************************/
+/*****************************************************************************/
+
+/*****************************************************************************
+**
+** Name: SyncEventGuard
+**
+** Description: Automatically start and end a synchronization event.
+**
+*****************************************************************************/
+class SyncEventGuard {
+ public:
+ /*******************************************************************************
+ **
+ ** Function: SyncEventGuard
+ **
+ ** Description: Start a synchronization operation.
+ **
+ ** Returns: None.
+ **
+ *******************************************************************************/
+ SyncEventGuard(SyncEvent& event) : mEvent(event) {
+ event.start(); // automatically start operation
+ };
+
+ /*******************************************************************************
+ **
+ ** Function: ~SyncEventGuard
+ **
+ ** Description: End a synchronization operation.
+ **
+ ** Returns: None.
+ **
+ *******************************************************************************/
+ ~SyncEventGuard() {
+ mEvent.end(); // automatically end operation
+ };
+
+ private:
+ SyncEvent& mEvent;
+};
diff --git a/nfc/aidl/vts/functional/VtsNfcBehaviorChangesTest.cpp b/nfc/aidl/vts/functional/VtsNfcBehaviorChangesTest.cpp
new file mode 100644
index 0000000..0b73cc9
--- /dev/null
+++ b/nfc/aidl/vts/functional/VtsNfcBehaviorChangesTest.cpp
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2024 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 "nfc_behavior_changes_test"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android/binder_process.h>
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <future>
+
+#include "NfcAdaptation.h"
+#include "SyncEvent.h"
+#include "nci_defs.h"
+#include "nfa_api.h"
+#include "nfa_ee_api.h"
+
+using android::base::StringPrintf;
+
+static SyncEvent sNfaEnableEvent; // event for NFA_Enable()
+static SyncEvent sNfaVsCommand; // event for VS commands
+static SyncEvent sNfaEnableDisablePollingEvent;
+static SyncEvent sNfaPowerChangeEvent;
+static bool sIsNfaEnabled;
+static tNFA_STATUS sVSCmdStatus;
+
+static void nfaDeviceManagementCallback(uint8_t dmEvent, tNFA_DM_CBACK_DATA* eventData) {
+ LOG(DEBUG) << StringPrintf("%s: enter; event=0x%X", __func__, dmEvent);
+
+ switch (dmEvent) {
+ case NFA_DM_ENABLE_EVT: /* Result of NFA_Enable */
+ {
+ SyncEventGuard guard(sNfaEnableEvent);
+ LOG(DEBUG) << StringPrintf("%s: NFA_DM_ENABLE_EVT; status=0x%X", __func__,
+ eventData->status);
+ sIsNfaEnabled = eventData->status == NFA_STATUS_OK;
+ sNfaEnableEvent.notifyOne();
+ } break;
+
+ case NFA_DM_DISABLE_EVT: /* Result of NFA_Disable */
+ {
+ SyncEventGuard guard(sNfaEnableEvent);
+ LOG(DEBUG) << StringPrintf("%s: NFA_DM_DISABLE_EVT; status=0x%X", __func__,
+ eventData->status);
+ sIsNfaEnabled = eventData->status == NFA_STATUS_OK;
+ sNfaEnableEvent.notifyOne();
+ } break;
+
+ case NFA_DM_PWR_MODE_CHANGE_EVT: {
+ SyncEventGuard guard(sNfaPowerChangeEvent);
+ LOG(DEBUG) << StringPrintf(
+ "%s: NFA_DM_PWR_MODE_CHANGE_EVT: status=0x%X, power_mode=0x%X", __func__,
+ eventData->status, eventData->power_mode.power_mode);
+
+ sNfaPowerChangeEvent.notifyOne();
+
+ } break;
+ }
+}
+
+static void nfaConnectionCallback(uint8_t connEvent, tNFA_CONN_EVT_DATA* eventData) {
+ LOG(DEBUG) << StringPrintf("%s: event= %u", __func__, connEvent);
+
+ switch (connEvent) {
+ case NFA_LISTEN_DISABLED_EVT: {
+ SyncEventGuard guard(sNfaEnableDisablePollingEvent);
+ sNfaEnableDisablePollingEvent.notifyOne();
+ } break;
+
+ case NFA_LISTEN_ENABLED_EVT: {
+ SyncEventGuard guard(sNfaEnableDisablePollingEvent);
+ sNfaEnableDisablePollingEvent.notifyOne();
+ } break;
+
+ case NFA_RF_DISCOVERY_STARTED_EVT: // RF Discovery started
+ {
+ LOG(DEBUG) << StringPrintf("%s: NFA_RF_DISCOVERY_STARTED_EVT: status = %u", __func__,
+ eventData->status);
+
+ SyncEventGuard guard(sNfaEnableDisablePollingEvent);
+ sNfaEnableDisablePollingEvent.notifyOne();
+ } break;
+
+ case NFA_RF_DISCOVERY_STOPPED_EVT: // RF Discovery stopped event
+ {
+ LOG(DEBUG) << StringPrintf("%s: NFA_RF_DISCOVERY_STOPPED_EVT: status = %u", __func__,
+ eventData->status);
+
+ SyncEventGuard guard(sNfaEnableDisablePollingEvent);
+ sNfaEnableDisablePollingEvent.notifyOne();
+ } break;
+ }
+}
+
+void static nfaVSCallback(uint8_t event, uint16_t /* param_len */, uint8_t* p_param) {
+ switch (event & NCI_OID_MASK) {
+ case NCI_MSG_PROP_ANDROID: {
+ uint8_t android_sub_opcode = p_param[3];
+ switch (android_sub_opcode) {
+ case NCI_ANDROID_PASSIVE_OBSERVE: {
+ sVSCmdStatus = p_param[4];
+ LOG(INFO) << StringPrintf("Observe mode RSP: status: %x", sVSCmdStatus);
+ SyncEventGuard guard(sNfaVsCommand);
+ sNfaVsCommand.notifyOne();
+ } break;
+ case NCI_ANDROID_POLLING_FRAME_NTF: {
+ // TODO
+ } break;
+ default:
+ LOG(WARNING) << StringPrintf("Unknown Android sub opcode %x",
+ android_sub_opcode);
+ }
+ } break;
+ default:
+ break;
+ }
+}
+
+/*
+ * Enable passive observe mode.
+ */
+tNFA_STATUS static nfaObserveModeEnable(bool enable) {
+ tNFA_STATUS status = NFA_STATUS_FAILED;
+
+ status = NFA_StopRfDiscovery();
+ if (status == NFA_STATUS_OK) {
+ if (!sNfaEnableDisablePollingEvent.wait(1000)) {
+ LOG(WARNING) << "Timeout waiting to disable NFC RF discovery";
+ return NFA_STATUS_TIMEOUT;
+ }
+ }
+
+ uint8_t cmd[] = {(NCI_MT_CMD << NCI_MT_SHIFT) | NCI_GID_PROP, NCI_MSG_PROP_ANDROID,
+ NCI_ANDROID_PASSIVE_OBSERVE_PARAM_SIZE, NCI_ANDROID_PASSIVE_OBSERVE,
+ static_cast<uint8_t>(enable ? NCI_ANDROID_PASSIVE_OBSERVE_PARAM_ENABLE
+ : NCI_ANDROID_PASSIVE_OBSERVE_PARAM_DISABLE)};
+
+ status = NFA_SendRawVsCommand(sizeof(cmd), cmd, nfaVSCallback);
+
+ if (status == NFA_STATUS_OK) {
+ if (!sNfaVsCommand.wait(1000)) {
+ LOG(WARNING) << "Timeout waiting for NFA VS command response";
+ return NFA_STATUS_TIMEOUT;
+ }
+ }
+
+ return status;
+}
+
+class NfcBehaviorChanges : public testing::Test {
+ protected:
+ void SetUp() override {
+ tNFA_STATUS status = NFA_STATUS_OK;
+
+ sIsNfaEnabled = false;
+ sVSCmdStatus = NFA_STATUS_OK;
+
+ NfcAdaptation& theInstance = NfcAdaptation::GetInstance();
+ theInstance.Initialize(); // start GKI, NCI task, NFC task
+
+ {
+ SyncEventGuard guard(sNfaEnableEvent);
+ tHAL_NFC_ENTRY* halFuncEntries = theInstance.GetHalEntryFuncs();
+
+ NFA_Init(halFuncEntries);
+
+ status = NFA_Enable(nfaDeviceManagementCallback, nfaConnectionCallback);
+ ASSERT_EQ(status, NFA_STATUS_OK);
+
+ // wait for NFA command to finish
+ ASSERT_TRUE(sNfaEnableEvent.wait(1000))
+ << "Timeout waiting for NFA command on NFA_Enable";
+ }
+
+ ASSERT_TRUE(sIsNfaEnabled) << "Could not initialize NFC controller";
+
+ status = NFA_StartRfDiscovery();
+ ASSERT_EQ(status, NFA_STATUS_OK);
+ ASSERT_TRUE(sNfaEnableDisablePollingEvent.wait(1000)) << "Timeout starting RF discovery";
+ }
+};
+
+/*
+ * ObserveModeEnable:
+ * Attempts to enable observe mode. Does not test Observe Mode functionality,
+ * but simply verifies that the enable command responds successfully.
+ *
+ * @VsrTest = GMS-VSR-3.2.8-001
+ */
+TEST_F(NfcBehaviorChanges, ObserveModeEnableDisable) {
+ tNFA_STATUS status = nfaObserveModeEnable(true);
+ ASSERT_EQ(status, NFA_STATUS_OK);
+
+ status = nfaObserveModeEnable(false);
+ ASSERT_EQ(status, NFA_STATUS_OK);
+}
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ ABinderProcess_startThreadPool();
+ std::system("/system/bin/svc nfc disable"); /* Turn off NFC service */
+ sleep(5);
+ int status = RUN_ALL_TESTS();
+ LOG(INFO) << "Test result = " << status;
+ std::system("/system/bin/svc nfc enable"); /* Turn on NFC service */
+ sleep(5);
+ return status;
+}