Add tizen_core_util 25/324525/11
authorjh9216.park <jh9216.park@samsung.com>
Tue, 20 May 2025 05:38:16 +0000 (14:38 +0900)
committerjh9216.park <jh9216.park@samsung.com>
Mon, 26 May 2025 06:45:44 +0000 (15:45 +0900)
- Add tool for making callback API

Change-Id: I2fffc461882e5f00f5d64e1eb4cfa915c08a3ad0
Signed-off-by: jh9216.park <jh9216.park@samsung.com>
CMakeLists.txt
packaging/tizen-core.spec
src/CMakeLists.txt
src/tizen-core-util/CMakeLists.txt [new file with mode: 0644]
src/tizen-core-util/tizen-core-util.pc.in [new file with mode: 0644]
src/tizen-core-util/tizen_core_thread_callback.hpp [new file with mode: 0644]
tests/CMakeLists.txt
tests/tizen-core-util_unittests/CMakeLists.txt [new file with mode: 0644]
tests/tizen-core-util_unittests/main.cc [new file with mode: 0644]
tests/tizen-core-util_unittests/tizen_core_util_test.cc [new file with mode: 0644]

index f23c4b4c12a15de98c5098fe98fa86811fc188f1..cb76f700c25c06cece2b923b5ed88db56d00a6f3 100644 (file)
@@ -25,6 +25,7 @@ SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
   "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/")
 
 SET(TARGET_TIZEN_CORE "tizen-core")
+SET(TARGET_TIZEN_CORE_UTIL "tizen-core-util")
 
 ENABLE_TESTING()
 SET(TARGET_TIZEN_CORE_UNITTESTS "tizen-core_unittests")
@@ -32,6 +33,11 @@ ADD_TEST(NAME ${TARGET_TIZEN_CORE_UNITTESTS}
   COMMAND ${TARGET_TIZEN_CORE_UNITTESTS}
   WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests/tizen-core_unittests)
 
+SET(TARGET_TIZEN_CORE_UTIL_UNITTESTS "tizen-core-util_unittests")
+ADD_TEST(NAME ${TARGET_TIZEN_CORE_UTIL_UNITTESTS}
+  COMMAND ${TARGET_TIZEN_CORE_UTIL_UNITTESTS}
+  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests/tizen-core-util_unittests)
+
 INCLUDE(FindPkgConfig)
 INCLUDE(ApplyPkgConfig)
 
index 07b3ff612a5bb1a35c43e3ef765e53f7f5d553dd..5fa9b938ea1a7ca85ac8c8b4e56ad74f1fb96fac 100644 (file)
@@ -30,6 +30,14 @@ Requires: %{name} = %{version}
 %description devel
 Tizen core library (Development) package.
 
+%package util-devel
+Summary:  Tizen core Util library (Development)
+Group:    System/API
+Requires: %{name} = %{version}
+
+%description util-devel
+Tizen core Util library (Development) package.
+
 %package unittests
 Summary:    GTest for tizen core API
 Group:      Development/Libraries
@@ -136,11 +144,16 @@ install -m 0755 run-unittest.sh %{buildroot}%{_bindir}/tizen-unittests/%{name}/
 
 %files devel
 %{_includedir}/tizen-core/*.h
-%{_libdir}/pkgconfig/*.pc
+%{_libdir}/pkgconfig/tizen-core.pc
 %{_libdir}/lib%{name}.so
 
+%files util-devel
+%{_includedir}/tizen-core-util/*.hpp
+%{_libdir}/pkgconfig/tizen-core-util.pc
+
 %files unittests
 %{_bindir}/tizen-core_unittests
+%{_bindir}/tizen-core-util_unittests
 %{_bindir}/tizen-unittests/%{name}/run-unittest.sh
 
 %if 0%{?gcov:1}
index 54d28604886f6c6f56fd757a8bc05d13a47a8cfe..71b6a68231b4b299e32062730bc23098fab1497e 100644 (file)
@@ -1 +1,2 @@
-ADD_SUBDIRECTORY(tizen-core)
\ No newline at end of file
+ADD_SUBDIRECTORY(tizen-core)
+ADD_SUBDIRECTORY(tizen-core-util)
diff --git a/src/tizen-core-util/CMakeLists.txt b/src/tizen-core-util/CMakeLists.txt
new file mode 100644 (file)
index 0000000..9580d11
--- /dev/null
@@ -0,0 +1,9 @@
+CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/${TARGET_TIZEN_CORE_UTIL}.pc.in
+  ${CMAKE_CURRENT_SOURCE_DIR}/${TARGET_TIZEN_CORE_UTIL}.pc @ONLY)
+INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${TARGET_TIZEN_CORE_UTIL}.pc
+  DESTINATION ${LIB_INSTALL_DIR}/pkgconfig)
+
+INSTALL(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+  DESTINATION include
+  FILES_MATCHING
+  PATTERN "*.hpp")
diff --git a/src/tizen-core-util/tizen-core-util.pc.in b/src/tizen-core-util/tizen-core-util.pc.in
new file mode 100644 (file)
index 0000000..c5059ec
--- /dev/null
@@ -0,0 +1,13 @@
+# Package Information for pkg-config
+
+prefix=@PREFIX@
+exec_prefix=/usr
+libdir=@LIB_INSTALL_DIR@
+includedir=@INCLUDE_INSTALL_DIR@/tizen-core-util
+
+Name: @PC_NAME@
+Description: @PC_DESCRIPTION@
+Version: @PC_VERSION@
+Requires: tizen-core
+Libs: -L${libdir} @PC_LDFLAGS@
+Cflags: -I${includedir}
diff --git a/src/tizen-core-util/tizen_core_thread_callback.hpp b/src/tizen-core-util/tizen_core_thread_callback.hpp
new file mode 100644 (file)
index 0000000..adce496
--- /dev/null
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * 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.
+ */
+
+#ifndef TIZEN_CORE_UTIL_TIZEN_CORE_THREAD_CALLBACK_H_
+#define TIZEN_CORE_UTIL_TIZEN_CORE_THREAD_CALLBACK_H_
+
+#include <tizen_core.h>
+#include <unistd.h>
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <tuple>
+
+namespace tizen_core_util {
+
+template <class HANDLE, class SETTER, class UNSETTER, class CB,
+          std::size_t USER_DATA_INDEX, std::copyable... TYPES>
+class ThreadCallbackContext {
+ public:
+  ThreadCallbackContext(const ThreadCallbackContext&) = delete;
+  ThreadCallbackContext(ThreadCallbackContext&&) noexcept = delete;
+
+  ThreadCallbackContext& operator=(const ThreadCallbackContext&) = delete;
+  ThreadCallbackContext& operator=(ThreadCallbackContext&&) = delete;
+
+  static auto& GetInst() {
+    static ThreadCallbackContext<HANDLE, SETTER, UNSETTER, CB, USER_DATA_INDEX,
+                                 TYPES...>
+        tcb;
+    return tcb;
+  }
+
+  struct HandleContext {
+    ~HandleContext() {
+      for (auto& [core, fields] : data_) {
+        auto* source = std::get<2>(fields);
+        if (source) tizen_core_remove_source(core, source);
+      }
+    }
+
+    std::map<tizen_core_h, std::tuple<CB, void*, tizen_core_source_h>> data_;
+  };
+
+  int Add(HANDLE handle, SETTER cb_setter, CB cb, void* data) {
+    std::lock_guard<std::recursive_mutex> lock(mutex_);
+    tizen_core_h core = nullptr;
+    int ret = tizen_core_find_from_this_thread(&core);
+    if (ret != 0) return ret;
+    bool first = false;
+    if (contexts_[handle].data_.empty()) first = true;
+    contexts_[handle].data_[core] = {cb, data, nullptr};
+    if (!first) return 0;
+    cb_setter(
+        handle,
+        [](TYPES... args) {
+          auto& obj =
+              ThreadCallbackContext<HANDLE, SETTER, UNSETTER, CB,
+                                    USER_DATA_INDEX, TYPES...>::GetInst();
+          std::lock_guard<std::recursive_mutex> lock(obj.mutex_);
+          auto params = std::make_shared<std::tuple<TYPES...>>(args...);
+          auto* cxt =
+              static_cast<HandleContext*>(std::get<USER_DATA_INDEX>(*params));
+
+          if (obj.on_copy_)
+            params = std::shared_ptr<std::tuple<TYPES...>>(
+                new std::tuple<TYPES...>(obj.on_copy_(*std::move(params))));
+
+          for (auto& [core, fields] : cxt->data_) {
+            auto* pair =
+                new std::pair<std::tuple<CB, void*, tizen_core_source_h>&,
+                              std::shared_ptr<std::tuple<TYPES...>>>{fields,
+                                                                     params};
+            tizen_core_add_idle_job(
+                core,
+                [](void* data) {
+                  auto& obj = ThreadCallbackContext<HANDLE, SETTER, UNSETTER,
+                                                    CB, USER_DATA_INDEX,
+                                                    TYPES...>::GetInst();
+                  std::lock_guard<std::recursive_mutex> lock(obj.GetLock());
+
+                  auto* pair = static_cast<
+                      std::pair<std::tuple<CB, void*, HANDLE>&,
+                                std::shared_ptr<std::tuple<TYPES...>>>*>(data);
+                  auto& fields = (*pair).first;
+                  CB cb = std::get<0>(fields);
+                  void* org_data = std::get<1>(fields);
+                  std::tuple<TYPES...> params = *(*pair).second;
+                  std::get<USER_DATA_INDEX>(params) = org_data;
+                  std::apply([cb](auto&&... args) { cb(args...); }, params);
+                  std::get<2>(fields) = nullptr;
+
+                  delete pair;
+                  return false;
+                },
+                pair, &std::get<2>(fields));
+          }
+        },
+        &contexts_[handle]);
+    return 0;
+  }
+
+  int Remove(HANDLE handle, UNSETTER cb_unsetter) {
+    std::lock_guard<std::recursive_mutex> lock(mutex_);
+    tizen_core_h core = nullptr;
+    int ret = tizen_core_find_from_this_thread(&core);
+    if (ret != 0) return ret;
+    contexts_[handle].data_.erase(core);
+
+    if (contexts_[handle].data_.empty()) cb_unsetter(handle);
+    return 0;
+  }
+
+  void SetOnCopy(
+      std::function<std::tuple<TYPES...>(std::tuple<TYPES...>)> on_copy) {
+    std::lock_guard<std::recursive_mutex> lock(mutex_);
+    on_copy_ = std::move(on_copy);
+  }
+
+  void Clear() {
+    contexts_.clear();
+    on_copy_ = nullptr;
+  }
+
+  std::recursive_mutex& GetLock() { return mutex_; }
+
+ private:
+  ThreadCallbackContext() = default;
+  std::map<HANDLE, HandleContext> contexts_;
+
+  std::function<std::tuple<TYPES...>(std::tuple<TYPES...>)> on_copy_;
+  std::recursive_mutex mutex_;
+};
+
+}  // namespace tizen_core_util
+
+/**
+ * @brief Creates a thread-specific callback registration API using existing
+setter APIs.
+ * @code
+#include <tizen_core_thread_callback.hpp>
+
+TCORE_MAKE_THREAD_CALLBACK_SETTER(package_manager_set_event_cb,
+package_manager_h, package_manager_event_cb, 6, const char*, const char*,
+package_manager_event_type_e, package_manager_event_state_e, int,
+package_manager_error_e, void*)
+
+void __event_cb(const char*, const char*,
+    package_manager_event_type_e, package_manager_event_state_e, int,
+    package_manager_error_e, void*)
+{
+}
+
+int main()
+{
+  package_manager_h handle;
+  ...
+
+  package_manager_set_event_cb(handle, __event_cb, nullptr);
+}
+* @endcode
+ */
+#define TCORE_MAKE_THREAD_CALLBACK_SETTER(ORG_SETTER, HANDLE_TYPE, CB_TYPE, \
+                                          USER_DATA_INDEX, ...)             \
+  int ORG_SETTER##_tcc(HANDLE_TYPE handle, CB_TYPE cb, void* user_data) {   \
+    auto setter = [](HANDLE_TYPE handle, CB_TYPE cb, void* user_data) {     \
+      ORG_SETTER(handle, cb, user_data);                                    \
+    };                                                                      \
+    auto& tcc = tizen_core_util::ThreadCallbackContext<                     \
+        HANDLE_TYPE, std::function<void(HANDLE_TYPE, CB_TYPE, void*)>,      \
+        std::function<void(HANDLE_TYPE)>, CB_TYPE, USER_DATA_INDEX,         \
+        __VA_ARGS__>::GetInst();                                            \
+    return tcc.Add(handle, setter, cb, user_data);                          \
+  }
+
+/**
+ * @brief Creates a thread-specific callback registration API using existing
+setter APIs.
+ * @code
+#include <tizen_core_thread_callback.hpp>
+
+TCORE_MAKE_THREAD_CALLBACK_SETTER_NO_HANDLE(app_manager_set_app_context_event_cb,
+app_manager_app_context_event_cb, 2, app_context_h, app_context_event_e, void*)
+
+void __event_cb(app_context_h app_context, app_context_event_e event, void
+*user_data)
+{
+}
+
+int main()
+{
+  app_manager_set_app_context_event_cb_tcc(app_context_event_cb, nullptr);
+}
+* @endcode
+ */
+#define TCORE_MAKE_THREAD_CALLBACK_SETTER_NO_HANDLE(ORG_SETTER, CB_TYPE,  \
+                                                    USER_DATA_INDEX, ...) \
+  int ORG_SETTER##_tcc(CB_TYPE cb, void* user_data) {                     \
+    auto setter = [](void*, CB_TYPE cb, void* user_data) {                \
+      ORG_SETTER(cb, user_data);                                          \
+    };                                                                    \
+    auto& tcc = tizen_core_util::ThreadCallbackContext<                   \
+        void*, std::function<void(void*, CB_TYPE, void*)>,                \
+        std::function<void(void*)>, CB_TYPE, USER_DATA_INDEX,             \
+        __VA_ARGS__>::GetInst();                                          \
+    return tcc.Add(nullptr, setter, cb, user_data);                       \
+  }
+
+/**
+ * @brief Creates a thread-specific callback unregistration API using existing
+unsetter APIs.
+ * @code
+#include <tizen_core_thread_callback.hpp>
+
+TCORE_MAKE_THREAD_CALLBACK_UNSETTER(package_manager_unset_event_cb,
+package_manager_h, package_manager_event_cb, 6, const char*, const char*,
+package_manager_event_type_e, package_manager_event_state_e, int,
+package_manager_error_e, void*)
+
+int main()
+{
+  package_manager_h handle;
+  ...
+
+  package_manager_unset_event_cb_tcc(handle);
+}
+* @endcode
+ */
+#define TCORE_MAKE_THREAD_CALLBACK_UNSETTER(ORG_UNSETTER, HANDLE_TYPE,     \
+                                            CB_TYPE, USER_DATA_INDEX, ...) \
+  int ORG_UNSETTER##_tcc(HANDLE_TYPE handle) {                             \
+    auto unsetter = [](HANDLE_TYPE handle) { ORG_UNSETTER(handle); };      \
+    auto& tcc = tizen_core_util::ThreadCallbackContext<                    \
+        HANDLE_TYPE, std::function<void(HANDLE_TYPE, CB_TYPE, void*)>,     \
+        std::function<void(HANDLE_TYPE)>, CB_TYPE, USER_DATA_INDEX,        \
+        __VA_ARGS__>::GetInst();                                           \
+    return tcc.Remove(handle, unsetter);                                   \
+  }
+
+/**
+ * @brief Creates a thread-specific callback unregistration API using existing
+unsetter APIs.
+ * @code
+#include <tizen_core_thread_callback.hpp>
+
+TCORE_MAKE_THREAD_CALLBACK_UNSETTER_NO_HANDLE(app_manager_unset_app_context_event_cb,
+app_manager_app_context_event_cb, 2, app_context_h, app_context_event_e, void*)
+
+int main()
+{
+  app_manager_unset_app_context_event_cb_tcc();
+}
+* @endcode
+ */
+#define TCORE_MAKE_THREAD_CALLBACK_UNSETTER_NO_HANDLE(ORG_UNSETTER, CB_TYPE, \
+                                                      USER_DATA_INDEX, ...)  \
+  int ORG_UNSETTER##_tcc(void) {                                             \
+    auto unsetter = [](void*) { ORG_UNSETTER(); };                           \
+    auto& tcc = tizen_core_util::ThreadCallbackContext<                      \
+        void*, std::function<void(void*, CB_TYPE, void*)>,                   \
+        std::function<void(void*)>, CB_TYPE, USER_DATA_INDEX,                \
+        __VA_ARGS__>::GetInst();                                             \
+    return tcc.Remove(nullptr, unsetter);                                    \
+  }
+
+#endif  // TIZEN_CORE_UTIL_TIZEN_CORE_THREAD_CALLBACK_H_
index 04787ebbe84df1bf74c5c646885c433ab0769b2d..a0915214dddd29b6b450a6ae650172353264e604 100644 (file)
@@ -1 +1,2 @@
 ADD_SUBDIRECTORY(tizen-core_unittests)
+ADD_SUBDIRECTORY(tizen-core-util_unittests)
diff --git a/tests/tizen-core-util_unittests/CMakeLists.txt b/tests/tizen-core-util_unittests/CMakeLists.txt
new file mode 100644 (file)
index 0000000..54b2f20
--- /dev/null
@@ -0,0 +1,26 @@
+AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} UNIT_TEST_SRCS)
+
+ADD_EXECUTABLE(${TARGET_TIZEN_CORE_UTIL_UNITTESTS}
+  ${UNIT_TEST_SRCS})
+
+TARGET_INCLUDE_DIRECTORIES(${TARGET_TIZEN_CORE_UTIL_UNITTESTS} PUBLIC
+  ${CMAKE_CURRENT_SOURCE_DIR}
+  ${CMAKE_CURRENT_SOURCE_DIR}/../
+  ${CMAKE_CURRENT_SOURCE_DIR}/../../
+  ${CMAKE_CURRENT_SOURCE_DIR}/../../src/
+)
+
+APPLY_PKG_CONFIG(${TARGET_TIZEN_CORE_UTIL_UNITTESTS} PUBLIC
+  DLOG_DEPS
+  GLIB_DEPS
+  GMOCK_DEPS
+)
+
+TARGET_LINK_LIBRARIES(${TARGET_TIZEN_CORE_UTIL_UNITTESTS} PUBLIC ${TARGET_TIZEN_CORE})
+
+SET_TARGET_PROPERTIES(${TARGET_TIZEN_CORE_UTIL_UNITTESTS}
+  PROPERTIES COMPILE_FLAGS "-fPIE")
+SET_TARGET_PROPERTIES(${TARGET_TIZEN_CORE_UTIL_UNITTESTS}
+  PROPERTIES LINK_FLAGS "-pie")
+
+INSTALL(TARGETS ${TARGET_TIZEN_CORE_UTIL_UNITTESTS} DESTINATION bin)
diff --git a/tests/tizen-core-util_unittests/main.cc b/tests/tizen-core-util_unittests/main.cc
new file mode 100644 (file)
index 0000000..9e8d062
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * 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 <dlog.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+// LCOV_EXCL_START
+extern "C" int __dlog_sec_print(log_id_t log_id, int prio, const char* tag,
+                                    const char* fmt, ...) {
+  printf("%s:", tag);
+  va_list ap;
+  va_start(ap, fmt);
+  vprintf(fmt, ap);
+  va_end(ap);
+  printf("\n");
+  return 0;
+}
+
+extern "C" int dlog_vprint(log_priority prio, const char* tag, const char* fmt,
+                           va_list ap) {
+  printf("%s:", tag);
+  vprintf(fmt, ap);
+  printf("\n");
+  return 0;
+}
+
+extern "C" int __dlog_print(log_id_t log_id, int prio, const char* tag,
+                            const char* fmt, ...) {
+  printf("%s:", tag);
+  va_list ap;
+  va_start(ap, fmt);
+  vprintf(fmt, ap);
+  va_end(ap);
+  printf("\n");
+  return 0;
+}
+// LCOV_EXCL_STOP
+
+int main(int argc, char** argv) {
+  try {
+    testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+  } catch (std::exception const &e) {
+    std::cout << "test_main caught exception: " << e.what() << std::endl;  // LCOV_EXCL_LINE
+    return -1;  // LCOV_EXCL_LINE
+  }
+}
diff --git a/tests/tizen-core-util_unittests/tizen_core_util_test.cc b/tests/tizen-core-util_unittests/tizen_core_util_test.cc
new file mode 100644 (file)
index 0000000..57b9194
--- /dev/null
@@ -0,0 +1,607 @@
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * 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 <glib.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <tizen_core.h>
+#include <unistd.h>
+
+#include "src/tizen-core-util/tizen_core_thread_callback.hpp"
+#include "src/tizen-core/include/tizen_core.h"
+
+// LCOV_EXCL_START
+
+namespace {
+int hit_cnt = 0;
+
+using simple_cb = void (*)(void* user_data);
+int sc_source = 0;
+std::pair<simple_cb, void*> sc_context;
+
+void invoke_simple_callback() {
+  sc_source = g_idle_add(
+      [](gpointer data) -> gboolean {
+        sc_context.first(sc_context.second);  // invoke callback
+        sc_source = 0;
+        return G_SOURCE_REMOVE;
+      },
+      nullptr);
+}
+
+void set_simple_callback(simple_cb cb, void* data) { sc_context = {cb, data}; }
+
+void unset_simple_callback() {
+  if (sc_source > 0) {
+    g_source_remove(sc_source);
+    sc_source = 0;
+  }
+}
+
+TCORE_MAKE_THREAD_CALLBACK_SETTER_NO_HANDLE(set_simple_callback, simple_cb, 0,
+                                            void*)
+TCORE_MAKE_THREAD_CALLBACK_UNSETTER_NO_HANDLE(unset_simple_callback, simple_cb,
+                                              0, void*)
+
+class HandleCallbackContext;
+using handle_callback_h = HandleCallbackContext*;
+using handle_cb = void (*)(handle_callback_h h, int state, void* user_data);
+
+struct HandleCallbackContext {
+  ~HandleCallbackContext() {
+    if (source_ > 0) g_source_remove(source_);
+  }
+
+  int source_ = 0;
+  std::pair<handle_cb, void*> context_;
+};
+
+handle_callback_h create_handle_callback() {
+  return new HandleCallbackContext();
+}
+
+void destory_handle_callback(handle_callback_h handle) { delete handle; }
+
+void invoke_handle_callback(handle_callback_h handle) {
+  handle->source_ = g_idle_add(
+      [](gpointer data) -> gboolean {
+        auto* self = static_cast<HandleCallbackContext*>(data);
+        self->context_.first(self, 3,
+                             self->context_.second);  // invoke callback
+        self->source_ = 0;
+        return G_SOURCE_REMOVE;
+      },
+      handle);
+}
+
+void set_handle_callback(handle_callback_h handle, handle_cb cb,
+                         void* user_data) {
+  handle->context_ = {cb, user_data};
+}
+
+void unset_handle_callback(handle_callback_h handle) {
+  if (handle->source_ > 0) {
+    g_source_remove(handle->source_);
+    handle->source_ = 0;
+  }
+}
+
+TCORE_MAKE_THREAD_CALLBACK_SETTER(set_handle_callback, handle_callback_h,
+                                  handle_cb, 2, handle_callback_h, int, void*)
+TCORE_MAKE_THREAD_CALLBACK_UNSETTER(unset_handle_callback, handle_callback_h,
+                                    handle_cb, 2, handle_callback_h, int, void*)
+
+}  // namespace
+
+using namespace tizen_core_util;
+
+class TizenCoreUtilTest : public ::testing::Test {
+ public:
+  TizenCoreUtilTest() {}
+  virtual ~TizenCoreUtilTest() {}
+
+  void SetUp() override {
+    TCoreSetUp();
+    SetTimeout();
+    handle_cb1_ = create_handle_callback();
+  }
+
+  void TearDown() override {
+    if (timeout_source) {
+      tizen_core_remove_source(main_task_, timeout_source);
+      timeout_source = nullptr;
+    }
+
+    TCoreTearDown();
+    ClearThreadCallbackContext();
+    ref_count_ = 1;
+    destory_handle_callback(handle_cb1_);
+  }
+
+  void Run() { tizen_core_task_run(main_task_); }
+
+  void RunWorker() {
+    tizen_core_task_run(task1_);
+    tizen_core_task_run(task2_);
+    tizen_core_task_run(task3_);
+    sleep(1);
+  }
+
+  void Stop() {
+    std::lock_guard<std::recursive_mutex> lock(mutex_);
+    if (--ref_count_ == 0) {
+      tizen_core_task_quit(task1_);
+      tizen_core_task_quit(task2_);
+      tizen_core_task_quit(task3_);
+      tizen_core_task_quit(main_task_);
+    }
+  }
+
+  void SendEvent() { invoke_simple_callback(); }
+  void SendEvent(handle_callback_h h) { invoke_handle_callback(h); }
+
+  void SetTimeout() {
+    tizen_core_add_timer(
+        main_task_, 2000,
+        [](void* data) {
+          auto* self = static_cast<TizenCoreUtilTest*>(data);
+          tizen_core_task_quit(self->task1_);
+          tizen_core_task_quit(self->task2_);
+          tizen_core_task_quit(self->task3_);
+          tizen_core_task_quit(self->main_task_);
+          EXPECT_TRUE(false);
+          return false;
+        },
+        this, &timeout_source);
+  }
+
+  void ClearThreadCallbackContext() {
+    ThreadCallbackContext<void*, std::function<void(void*, simple_cb, void*)>,
+                          std::function<void(void*)>, simple_cb, 0,
+                          void*>::GetInst()
+        .Clear();
+    ThreadCallbackContext<handle_callback_h,
+                          std::function<void(void*, handle_cb, void*)>,
+                          std::function<void(handle_cb)>, handle_cb, 2,
+                          handle_callback_h, int, void*>::GetInst()
+        .Clear();
+  }
+
+  void SetStopRef(int cnt) {
+    std::lock_guard<std::recursive_mutex> lock(mutex_);
+    ref_count_ = cnt;
+  }
+
+  tizen_core_task_h task1_ = nullptr;
+  tizen_core_task_h task2_ = nullptr;
+  tizen_core_task_h task3_ = nullptr;
+  tizen_core_task_h main_task_ = nullptr;
+  tizen_core_source_h timeout_source = nullptr;
+  int tid_ = 0;
+  int ref_count_ = 1;
+  std::recursive_mutex mutex_;
+  handle_callback_h handle_cb1_ = nullptr;
+
+ private:
+  void TCoreSetUp() {
+    tizen_core_init();
+    tizen_core_task_create("test_task1", true, &task1_);
+    tizen_core_task_create("test_task2", true, &task2_);
+    tizen_core_task_create("test_task3", true, &task3_);
+    tizen_core_task_create("main", false, &main_task_);
+    tid_ = gettid();
+  }
+
+  void TCoreTearDown() {
+    if (task1_) {
+      tizen_core_task_destroy(task1_);
+      task1_ = nullptr;
+    }
+
+    if (task2_) {
+      tizen_core_task_destroy(task2_);
+      task2_ = nullptr;
+    }
+
+    if (task3_) {
+      tizen_core_task_destroy(task3_);
+      task3_ = nullptr;
+    }
+
+    if (main_task_) {
+      tizen_core_task_destroy(main_task_);
+      main_task_ = nullptr;
+    }
+
+    tizen_core_shutdown();
+  }
+};
+
+TEST_F(TizenCoreUtilTest, ThreadCallbackContext_Add_MainThread_P) {
+  auto setter = [](void* handle, simple_cb cb, void* data) {
+    set_simple_callback(cb, data);
+  };
+
+  auto& tcc =
+      ThreadCallbackContext<void*, std::function<void(void*, simple_cb, void*)>,
+                            std::function<void(void*)>, simple_cb, 0,
+                            void*>::GetInst();
+  tcc.Add(
+      nullptr, setter,
+      [](void* data) {
+        auto* self = static_cast<TizenCoreUtilTest*>(data);
+        self->Stop();
+      },
+      this);
+
+  SendEvent();
+  Run();
+}
+
+TEST_F(TizenCoreUtilTest, ThreadCallbackContext_Add_WorkerThread_P) {
+  tizen_core_source_h source = nullptr;
+  hit_cnt = 0;
+  tizen_core_add_idle_job(
+      task1_,
+      [](void* data) {
+        auto setter = [](void* handle, simple_cb cb, void* data) {
+          set_simple_callback(cb, data);
+        };
+
+        auto& tcc = ThreadCallbackContext<
+            void*, std::function<void(void*, simple_cb, void*)>,
+            std::function<void(void*)>, simple_cb, 0, void*>::GetInst();
+
+        tcc.Add(
+            nullptr, setter,
+            [](void* data) {
+              auto* self = static_cast<TizenCoreUtilTest*>(data);
+              EXPECT_NE(gettid(), self->tid_);
+              hit_cnt++;
+              self->Stop();
+            },
+            data);
+
+        return false;
+      },
+      this, &source);
+
+  RunWorker();
+  SendEvent();
+  Run();
+  EXPECT_EQ(hit_cnt, 1);
+}
+
+TEST_F(TizenCoreUtilTest, ThreadCallbackContext_Add_WorkerThread_Multi_P) {
+  tizen_core_source_h source = nullptr;
+  SetStopRef(2);
+  hit_cnt = 0;
+  tizen_core_add_idle_job(
+      task1_,
+      [](void* data) {
+        auto setter = [](void* handle, simple_cb cb, void* data) {
+          set_simple_callback(cb, data);
+        };
+
+        auto& tcc = ThreadCallbackContext<
+            void*, std::function<void(void*, simple_cb, void*)>,
+            std::function<void(void*)>, simple_cb, 0, void*>::GetInst();
+
+        tcc.Add(
+            nullptr, setter,
+            [](void* data) {
+              auto* self = static_cast<TizenCoreUtilTest*>(data);
+              EXPECT_NE(gettid(), self->tid_);
+              hit_cnt++;
+              self->Stop();
+            },
+            data);
+
+        return false;
+      },
+      this, &source);
+
+  tizen_core_add_idle_job(
+      task2_,
+      [](void* data) {
+        auto setter = [](void* handle, simple_cb cb, void* data) {
+          set_simple_callback(cb, data);
+        };
+
+        auto& tcc = ThreadCallbackContext<
+            void*, std::function<void(void*, simple_cb, void*)>,
+            std::function<void(void*)>, simple_cb, 0, void*>::GetInst();
+
+        tcc.Add(
+            nullptr, setter,
+            [](void* data) {
+              auto* self = static_cast<TizenCoreUtilTest*>(data);
+              EXPECT_NE(gettid(), self->tid_);
+              hit_cnt++;
+              self->Stop();
+            },
+            data);
+
+        return false;
+      },
+      this, &source);
+
+  RunWorker();
+  SendEvent();
+  Run();
+  EXPECT_EQ(hit_cnt, 2);
+}
+
+TEST_F(TizenCoreUtilTest, ThreadCallbackContext_Add_WorkerThread_Multi_P2) {
+  tizen_core_source_h source = nullptr;
+  SetStopRef(3);
+  hit_cnt = 0;
+
+  tizen_core_add_idle_job(
+      task1_,
+      [](void* data) {
+        auto setter = [](void* handle, simple_cb cb, void* data) {
+          set_simple_callback(cb, data);
+        };
+
+        auto& tcc = ThreadCallbackContext<
+            void*, std::function<void(void*, simple_cb, void*)>,
+            std::function<void(void*)>, simple_cb, 0, void*>::GetInst();
+
+        tcc.Add(
+            nullptr, setter,
+            [](void* data) {
+              auto* self = static_cast<TizenCoreUtilTest*>(data);
+              EXPECT_NE(gettid(), self->tid_);
+              hit_cnt++;
+              self->Stop();
+            },
+            data);
+
+        return false;
+      },
+      this, &source);
+
+  tizen_core_add_idle_job(
+      task2_,
+      [](void* data) {
+        auto setter = [](void* handle, simple_cb cb, void* data) {
+          set_simple_callback(cb, data);
+        };
+
+        auto& tcc = ThreadCallbackContext<
+            void*, std::function<void(void*, simple_cb, void*)>,
+            std::function<void(void*)>, simple_cb, 0, void*>::GetInst();
+
+        tcc.Add(
+            nullptr, setter,
+            [](void* data) {
+              auto* self = static_cast<TizenCoreUtilTest*>(data);
+              EXPECT_NE(gettid(), self->tid_);
+              hit_cnt++;
+              self->Stop();
+            },
+            data);
+
+        return false;
+      },
+      this, &source);
+
+  tizen_core_add_idle_job(
+      task3_,
+      [](void* data) {
+        auto setter = [](void* handle, simple_cb cb, void* data) {
+          set_simple_callback(cb, data);
+        };
+
+        auto& tcc = ThreadCallbackContext<
+            void*, std::function<void(void*, simple_cb, void*)>,
+            std::function<void(void*)>, simple_cb, 0, void*>::GetInst();
+
+        tcc.Add(
+            nullptr, setter,
+            [](void* data) {
+              auto* self = static_cast<TizenCoreUtilTest*>(data);
+              EXPECT_NE(gettid(), self->tid_);
+              hit_cnt++;
+              self->Stop();
+            },
+            data);
+
+        return false;
+      },
+      this, &source);
+
+  RunWorker();
+  SendEvent();
+  Run();
+  EXPECT_EQ(hit_cnt, 3);
+}
+
+TEST_F(TizenCoreUtilTest, ThreadCallbackContext_Remove_P) {
+  tizen_core_source_h source = nullptr;
+  hit_cnt = 0;
+  tizen_core_add_idle_job(
+      task1_,
+      [](void* data) {
+        auto setter = [](void* handle, simple_cb cb, void* data) {
+          set_simple_callback(cb, data);
+        };
+
+        auto& tcc = ThreadCallbackContext<
+            void*, std::function<void(void*, simple_cb, void*)>,
+            std::function<void(void*)>, simple_cb, 0, void*>::GetInst();
+
+        tcc.Add(
+            nullptr, setter,
+            [](void* data) {
+              auto* self = static_cast<TizenCoreUtilTest*>(data);
+              EXPECT_NE(gettid(), self->tid_);
+              hit_cnt++;
+              self->Stop();
+            },
+            data);
+
+        return false;
+      },
+      this, &source);
+  tizen_core_add_idle_job(
+      task2_,
+      [](void* data) {
+        auto setter = [](void* handle, simple_cb cb, void* data) {
+          set_simple_callback(cb, data);
+        };
+        auto unsetter = [](void* handle) { unset_simple_callback(); };
+
+        auto& tcc = ThreadCallbackContext<
+            void*, std::function<void(void*, simple_cb, void*)>,
+            std::function<void(void*)>, simple_cb, 0, void*>::GetInst();
+
+        tcc.Add(
+            nullptr, setter, [](void* data) { hit_cnt++; }, data);
+        tcc.Remove(nullptr, unsetter);
+
+        return false;
+      },
+      this, &source);
+
+  RunWorker();
+  SendEvent();
+  Run();
+  EXPECT_EQ(hit_cnt, 1);
+}
+
+TEST_F(TizenCoreUtilTest, ThreadCallbackContext_Macro_Set_P) {
+  tizen_core_source_h source = nullptr;
+  hit_cnt = 0;
+  tizen_core_add_idle_job(
+      task1_,
+      [](void* data) {
+        set_simple_callback_tcc(
+            [](void* data) {
+              auto* self = static_cast<TizenCoreUtilTest*>(data);
+              EXPECT_NE(gettid(), self->tid_);
+              hit_cnt++;
+              self->Stop();
+            },
+            data);
+        return false;
+      },
+      this, &source);
+
+  RunWorker();
+  SendEvent();
+  Run();
+  EXPECT_EQ(hit_cnt, 1);
+}
+
+TEST_F(TizenCoreUtilTest, ThreadCallbackContext_Macro_Set_P2) {
+  tizen_core_source_h source = nullptr;
+  hit_cnt = 0;
+  tizen_core_add_idle_job(
+      task1_,
+      [](void* data) {
+        auto* self = static_cast<TizenCoreUtilTest*>(data);
+        set_handle_callback_tcc(
+            self->handle_cb1_,
+            [](handle_callback_h h, int state, void* user_data) {
+              auto* self = static_cast<TizenCoreUtilTest*>(user_data);
+              EXPECT_NE(gettid(), self->tid_);
+              hit_cnt++;
+              self->Stop();
+            },
+            data);
+        return false;
+      },
+      this, &source);
+
+  RunWorker();
+  SendEvent(handle_cb1_);
+  Run();
+  EXPECT_EQ(hit_cnt, 1);
+}
+
+TEST_F(TizenCoreUtilTest, ThreadCallbackContext_Macro_Unset_P) {
+  tizen_core_source_h source = nullptr;
+  hit_cnt = 0;
+  tizen_core_add_idle_job(
+      task1_,
+      [](void* data) {
+        set_simple_callback_tcc(
+            [](void* data) {
+              auto* self = static_cast<TizenCoreUtilTest*>(data);
+              EXPECT_NE(gettid(), self->tid_);
+              hit_cnt++;
+              self->Stop();
+            },
+            data);
+        return false;
+      },
+      this, &source);
+  tizen_core_add_idle_job(
+      task2_,
+      [](void* data) {
+        set_simple_callback_tcc([](void* data) { hit_cnt++; }, data);
+        unset_simple_callback_tcc();
+        return false;
+      },
+      this, &source);
+
+  RunWorker();
+  SendEvent();
+  Run();
+  EXPECT_EQ(hit_cnt, 1);
+}
+
+TEST_F(TizenCoreUtilTest, ThreadCallbackContext_Macro_Unset_P2) {
+  tizen_core_source_h source = nullptr;
+  hit_cnt = 0;
+  tizen_core_add_idle_job(
+      task1_,
+      [](void* data) {
+        auto* self = static_cast<TizenCoreUtilTest*>(data);
+        set_handle_callback_tcc(
+            self->handle_cb1_,
+            [](handle_callback_h h, int state, void* user_data) {
+              auto* self = static_cast<TizenCoreUtilTest*>(user_data);
+              EXPECT_NE(gettid(), self->tid_);
+              hit_cnt++;
+              self->Stop();
+            },
+            data);
+        return false;
+      },
+      this, &source);
+  tizen_core_add_idle_job(
+      task2_,
+      [](void* data) {
+        auto* self = static_cast<TizenCoreUtilTest*>(data);
+        set_handle_callback_tcc(
+            self->handle_cb1_,
+            [](handle_callback_h h, int state, void* user_data) { hit_cnt++; },
+            data);
+        unset_handle_callback_tcc(self->handle_cb1_);
+        return false;
+      },
+      this, &source);
+
+  RunWorker();
+  SendEvent(handle_cb1_);
+  Run();
+  EXPECT_EQ(hit_cnt, 1);
+}
+
+// LCOV_EXCL_STOP