Initial checkin for phone daemon for the HTML5 UI 12/17712/1 tizen_ivi_panda accepted/tizen/ivi/20140308.072116 accepted/tizen/ivi/panda/20140403.015912 accepted/tizen/ivi/release/20140315.041435 submit/tizen/20140307.230447 submit/tizen_ivi_panda/20140403.012219 submit/tizen_ivi_release/20140315.041418
authorbrianjjones <brian.j.jones@intel.com>
Fri, 7 Mar 2014 22:48:57 +0000 (14:48 -0800)
committerbrianjjones <brian.j.jones@intel.com>
Fri, 7 Mar 2014 22:54:29 +0000 (14:54 -0800)
Change-Id: I147a59574aeed9817da096bee351faf813323841

22 files changed:
CMakeLists.txt [new file with mode: 0644]
packaging/phoned.changes [new file with mode: 0644]
packaging/phoned.spec [new file with mode: 0644]
pkgconfig/phoned.pc.in [new file with mode: 0644]
scripts/org.tizen.phone.service [new file with mode: 0644]
scripts/phoned.service [new file with mode: 0644]
src/Logger.h [new file with mode: 0644]
src/bluez.cpp [new file with mode: 0644]
src/bluez.h [new file with mode: 0644]
src/connman.cpp [new file with mode: 0644]
src/connman.h [new file with mode: 0644]
src/obex.cpp [new file with mode: 0644]
src/obex.h [new file with mode: 0644]
src/ofono.cpp [new file with mode: 0644]
src/ofono.h [new file with mode: 0644]
src/phone.cpp [new file with mode: 0644]
src/phone.h [new file with mode: 0644]
src/phoned.cpp [new file with mode: 0644]
src/utils.cpp [new file with mode: 0644]
src/utils.h [new file with mode: 0644]
test/Makefile [new file with mode: 0644]
test/main.cpp [new file with mode: 0644]

diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..e12dcb9
--- /dev/null
@@ -0,0 +1,121 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
+PROJECT(phoned)
+
+SET(CMAKE_INSTALL_PREFIX "/usr")
+
+# -----------------------------------------------------------------------------
+# Required platform modules
+# -----------------------------------------------------------------------------
+INCLUDE(FindPkgConfig)
+
+PKG_CHECK_MODULES(dpl REQUIRED dpl-efl)
+PKG_CHECK_MODULES(glib REQUIRED glib-2.0)
+PKG_CHECK_MODULES(gio REQUIRED gio-2.0)
+PKG_CHECK_MODULES(dbus REQUIRED dbus-1)
+PKG_CHECK_MODULES(libebook-contacts REQUIRED libebook-contacts-1.2)
+
+INCLUDE_DIRECTORIES(
+  ${dpl_INCLUDE_DIRS}
+  ${glib_INCLUDE_DIRS}
+  ${gio_INCLUDE_DIRS}
+  ${dbus_INCLUDE_DIRS}
+  ${libebook-contacts_INCLUDE_DIRS}
+)
+
+# -----------------------------------------------------------------------------
+# Determine the log option
+# -----------------------------------------------------------------------------
+
+OPTION(DPL_LOG "DPL logs status" ON)
+
+IF(DPL_LOG)
+    MESSAGE(STATUS "Logging enabled for DPL")
+    ADD_DEFINITIONS("-DDPL_LOGS_ENABLED")
+ELSE(DPL_LOG)
+    MESSAGE(STATUS "Logging disabled for DPL")
+ENDIF(DPL_LOG)
+
+# -----------------------------------------------------------------------------
+# Determine the time tracing option
+# -----------------------------------------------------------------------------
+
+OPTION(ENABLE_TIME_TRACER "TIME TRACING" OFF)
+
+IF(ENABLE_TIME_TRACER)
+    MESSAGE(STATUS "Time tracer enabled")
+    ADD_DEFINITIONS("-DENABLE_TIME_TRACER")
+ELSE(ENABLE_TIME_TRACER)
+    MESSAGE(STATUS "Time tracer disabled")
+ENDIF(ENABLE_TIME_TRACER)
+
+# -----------------------------------------------------------------------------
+# Set build type (Release by default)
+# -----------------------------------------------------------------------------
+IF("${CMAKE_BUILD_TYPE}" STREQUAL "")
+     SET(CMAKE_BUILD_TYPE Release)
+ENDIF("${CMAKE_BUILD_TYPE}" STREQUAL "")
+
+MESSAGE("Build type: ${CMAKE_BUILD_TYPE}")
+
+# -----------------------------------------------------------------------------
+# CFlags
+# -----------------------------------------------------------------------------
+SET(CMAKE_C_FLAGS_PROFILING    "-O0 -g -pg")
+SET(CMAKE_CXX_FLAGS_PROFILING  "-O0 -std=c++0x -g -pg")
+SET(CMAKE_C_FLAGS_DEBUG        "-O0 -g")
+SET(CMAKE_CXX_FLAGS_DEBUG      "-O0 -std=c++0x -g")
+SET(CMAKE_C_FLAGS_RELEASE      "-O2 -g")
+SET(CMAKE_CXX_FLAGS_RELEASE    "-O2 -std=c++0x -g")
+ADD_DEFINITIONS("-DCLIENT_IPC_THREAD")
+ADD_DEFINITIONS("-DEXPORT_API=")
+ADD_DEFINITIONS("-Wall")
+ADD_DEFINITIONS("-D_FILE_OFFSET_BITS=64")
+#ADD_DEFINITIONS("-Werror")
+#ADD_DEFINITIONS("-Wextra")
+ADD_DEFINITIONS("-DAPPLICATION_API_BACKWARD_COMPATIBILITY")
+ADD_DEFINITIONS("-std=c++11")
+
+# -----------------------------------------------------------------------------
+# Global variables
+# -----------------------------------------------------------------------------
+
+SET(DESTINATION_PREFIX sbin)
+SET(DBUS_SERVICE_PREFIX share/dbus-1/services)
+SET(SYSTEMD_SERVICE_PREFIX lib/systemd/user)
+
+# -----------------------------------------------------------------------------
+# Macros for pkgconfig
+# -----------------------------------------------------------------------------
+SET(PKGCONFIG_DIR ${CMAKE_SOURCE_DIR}/pkgconfig)
+
+MACRO(configure_and_install_pkg PKG_FILE)
+    CONFIGURE_FILE(${PKGCONFIG_DIR}/${PKG_FILE}.in
+               ${PKGCONFIG_DIR}/${PKG_FILE} @ONLY)
+    INSTALL(FILES ${PKGCONFIG_DIR}/${PKG_FILE} DESTINATION lib/pkgconfig)
+ENDMACRO(configure_and_install_pkg)
+
+SET(TARGET_NAME phoned)
+
+configure_and_install_pkg(phoned.pc)
+
+SET(SRCS src/phoned.cpp
+         src/phone.cpp
+         src/connman.cpp
+         src/bluez.cpp
+         src/obex.cpp
+         src/ofono.cpp
+         src/utils.cpp
+)
+
+ADD_EXECUTABLE(${TARGET_NAME} ${SRCS})
+TARGET_LINK_LIBRARIES(${TARGET_NAME}
+                      ${dpl_LDFLAGS}
+                      ${glib_LDFLAGS}
+                      ${gio_LDFLAGS}
+                      ${dbus_LDFLAGS}
+                      ${libebook-contacts_LDFLAGS}
+)
+INSTALL(TARGETS ${TARGET_NAME} DESTINATION ${DESTINATION_PREFIX})
+INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/scripts/org.tizen.phone.service DESTINATION ${DBUS_SERVICE_PREFIX})
+INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/scripts/phoned.service DESTINATION ${SYSTEMD_SERVICE_PREFIX})
+
diff --git a/packaging/phoned.changes b/packaging/phoned.changes
new file mode 100644 (file)
index 0000000..833e01f
--- /dev/null
@@ -0,0 +1,5 @@
+* Fri Mar 07 2014 brianjjones <brian.j.jones@intel.com> c7ee743
+- Initial checkin for phone daemon for the HTML5 UI
+
+
+
diff --git a/packaging/phoned.spec b/packaging/phoned.spec
new file mode 100644 (file)
index 0000000..6daf445
--- /dev/null
@@ -0,0 +1,51 @@
+Name:       phoned
+Summary:    A service to export OFono/Obex functionality over DBUS, to be used by WebRuntime plugin
+Version:    0.0.0
+Release:    1
+Group:      Development/Libraries
+License:    Apache-2.0
+Source0:    %{name}-%{version}.tar.gz
+
+BuildRequires:  pkgconfig(ewebkit2)
+BuildRequires:  pkgconfig(dpl-efl)
+BuildRequires:  pkgconfig(dpl-event-efl)
+BuildRequires:  pkgconfig(wrt-plugins-commons)
+BuildRequires:  pkgconfig(wrt-plugins-commons-javascript)
+
+BuildRequires:  evolution-data-server-devel
+BuildRequires:  wrt-plugins-tizen-devel
+BuildRequires:  expat-devel
+BuildRequires:  cmake
+BuildRequires:  gettext-devel
+BuildRequires:  pkgconfig(json-glib-1.0)
+
+%description
+A service to export OFono/Obex functionality over DBUS, to be used by WebRuntime plugin
+
+%prep
+%setup -q
+
+%build
+
+%define PREFIX "%{_libdir}/wrt-plugins"
+
+export LDFLAGS+="-Wl,--rpath=%{PREFIX} -Wl,--as-needed"
+
+cmake . -DCMAKE_INSTALL_PREFIX=%{_prefix} -DDPL_LOG="ON" -DENABLE_TIME_TRACER="OFF"
+
+make %{?jobs:-j%jobs} VERBOSE=1
+
+%install
+rm -rf %{buildroot}
+%make_install
+
+%post
+
+%postun
+
+%files
+%{_libdir}/pkgconfig/phoned.pc
+%{_prefix}/sbin/phoned
+%{_prefix}/share/dbus-1/services/org.tizen.phone.service
+%{_prefix}/lib/systemd/user/phoned.service
+
diff --git a/pkgconfig/phoned.pc.in b/pkgconfig/phoned.pc.in
new file mode 100644 (file)
index 0000000..976c37a
--- /dev/null
@@ -0,0 +1,8 @@
+prefix=/usr
+project_name=phoned
+exec_prefix=${prefix}
+
+Name: phoned
+Description: phoned
+Version:
+Requires:
diff --git a/scripts/org.tizen.phone.service b/scripts/org.tizen.phone.service
new file mode 100644 (file)
index 0000000..40f2a64
--- /dev/null
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.tizen.phone
+Exec=/usr/bin/systemctl --user start phoned
diff --git a/scripts/phoned.service b/scripts/phoned.service
new file mode 100644 (file)
index 0000000..b72d284
--- /dev/null
@@ -0,0 +1,10 @@
+[Unit]
+Description=Service to provide/export Phone functionality
+After=sound.target
+
+[Service]
+ExecStart=/usr/sbin/phoned
+Type=simple
+
+[Install]
+WantedBy=tizen-middleware.target
diff --git a/src/Logger.h b/src/Logger.h
new file mode 100644 (file)
index 0000000..b28f1a5
--- /dev/null
@@ -0,0 +1,34 @@
+
+/*
+ *
+ * A logger to be used instead of the
+ * default one <Logger.h>, which prints
+ * to DLOG
+ *
+ */
+
+
+#ifndef LOGGER_H__
+#define LOGGER_H__
+
+#include <sstream>
+
+#undef LOG_TAG
+#define LOG_TAG "WRT_PLUGINS/TIZEN"
+
+#define _LOGGER(fmt, args...) \
+    do { \
+        std::ostringstream platformLog; \
+        platformLog << fmt; \
+        char buf[1024]; \
+        strcpy(buf, platformLog.str().c_str(), ##args); \
+        printf("%s(%d) > %s\n", __func__, __LINE__, buf); \
+    } while(0)
+
+#define LoggerD(fmt, args...)    _LOGGER(fmt, ##args)
+#define LoggerI(fmt, args...)    _LOGGER(fmt, ##args)
+#define LoggerW(fmt, args...)    _LOGGER(fmt, ##args)
+#define LoggerE(fmt, args...)    _LOGGER(fmt, ##args)
+
+#endif // LOGGER_H__
+
diff --git a/src/bluez.cpp b/src/bluez.cpp
new file mode 100644 (file)
index 0000000..743d9a6
--- /dev/null
@@ -0,0 +1,453 @@
+#include "bluez.h"
+#include "utils.h"
+
+#include <gio/gio.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <stdlib.h>
+#include <dbus/dbus.h>
+
+#include "Logger.h"
+
+namespace PhoneD {
+
+#define BLUEZ_PREFIX            "org.bluez"
+
+#define BLUEZ_SERVICE           BLUEZ_PREFIX
+#define BLUEZ_MANAGER_IFACE     BLUEZ_PREFIX ".Manager"
+#define BLUEZ_ADAPTER_IFACE     BLUEZ_PREFIX ".Adapter"
+#define BLUEZ_DEVICE_IFACE      BLUEZ_PREFIX ".Device"
+#define BLUEZ_AGENT_IFACE       BLUEZ_PREFIX ".Agent"
+
+#define AGENT_PATH              "/org/bluez/agent_poc"
+#define AGENT_CAPABILITIES      "KeyboardDisplay"
+
+#define AGENT_PASSKEY            123456
+#define AGENT_PINCODE           "123456"
+
+#define AGENT_INTERFACE_XML                                 \
+    "<node>"                                                \
+    "  <interface name='org.bluez.Agent'>"                  \
+    "    <method name='Release'>"                           \
+    "    </method>"                                         \
+    "    <method name='Authorize'>"                         \
+    "      <arg type='o' name='device' direction='in'/>"    \
+    "      <arg type='s' name='uuid' direction='in'/>"      \
+    "    </method>"                                         \
+    "    <method name='RequestPinCode'>"                    \
+    "      <arg type='o' name='device' direction='in'/>"    \
+    "      <arg type='s' name='pincode' direction='out'/>"  \
+    "    </method>"                                         \
+    "    <method name='RequestPasskey'>"                    \
+    "      <arg type='o' name='device' direction='in'/>"    \
+    "      <arg type='u' name='passkey' direction='out'/>"  \
+    "    </method>"                                         \
+    "    <method name='DisplayPasskey'>"                    \
+    "      <arg type='o' name='device' direction='in'/>"    \
+    "      <arg type='u' name='passkey' direction='in'/>"   \
+    "    </method>"                                         \
+    "    <method name='DisplayPinCode'>"                    \
+    "      <arg type='o' name='device' direction='in'/>"    \
+    "      <arg type='s' name='pincode' direction='in'/>"   \
+    "    </method>"                                         \
+    "    <method name='RequestConfirmation'>"               \
+    "      <arg type='o' name='device' direction='in'/>"    \
+    "      <arg type='u' name='passkey' direction='in'/>"   \
+    "    </method>"                                         \
+    "    <method name='ConfirmModeChange'>"                 \
+    "      <arg type='s' name='mode' direction='in'/>"      \
+    "    </method>"                                         \
+    "    <method name='Cancel'>"                            \
+    "    </method>"                                         \
+    "  </interface>"                                        \
+    "</node>"
+
+/* NOTE:
+ * "Release"             ... does nothing
+ * "Authorize"           ... automatically authorized
+ * "RequestPinCode"      ... used default pin code (AGENT_PINCODE)
+ * "RequestPasskey"      ... used default passkey (AGENT_PASSKEY)
+ * "RequestConfirmation" ... automatically confirmed
+ * "DisplayPinCode"      ... does nothing
+ * "DisplayPasskey"      ... does nothing
+ * "ConfirmModeChange"   ... automatically confirmed
+ * "Cancel"              ... does nothing
+ */
+
+Bluez::Bluez() :
+    mAdapterPath(NULL),
+    mAgentRegistrationId(-1),
+    mAgentIntrospectionData(NULL)
+{
+    LoggerD("entered");
+
+    mAdapterPath = getDefaultAdapter();
+    if(!mAdapterPath) {
+        LoggerE("Unable to get default adapter");
+    }
+    memset(&mAgentIfaceVTable, 0, sizeof(mAgentIfaceVTable));
+
+    // subscribe for AdapterAdded/AdapterRemoved to get notification about the change
+    Utils::setSignalListener(G_BUS_TYPE_SYSTEM, BLUEZ_SERVICE, BLUEZ_MANAGER_IFACE,
+                             "/", "AdapterAdded", Bluez::handleSignal,
+                             this);
+    Utils::setSignalListener(G_BUS_TYPE_SYSTEM, BLUEZ_SERVICE, BLUEZ_MANAGER_IFACE,
+                             "/", "AdapterRemoved", Bluez::handleSignal,
+                             this);
+
+    if(mAdapterPath) {
+        Utils::setSignalListener(G_BUS_TYPE_SYSTEM, BLUEZ_SERVICE, BLUEZ_ADAPTER_IFACE,
+                                 mAdapterPath, "DeviceCreated", Bluez::handleSignal,
+                                 this);
+        Utils::setSignalListener(G_BUS_TYPE_SYSTEM, BLUEZ_SERVICE, BLUEZ_ADAPTER_IFACE,
+                                 mAdapterPath, "DeviceRemoved", Bluez::handleSignal,
+                                 this);
+        Utils::setSignalListener(G_BUS_TYPE_SYSTEM, BLUEZ_SERVICE, BLUEZ_ADAPTER_IFACE,
+                                 mAdapterPath, "PropertyChanged", Bluez::handleSignal,
+                                 this);
+    }
+}
+
+Bluez::~Bluez() {
+    if(mAdapterPath) {
+        free(mAdapterPath);
+        mAdapterPath = NULL;
+    }
+}
+
+gchar* Bluez::getDefaultAdapter()
+{
+    GError *err = NULL;
+    GVariant *reply = NULL;
+    reply = g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL),
+                                         BLUEZ_SERVICE,
+                                         "/",
+                                         BLUEZ_MANAGER_IFACE,
+                                         "DefaultAdapter",
+                                         NULL,
+                                         NULL,
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         -1,
+                                         NULL,
+                                         &err);
+    if(err || !reply) {
+        if(err) {
+            LoggerE("Failed to get default adapter: " << err->message);
+            g_error_free(err);
+        }
+        if(!reply)
+            LoggerE("Reply from 'DefaultAdapter' is null");
+        return NULL;
+    }
+
+    char *adapter = NULL;
+    g_variant_get(reply, "(o)", &adapter);
+    LoggerD("DefaultAdapter: " << adapter);
+
+    // make a copy of adapter, 'cause it will be destroyed when 'reply' is un-refed
+    char *result = adapter?strdup(adapter):NULL;
+
+    g_variant_unref(reply);
+
+    return result;
+}
+
+bool Bluez::setAdapterPowered(bool value) {
+    if(mAdapterPath) {
+        GError *err = NULL;
+        g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL,NULL),
+                                     BLUEZ_SERVICE,
+                                     mAdapterPath,
+                                     BLUEZ_ADAPTER_IFACE,
+                                     "SetProperty",
+                                     g_variant_new ("(sv)", // floating parameters are consumed, no cleanup/unref needed
+                                         "Powered",
+                                         g_variant_new("b", &value) // floating parameters are consumed, no cleanup/unref needed
+                                     ),
+                                     NULL,
+                                     G_DBUS_CALL_FLAGS_NONE,
+                                     -1,
+                                     NULL,
+                                     &err);
+
+        if(err) {
+            LoggerE("Failed to call \"SetProperty\" DBUS method: " << err->message);
+            g_error_free(err);
+            return false;
+        }
+
+        return true;
+    }
+
+    return false;
+}
+
+void Bluez::handleSignal(GDBusConnection  *connection,
+                         const gchar      *sender,
+                         const gchar      *object_path,
+                         const gchar      *interface_name,
+                         const gchar      *signal_name,
+                         GVariant         *parameters,
+                         gpointer          user_data)
+{
+    LoggerD("signal received: '" << interface_name << "' -> '" << signal_name << "' -> '" << object_path << "'");
+
+    Bluez *ctx = static_cast<Bluez*>(user_data);
+    if(!ctx) {
+        LoggerD("Failed to cast to Bluez");
+        return;
+    }
+
+    if(!strcmp(interface_name, BLUEZ_MANAGER_IFACE)) {
+        if(!strcmp(signal_name, "AdapterAdded")) {
+            const char *adapter = NULL;
+            g_variant_get(parameters, "(o)", &adapter);
+            if(adapter) {
+                LoggerD("Adapter added: " << adapter);
+                if(!ctx->mAdapterPath) {
+                    // make added adapter as default
+                    ctx->mAdapterPath = strdup(adapter);
+                    //ctx->setupAgent();
+                    ctx->registerAgent();
+                    ctx->defaultAdapterAdded();
+                }
+            }
+        }
+        else if(!strcmp(signal_name, "AdapterRemoved")) {
+            const char *adapter = NULL;
+            g_variant_get(parameters, "(o)", &adapter);
+            if(adapter) {
+                LoggerD("Adapter removed: " << adapter);
+                if(ctx->mAdapterPath && !strcmp(ctx->mAdapterPath, adapter)) {
+                    // removed the default adapter
+                    free(ctx->mAdapterPath);
+                    ctx->mAdapterPath = NULL;
+                    ctx->defaultAdapterRemoved();
+                }
+            }
+        }
+    }
+    else if(!strcmp(interface_name, BLUEZ_ADAPTER_IFACE)) {
+        if(!strcmp(signal_name, "DeviceCreated")) {
+            const char *device;
+            g_variant_get(parameters, "(o)", &device);
+            LoggerD("DeviceCreated: " << (device?device:"UNKNOWN"));
+
+            // subscribe for PropertyChanged signal on the device,
+            // to get notification about device being paired
+            Utils::setSignalListener(G_BUS_TYPE_SYSTEM, BLUEZ_SERVICE, BLUEZ_DEVICE_IFACE,
+                                     device, "PropertyChanged", Bluez::handleSignal,
+                                     ctx);
+
+        }
+        else if(!strcmp(signal_name, "DeviceRemoved")) {
+            const char *device;
+            g_variant_get(parameters, "(o)", &device);
+            LoggerD("DeviceRemoved: " << (device?device:"UNKNOWN"));
+            ctx->deviceRemoved(device);
+        }
+        else if(!strcmp(signal_name, "PropertyChanged")) {
+            const char *name;
+            GVariant *v_value;
+            g_variant_get(parameters, "(sv)", &name, &v_value);
+            LoggerD("\tname=" << name);
+            if(!strcmp(name, "Powered")) {
+                bool value = g_variant_get_boolean(v_value);
+                ctx->adapterPowered(value);
+                //LoggerD("\tvalue=" << (value?"TRUE":"FALSE"));
+            }
+        }
+    }
+    else if(!strcmp(interface_name, BLUEZ_DEVICE_IFACE)) {
+        if(!strcmp(signal_name, "PropertyChanged")) {
+            const char *name;
+            GVariant *value;
+            g_variant_get(parameters, "(sv)", &name, &value);
+            if(!strcmp(name, "Paired")) {
+                bool paired = g_variant_get_boolean(value);
+                if(paired) { // the device has been paired
+                    ctx->deviceCreated(object_path);
+                }
+            }
+        }
+    }
+}
+
+void Bluez::agentHandleMethodCall( GDBusConnection       *connection,
+                                   const gchar           *sender,
+                                   const gchar           *object_path,
+                                   const gchar           *interface_name,
+                                   const gchar           *method_name,
+                                   GVariant              *parameters,
+                                   GDBusMethodInvocation *invocation,
+                                   gpointer               user_data)
+{
+    LoggerD("entered\n\tsender=" << sender << "\n\tobject_path=" << object_path << "\n\tinterface_name=" << interface_name << "\n\tmethod_name=" << method_name);
+
+    Bluez *ctx = static_cast<Bluez*>(user_data);
+    if(!ctx) {
+        LoggerD("Failed to cast to Bluez");
+        g_dbus_method_invocation_return_value(invocation, NULL);
+        return;
+    }
+
+    if(!strcmp(method_name, "Authorize")) {
+        g_dbus_method_invocation_return_value(invocation, NULL);
+    }
+    else if(!strcmp(method_name, "RequestPinCode")) {
+        g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", AGENT_PINCODE));
+    }
+    else if(!strcmp(method_name, "RequestPasskey")) {
+        g_dbus_method_invocation_return_value(invocation, g_variant_new("(u)", AGENT_PASSKEY));
+    }
+    else if (!strcmp(method_name, "Release")) {
+        if(!strcmp(object_path, AGENT_PATH)) { // released agent for pairing
+            bool unregistered = g_dbus_connection_unregister_object(g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL), ctx->mAgentRegistrationId);
+            if(unregistered)
+                ctx->mAgentRegistrationId = -1;
+        }
+        g_dbus_method_invocation_return_value(invocation, NULL);
+    }
+    else {
+        // DisplayPasskey, DisplayPinCode, RequestConfirmation, ConfirmModeChange, Cancel
+        g_dbus_method_invocation_return_value(invocation, NULL);
+    }
+}
+
+bool Bluez::isDevicePaired(const char *bt_addr) {
+    bool paired = false;
+
+    if(!mAdapterPath)
+        return paired;
+
+    GError *err = NULL;
+    GVariant *reply = NULL;
+    reply = g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL),
+                                         BLUEZ_SERVICE,
+                                         mAdapterPath,
+                                         BLUEZ_ADAPTER_IFACE,
+                                         "FindDevice",
+                                         g_variant_new("(s)", bt_addr),
+                                         NULL,
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         -1,
+                                         NULL,
+                                         &err);
+
+    if(err || !reply) {
+        if(err)
+            g_error_free(err);
+        return paired;
+    }
+
+    const char *tmp = NULL;
+    g_variant_get(reply, "(o)", &tmp);
+    if(!tmp) {
+        g_variant_unref(reply);
+        return paired;
+    }
+
+    char *device = strdup(tmp);
+    g_variant_unref(reply);
+
+    // get device properties and check if the device is Paired
+    reply = g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL),
+                                         BLUEZ_SERVICE,
+                                         device,
+                                         BLUEZ_DEVICE_IFACE,
+                                         "GetProperties",
+                                         NULL,
+                                         NULL,
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         -1,
+                                         NULL,
+                                         &err);
+    if(err || !reply) {
+        if(err)
+            g_error_free(err);
+        free(device);
+        return paired;
+    }
+
+    GVariantIter *iter;
+    g_variant_get(reply, "(a{sv})", &iter);
+    const char *key;
+    GVariant *value;
+    while(g_variant_iter_next(iter, "{sv}", &key, &value)) {
+        if(!strcmp(key, "Paired")) {
+            paired = g_variant_get_boolean(value);
+            break;
+        }
+    }
+
+    free(device);
+    g_variant_unref(reply);
+
+    return paired;
+}
+
+void Bluez::setupAgent()
+{
+    LoggerD("entered: registering agent " << AGENT_PATH);
+
+    /*
+    if(mAgentRegistrationId > 0) { // alread registered
+        LoggerD("Bluez agent registered");
+        return;
+    }
+    */
+
+    mAgentIfaceVTable.method_call = Bluez::agentHandleMethodCall;
+    mAgentIntrospectionData = g_dbus_node_info_new_for_xml(AGENT_INTERFACE_XML, NULL);
+
+    if (mAgentIntrospectionData == NULL) {
+        LoggerD("failed to create introspection data.");
+        return;
+    }
+    LoggerD("introspection data parsed OK");
+
+    GError *err = NULL;
+    mAgentRegistrationId = g_dbus_connection_register_object( g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL),
+                                                  AGENT_PATH,
+                                                  mAgentIntrospectionData->interfaces[0],
+                                                  &mAgentIfaceVTable, //const GDBusInterfaceVTable *vtable,
+                                                  this, //user_data
+                                                  NULL, //GDestroyNotify
+                                                  &err);
+
+    if(err) {
+        LoggerD("Failed to register object: " << AGENT_PATH << " : " << err->message);
+        g_error_free(err);
+        return;
+    }
+    LoggerD("object registered with id=" << mAgentRegistrationId);
+}
+
+void Bluez::registerAgent()
+{
+    LoggerD("entered");
+
+    GError *err = NULL;
+    g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL),
+                                 BLUEZ_SERVICE,
+                                 mAdapterPath,
+                                 BLUEZ_ADAPTER_IFACE,
+                                 "RegisterAgent",
+                                 g_variant_new("(os)", AGENT_PATH, AGENT_CAPABILITIES), // floating variants are consumed
+                                 NULL,
+                                 G_DBUS_CALL_FLAGS_NONE,
+                                 -1,
+                                 NULL,
+                                 &err);
+    if(err) {
+        LoggerE("Failed to register agent: " << err->message);
+        g_error_free(err);
+        return;
+    }
+
+    LoggerD("Agent registered");
+}
+
+} // PhoneD
+
diff --git a/src/bluez.h b/src/bluez.h
new file mode 100644 (file)
index 0000000..bd7f4dc
--- /dev/null
@@ -0,0 +1,110 @@
+#ifndef BLUEZ_H_
+#define BLUEZ_H_
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gio/gio.h>
+#include <string.h>
+#include <string>
+#include <vector>
+#include <map>
+
+namespace PhoneD {
+
+/**
+ * @addtogroup phoned
+ * @{
+ */
+
+/*! \class PhoneD::Bluez
+ *  \brief Class which is utilizing Bluez D-Bus service. It is a base class and is not meant to be instantiated directly.
+ *
+ * A class providing access to <a href="http://www.bluez.org">Bluez</a> functionality. Subscribes to certain DBUS signals of org.bluez service, see Bluez::Bluez() for more details. It registers an agent for BT operation authentification.
+ */
+class Bluez {
+
+    public:
+        /**
+         * A default constructor. Constructs the object and subscribes for certain D-Bus signals of \b org.bluez service:
+         * \li "AdapterAdded" on \b org.bluez.Manager interface - To get notified when hci adapter is added.
+         * \li "AdapterRemoved" on \b org.bluez.Manager interface - To get notified when hci adapter is removed.
+         * \li "DeviceCreated" on \b org.bluez.Adapter interface - To get notified when a device (remote device) is created, ie. when pairing is initiated.
+         * \li "DeviceRemoved" on \b org.bluez.Adapter interface - To get notified when a device (remote device) is removed, ie. when the device is unpaired.
+         * \li "PropertyChanged" on \b org.bluez.Adapter interface - To get notified when there is a change in some of adapter's properties, eg. when the adapter is "Powered", the name of adapter has changed, etc.
+         */
+        Bluez();
+
+        /**
+         * A destructor.
+         * Destructs the object.
+         */
+        ~Bluez();
+
+    protected:
+
+        /**
+         * Method to setup an agent for BT authentication operations and register the agent object on the D-Bus.
+         * @see registerAgent()
+         */
+        void setupAgent();
+
+        /**
+         * Registers created agent via setupAgent() method to the adapter (the default one). It is done by calling \b RegisterAgent method on \b org.bluez.Adapter interface.
+         * @see setupAgent()
+         */
+        void registerAgent();
+
+        /**
+         * Gets the state whether the remote device is paired, or not.
+         * @param[in] bt_address A MAC address of the remote device to get paired state of.
+         * @return \b True if the device is paired, or \b false if it is not.
+         */
+        bool isDevicePaired(const char *bt_address);
+
+        /**
+         * Sets \b Powered state of the default adapter.
+         * @param[in] value Specifies whether to power ON, or OFF the adapter.
+         * @return \b True, if the setting was successful, otherwise returns \b false.
+         */
+        bool setAdapterPowered(bool value); // Power ON/OFF hci0 adapter
+
+    private:
+        gchar* getDefaultAdapter();
+        static void handleSignal(GDBusConnection *connection,
+                                 const gchar     *sender,
+                                 const gchar     *object_path,
+                                 const gchar     *interface_name,
+                                 const gchar     *signal_name,
+                                 GVariant        *parameters,
+                                 gpointer         user_data);
+
+        static void agentHandleMethodCall( GDBusConnection       *connection,
+                                           const gchar           *sender,
+                                           const gchar           *object_path,
+                                           const gchar           *interface_name,
+                                           const gchar           *method_name,
+                                           GVariant              *parameters,
+                                           GDBusMethodInvocation *invocation,
+                                           gpointer               user_data);
+
+        virtual void adapterPowered(bool value) = 0; // to handle "Powered" property changed on ADAPTER, due to eg. RF-kill
+        virtual void defaultAdapterAdded() = 0;
+        virtual void defaultAdapterRemoved() = 0;
+        virtual void deviceCreated(const char *device) = 0;
+        virtual void deviceRemoved(const char *device) = 0;
+
+    private:
+        gchar* mAdapterPath;
+
+        // Agent
+        int mAgentRegistrationId;
+        GDBusInterfaceVTable mAgentIfaceVTable;
+        GDBusNodeInfo   *mAgentIntrospectionData;
+};
+
+} // PhoneD
+
+#endif /* BLUEZ_H_ */
+
+/** @} */
+
diff --git a/src/connman.cpp b/src/connman.cpp
new file mode 100644 (file)
index 0000000..451169f
--- /dev/null
@@ -0,0 +1,167 @@
+
+#include "connman.h"
+#include "utils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gio/gio.h>
+
+#include "Logger.h"
+
+namespace PhoneD {
+
+#define CONNMAN_PREFIX                     "net.connman"
+
+#define CONNMAN_SERVICE                    CONNMAN_PREFIX
+#define CONNMAN_MANAGER_IFACE              CONNMAN_PREFIX ".Manager"
+#define CONNMAN_TECHNOLOGY_IFACE           CONNMAN_PREFIX ".Technology"
+
+ConnMan::ConnMan() :
+    mBluetoothPowered(false)
+{
+    char *bluetooth = getBluetoothTechnology();
+    if(bluetooth) {
+        Utils::setSignalListener(G_BUS_TYPE_SYSTEM, CONNMAN_SERVICE, CONNMAN_TECHNOLOGY_IFACE,
+                                 bluetooth, "PropertyChanged", ConnMan::handleSignal,
+                                 this);
+        free(bluetooth);
+    }
+}
+
+ConnMan::~ConnMan() {
+    char *bluetooth = getBluetoothTechnology();
+    if(bluetooth) {
+        Utils::removeSignalListener(G_BUS_TYPE_SYSTEM, CONNMAN_SERVICE, CONNMAN_TECHNOLOGY_IFACE, bluetooth, "PropertyChanged");
+        free(bluetooth);
+    }
+}
+
+void ConnMan::init() {
+    char *bluetooth = getBluetoothTechnology();
+    if(bluetooth)
+        free(bluetooth);
+}
+
+char *ConnMan::getBluetoothTechnology() {
+    GError *err = NULL;
+    GVariant *reply = NULL;
+    reply = g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL,NULL),
+                                         CONNMAN_SERVICE,
+                                         "/",
+                                         CONNMAN_MANAGER_IFACE,
+                                         "GetTechnologies",
+                                         NULL,
+                                         NULL,
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         -1,
+                                         NULL,
+                                         &err);
+
+    if(err || !reply) {
+        if(err) {
+            LoggerE("Failed to call \"GetTechnologies\" DBUS method: " << err->message);
+            g_error_free(err);
+        }
+        else if(!reply)
+            LoggerE("Reply from \"GetTechnologies\" DBUS method is NULL");
+        return NULL;
+    }
+
+    char *technology = NULL, *result = NULL;
+    GVariantIter *props = NULL;
+    GVariantIter *technologies = NULL;
+    g_variant_get(reply, "(a(oa{sv}))", &technologies);
+    while(g_variant_iter_next(technologies, "(oa{sv})", &technology, &props)) {
+        if(technology && strstr(technology, "bluetooth")) {
+            result = strdup(technology);
+            const char *key;
+            GVariant *value;
+            while(g_variant_iter_next(props, "{sv}", &key, &value)) {
+                if(!strcmp(key, "Powered")) {
+                    bool powered = g_variant_get_boolean(value);
+                    LoggerD("powered = " << powered);
+                    mBluetoothPowered = powered;
+                    break;
+                }
+            }
+            break;
+        }
+    }
+
+    g_variant_unref(reply);
+    return result;
+}
+
+bool ConnMan::setBluetoothPowered(bool value) {
+    char *bluetooth = getBluetoothTechnology();
+    if(bluetooth) {
+        GError *err = NULL;
+        g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL,NULL),
+                                     CONNMAN_SERVICE,
+                                     bluetooth,
+                                     CONNMAN_TECHNOLOGY_IFACE,
+                                     "SetProperty",
+                                     g_variant_new ("(sv)", // floating parameters are consumed, no cleanup/unref needed
+                                         "Powered",
+                                         g_variant_new_boolean(value)
+                                     ),
+                                     NULL,
+                                     G_DBUS_CALL_FLAGS_NONE,
+                                     -1,
+                                     NULL,
+                                     &err);
+
+        if(err) {
+            if((value  && strstr(err->message, "Already enabled")) || // it's not an error, 'casue the BT is already Powered ON
+               (!value && strstr(err->message, "Already disabled")))  // it's not an error, 'cause the BT is already Powered OFF
+            {
+                free(bluetooth);
+                g_error_free(err);
+                return true;
+            }
+            LoggerE("Failed to call \"SetProperty\" DBUS method: " << err->message);
+            free(bluetooth);
+            g_error_free(err);
+            return false;
+        }
+
+        free(bluetooth);
+        return true;
+    }
+
+    return false;
+}
+
+void ConnMan::handleSignal(GDBusConnection  *connection,
+                         const gchar      *sender,
+                         const gchar      *object_path,
+                         const gchar      *interface_name,
+                         const gchar      *signal_name,
+                         GVariant         *parameters,
+                         gpointer          user_data)
+{
+    LoggerD("signal received: '" << interface_name << "' -> '" << signal_name << "' -> '" << object_path << "'");
+
+    ConnMan *ctx = static_cast<ConnMan*>(user_data);
+    if(!ctx) {
+        LoggerD("Failed to cast to ConnMan");
+        return;
+    }
+
+    if(!strcmp(interface_name, CONNMAN_TECHNOLOGY_IFACE)) {
+        if(!strcmp(signal_name, "PropertyChanged")) {
+            const char *name;
+            GVariant *value;
+            g_variant_get(parameters, "(sv)", &name, &value);
+            if(!strcmp(name, "Powered")) {
+                bool powered = g_variant_get_boolean(value);
+                ctx->mBluetoothPowered = powered;
+                //LoggerD("\tBT Powered set to " << (powered?"TRUE":"FALSE"));
+            }
+        }
+    }
+}
+
+} // PhoneD
+
diff --git a/src/connman.h b/src/connman.h
new file mode 100644 (file)
index 0000000..7c61035
--- /dev/null
@@ -0,0 +1,72 @@
+#ifndef CONNMAN_H_
+#define CONNMAN_H_
+
+#include <dbus/dbus.h>
+#include <gio/gio.h>
+
+namespace PhoneD {
+
+/**
+ * @addtogroup phoned
+ * @{
+ */
+
+/*! \class PhoneD::ConnMan
+ *  \brief Class which is utilizing ConnMan D-Bus service. It is a base class and is not meant to be instantiated directly.
+ *
+ * A class providing basic <a href="https://connman.net">ConnMan</a> functionality on Bluetooth technology, like setting 'Powered' state to ON/OFF.
+ */
+class ConnMan {
+    public:
+        /**
+         * A constructor. Constructs the object.
+         */
+        ConnMan();
+
+        /**
+         * A destructor. Destructs the object.
+         */
+        ~ConnMan();
+
+        /**
+         * Sets the Bluetooth technology "Powered" state to ON/OFF, ie. sets the Soft-block on Bluetooth, just like you would achieve via `rfkill block bluetooth`. You can check the actual  state of "Powered" property via RFkill utility, typing the command `rfkill list`. The method takes one argument specifying the state that the Bluetooth will be set to.
+         * @param[in] value Specifies whether the BT should be switched ON, or OFF
+         * @return A \b bool Indicates the result of the operation.
+         */
+        bool setBluetoothPowered(bool value);
+
+    private:
+        /**
+         * Method to get Bluetooth technology object. If it returns \b NULL, BT technology is not found.
+         * @see setBluetoothPowered()
+         * @return  A C-string describing Bluetooth technology object, or \b NULL, if the technology is not found. The caller is responsible for feeing the memory.
+         */
+        char *getBluetoothTechnology();
+
+        static void handleSignal(GDBusConnection  *connection,
+                                 const gchar      *sender,
+                                 const gchar      *object_path,
+                                 const gchar      *interface_name,
+                                 const gchar      *signal_name,
+                                 GVariant         *parameters,
+                                 gpointer          user_data);
+    protected:
+        /**
+         * Method to set initial value of mBluetoothPowered variable.
+         * @see mBluetoothPowered
+         */
+        void init();
+
+    protected:
+        /**
+         * A variable to store \b Powered state of BT.
+         */
+        bool mBluetoothPowered;
+};
+
+} // PhoneD
+
+#endif /* CONNMAN_H_ */
+
+/** @} */
+
diff --git a/src/obex.cpp b/src/obex.cpp
new file mode 100644 (file)
index 0000000..4741358
--- /dev/null
@@ -0,0 +1,1046 @@
+
+#include "obex.h"
+#include "utils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gio/gio.h>
+#include <fstream>
+
+#include "Logger.h"
+
+namespace PhoneD {
+
+#define OBEX_PREFIX                        "org.bluez.obex"
+
+#define OBEX_CLIENT_SERVICE                OBEX_PREFIX ".client"
+#define OBEX_CLIENT_IFACE                  OBEX_PREFIX ".Client"
+#define OBEX_PHONEBOOK_IFACE               OBEX_PREFIX ".PhonebookAccess"
+#define OBEX_TRANSFER_IFACE                OBEX_PREFIX ".Transfer"
+
+// check for stalled active transfer (in seconds)
+#define CHECK_STALLED_TRANSFER_TIMEOUT     120
+
+/*! \class PhoneD::SyncPBData
+ * A Class to provide a storage for Queued synchronization requests.
+ */
+class SyncPBData {
+    public:
+        /**
+         * A default constructor which allows to specify object data in the construction phase.
+         * @param[in] location Location of phonebook data, see SyncPBData::location.
+         * @param[in] phonebook Phonebook data identification, see SyncPBData::phonebook.
+         * @param[in] count Number of latest entries to be synchronized (the default is 0), see SyncPBData::count.
+         */
+        SyncPBData(const char *location, const char *phonebook, unsigned long count = 0)
+        {
+            this->location = location;
+            this->phonebook = phonebook;
+            this->count = count;
+        }
+    public:
+        const char *location;   /*!< Location of phonebook data: "INT", "SIM1", "SIM2". */
+        const char *phonebook;  /*!< Phonebook data identification: "pb", "ich", "och", "mch", "cch". */
+        unsigned long count;    /*!< Number of latest entries to be synchronized (0 means to request all). */
+};
+
+Obex::Obex() :
+    mSelectedRemoteDevice(""),
+    mSession(NULL),
+    mActiveTransfer(NULL)
+{
+    LoggerD("entered");
+    mContacts.clear();
+    mContactsOrder.clear();
+    mCallHistory.clear();
+    mCallHistoryOrder.clear();
+}
+
+Obex::~Obex() {
+    LoggerD("entered");
+    removeSession(false); // remove session if it's active
+}
+
+void Obex::createSession(const char *bt_address) {
+    LoggerD("entered");
+
+    // remove existing session if exists
+    removeSession(false);
+
+    GVariant *args[8];
+    int nargs = 0;
+
+    // add dict entry for "PBAP" target
+    GVariant * key = g_variant_new_string("Target");
+    GVariant * str = g_variant_new_string("PBAP");
+    GVariant * var = g_variant_new_variant(str);
+    args[nargs++] = g_variant_new_dict_entry(key, var);
+    GVariant *array = g_variant_new_array(G_VARIANT_TYPE("{sv}"), args, nargs);
+
+    // build the parameters variant
+    GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("(sa{sv})"));
+    g_variant_builder_add_value(builder, g_variant_new("s", bt_address));
+    g_variant_builder_add_value(builder, array);
+    GVariant *parameters = g_variant_builder_end(builder);
+
+    g_dbus_connection_call( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                            OBEX_CLIENT_SERVICE,
+                            "/",
+                            OBEX_CLIENT_IFACE,
+                            "CreateSession",
+                            parameters,
+                            NULL,
+                            G_DBUS_CALL_FLAGS_NONE,
+                            -1,
+                            NULL,
+                            Obex::asyncCreateSessionReadyCallback,
+                            this);
+}
+
+// callback for async call of "CreateSession" method
+void Obex::asyncCreateSessionReadyCallback(GObject *source, GAsyncResult *result, gpointer user_data) {
+    Obex *ctx = static_cast<Obex*>(user_data);
+    if(!ctx) {
+        LoggerE("Failed to cast object: Obex");
+        return;
+    }
+
+    GError *err = NULL;
+    GVariant *reply;
+    reply = g_dbus_connection_call_finish(g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL), result, &err);
+    if(err || !reply) {
+        ctx->createSessionFailed(err?err->message:"Invalid reply from 'CreateSession'");
+        if(err)
+            g_error_free(err);
+        return;
+    }
+
+    const char *session = NULL;
+    g_variant_get(reply, "(o)", &session);
+    LoggerD("Created session: " << session);
+
+    // make a copy of object path, since it will be destroyed when the reply is unref-ed
+    if(session) {
+        ctx->mSession = strdup(session);
+        ctx->createSessionDone(ctx->mSession);
+    }
+    else
+        ctx->createSessionFailed("Failed to get 'session' from the 'CreateSession' reply");
+
+    g_variant_unref(reply);
+}
+
+// location:  "INT", "SIM1", "SIM2"
+// phonebook: "pb", "ich", "och", "mch", "cch"
+bool Obex::select(const char *location, const char *phonebook) {
+    LoggerD("Selecting phonebook: " << location << "/" << phonebook);
+
+    if(!mSession) {
+        LoggerE("No session to execute operation on");
+        return false;
+    }
+
+    GError *err = NULL;
+    g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                 OBEX_CLIENT_SERVICE,
+                                 mSession,
+                                 OBEX_PHONEBOOK_IFACE,
+                                 "Select",
+                                 g_variant_new("(ss)", location, phonebook), // floating variants are consumed
+                                 NULL,
+                                 G_DBUS_CALL_FLAGS_NONE,
+                                 -1,
+                                 NULL,
+                                 &err);
+    if(err) {
+        LoggerE("Failed to select phonebook " << location << "/" << phonebook << ": " << err->message);
+        g_error_free(err);
+        return false;
+    }
+
+    return true;
+}
+
+void Obex::removeSession(bool notify) {
+    if(!mSession) // there isn't active session to be removed
+        return;
+
+    LoggerD("Removing session:" << mSession);
+
+    // delete/unref individual contacts
+    for(auto it=mContacts.begin(); it!=mContacts.end(); ++it) {
+        EContact *contact = (*it).second;
+        if(contact) {
+            // TODO: delete also all its attribs?
+            g_object_unref(contact);
+        }
+    }
+    mContacts.clear();
+    mContactsOrder.clear();
+
+    // delete/unref individual cll history entries
+    for(auto it=mCallHistory.begin(); it!=mCallHistory.end(); ++it) {
+        EContact *item = (*it).second;
+        if(item) {
+            // TODO: delete also all its attribs?
+            g_object_unref(item);
+        }
+    }
+    mCallHistory.clear();
+    mCallHistoryOrder.clear();
+
+    GError *err = NULL;
+    g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                 OBEX_CLIENT_SERVICE,
+                                 "/",
+                                 OBEX_CLIENT_IFACE,
+                                 "RemoveSession",
+                                 g_variant_new("(o)", mSession), // floating variants are consumed
+                                 NULL,
+                                 G_DBUS_CALL_FLAGS_NONE,
+                                 -1,
+                                 NULL,
+                                 &err);
+    if(err) {
+        LoggerE("Failed to remove session " << mSession << ": " << err->message);
+        g_error_free(err);
+    }
+
+    free(mSession);
+    mSession = NULL;
+
+
+    // clear sync queue, since all data/requests in the queue are not valid anymore
+    clearSyncQueue();
+
+    if(notify)
+        removeSessionDone();
+}
+
+// this method should be called once the individual sync operation has finished
+// the sync operation that is on-going is at the top of the queue
+void Obex::initiateNextSyncRequest() {
+    // remove the actual sync operation, which has just finished
+    if(!mSyncQueue.empty()) {
+        delete mSyncQueue.front();
+        mSyncQueue.pop_front();
+        if(!mSyncQueue.empty()) {
+            // there is another sync request in the queue
+            SyncPBData *sync = mSyncQueue.front();
+            LoggerD("synchronizing data: " << sync->location << "/" << sync->phonebook);
+            if(select(sync->location, sync->phonebook)) { // do call 'pullAll' only if 'select' operation was successful
+                if(OBEX_ERR_NONE != pullAll(sync->phonebook, sync->count)) {
+                    // 'PullAll' has not started at all, ie. there will be no 'Complete'/'Error' signals
+                    // on 'Transport' - no signal at all, threfore go to next sync request from sync queue
+                    initiateNextSyncRequest();
+                }
+            }
+        }
+        else {
+            LoggerD("Synchronization done");
+            pbSynchronizationDone();
+        }
+    }
+    else {
+        // we should never get here, but just in case
+        // inform the user that the sync has finished
+        // TODO: emit the signal here
+    }
+}
+
+void Obex::clearSyncQueue() {
+    /*
+    if(mActiveTransfer) {
+        GError *err = NULL;
+        g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                     OBEX_CLIENT_SERVICE,
+                                     mActiveTransfer,
+                                     OBEX_TRANSFER_IFACE,
+                                     "Cancel",
+                                     NULL,
+                                     NULL,
+                                     G_DBUS_CALL_FLAGS_NONE,
+                                     -1,
+                                     NULL,
+                                     &err);
+        if(err) {
+            LoggerE("Failed to 'Cancel' active Transfer: " << err->message);
+            g_error_free(err);
+            return;
+        }
+        LoggerD("Active transfer 'Cancel'ed");
+    }
+    */
+
+    for(unsigned int i=0; i<mSyncQueue.size(); i++) {
+        delete mSyncQueue.at(i);
+    }
+    mSyncQueue.clear();
+}
+
+void Obex::setSelectedRemoteDevice(std::string &btAddress) {
+    mSelectedRemoteDevice = btAddress;
+}
+
+//DBUS: object, dict PullAll(string targetfile, dict filters)
+Obex::Error Obex::pullAll(const char *type, unsigned long count) {
+    LoggerD("entered");
+
+    if(!type) {
+        LoggerD("Invalid input argument(s)");
+        return OBEX_ERR_INVALID_ARGUMENTS;
+    }
+
+    if(!mSession) {
+        LoggerE("No session to execute operation on");
+        initiateNextSyncRequest();
+        return OBEX_ERR_INVALID_SESSION;
+    }
+
+    GError *err = NULL;
+    GVariant *reply;
+
+    GVariant *filters[8];
+    int nfilters = 0;
+
+    GVariant *name, *str, *var;
+    // "Format" -> "vcard30"
+    name = g_variant_new_string("Format");
+    str = g_variant_new_string("vcard30");
+    var = g_variant_new_variant(str);
+    filters[nfilters++] = g_variant_new_dict_entry(name, var);
+
+    // "Offset" -> Offset of the first item, default is 0
+    if(count > 0) {
+        name = g_variant_new_string("MaxCount");
+        str = g_variant_new_uint16(count);
+        var = g_variant_new_variant(str);
+        filters[nfilters++] = g_variant_new_dict_entry(name, var);
+    }
+
+    GVariant *array = g_variant_new_array(G_VARIANT_TYPE("{sv}"), filters, nfilters);
+
+    // build the parameters variant
+    GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("(sa{sv})"));
+    g_variant_builder_add_value(builder, g_variant_new("s", "")); // target file name will be automatically calculated
+    g_variant_builder_add_value(builder, array);
+    GVariant *parameters = g_variant_builder_end(builder);
+
+    reply = g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                         OBEX_CLIENT_SERVICE,
+                                         mSession,
+                                         OBEX_PHONEBOOK_IFACE,
+                                         "PullAll",
+                                         parameters,
+                                         NULL,
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         -1,
+                                         NULL,
+                                         &err);
+
+    if(err) {
+        LoggerE("Failed to 'PullAll': " << err->message);
+        initiateNextSyncRequest();
+        g_error_free(err);
+        return OBEX_ERR_DBUS_ERROR;
+    }
+
+    if(!reply) {
+        LoggerE("Reply from call 'PullAll' is NULL");
+        initiateNextSyncRequest();
+        return OBEX_ERR_DBUS_INVALID_REPLY;
+    }
+
+    const char *transfer = NULL;
+    GVariantIter *iter;
+    g_variant_get(reply, "((oa{sv}))", &transfer, &iter);
+    LoggerD("transfer path = " << transfer);
+    mActiveTransfer = strdup(transfer);
+    g_timeout_add(CHECK_STALLED_TRANSFER_TIMEOUT*1000,
+                  Obex::checkStalledTransfer,
+                  new CtxCbData(this, NULL, strdup(transfer), NULL));
+
+    // let's abuse 'cb' field from CtxCbData to store selected remote device's MAC
+    CtxCbData *data = new CtxCbData(this, strdup(mSelectedRemoteDevice.c_str()), NULL, (void*)type);
+    // we can set signal listener for "Error" from here,
+    // since we don't need to know file path of stored transfer data
+    Utils::setSignalListener(G_BUS_TYPE_SESSION, OBEX_CLIENT_SERVICE,
+                             OBEX_TRANSFER_IFACE, transfer, "Error",
+                             Obex::handleSignal, data); //TODO: think of using 'error' callback here,
+                                                        //      or if error occurs, returns empty NULL string
+
+    const char *key;
+    GVariant *value;
+    while(g_variant_iter_next(iter, "{sv}", &key, &value)) {
+        if(!strcmp(key, "Size")) { // "Size"
+            //guint64 val = g_variant_get_uint64(value);
+            //LoggerD(key << " = " << val);
+        }
+        else { // "Name", "Filename"
+            //LoggerD(key << " = " << g_variant_get_string(value, NULL));
+            if(!strcmp(key, "Filename")) {
+                const char *fileName = g_variant_get_string(value, NULL);
+                if(fileName) {
+                    LoggerD("Saving pulled data/VCards into: " << fileName);
+                    // we call subscribe for "Complete" signal here, since we need to know path of stored file
+                    // !!! what if signal comes before we subscribe for it? ... CAN IT? ... signals from DBUS
+                    // should be executed in the thread the registration was made from, ie. this method has to
+                    // to be completed first
+                    data->data1 = strdup(fileName);
+                    Utils::setSignalListener(G_BUS_TYPE_SESSION, OBEX_CLIENT_SERVICE,
+                                             OBEX_TRANSFER_IFACE, transfer, "Complete",
+                                             Obex::handleSignal, data);
+                }
+            }
+        }
+    }
+
+    g_variant_unref(reply);
+
+    return OBEX_ERR_NONE;
+}
+
+gboolean Obex::checkStalledTransfer(gpointer user_data) {
+    CtxCbData *data = static_cast<CtxCbData*>(user_data);
+    if(!data)
+        return G_SOURCE_REMOVE; // single shot timeout
+
+    Obex *ctx = static_cast<Obex*>(data->ctx);
+    char *transfer = static_cast<char*>(data->data1);
+    if(!ctx || !transfer) {
+        LoggerE("Failed to cast to Obex");
+        return G_SOURCE_REMOVE; // single shot timeout
+    }
+    if(ctx->mActiveTransfer && !strcmp(ctx->mActiveTransfer, transfer)) {
+        LoggerD("The active transfer is Stalled");
+        ctx->clearSyncQueue();
+        ctx->transferStalled();
+    }
+    free(transfer);
+    delete data;
+    return G_SOURCE_REMOVE; // single shot timeout
+}
+
+Obex::Error Obex::syncContacts(unsigned long count) {
+    LoggerD("entered");
+    if(!mSession) {
+        LoggerD("Session not created, you have to call createSession before calling any method");
+        return OBEX_ERR_INVALID_SESSION;
+    }
+    mSyncQueue.push_back(new SyncPBData("INT", "pb", count));
+
+    // if the size is one, that means that there has not been
+    // synchronization on-going and therefore we can initiate
+    // synchronization from here, otherwise it will be initiated
+    // once the current one will have finished
+    if(mSyncQueue.size() == 1) {
+        SyncPBData *sync = mSyncQueue.front();
+        LoggerD("synchronizing data: " << sync->location << "/" << sync->phonebook << " count=" << count);
+        if(select(sync->location, sync->phonebook)) { // do call 'pullAll' only if 'select' operation was successful
+            if(OBEX_ERR_NONE != pullAll(sync->phonebook, sync->count)) {
+                // 'PullAll' has not started at all, ie. there will be no 'Complete'/'Error' signals
+                // on 'Transport' - no signal at all, threfore go to next sync request from sync queue
+                initiateNextSyncRequest();
+            }
+        }
+    }
+
+    return OBEX_ERR_NONE;
+}
+
+void Obex::getContactByPhoneNumber(const char *phoneNumber, std::string &contact) {
+    if(!phoneNumber) {
+        // return empty JSON contact, in case phoneNumber is invalid
+        contact = "{}";
+        return;
+    }
+
+    for(auto it=mContacts.begin(); it!=mContacts.end(); ++it) {
+        GList *phoneNumbersList = (GList*)e_contact_get((*it).second, E_CONTACT_TEL);
+        if(phoneNumbersList) {
+            const char *phoneNumberToCheck = phoneNumbersList->data?(const char*)phoneNumbersList->data:NULL;
+            if(phoneNumberToCheck && !strcmp(phoneNumberToCheck, phoneNumber)) {
+                parseEContactToJsonTizenContact((*it).second, contact);
+                g_list_free(phoneNumbersList);
+                return;
+            }
+            g_list_free(phoneNumbersList);
+        }
+    }
+
+    // if the contact is not found, return empty JSON contact
+    contact = "{}";
+}
+
+void Obex::getJsonContacts(std::string& contacts, unsigned long count) {
+    LoggerD("entered");
+
+    contacts = "[";
+
+    // if count == 0, ie. return all contacts
+    count = (count>0 && count<mContactsOrder.size())?count:mContactsOrder.size();
+
+    for(unsigned int i = 0; i<count; ++i) { // get 'count' latest contacts, ie. 'count' first from the list
+        EContact *item = mContacts[mContactsOrder.at(i)];
+        if(item) { // make sure, that the item exists
+            //if(i!=0) // exclude ',' for the first entry - DON'T compare it against the index - What if first item is not found in the map?
+            if(contacts.compare("[")) // exclude ',' for the first entry
+                contacts += ",";
+            std::string contact;
+            parseEContactToJsonTizenContact(item, contact);
+            contacts += contact;
+        }
+    }
+
+    contacts += "]";
+}
+
+void Obex::parseEContactToJsonTizenContact(EContact *econtact, std::string &contact) {
+       const char *uid = (const char*)e_contact_get_const(econtact, E_CONTACT_UID);
+
+       if(!econtact || !uid) {
+           contact = "{}"; // empty contact
+           return;
+       }
+
+       // uid:
+       contact = "{";
+       contact += "\"uid\":\"";
+       contact += uid;
+       contact += "\"";
+
+       // personId:
+       GList *phoneNumbersList = (GList*)e_contact_get(econtact, E_CONTACT_TEL);
+       const char *personId = (phoneNumbersList && phoneNumbersList->data)?(const char*)phoneNumbersList->data:NULL; // phoneNumber is used as personId - first number from the list is used
+       if(personId && strcmp(personId, "")) {
+           contact += ",\"personId\":\"";
+           contact += personId;
+           contact += "\"";
+       }
+       // DON'T free the list yet, it will be used later in the function
+       //if(phoneNumbersList)
+       //    g_list_free(phoneNumbersList);
+
+       // addressBookId: not parsed
+
+       // lastUpdated: not parsed
+
+       // isFavorite: not parsed
+
+       // name:
+       contact += ",\"name\":{";
+       const char *firstName = (const char*)e_contact_get_const(econtact, E_CONTACT_GIVEN_NAME);
+       const char *lastName = (const char*)e_contact_get_const(econtact, E_CONTACT_FAMILY_NAME);
+       const char *displayName = (const char*)e_contact_get_const(econtact, E_CONTACT_FULL_NAME);
+       bool firstAttr = true; // used to indicate whether separating comma should be used
+       if(firstName && strcmp(firstName, "")) {
+           firstAttr = false;
+           contact += "\"firstName\":\"";
+           contact += firstName;
+           contact += "\"";
+       }
+       if(lastName && strcmp(lastName, "")) {
+           if(firstAttr)
+               contact += "\"lastName\":\"";
+           else
+               contact += ",\"lastName\":\"";
+           firstAttr = false;
+           contact += lastName;
+           contact += "\"";
+       }
+       if(displayName && strcmp(displayName, "")) {
+           if(firstAttr)
+               contact += "\"displayName\":\"";
+           else
+               contact += ",\"displayName\":\"";
+           firstAttr = false;
+           contact += displayName;
+           contact += "\"";
+       }
+       contact += "}";
+
+       // addresses:
+       contact += ",\"addresses\":[";
+       for(int id=E_CONTACT_ADDRESS_HOME; id<=E_CONTACT_ADDRESS_OTHER; id++) {
+           EContactAddress *address = (EContactAddress*)e_contact_get(econtact, (EContactField)id);
+           if(address) {
+               contact += "{";
+               contact += "\"isDefault\":\"false\"";
+               if(address->country && strcmp(address->country,"")) {
+                   contact += ",\"country\":\"";
+                   contact += address->country;
+                   contact += "\"";
+               }
+               if(address->region && strcmp(address->region,"")) {
+                   contact += ",\"region\":\"";
+                   contact += address->region;
+                   contact += "\"";
+               }
+               if(address->locality && strcmp(address->locality,"")) {
+                   contact += ",\"city\":\"";
+                   contact += address->locality;
+                   contact += "\"";
+               }
+               if(address->street && strcmp(address->street,"")) {
+                   contact += ",\"streetAddress\":\"";
+                   contact += address->street;
+                   contact += "\"";
+               }
+               if(address->code && strcmp(address->code,"")) {
+                   contact += ",\"postalCode\":\"";
+                   contact += address->code;
+                   contact += "\"";
+               }
+               contact += ",\"types\":[\"";
+               contact += id==E_CONTACT_ADDRESS_HOME ? "HOME" : (id==E_CONTACT_ADDRESS_WORK ? "WORK" : (id==E_CONTACT_ADDRESS_OTHER ? "OTHER" : ""));
+               contact += "\"]";
+               contact += "}";
+
+               e_contact_address_free(address);
+           }
+       }
+       contact += "]";
+
+       // photoURI:
+       EContactPhoto *photo = (EContactPhoto*)e_contact_get(econtact, E_CONTACT_PHOTO);
+       if(photo) {
+           // we should have only URI type of contact photo, ... see processVCards() method
+           if(E_CONTACT_PHOTO_TYPE_URI == photo->type) {
+                const char *uri = e_contact_photo_get_uri(photo);
+                if(uri && strcmp(uri, "")) {
+                    //LoggerD("photoURI = " << uri);
+                    contact += ",\"photoURI\":\"";
+                    contact += uri;
+                    contact += "\"";
+                }
+           }
+       }
+       e_contact_photo_free(photo);
+
+       // phoneNumbers
+       contact += ",\"phoneNumbers\":[";
+       bool firstNumber = true;
+       while(phoneNumbersList && phoneNumbersList->data) {
+           const char *phoneNumber = (phoneNumbersList && phoneNumbersList->data)?(const char*)phoneNumbersList->data:"";
+           if(phoneNumber && strcmp(phoneNumber, "")) {
+               if(firstNumber)
+                   contact += "{\"number\":\"";
+               else
+                   contact += ",{\"number\":\"";
+               firstNumber = false;
+               contact += phoneNumber;
+               contact += "\"}";
+           }
+           phoneNumbersList = phoneNumbersList->next;
+       }
+       contact += "]";
+       // now we can free the list
+       if(phoneNumbersList)
+           g_list_free(phoneNumbersList);
+
+       // emails:
+       contact += ",\"emails\":[";
+       bool firstEmail = true;
+       for(int id=E_CONTACT_FIRST_EMAIL_ID; id<=E_CONTACT_LAST_EMAIL_ID; id++) {
+           const char *email = (const char*)e_contact_get_const(econtact, (EContactField)id);
+           if(email && strcmp(email, "")) {
+               if(firstEmail)
+                   contact += "{\"email\":\"";
+               else
+                   contact += ",{\"email\":\"";
+
+               contact += email;
+               contact += "\"";
+               contact += ",\"isDefault\":\"false\""; // TODO: ?use 'firstEmail' value to set the first e-mail address as default?
+               contact += ",\"types\":[\"WORK\"]"; // just some default value
+
+               firstEmail = false;
+               contact += "}";
+           }
+       }
+       contact += "]";
+
+       // birthday: not parsed
+
+       // anniversaries: not parsed
+
+       // organizations: not parsed
+
+       // notes: not parsed
+
+       // urls: not parsed
+
+       // ringtoneURI: not parsed
+
+       // groupIds: not parsed
+
+       contact += "}";
+}
+
+Obex::Error Obex::syncCallHistory(unsigned long count) {
+    LoggerD("entered");
+    if(!mSession) {
+        LoggerD("Session not created, you have to call createSession before calling any method");
+        return OBEX_ERR_INVALID_SESSION;
+    }
+    mSyncQueue.push_back(new SyncPBData("INT", "cch", count));
+
+    // if the size is one, that means that there has not been
+    // synchronization on-going and therefore we can initiate
+    // synchronization from here, otherwise it will be initiated
+    // once the current one will have finished
+    if(mSyncQueue.size() == 1) {
+        SyncPBData *sync = mSyncQueue.front();
+        LoggerD("synchronizing data: " << sync->location << "/" << sync->phonebook << " count=" << count);
+        if(select(sync->location, sync->phonebook)) { // do call 'pullAll' only if 'select' operation was successful
+            if(OBEX_ERR_NONE != pullAll(sync->phonebook, sync->count)) {
+                // 'PullAll' has not started at all, ie. there will be no 'Complete'/'Error' signals
+                // on 'Transport' - no signal at all, threfore go to next sync request from sync queue
+                initiateNextSyncRequest();
+            }
+        }
+    }
+
+    return OBEX_ERR_NONE;
+}
+
+void Obex::getJsonCallHistory(std::string& calls, unsigned long count) {
+    LoggerD("entered");
+
+    calls = "[";
+
+    // if count == 0, ie. return all calls
+    count = (count>0 && count<mCallHistoryOrder.size())?count:mCallHistoryOrder.size();
+
+    for(unsigned int i = 0; i<count; ++i) { // get 'count' latest calls, ie. 'count' first from the list
+        EContact *item = mCallHistory[mCallHistoryOrder.at(i)];
+        if(item) { // make sure, that the item exists
+            //if(i!=0) // exclude ',' for the first entry - DON'T compare it against the index - What if first item is not found in the map?
+            if(calls.compare("[")) // exclude ',' for the first entry
+                calls += ",";
+            std::string call;
+            parseEContactToJsonTizenCallHistoryEntry(item, call);
+            calls += call;
+        }
+    }
+
+    calls += "]";
+}
+
+void Obex::parseEContactToJsonTizenCallHistoryEntry(EContact *econtact, std::string &call) {
+       const char *uid = (const char*)e_contact_get_const(econtact, E_CONTACT_UID);
+       if(!econtact || !uid) {
+           call = "{}"; // empty call history entry
+           return;
+       }
+
+       // uid:
+       call = "{";
+       call += "\"uid\":\"";
+       call += uid;
+       call += "\",";
+
+       // type: not parsing - use some DEFAULT value, eg. "TEL"
+       call += "\"type\":\"TEL\",";
+
+       // features: not parsing - use some DEFAULT value, eg. "VOICECALL"
+       call += "\"features\":[\"VOICECALL\"],";
+
+       // remoteParties
+       call += "\"remoteParties\":[";
+       call += "{";
+       call += "\"personId\":\"";
+       GList *phoneNumbersList = (GList*)e_contact_get(econtact, E_CONTACT_TEL);
+       const char *personId = (phoneNumbersList && phoneNumbersList->data)?(const char*)phoneNumbersList->data:""; // phoneNumber is used as personId - first number from the list is used
+       call += personId;
+       call += "\"";
+       if(phoneNumbersList)
+           g_list_free(phoneNumbersList);
+       const char *fullName = (const char*)e_contact_get_const(econtact, E_CONTACT_FULL_NAME);
+       if(fullName && strcmp(fullName, "")) {
+           call += ",\"remoteParty\":\"";
+           call += fullName;
+           call += "\"";
+       }
+       call += "}";
+       call += "],";
+
+       // startTime
+       const char *startTime = (const char*)e_contact_get_const(econtact, E_CONTACT_REV); // 'REV' holds call date/time
+       if(startTime && strcmp(startTime, "")) {
+           std::string startTimeStr = startTime;
+           startTimeStr.insert(13,":");
+           startTimeStr.insert(11,":");
+           startTimeStr.insert(6,"-");
+           startTimeStr.insert(4,"-");
+           call += "\"startTime\":\"";
+           call += startTimeStr;
+           call += "\",";
+       }
+
+       // duration: not parsing - use 0 as default value
+       call += "\"duration\":\"0\",";
+
+       //  direction:
+       call += "\"direction\":\"";
+       const char *direction = (const char*)e_contact_get_const(econtact, E_CONTACT_NOTE); // 'NOTE' holds direction of the call
+       call += direction?direction:(char*)"UNDEFINED";
+       call += "\"";
+
+       call += "}";
+}
+
+void Obex::handleSignal(GDBusConnection       *connection,
+                         const gchar           *sender,
+                         const gchar           *object_path,
+                         const gchar           *interface_name,
+                         const gchar           *signal_name,
+                         GVariant              *parameters,
+                         gpointer               user_data)
+{
+    LoggerD("signal received: '" << interface_name << "' -> '" << signal_name << "' -> '" << object_path << "'");
+
+    CtxCbData *data = static_cast<CtxCbData*>(user_data);
+    if(!data) {
+        LoggerE("Failed to cast object: CtxCbData");
+        return;
+    }
+    Obex *ctx = static_cast<Obex*>(data->ctx);
+    if(!ctx) {
+        LoggerE("Failed to cast object: Obex");
+        return;
+    }
+
+    if(!strcmp(interface_name, OBEX_TRANSFER_IFACE)) {
+        if(!strcmp(signal_name, "Complete") || !strcmp(signal_name, "Error")) {
+            Utils::removeSignalListener(G_BUS_TYPE_SESSION, OBEX_CLIENT_SERVICE,
+                                        OBEX_TRANSFER_IFACE, object_path,
+                                        "Complete");
+            Utils::removeSignalListener(G_BUS_TYPE_SESSION, OBEX_CLIENT_SERVICE,
+                                        OBEX_TRANSFER_IFACE, object_path,
+                                        "Error");
+            if(ctx->mActiveTransfer) {
+                free(ctx->mActiveTransfer);
+                ctx->mActiveTransfer = NULL;
+            }
+
+            if(!strcmp(signal_name, "Complete")) {
+                const char *path = static_cast<const char *>(data->data1);
+                const char *type = static_cast<const char *>(data->data2);
+                const char *origin = static_cast<const char *>(data->cb);
+                ctx->processVCards(path, type, origin);
+            }
+            else if(!strcmp(signal_name, "Error")) {
+                // dont' have to do anything
+            }
+            if(data->data1) free(data->data1); // path - to the file containing received VCards
+            if(data->cb) free(data->cb);       // origin - MAC address of selected remote device
+            delete data;
+            ctx->initiateNextSyncRequest();
+        }
+    }
+}
+
+void Obex::processVCards(const char *filePath, const char *type, const char *origin) {
+    LoggerD("entered");
+
+    if(!filePath || !type || !origin) {
+        LoggerE("Invalid argument(s)");
+        return;
+    }
+
+    if(strcmp(origin, mSelectedRemoteDevice.c_str())) {
+        LoggerD("Received VCards don't belong to currently selected device - IGNORING");
+        return;
+    }
+
+    std::map<std::string, EContact*> *items = NULL;
+    std::vector<std::string> *order = NULL;
+    if(!strcmp(type, "pb")) { // Contacts
+        items = &mContacts;
+        order = &mContactsOrder;
+    }
+    else if(!strcmp(type, "cch")) { // CallHistory
+        items = &mCallHistory;
+        order = &mCallHistoryOrder;
+    }
+    // if the size of items map is 0, ie. that the received
+    // VCards are from first sync request and they should
+    // be added to the map (uid order vector) in the order they
+    // are processed (push_back), otherwise they should be
+    // inserted at the front (push_front)
+    bool firstData = items->size() == 0 ? true : false;
+
+    // process VCards one-by-one
+    std::ifstream file(filePath);
+    std::string vcard;
+    for(std::string line; getline(file, line);)
+    {
+        //std::replace( line.begin(), line.end(), '\r', '\n'); // removes carriage return
+        line.replace(line.find("\r"), 1, "\n");
+
+        if(line.find("BEGIN:VCARD") == 0) {
+            vcard = line; // start collecting new VCard
+        }
+        else if(line.find("END:VCARD") == 0) {
+            vcard += line;
+
+            // start processing VCard
+            //printf("%s\n", vcard.c_str());
+
+            EContact *item = e_contact_new_from_vcard(vcard.c_str());
+            if(!item) {
+                LoggerD("Failed to create EContact from vcard");
+                continue;
+            }
+
+            // won't use E_CONTACT_UID as a key to the map, since it is not returned by all phone devices
+            if(!makeUid(item)) {
+                // failed to create UID from EContact
+                // won't add the entry to the list - UID used as a key to the map
+                continue;
+            }
+            const char *uid = (const char*)e_contact_get_const(item, E_CONTACT_UID);
+
+            // check if item has photo and it's INLINED type
+            // if so, change it to URI type, since the data are in binary form
+            // and as such can't be processed in JSON directly
+            // to avoid yet another conversion to eg. BASE64 format, save the
+            // contact photo in /tmp and use the URI to reference the photo instead
+            EContactPhoto *photo = (EContactPhoto*)e_contact_get(item, E_CONTACT_PHOTO);
+            if(photo) {
+                if(E_CONTACT_PHOTO_TYPE_INLINED == photo->type) {
+                    gsize length = 0;
+                    const guchar *data = e_contact_photo_get_inlined (photo, &length);
+                    //uid is used as a file name
+                    char fileName[128];
+                    snprintf(fileName, sizeof(fileName), "/tmp/%s.jif", uid);
+                    FILE *fd = fopen(fileName,"wb");
+                    if(!fd) {
+                        LoggerD("Unable to store contact photo: " << fileName);
+                    }
+                    else {
+                        LoggerD("Saving contact photo: " << fileName);
+                        size_t written = fwrite(data, sizeof(guchar), length, fd);
+                        fclose(fd);
+                        if(written == length) {
+                            // contact photo has been successfully saved
+                            // change photo attribute from INLINED to URI
+                            e_contact_photo_free(photo);
+                            photo = NULL;
+                            photo = e_contact_photo_new();
+                            if(photo) {
+                                photo->type = E_CONTACT_PHOTO_TYPE_URI;
+                                //e_contact_photo_set_mime_type(photo, "");
+                                char uri[128];
+                                snprintf(uri, sizeof(uri), "file://%s", fileName);
+                                e_contact_photo_set_uri(photo, uri);
+                                e_contact_set(item, E_CONTACT_PHOTO, photo);
+                            }
+                        }
+                    }
+                }
+                e_contact_photo_free(photo);
+            }
+
+            // check if an item with the given UID exists in the list
+            if((*items)[uid] == NULL) {
+                //LoggerD("NEW ITEM: " << uid);
+                (*items)[uid] = item;
+                if(firstData)
+                    order->push_back(uid);
+                else
+                    order->insert(order->begin(), uid); // push at the front
+                if(!strcmp(type, "cch")) { // notify only for CallHistory
+                    std::string entry;
+                    parseEContactToJsonTizenCallHistoryEntry(item, entry);
+                    callHistoryEntryAdded(entry);
+                }
+            }
+            else {
+                // the item already exists in the list, unref the item,
+                // since we loose any reference to it
+                g_object_unref(item);
+            }
+        }
+        else {
+            // the current implementation of EContact doesn't support
+            // X-IRMC-CALL-DATETIME field, so as a workaround we use
+            // two separate fields instead: E_CONTACT_NOTE
+            //                              E_CONTACT_REV
+            if((line.find("NOTE") == 0) || (line.find("REV") == 0)) {
+                // exclude NOTE and REV as we are using it to store
+                // X-IRMC-CALL-DATETIME attribute
+                // exclude = do not copy it to vcard
+            }
+            else if(line.find("UID") == 0) {
+                // exclude UID as we are creating own UID
+                // exclude = do not copy it to vcard
+            }
+            else if(line.find("X-IRMC-CALL-DATETIME") == 0) {
+                size_t index1 = line.find( "TYPE=" ) + 5;
+                size_t index2 = line.find( ":", index1 ) + 1;
+
+                std::string note = line.substr (index1, index2-index1-1);
+                std::string rev = line.substr (index2, line.length()-index2);
+
+                vcard += "NOTE:" + note + "\n";
+                vcard += "REV:" + rev; // '\n' is taken from 'line'
+            }
+            else {
+                vcard += line;
+            }
+        }
+    }
+
+    // notify listener about Contacts/CallHistory being changed/synchronized
+    if(type) {
+        if(!strcmp(type, "pb")) // Contacts
+            contactsChanged();
+        else if(!strcmp(type, "cch")) // CallHistory
+            callHistoryChanged();
+    }
+}
+
+bool Obex::makeUid(EContact *entry) {
+    // use combination of phone number, given/family name and the modification date
+    const char *_uid = (const char*)e_contact_get_const(entry, E_CONTACT_UID);
+    if(_uid) {
+        // we shouldn't get here, since E_CONTACT_UID is filtered-out in processVCards() method
+        // does "e_contact_set" frees the memory if the field already exists?
+        return false;
+    }
+    GList *phoneNumbersList = (GList*)e_contact_get(entry, E_CONTACT_TEL);
+    const char *phoneNumber = (phoneNumbersList && phoneNumbersList->data) ? (const char*)phoneNumbersList->data : NULL;
+    const char *givenName = (const char*)e_contact_get_const(entry, E_CONTACT_GIVEN_NAME);
+    const char *familyName = (const char*)e_contact_get_const(entry, E_CONTACT_FAMILY_NAME);
+    const char *call_rev = (const char*)e_contact_get_const(entry, E_CONTACT_REV);
+
+    if((!phoneNumber || !phoneNumber[0]) && (!givenName || !givenName[0]) && (!familyName || !familyName[0]) && (!call_rev || !call_rev[0])) {
+        // uid is used as a key to the map
+        LoggerD("Invalid EContact entry - not adding to the list");
+        if(phoneNumbersList)
+            g_list_free(phoneNumbersList);
+        return false;
+    }
+
+    char uid[128];
+    snprintf(uid, sizeof(uid), "%s:%s:%s:%s", phoneNumber?phoneNumber:"",
+                                              givenName?givenName:"",
+                                              familyName?familyName:"",
+                                              call_rev?call_rev:"");
+
+    // TODO: check
+    // does "e_contact_set" make a copy of value, or
+    // do we need make a copy of the value on the HEAP?
+    e_contact_set(entry, E_CONTACT_UID, uid);
+
+    if(phoneNumbersList)
+        g_list_free(phoneNumbersList);
+
+    return true;
+}
+
+} // PhoneD
+
diff --git a/src/obex.h b/src/obex.h
new file mode 100644 (file)
index 0000000..fbcfd32
--- /dev/null
@@ -0,0 +1,172 @@
+#ifndef OBEX_H_
+#define OBEX_H_
+
+#include <libebook-contacts/libebook-contacts.h>
+#include <dbus/dbus.h>
+#include <gio/gio.h>
+#include <string>
+#include <map>
+#include <vector>
+#include <deque>
+
+namespace PhoneD {
+
+/**
+ * @addtogroup phoned
+ * @{
+ */
+
+class SyncPBData;
+
+/*! \class PhoneD::Obex
+ *  \brief Class which is utilizing Obex D-Bus service. It is a base class and is not meant to be instantiated directly.
+ *
+ * A class providing access to <a href="http://www.bluez.org">Obex</a> functionality.
+ */
+class Obex {
+    public:
+        /*! Error codes for Obex operations. */
+        enum Error {
+            OBEX_ERR_NONE = 0,            /*!< No error. */
+            OBEX_ERR_INVALID_SESSION,     /*!< Invalid session, or Obex session with remote device not created. */
+            OBEX_ERR_DBUS_ERROR,          /*!< D-Bus specific error. */
+            OBEX_ERR_DBUS_INVALID_REPLY,  /*!< D-Bus invalid reply. */
+            OBEX_ERR_INVALID_ARGUMENTS    /*!< Invalid arguments specified. */
+        };
+
+    public:
+        /**
+         * A default constructor. Constructs and initializes an object.
+         */
+        Obex();
+
+        /**
+         * A destructor. Removes session created with Obex::createSession() and destructs the object.
+         * @see createSession()
+         */
+        ~Obex();
+
+        /**
+         * Creates an Obex session to the BT device (paired) specified by given MAC address. It calls \b CreateSession on \b org.bluez.obex.Client interface with the \b Target specified to be \b "PBAP".
+         * @param[in] bt_address A MAC address of paired device that the Obex session should be created to.
+         * @see removeSession()
+         */
+        void createSession(const char *bt_address);
+
+        /**
+         * Removes the session (the default/only-one) created by createSession() method.
+         * @param[in] notify Specifies whether to notify the class which is deriving from this class about the session being removed.
+         * @see createSession()
+         */
+        void removeSession(bool notify = true);
+
+        /**
+         * Synchronizes phones PhoneBook contacts. Pulls the contacts from remote device that the Obex session is created to. The pull is done on \b "INT" internal phone's contacts list.
+         * @param[in] count Specifies the number of latest contacts to be pulled from the phone. \b 0 means to pull all contacts.
+         * @see createSession()
+         * @return The status of the operation, ie. whether pull-ing the contacts has successfuly started.
+         */
+        Obex::Error syncContacts(unsigned long count = 0);
+
+        /**
+         * Synchronizes phone's call history . Pulls the call history entries from remote device that the Obex session is created to. The pull is done on \b "INT" internal phone's call history. It retrieves \b "cch", ie. any kind of call (DIALED,MISSED,....).
+         * @param[in] count Specifies the number of latest calls from the call history to be pulled from the phone. \b 0 means to pull all call entries.
+         * @see createSession()
+         * @return The status of the operation, ie. whether pull-ing the call entries has successfuly started.
+         */
+        Obex::Error syncCallHistory(unsigned long count = 0);
+
+        /**
+         * Method to get synchronized contacts in JSON format as an array of \b tizen.Contacts. Returns empty array \b "[]", if the contacts are not yet synchronized , or if there are no contacts.
+         * @param[out] contacts A container for the contacts. The contacts are in \b tizen.Contact format.
+         * @param[in] count Specifies the number of latest contacts to be returned. \0 means to return all contacts.
+         */
+        void getJsonContacts(std::string& contacts, unsigned long count);
+
+        /**
+         * Method to get synchronized call history entries in JSON format as an array of \b tizen.CallHisoryEntry-ies. Returns empty array \b "[]", if the call history is not yet synchronized, or if there are no calls in history.
+         * @param[out] calls A container for the call history entries. The call history entries are in \b tizen.CallHistoryEntry format.
+         * @param[in] count Specifies the number of latest call history entries to be returned. \b 0 means to return all calls from the history.
+         */
+        void getJsonCallHistory(std::string& calls, unsigned long count);
+
+        /**
+         * Returns contact in \b tizen.Contact JSON format, which matches given phone number. It returns an empty JSON object "{}" if the contact is not found.
+         * @param[in] phoneNumber A phone number for which the contact should be returned.
+         * @param[out] contact A container for the contact that match the phone number. The contact is in \b tizen.Contact JSON format.
+         */
+        void getContactByPhoneNumber(const char *phoneNumber, std::string &contact);
+
+    protected:
+        /**
+         * Sets selected remote device, which is used when processing received VCards to make sure that they belong to the device selected remote device.
+         * @param[in] &btAddress A MAC address of remote BT device for processing VCards.
+         */
+        void setSelectedRemoteDevice(std::string &btAddress);
+
+    private: // methods
+
+        virtual void contactsChanged() = 0;
+        virtual void callHistoryChanged() = 0;
+        virtual void pbSynchronizationDone() = 0;
+        virtual void createSessionFailed(const char *err) = 0;
+        virtual void createSessionDone(const char *s) = 0;
+        virtual void removeSessionDone() = 0;
+        // to get notifications about CallHistoryEntry added to the CallHistory list
+        // ie, when a call has been made ("disconnected" received), new entry is added to the list
+        virtual void callHistoryEntryAdded(std::string &entry) = 0;
+        // the method, which will be called when active Transfer is stalled
+        virtual void transferStalled() = 0;
+        // method to clear the sync queue, eg. when the transfer is stalled
+        void clearSyncQueue();
+
+        // Select phonebook object for other operations
+        bool select(const char *location, const char *phonebook);
+
+        // type: type of pull request - "pb" for Contacts, "cch" for CallHistory
+        Obex::Error pullAll(const char *type, unsigned long count); // retrieves 'count' selected (select()) entries from
+                                                                    // the phonebook (0=ALL)
+
+        static void handleSignal(GDBusConnection *connection,  const gchar     *sender,
+                                 const gchar     *object_path, const gchar     *interface_name,
+                                 const gchar     *signal_name, GVariant        *parameters,
+                                 gpointer         user_data);
+
+        // method to add "E_CONTACT_UID" to the EContact
+        // will remove existing one, if it exists
+        // returns: bool indicating successfull UID creation
+        bool makeUid(EContact *entry);
+        // process received VCARD contacts' data
+        // check the data against origin MAC, ie. the user may have selected other
+        // remote device while synchronization was ongoing and thus the data (VCards)
+        // may not belong to the device selected at the time
+        void processVCards(const char *filePath, const char *type, const char *origin);
+        static void asyncCreateSessionReadyCallback(GObject *source, GAsyncResult *result, gpointer user_data);
+
+        void initiateNextSyncRequest();
+
+        void parseEContactToJsonTizenContact(EContact *econtact, std::string &contact);
+        void parseEContactToJsonTizenCallHistoryEntry(EContact *econtact, std::string &call);
+
+        static gboolean checkStalledTransfer(gpointer user_data);
+
+    private: // variables
+        std::string mSelectedRemoteDevice;
+        char *mSession;
+        char *mActiveTransfer;
+        // only one synchronization operation getContacts/getCallHistory,
+        // is allowed at a time via Obex due to the selection of phonebook
+        // use std::deque to handle this limitation
+        std::deque<SyncPBData*> mSyncQueue;
+        std::map<std::string, EContact*> mContacts;
+        std::vector<std::string> mContactsOrder; // order of contacts inserted into the MAP
+        std::map<std::string, EContact*> mCallHistory;
+        std::vector<std::string> mCallHistoryOrder; // order of calls inserted into the MAP
+};
+
+#endif /* BLUEZ_H_ */
+
+} // PhoneD
+
+/** @} */
+
diff --git a/src/ofono.cpp b/src/ofono.cpp
new file mode 100644 (file)
index 0000000..1a60cfa
--- /dev/null
@@ -0,0 +1,726 @@
+#include "ofono.h"
+#include "utils.h"
+
+#include <gio/gio.h>
+#include <stdio.h>
+#include <string.h>
+#include <algorithm>
+
+#include <stdlib.h>
+
+#include "Logger.h"
+
+namespace PhoneD {
+
+#define OFONO_PREFIX                     "org.ofono"
+
+#define OFONO_SERVICE                    OFONO_PREFIX
+#define OFONO_MANAGER_IFACE              OFONO_PREFIX ".Manager"
+#define OFONO_MODEM_IFACE                OFONO_PREFIX ".Modem"
+#define OFONO_VOICECALLMANAGER_IFACE     OFONO_PREFIX ".VoiceCallManager"
+#define OFONO_VOICECALL_IFACE            OFONO_PREFIX ".VoiceCall"
+#define OFONO_CALLVOLUME_IFACE           OFONO_PREFIX ".CallVolume"
+
+#define CHECK_FOR_MODEM_POWERED_INTERVAL 60 // interval for check whether 'Selected' remote device is Powered, and power it on, if it is not (in seconds)
+
+OFono::OFono() :
+    mModemPath( NULL ),
+    mActiveCall( NULL )
+{
+    LoggerD("entered");
+
+    /*
+    if(getModems()) {
+        LoggerD("Failed to call 'GetModems'");
+    }
+    */
+    Utils::setSignalListener(G_BUS_TYPE_SYSTEM, OFONO_SERVICE,
+                             OFONO_MANAGER_IFACE, "/",
+                             "ModemAdded", OFono::handleSignal,
+                             this);
+    Utils::setSignalListener(G_BUS_TYPE_SYSTEM, OFONO_SERVICE,
+                             OFONO_MANAGER_IFACE, "/",
+                             "ModemRemoved", OFono::handleSignal,
+                             this);
+
+    // won't request calls here, since the service OFONO_VOICECALLMANAGER_IFACE may not be available yet
+    //get active calls
+    //if(mModemPath) {
+    //    getCalls();
+    //}
+
+    // periodic check whether modem for 'Selected' remote device is powered
+    // among other things, it handles also the case when the phone gets out of the range of HMI BT
+    g_timeout_add(CHECK_FOR_MODEM_POWERED_INTERVAL*1000, OFono::checkForModemPowered, this);
+}
+
+OFono::~OFono() {
+    LoggerD("entered");
+    removeModem(mModemPath);
+}
+
+gboolean OFono::checkForModemPowered(gpointer user_data) {
+    OFono *ctx = static_cast<OFono*>(user_data);
+    if(!ctx || !ctx->mModemPath)
+        return G_SOURCE_CONTINUE; // continuous timeout
+
+    if(!ctx->isModemPowered(ctx->mModemPath)) {
+        ctx->setModemPowered(ctx->mModemPath, true);
+    }
+
+    return G_SOURCE_CONTINUE; // continuous timeout
+}
+
+bool OFono::isModemPowered(const char *modem) {
+    if(modem)
+        return false;
+
+    GError *err = NULL;
+    GVariant *reply = NULL;
+    reply = g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL),
+                                         OFONO_SERVICE,
+                                         modem,
+                                         OFONO_MODEM_IFACE,
+                                         "GetProperties",
+                                         NULL,
+                                         NULL,
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         -1,
+                                         NULL,
+                                         &err);
+
+
+    if(err || !reply) {
+        if(err)
+            g_error_free(err);
+        return false;
+    }
+
+    GVariant *prop = NULL;
+    GVariantIter *iter = NULL;
+    g_variant_get(reply, "(a{sv})", &iter);
+    bool powered = false;
+    while((prop = g_variant_iter_next_value(iter))) {
+        const char *name = NULL;
+        GVariant *value = NULL;
+        g_variant_get(prop, "{sv}", &name, &value);
+        if(name && !strcmp(name, "Powered")) {
+            powered = g_variant_get_boolean(value);
+        }
+    }
+
+    g_variant_unref(reply);
+
+    return powered;
+}
+
+// synchronous version of method for setting property
+bool OFono::setProperty(const char* iface, const char* path, const char *property, int type, void *value) {
+    LoggerD("OFono::setProperty(): name= " << property << " on " << path);
+
+    char signature[2];
+    sprintf(signature, "%c", type); // eg. "b" for boolean
+
+    GError *err = NULL;
+    g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL,NULL),
+                                 OFONO_SERVICE,
+                                 path,
+                                 iface,
+                                 "SetProperty",
+                                 g_variant_new ("(sv)", // floating parameters are consumed, no cleanup/unref needed
+                                     property,
+                                     g_variant_new(signature, value) // floating parameters are consumed, no cleanup/unref needed
+                                 ),
+                                 NULL,
+                                 G_DBUS_CALL_FLAGS_NONE,
+                                 -1,
+                                 NULL,
+                                 &err);
+
+    if(err) {
+        LoggerE("Failed to call \"SetProperty\" DBUS method: " << err->message);
+        g_error_free(err);
+        return false;
+    }
+
+    return true;
+}
+
+// asynchronous version of method for setting property
+void OFono::setPropertyAsync(const char* iface, const char* path, const char *property, int type, void *value, GAsyncReadyCallback callback) {
+    LoggerD("Setting OFono property via asynchronous call: name= " << property << " on " << path);
+
+    char signature[2];
+    sprintf(signature, "%c", type); // eg. "b" for boolean
+
+    g_dbus_connection_call( g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL,NULL),
+                            OFONO_SERVICE,
+                            path,
+                            iface,
+                            "SetProperty",
+                            g_variant_new ("(sv)", // floating parameters are consumed, no cleanup/unref needed
+                                property,
+                                g_variant_new(signature, value) // floating parameters are consumed, no cleanup/unref needed
+                            ),
+                            NULL,
+                            G_DBUS_CALL_FLAGS_NONE,
+                            -1,
+                            NULL,
+                            callback,
+                            this);
+}
+
+// callback for async calls on 'g_dbus'
+void OFono::GDbusAsyncReadyCallback(GObject *source, GAsyncResult *result, gpointer user_data) {
+    //process result here if needed
+    //LoggerD("Async method call finished, ie. callback received");
+}
+
+void OFono::setModemPowered(const char *modem, bool powered) {
+    LoggerD("Setting modem 'Powered': " << modem << " to " << powered);
+    setPropertyAsync(OFONO_MODEM_IFACE, modem, "Powered", DBUS_TYPE_BOOLEAN, &powered, OFono::asyncSetModemPoweredCallback);
+}
+
+void OFono::asyncSetModemPoweredCallback(GObject *source, GAsyncResult *result, gpointer user_data) {
+    GError *err = NULL;
+    g_dbus_connection_call_finish(g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL), result, &err);
+    if(err) {
+        LoggerE("Failed to set 'Powered' property on modem: " << err->message);
+        OFono *ctx = static_cast<OFono*>(user_data);
+        if(!ctx) {
+            LoggerE("Invalid ctx");
+            return;
+        }
+        // notify about the failure
+        if(!ctx->isModemPowered(ctx->mModemPath)) // Modem is not 'Powered', ie. the remote BT device is not 'Selected' for BT operations
+            ctx->setModemPoweredFailed(err?err->message:"Failed to set 'Powered' property on Modem.");
+        g_error_free(err);
+    }
+    else
+        LoggerE("Property 'Powered' successfuly set on modem");
+}
+
+void OFono::asyncSelectModemCallback(GObject *source, GAsyncResult *result, gpointer user_data) {
+    CtxCbData *data = static_cast<CtxCbData*>(user_data);
+    if(!data || !data->ctx || !data->data1) {
+        LoggerE("Invalid callback data");
+        return;
+    }
+    OFono *ctx = static_cast<OFono*>(data->ctx);
+    char *addr = static_cast<char*>(data->data1);
+
+    std::string btAddress = addr;
+
+    free(addr);
+    delete data;
+
+    GError *err = NULL;
+    GVariant *reply;
+    reply = g_dbus_connection_call_finish(g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL), result, &err);
+    if(err || !reply) {
+        if(err) {
+            LoggerE("Failed to 'GetModems': " << err->message);
+            g_error_free(err);
+        }
+        else if(!reply)
+            LoggerE("Reply from calling 'GetModems' method is NULL");
+
+        return;
+    }
+
+    // path of retrieved modems is in form: /hfp/AABBCCDDEEFF_FFEEDDCCBBAA
+    // where: AABBCCDDEEFF is MAC address of default local BT device
+    //   and: FFEEDDCCBBAA is MAC address of selected remote BT device
+    // we expect given MAC address to be in the form: AA:BB:CC:DD:EE:FF
+    // remove all non-digit characters, eg. ":","-"
+    btAddress.erase(std::remove_if(btAddress.begin(), btAddress.end(), isnxdigit), btAddress.end());
+
+    GVariantIter *modems;
+    GVariantIter *props;
+    const char *path;
+    g_variant_get(reply, "(a(oa{sv}))", &modems);
+    while(g_variant_iter_next(modems, "(oa{sv})", &path, &props)) {
+        // check if the modem is from selected remote device
+        std::string pathString(path);
+        size_t idx = pathString.find( "_" ) + 1; // index of address of remote device
+        std::string modemRemoteBtAddress = pathString.substr (idx, pathString.length()-idx);
+        if(!modemRemoteBtAddress.compare(btAddress)) {
+            ctx->addModem(path, props);
+            // currently only one modem is supported - that's why the break is here
+            // take the first one from the list and make it 'default'
+            break;
+        }
+    }
+
+    g_variant_unref(reply);
+}
+
+void OFono::selectModem(std::string &btAddress) {
+    LoggerD("Selecting modem: " << btAddress);
+    // Retrieving list of available modems to get the one that is for 'wanted' device
+    g_dbus_connection_call( g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL),
+                            OFONO_SERVICE,
+                            "/",
+                            OFONO_MANAGER_IFACE,
+                            "GetModems",
+                            NULL,
+                            NULL,
+                            G_DBUS_CALL_FLAGS_NONE,
+                            -1,
+                            NULL,
+                            OFono::asyncSelectModemCallback,
+                            new CtxCbData(this, NULL, strdup(btAddress.c_str()), NULL));
+}
+
+void OFono::addModem(const char *path, GVariantIter *props) {
+    LoggerD("entered");
+
+    if(!path || !props)
+        return;
+
+    // shouldn't happen, but just in case free the memory here
+    if(mModemPath)
+        free(mModemPath);
+
+    mModemPath = strdup(path); // make a copy of path, 'cause it will be unref-ed
+
+    const char *key = NULL;
+    GVariant *value = NULL;
+    bool online = FALSE;
+    while(g_variant_iter_next(props, "{sv}", &key, &value)) {
+        if(!strcmp(key, "Powered")) {
+            //bool powered = g_variant_get_boolean(value);
+        }
+        else if(!strcmp(key, "Online")) {
+            online = g_variant_get_boolean(value);
+        }
+    }
+
+    Utils::setSignalListener(G_BUS_TYPE_SYSTEM, OFONO_SERVICE,
+                             OFONO_VOICECALLMANAGER_IFACE, mModemPath,
+                             "CallAdded", OFono::handleSignal,
+                             this);
+
+    Utils::setSignalListener(G_BUS_TYPE_SYSTEM, OFONO_SERVICE,
+                             OFONO_VOICECALLMANAGER_IFACE, mModemPath,
+                             "PropertyChanged", OFono::handleSignal,
+                             this);
+
+    Utils::setSignalListener(G_BUS_TYPE_SYSTEM, OFONO_SERVICE,
+                             OFONO_VOICECALLMANAGER_IFACE, mModemPath,
+                             "CallRemoved", OFono::handleSignal,
+                             this);
+
+    Utils::setSignalListener(G_BUS_TYPE_SYSTEM, OFONO_SERVICE,
+                             OFONO_MODEM_IFACE, mModemPath,
+                             "PropertyChanged", OFono::handleSignal,
+                             this);
+
+    if(!online) {
+        // power on modem
+        setModemPowered(mModemPath, true);
+    }
+    else
+    {
+        // we can query calls from here, since the modem is already powered
+        getCalls();
+        // when the modem is already online, "PropertyChanged" for "Paired" is not emited
+        // to handle all functionality to modem being powered, call modemPowered from here as well
+        modemPowered(true);
+    }
+}
+
+void OFono::unselectModem() {
+    removeModem(mModemPath);
+}
+
+void OFono::removeModem(const char *modemPath) {
+    LoggerD("Removing modem: " << (modemPath?modemPath:"INVALID DATA"));
+
+    // remove all subscriptions to DBUS signals from the modem
+    if(modemPath) {
+        Utils::removeSignalListener(G_BUS_TYPE_SYSTEM, OFONO_SERVICE,
+                                    OFONO_VOICECALLMANAGER_IFACE, modemPath,
+                                    "CallAdded");
+        Utils::removeSignalListener(G_BUS_TYPE_SYSTEM, OFONO_SERVICE,
+                                    OFONO_VOICECALLMANAGER_IFACE, modemPath,
+                                    "PropertyChanged");
+        Utils::removeSignalListener(G_BUS_TYPE_SYSTEM, OFONO_SERVICE,
+                                    OFONO_VOICECALLMANAGER_IFACE, modemPath,
+                                    "CallRemoved");
+        Utils::removeSignalListener(G_BUS_TYPE_SYSTEM, OFONO_SERVICE,
+                                    OFONO_MODEM_IFACE, modemPath,
+                                    "PropertyChanged");
+
+        // power off modem
+        setModemPowered(modemPath, false);
+        // at this point we will not get notification "PropertyChanged" on 'Powered' property,
+        // call 'modemPowered()' method directly from here
+        modemPowered(false);
+
+        // free the memory
+        if(modemPath && mModemPath && !strcmp(modemPath, mModemPath)) {
+            free(mModemPath);
+            mModemPath = NULL;
+        }
+    }
+}
+
+void OFono::getCalls()
+{
+    LoggerD("entered");
+
+    GError *err = NULL;
+    GVariant *reply = NULL;
+    reply = g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL),
+                                         OFONO_SERVICE,
+                                         mModemPath,
+                                         OFONO_VOICECALLMANAGER_IFACE,
+                                         "GetCalls",
+                                         NULL,
+                                         NULL,
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         -1,
+                                         NULL,
+                                         &err);
+
+
+    if(err || !reply) {
+        if(err) {
+            LoggerE("error calling GetCalls method");
+            g_error_free(err);
+        }
+        else if(!reply)
+            LoggerE("reply is NULL");
+
+        return;
+    }
+
+    GVariantIter *calls;
+    GVariantIter *props;
+    const char *path;
+    g_variant_get(reply, "(a(oa{sv}))", &calls);
+    while(g_variant_iter_next(calls, "(oa{sv})", &path, &props)) {
+        addCall(path, props);
+    }
+
+    g_variant_unref(reply);
+
+    return;
+}
+
+void OFono::handleSignal(GDBusConnection       *connection,
+                         const gchar           *sender,
+                         const gchar           *object_path,
+                         const gchar           *interface_name,
+                         const gchar           *signal_name,
+                         GVariant              *parameters,
+                         gpointer               user_data)
+{
+    LoggerD("signal received: \"" << interface_name << "\" -> \"" << signal_name << "\"");
+
+    OFono* ctx = static_cast<OFono*>(user_data);
+    if(!ctx) {
+        LoggerE("Failed to cast object");
+        return;
+    }
+
+    if(!interface_name || !signal_name) {
+        LoggerE("Invalid callback data");
+        return;
+    }
+    if(!strcmp(interface_name, OFONO_MANAGER_IFACE)) {
+        if(!strcmp(signal_name, "ModemAdded")) {
+            const char *modem = NULL;
+            GVariantIter *props = NULL;
+            g_variant_get(parameters, "(oa{sv})", &modem, &props);
+            if(modem) {
+                std::string modemString(modem);
+                size_t idx = modemString.find( "_" ) + 1; // index of address of remote device
+                std::string modemRemoteBtAddress = modemString.substr (idx, modemString.length()-idx);
+                if(makeMACFromRawMAC(modemRemoteBtAddress)) {
+                    ctx->modemAdded(modemRemoteBtAddress);
+                }
+            }
+        }
+    }
+    else if(!strcmp(interface_name, OFONO_MANAGER_IFACE)) {
+        if(!strcmp(signal_name, "ModemRemoved")) {
+            const char *modem = NULL;
+            g_variant_get(parameters, "(o)", &modem);
+            if(modem) {
+                LoggerD("Modem removed: " << modem);
+                if(ctx->mModemPath && !strcmp(modem, ctx->mModemPath)) {
+                    ctx->removeModem(ctx->mModemPath);
+                }
+            }
+        }
+    }
+    else if(!strcmp(interface_name, OFONO_MODEM_IFACE)) {
+        if(!strcmp(signal_name, "PropertyChanged")) {
+            const char *name = NULL;
+            GVariant *value = NULL;
+            g_variant_get(parameters, "(sv)", &name, &value);
+            if(!strcmp(name, "Powered")) {
+                bool powered = g_variant_get_boolean(value);
+                LoggerD("\t" << name << " = " << (powered?"TRUE":"FALSE"));
+                ctx->modemPowered(powered);
+                // !!! won't request calls here, since the service may not be available yet
+                // see "Interfaces" "PropertyChanged" on OFONO_MODEM_IFACE
+                //if(powered)
+                //    ctx->getCalls();
+            }
+            else if(!strcmp(name, "Online")) {
+                bool online = g_variant_get_boolean(value);
+                LoggerD("\t" << name << " = " << (online?"TRUE":"FALSE"));
+            }
+            else if(!strcmp(name, "Interfaces")) {
+                GVariantIter iter;
+                GVariant *interface;
+                GVariant *variant = g_variant_get_child_value(parameters,1); // signature: "v"
+                GVariant *interfaces = g_variant_get_child_value(variant,0); // signature: "as"
+                g_variant_iter_init(&iter, interfaces);
+                while((interface = g_variant_iter_next_value(&iter))) {
+                    const char *iface = g_variant_get_string(interface, NULL);
+                    if(!strcmp(iface, OFONO_VOICECALLMANAGER_IFACE)) {
+                        //TODO: ??? check if the service is newly added ??? - not to request calls multiple times
+                        // service is up, request active calls now
+                        ctx->getCalls();
+                    }
+                };
+            }
+        }
+    }
+    else if(!strcmp(interface_name, OFONO_VOICECALLMANAGER_IFACE)) {
+        if(!strcmp(signal_name, "CallAdded")) {
+            if(!parameters) {
+                LoggerE("Invalid parameters");
+                return;
+            }
+
+            const char *path = NULL;
+            GVariantIter *props = NULL;
+            g_variant_get(parameters, "(oa{sv})", &path, &props);
+            ctx->addCall(path, props);
+        }
+        else if(!strcmp(signal_name, "CallRemoved")) {
+            if(!parameters) {
+                LoggerD("Invalid parameters");
+                return;
+            }
+            // TODO: do the check for the call
+            // for now, remove just the active one
+            ctx->removeCall(&ctx->mActiveCall);
+        }
+        else {
+            LoggerD("un-handled signal \"" << signal_name << "\" on " << interface_name);
+        }
+    }
+    else if(!strcmp(interface_name, OFONO_VOICECALL_IFACE)) {
+        if(!strcmp(signal_name, "PropertyChanged")) {
+            const char *key = NULL;
+            GVariant *value = NULL;
+            g_variant_get(parameters, "(sv)", &key, &value);
+            if(!strcmp(key, "State")) {
+                const char *state = NULL;
+                g_variant_get(value, "s", &state);
+                if(ctx->mActiveCall->state)
+                    g_free(ctx->mActiveCall->state);
+                ctx->mActiveCall->state = strdup(state);
+                LoggerD("PROPERTY CHANGED: " << key << " = " << state);
+            }
+            else {
+                LoggerD("PROPERTY CHANGED: " << key);
+            }
+            // TODO: check for the remaining properties
+            ctx->callChanged(ctx->mActiveCall->state, ctx->mActiveCall->line_id);
+        }
+    }
+    else {
+        LoggerD("signal received for un-handled IFACE: " << interface_name);
+    }
+}
+
+void OFono::addCall(const char *path, GVariantIter *props) {
+    LoggerD("entered");
+
+    // only one call at a time is curently supported
+    if(mActiveCall) {
+        LoggerD("Unable to add a call when another is on-going");
+        return;
+    }
+
+    if(!path)
+        return;
+
+    mActiveCall = new OFono::Call();
+    mActiveCall->path = strdup(path);
+
+    Utils::setSignalListener(G_BUS_TYPE_SYSTEM, OFONO_SERVICE,
+                             OFONO_VOICECALL_IFACE, path,
+                             "PropertyChanged", OFono::handleSignal,
+                             this);
+
+    const char *key = NULL;
+    GVariant *value = NULL;
+    while(g_variant_iter_next(props, "{sv}", &key, &value)) {
+        if(!strcmp(key, "State")) {
+            const char *state = NULL;
+            g_variant_get(value, "s", &state);
+            mActiveCall->state = state ? strdup(state) : NULL;
+        }
+        else if(!strcmp(key, "LineIdentification")) {
+            const char *line_id = NULL;
+            g_variant_get(value, "s", &line_id);
+            mActiveCall->line_id = line_id ? strdup(line_id) : NULL;
+        }
+    }
+
+    // notify listener about call state changed
+    if(!!mActiveCall->state && !!mActiveCall->line_id) {
+        callChanged(mActiveCall->state, mActiveCall->line_id);
+    }
+}
+
+void OFono::removeCall(OFono::Call **call) {
+    LoggerD("entered");
+
+    // unsubscribe and remove signal listeners for the call
+    Utils::removeSignalListener(G_BUS_TYPE_SYSTEM, OFONO_SERVICE,
+                                OFONO_VOICECALL_IFACE, (*call)->path,
+                                "PropertyChanged");
+
+    if(*call) {
+        delete *call;
+        *call = NULL;
+    }
+}
+
+bool OFono::invokeCall(const char* phoneNumber, char **error) {
+    if(!mModemPath) { // no selected modem to perform operation on
+        *error = strdup("No active modem set");
+        return false;
+    }
+
+    if(mActiveCall) { // active call
+        *error = strdup("Already active call");
+        return false;
+    }
+
+    GError *err = NULL;
+    GVariant *reply;
+    reply = g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL,NULL),
+                                         OFONO_SERVICE,
+                                         mModemPath,
+                                         OFONO_VOICECALLMANAGER_IFACE,
+                                         "Dial",
+                                         g_variant_new ("(ss)", // floating parameters are consumed, no cleanup/unref needed
+                                             phoneNumber,
+                                             ""
+                                         ),
+                                         NULL,
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         -1,
+                                         NULL,
+                                         &err);
+
+    if(err) {
+        LoggerE("Failed to call 'Dial' DBUS method: " << err->message);
+        *error = strdup(err->message);
+        g_error_free(err);
+        return false;
+    }
+
+    if(!reply) {
+        LoggerE("Reply from calling 'Dial' is NULL");
+        *error = strdup("Failed to get 'call' object from OFONO's 'Dial' reply");
+        return false;
+    }
+
+    // TODO: process reply here - ??? is it really needed 'cause we are handling CallAdded signal anyway ???
+    g_variant_unref(reply);
+
+    return true;
+}
+
+bool OFono::answerCall(char **error) {
+    if(!mActiveCall) { // no active call to hangup
+        *error = strdup("No active call");
+        return false;
+    }
+
+    GError *err = NULL;
+    g_dbus_connection_call_sync (g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL,NULL),
+                    OFONO_SERVICE,
+                    mActiveCall->path,
+                    OFONO_VOICECALL_IFACE,
+                    "Answer",
+                    NULL,
+                    NULL,
+                    G_DBUS_CALL_FLAGS_NONE,
+                    -1,
+                    NULL,
+                    &err);
+
+    if(err) {
+        LoggerD("Failed to call 'Answer' DBUS method: " << err->message);
+        *error = strdup(err->message);
+        g_error_free(err);
+        return false;
+    }
+
+    return true;
+}
+
+bool OFono::hangupCall(char **error) {
+    if(!mActiveCall) { // no active call to hangup
+        *error = strdup("No active call");
+        return false;
+    }
+
+    GError *err = NULL;
+    g_dbus_connection_call_sync (g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL,NULL),
+                    OFONO_SERVICE,
+                    mActiveCall->path,
+                    OFONO_VOICECALL_IFACE,
+                    "Hangup",
+                    NULL,
+                    NULL,
+                    G_DBUS_CALL_FLAGS_NONE,
+                    -1,
+                    NULL,
+                    &err);
+
+    if(err) {
+        LoggerD("Failed to call 'Hangup' DBUS method: " << err->message);
+        *error = strdup(err->message);
+        g_error_free(err);
+        return false;
+    }
+
+    return true;
+}
+
+OFono::Call *OFono::activeCall() {
+    LoggerD("OFono::activeCall()");
+    // make a copy of object, since it may be destroyed meanwhile
+    return mActiveCall ? new OFono::Call(mActiveCall) : NULL;
+}
+
+bool OFono::muteCall(bool mute, char **error) {
+    if(!mActiveCall) { // no active call to hangup
+        *error = strdup("No active call");
+        return false;
+    }
+
+    bool success = setProperty(OFONO_CALLVOLUME_IFACE, mModemPath, "Muted", DBUS_TYPE_BOOLEAN, &mute);
+    if(!success) {
+        *error = strdup("Failed to set 'Muted' property on the call");
+        return false;
+    }
+
+    return true;
+}
+
+} // PhoneD
+
diff --git a/src/ofono.h b/src/ofono.h
new file mode 100644 (file)
index 0000000..19cd166
--- /dev/null
@@ -0,0 +1,169 @@
+#ifndef OFONO_H_
+#define OFONO_H_
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gio/gio.h>
+#include <string>
+#include <string.h>
+#include <map>
+
+namespace PhoneD {
+
+/**
+ * @addtogroup phoned
+ * @{
+ */
+
+/*! \class PhoneD::OFono
+ *  \brief Class which is utilizing OFono D-Bus service. It is a base class and is not meant to be instantiated directly.
+ *
+ * A class providing access to <a href="http://www.ofono.org">OFono</a> functionality. It allows make a phone call, answer/decline incoming call, hang-up active phone call.
+ */
+class OFono {
+
+    public:
+
+    /*! \class Call
+     *  \brief A Class describing phone call object.
+     *
+     * A class describing phone call object. It is used to get information about active call.
+     */
+    class Call {
+        public:
+            /**
+             * A default constructor which constructs an empty object with NULL initialized members.
+             */
+            Call() : path(NULL), state(NULL), line_id(NULL) {};
+            /**
+             * Copy constructor to make a copy from the Call object specified by the pointer to it.
+             * @param[in] call A pointer to Call object to make a copy of.
+             */
+            Call(OFono::Call *call) {
+                if(call->path)  path = strdup(call->path);
+                if(call->state) state = strdup(call->state);
+                if(call->line_id) line_id = strdup(call->line_id);
+            };
+            /**
+             * A destructor which frees the allocated memory of member variables and destroys the object.
+             */
+            ~Call() {
+                if(path)    { g_free(path);    path=NULL;    }
+                if(state)   { g_free(state);   state=NULL;   }
+                if(line_id) { g_free(line_id); line_id=NULL; }
+            };
+        public:
+            char *path;     /*!< A path to the call object */
+            char *state;    /*!< A state of phone call (incoming,dialing,active,disconnected) */
+            char *line_id;  /*!< A line identifier. It contains a phone number of the caller, or the calling person respectively. */
+    };
+
+    public:
+        /**
+         * A default constructor. Constructs the object and subscribes for certain D-Bus signals of \b org.ofono service:
+         * <ul>
+         * <li> "ModemAdded" on \b org.ofono.Manager interface - To get notified when modem is added. </li>
+         * <li> "ModemRemoved" on \b org.ofono.Manager interface - To get notified when modem is removed.</li>
+         * </ul>
+         * It starts a timer to check whether default modem is \b Powered, or not. It handles the case, when the device gets out of the range, or when the user
+         * turns OFF and then turns ON the BT on the phone device, in which case there isn't a notification that the remote device got back.
+         * The timer in specific interval checks whether the modem is \b Powered and if not, it tries to power it up.
+         */
+        OFono();
+
+        /**
+         * A destructor. Unselects default modem and destructs the object. @see selectModem() and unselectModem()
+         */
+        ~OFono();
+
+        /**
+         * A method to select modem. It gets the list of available modems and checks them against the given MAC address. If the modem is for the device specified by the given MAC address, it is selected as default one.
+         * @param[in] btAddress A MAC address of a remote BT device for which the modem should be selected.
+         * @see unselectModem()
+         */
+        void selectModem(std::string &btAddress);
+
+        /**
+         * A method to unselect the default modem.
+         * @see selectModem()
+         */
+        void unselectModem();
+
+        /**
+         * A method to invoke phone call.
+         * @param[in] phoneNumber A phone number to invoke phone call.
+         * @param[out] error If the return value from the method is \b false, it contains a description of the error. The caller is responsible for freeing the memory if the error is set.
+         * @return \b True if D-Bus "Dial" method was successfuly called on \b org.ofono.VoiceCallManager interface, otherwise it returns \b false.
+         */
+        bool invokeCall(const char* phoneNumber, char **error);
+
+        /**
+         * A method to answer incoming phone call.
+         * @param[out] error If the return value from the method is \b false, it contains a description of the error. The caller is responsible for freeing the memory if the error is set.
+         * @return \b True if D-Bus "Answer" method was successfuly called on \b org.ofono.VoiceCall interface, otherwise it returns \b false.
+         */
+        bool answerCall(char **error);
+
+        /**
+         * A method to decline incoming, or hangup active phone call.
+         * @param[out] error If the return value from the method is \b false, it contains a description of the error. The caller is responsible for freeing the memory if the error is set.
+         * @return \b True if D-Bus "Hangup" method was successfuly called on \b org.ofono.VoiceCall interface, otherwise it returns \b false.
+         */
+        bool hangupCall(char **error);
+
+        /**
+         * A method to mute/unmute active phone call.
+         * @param[in] mute Specifies whether to mute/unmute phone call.
+         * @param[out] error If the return value from the method is \b false, it contains a description of the error. The caller is responsible for freeing the memory if the error is set.
+         * @return \b True if D-Bus "SetProperty" method to set "Muted" property was successfuly called on \b org.ofono.CallVolume interface, otherwise it returns \b false.
+         */
+        bool muteCall(bool mute, char **error);
+
+        /**
+         * Gets the information about active phone call. It creates new object (allocates a memory) and the caller has to delete it.
+         * @return OFono::Call object describing active phone call.
+         */
+        OFono::Call *activeCall();
+
+    private:
+        static void asyncSelectModemCallback(GObject *source, GAsyncResult *result, gpointer user_data); // async callback for "GetModems" invoked from selectModem() method
+        virtual void callChanged(const char* state, const char* phoneNumber) = 0;
+
+        void addModem(const char *path, GVariantIter *props);
+        virtual void modemAdded(std::string &modem) = 0; // MAC address of modem ... from ModemAdded DBUS
+        void removeModem(const char *modemPath);
+        virtual void modemPowered(bool powered) = 0;
+        virtual void setModemPoweredFailed(const char *err) = 0;
+        static void handleSignal(GDBusConnection *connection,  const gchar     *sender,
+                                 const gchar     *object_path, const gchar     *interface_name,
+                                 const gchar     *signal_name, GVariant        *parameters,
+                                 gpointer         user_data);
+
+        //DBUS: array{object,dict} GetCalls()
+        void getCalls(); //Get an array of call object paths and properties that represents the currently present calls.
+        void addCall(const char *path, GVariantIter *props);
+        void removeCall(OFono::Call **call);
+
+        // used to set property on ifaces that do have same object path, eg. Modem, CallVolume, ...
+        bool setProperty(const char* iface, const char* path, const char *property, int type, void *value); // returns success of the operation
+        void setPropertyAsync(const char* iface, const char* path, const char *property, int type, void *value, GAsyncReadyCallback callback);
+
+        void setModemPowered(const char *path, bool powered); // path of Modem object
+        bool isModemPowered(const char *modem);
+        static void asyncSetModemPoweredCallback(GObject *source, GAsyncResult *result, gpointer user_data); // async callback for "SetProperty" invoked from setModemPowered() method
+        static gboolean checkForModemPowered(gpointer user_data); // continuous timeout method to check for 'Selected' modem being Powered and power it on, if it's not
+
+        static void GDbusAsyncReadyCallback(GObject *source, GAsyncResult *result, gpointer user_data);
+
+    private:
+        DBusConnection               *mDBusConnection;
+        gchar                        *mModemPath;
+        OFono::Call                  *mActiveCall;
+};
+
+#endif /* OFONO_H_ */
+
+} // PhoneD
+
+/** @} */
+
diff --git a/src/phone.cpp b/src/phone.cpp
new file mode 100644 (file)
index 0000000..3e3cdfc
--- /dev/null
@@ -0,0 +1,773 @@
+#include <string.h>
+#include <fstream>
+#include <algorithm>
+
+#include "phone.h"
+#include "utils.h"
+
+#include "Logger.h"
+
+namespace PhoneD {
+
+#define TIZEN_PREFIX           "org.tizen"
+
+#define PHONE_SERVICE          TIZEN_PREFIX ".phone"
+#define PHONE_IFACE            TIZEN_PREFIX ".Phone"
+#define PHONE_OBJ_PATH         "/"
+
+#define DELAYED_SYNC_CALLHISTORY_INTERVAL  5000 // delayed synchronization of CallHistory from the phone (in ms)
+                                                // when a call is made, it takes certain time until the entry gets added in the list
+                                                // use a timeout to delay synchronization request
+
+#define CALLHISTORY_UPDATED_SYNC_COUNT     10   // number of latest CallHistory entries to be requested from the phone
+
+#define PHONE_INTERFACE_XML                                     \
+    "<node>"                                                    \
+    "  <interface name='" PHONE_IFACE "'>"                      \
+    "    <method name='SelectRemoteDevice'>"                    \
+    "      <arg type='s' name='address' direction='in'/>"       \
+    "    </method>"                                             \
+    "    <method name='GetSelectedRemoteDevice'>"               \
+    "      <arg type='s' name='address' direction='out'/>"      \
+    "    </method>"                                             \
+    "    <method name='UnselectRemoteDevice'>"                  \
+    "    </method>"                                             \
+    "    <method name='Dial'>"                                  \
+    "      <arg type='s' name='number' direction='in'/>"        \
+    "    </method>"                                             \
+    "    <method name='Answer'>"                                \
+    "    </method>"                                             \
+    "    <method name='Mute'>"                                  \
+    "      <arg type='b' name='muted' direction='in'/>"         \
+    "    </method>"                                             \
+    "    <method name='Hangup'>"                                \
+    "    </method>"                                             \
+    "    <method name='ActiveCall'>"                            \
+    "      <arg type='a{sv}' name='call' direction='out'/>"     \
+    "    </method>"                                             \
+    "    <method name='Synchronize'>"                           \
+    "    </method>"                                             \
+    "    <method name='GetContacts'>"                           \
+    "      <arg type='u' name='count' direction='in'/>"         \
+    "      <arg type='s' name='contacts' direction='out'/>"     \
+    "    </method>"                                             \
+    "    <method name='GetCallHistory'>"                        \
+    "      <arg type='u' name='count' direction='in'/>"         \
+    "      <arg type='s' name='calls' direction='out'/>"        \
+    "    </method>"                                             \
+    "  </interface>"                                            \
+    "</node>"
+
+/*
+Signals:
+
+       "RemoteDeviceSelected"  : "(s)" ... {?value,?error}
+       "ContactsChanged"       : ""
+       "CallHistoryChanged"    : ""
+       "CallHistoryEntryAdded" : "(s)" ... tizen.CallHistoryEntry
+       "CallChanged"           : "(a{sv})" ... "state", "line_id", "contact"
+*/
+
+Phone::Phone() :
+    ConnMan(),
+    Bluez(),
+    OFono(),
+    Obex(),
+    mAdapterPowered(false),
+    mWantedRemoteDevice(""),
+    mSelectedRemoteDevice(""),
+    mPBSynchronized(false),
+    mNameRequestId(0),
+    mRegistrationId(0),
+    mIntrospectionData(NULL)
+{
+    LoggerD("entered");
+
+    memset(&mIfaceVTable, 0, sizeof(mIfaceVTable));
+
+    // initialize DBUS
+    mNameRequestId = g_bus_own_name(G_BUS_TYPE_SESSION,
+                                    PHONE_SERVICE,
+                                    G_BUS_NAME_OWNER_FLAGS_NONE,
+                                    Phone::busAcquiredCb,
+                                    Phone::nameAcquiredCb,
+                                    Phone::nameLostCb,
+                                    this,
+                                    NULL); //GDestroyNotify
+
+    // Request to get own name is sent, now
+    // busAcquiredCb should be called and thus setup() initiated
+    if(setBluetoothPowered(true)) {
+        LoggerD("Bluetooth powered");
+        if(setAdapterPowered(true)) {
+            LoggerD("hci adapter powered");
+        }
+        else {
+            LoggerE("Failed to Power-ON hci adapter");
+        }
+    }
+    else {
+        LoggerE("Failed to Power-ON Bluetooth");
+    }
+}
+
+Phone::~Phone() {
+    LoggerD("entered");
+    if(mRegistrationId > 0) {
+        g_dbus_connection_unregister_object(g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL), mRegistrationId);
+        LoggerD("Unregistered object with id: " << mRegistrationId);
+        mRegistrationId = 0;
+    }
+    if(mNameRequestId > 0) {
+        g_bus_unown_name(mNameRequestId);
+        LoggerD("Un-owned name with id: " << mNameRequestId);
+        mNameRequestId = 0;
+    }
+    removeSession();
+}
+
+void Phone::busAcquiredCb(GDBusConnection *gdbus, const gchar *name, gpointer data)
+{
+    LoggerD("acquired bus " << name);
+
+    Phone *phone = static_cast<Phone*>(data);
+    if(!phone) {
+        LoggerE("Failed to cast data to Phone*");
+        return;
+    }
+
+    phone->setup();
+}
+
+void Phone::nameAcquiredCb(GDBusConnection *gdbus, const gchar *name, gpointer data)
+{
+    LoggerD("acquired name " << name);
+
+    Phone *phone = static_cast<Phone*>(data);
+    if(!phone) {
+        LoggerE("Failed to cast data to Phone*");
+        return;
+    }
+
+    // it is generally too late to export the objects in name_acquired_handler,
+    // which is done in setup() method (calling: g_dbus_connection_register_object())
+    // Instead, you can do this in bus_acquired_handler, ie. in busAcquiredCb() callback method
+    //phone->setup();
+
+    phone->init(); // ConnMan
+}
+
+void Phone::nameLostCb(GDBusConnection *gdbus, const gchar *name, gpointer data)
+{
+    LoggerD("lost name " << name);
+
+    Phone *phone = static_cast<Phone*>(data);
+    if(!phone) {
+        LoggerE("Failed to cast data to Phone*");
+        return;
+    }
+    //TODO: think of an algorithm to re-request own name in case name is lost
+}
+
+void Phone::setup() {
+    LoggerD("entered");
+
+    mIfaceVTable.method_call = Phone::handleMethodCall;
+    mIntrospectionData = g_dbus_node_info_new_for_xml(PHONE_INTERFACE_XML, NULL);
+
+    if (mIntrospectionData == NULL) {
+        LoggerE("Failed to create introspection data from XML");
+        return;
+    }
+
+    GError *error = NULL;
+    mRegistrationId = g_dbus_connection_register_object( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                                         PHONE_OBJ_PATH,
+                                                         mIntrospectionData->interfaces[0],
+                                                         &mIfaceVTable,
+                                                         this,
+                                                         NULL, //GDestroyNotify
+                                                         &error);
+
+    if(error) {
+        LoggerE("Failed to register object: " << PHONE_OBJ_PATH << " : " << error->message);
+        g_error_free(error);
+        return;
+    }
+
+    // Obex/OFono needs an agent to be registered on DBus
+    // TODO: implement a check whether agent is registered and running
+    // NOTE: if the device is not yet paired, you need to pair it first
+    // NOTE: if the device is not yet "trusted", the user has to accept
+    // the request to trust on the device
+    setupAgent();    // Bluez
+    registerAgent(); // Bluez
+
+    // read MAC address of selected remote device from persistent storage
+    std::string btAddress;
+    bool isSelectedRemoteDevice = readSelectedRemoteDeviceMAC(btAddress);
+    if(isSelectedRemoteDevice) {
+        mWantedRemoteDevice = btAddress;
+        bool paired = isDevicePaired(btAddress.c_str());
+        if(paired) {
+            // TODO: here should be also a check whether the device is visible (is in the range)
+            LoggerD("The device is paired ... starting services");
+            startServices();
+        }
+    }
+}
+
+void Phone::adapterPowered(bool value) {
+    LoggerD("Default adapter powered: " << (value?"ON":"OFF"));
+    mAdapterPowered = value;
+    return value ? startServices() : stopServices();
+}
+
+void Phone::defaultAdapterAdded() {
+    LoggerD("Default adapter added");
+    startServices();
+}
+
+void Phone::defaultAdapterRemoved() {
+    LoggerD("Default adapter removed");
+    stopServices();
+}
+
+void Phone::startServices() {
+    std::string device = "";
+    setSelectedRemoteDevice(device);
+    // select the modem for the 'wanted' device - once the modem is found, it is
+    // set 'Powered' ON, which will call 'modemPowered', from where 'createSession' is called
+    selectModem(mWantedRemoteDevice);
+    //createSession(mWantedRemoteDevice.c_str());
+}
+
+void Phone::stopServices() {
+    std::string device = "";
+    setSelectedRemoteDevice(device);
+    // call 'unselectModem', which unselects and sets modem "Powered" OFF and as a result,
+    // modemPowered method is called in which 'removeSession' is called
+    unselectModem();
+    //removeSession();
+}
+
+// this method is called when the device is paired (it's not directly connected
+// to Bluez' DBUS "DeviceCreated" method, but rather to Paired PropertyChanged
+void Phone::deviceCreated(const char *device) {
+    LoggerD("created&paired device: " << device);
+
+    std::string dev = device;
+    makeMACFromDevicePath(dev); // AA:BB:CC:DD:EE:FF
+    makeRawMAC(dev); // AABBCCDDEEFF
+
+    // make a copy, since we will modify it
+    std::string wanted = mWantedRemoteDevice;
+    wanted.erase(std::remove_if(wanted.begin(), wanted.end(), isnxdigit), wanted.end());
+    if(!dev.compare(wanted)) {
+        LoggerD("Paired wanted device");
+        startServices();
+    }
+}
+
+void Phone::deviceRemoved(const char *device) {
+    LoggerD("removed device: " << device);
+
+    std::string dev = device;
+    makeMACFromDevicePath(dev); // AA:BB:CC:DD:EE:FF
+    makeRawMAC(dev); // AABBCCDDEEFF
+
+    // make a copy, since we will modify it
+    std::string selected = mSelectedRemoteDevice;
+    selected.erase(std::remove_if(selected.begin(), selected.end(), isnxdigit), selected.end());
+    if(!dev.compare(selected)) {
+        LoggerD("Removed selected device");
+        stopServices();
+    }
+}
+
+void Phone::modemAdded(std::string &modem) {
+    LoggerD("modem added: " << modem);
+    if(!mWantedRemoteDevice.compare(modem)) {
+        selectModem(mWantedRemoteDevice);
+    }
+}
+
+void Phone::modemPowered(bool powered) {
+    LoggerD("Modem powered: " << (powered?"ON":"OFF"));
+    if(powered) {
+        mPBSynchronized = false;
+        createSession(mWantedRemoteDevice.c_str());
+    }
+    else {
+        mPBSynchronized = false;
+        removeSession();
+    }
+}
+
+void Phone::setModemPoweredFailed(const char *error) {
+    LoggerD("SelectModem failed: " << error);
+    std::string result("");
+    result += "{\"error\":\"";
+    result += (error?error:"Unknown error");
+    result += "\"}";
+    g_dbus_connection_emit_signal( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                   NULL,
+                                   PHONE_OBJ_PATH,
+                                   PHONE_IFACE,
+                                   "RemoteDeviceSelected",
+                                   g_variant_new("(s)", result.c_str()),
+                                   NULL);
+}
+
+void Phone::createSessionFailed(const char *error) {
+    LoggerD("CreateSession failed: " << error);
+    std::string result("");
+    result += "{\"error\":\"";
+    result += (error?error:"Unknown error");
+    result += "\"}";
+    g_dbus_connection_emit_signal( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                   NULL,
+                                   PHONE_OBJ_PATH,
+                                   PHONE_IFACE,
+                                   "RemoteDeviceSelected",
+                                   g_variant_new("(s)", result.c_str()),
+                                   NULL);
+}
+
+// creates a copy of session name ... don't forget to free the memory in dtor
+// and possibly on other places as well ( ??? session closed ??? )
+void Phone::createSessionDone(const char *session) {
+    LoggerD("CreateSession DONE: " << (session?session:"SESSION NOT CREATED"));
+
+    setSelectedRemoteDevice(mWantedRemoteDevice);
+
+    // emit signal that the remote device has been selected
+    std::string result("");
+    result += "{\"value\":\"";
+    result += mSelectedRemoteDevice;
+    result += "\"}";
+    g_dbus_connection_emit_signal( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                   NULL,
+                                   PHONE_OBJ_PATH,
+                                   PHONE_IFACE,
+                                   "RemoteDeviceSelected",
+                                   g_variant_new("(s)", result.c_str()), // empty object indicates that the selecting remote device has been successfull
+                                   NULL);
+
+    LoggerD("starting synchronization process: contacts/call history");
+    /*Obex::Error err = */syncContacts();
+    /*Obex::Error err = */syncCallHistory();
+}
+
+void Phone::removeSessionDone() {
+    LoggerD("entered");
+
+    g_dbus_connection_emit_signal( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                   NULL,
+                                   PHONE_OBJ_PATH,
+                                   PHONE_IFACE,
+                                   "RemoteDeviceSelected",
+                                   g_variant_new("(s)", "{\"value\":\"\"}"),
+                                   NULL);
+
+    // cleared list of contacts
+    g_dbus_connection_emit_signal( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                   NULL,
+                                   PHONE_OBJ_PATH,
+                                   PHONE_IFACE,
+                                   "ContactsChanged",
+                                   NULL,
+                                   NULL);
+
+    // cleared call history
+    g_dbus_connection_emit_signal( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                   NULL,
+                                   PHONE_OBJ_PATH,
+                                   PHONE_IFACE,
+                                   "CallHistoryChanged",
+                                   NULL,
+                                   NULL);
+}
+
+void Phone::transferStalled() {
+    if(mAdapterPowered) {
+        LoggerD("Adapter is powered ... calling 'CreateSession'");
+        mPBSynchronized = false;
+        std::string device = "";
+        setSelectedRemoteDevice(device);
+        createSession(mWantedRemoteDevice.c_str());
+    }
+    else {
+        LoggerD("Adapter is not powered ... not calling 'CreateSession'");
+    }
+}
+
+void Phone::setSelectedRemoteDevice(std::string &btAddress) {
+    mSelectedRemoteDevice = btAddress;
+    // Obex also needs to know selected remote device to pair received VCards with
+    Obex::setSelectedRemoteDevice(btAddress);
+}
+
+void Phone::callHistoryEntryAdded(std::string &entry) {
+    //LoggerD("CallHistoryEntryAdded: " << entry);
+
+    // do not emit signal if the PB has not yet been synchronized
+    // doing so would result in emiting too many signals: one signal
+    // per entry in the call history list
+    // only newly placed/received calls will be emited
+    if(mPBSynchronized) {
+        g_dbus_connection_emit_signal( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                       NULL,
+                                       PHONE_OBJ_PATH,
+                                       PHONE_IFACE,
+                                       "CallHistoryEntryAdded",
+                                       g_variant_new("(s)", entry.c_str()),
+                                       NULL);
+    }
+}
+
+void Phone::contactsChanged() {
+    LoggerD("entered");
+
+    // do emit signal only if PB is not yet synchronized
+    // the current implementation doesn't handle contacts added/updated/removed
+    // this signal is to notify client app that contacts list has chaned, eg. as
+    // a result of selecting other remote device via 'selectRemoteDevice' method
+    if(!mPBSynchronized) {
+        g_dbus_connection_emit_signal( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                       NULL,
+                                       PHONE_OBJ_PATH,
+                                       PHONE_IFACE,
+                                       "ContactsChanged",
+                                       NULL,
+                                       NULL);
+    }
+}
+
+void Phone::callHistoryChanged() {
+    LoggerD("entered");
+
+    // do emit signal only if PB is not yet synchronized
+    // the current implementation doesn't handle calls updated/removed (adding
+    // call entry into call history is notified via CallHistoryEntryAdded signal
+    // this signal is to notify client app that contacts list has chaned, eg. as
+    // a result of selecting other remote device via 'selectRemoteDevice' method
+    if(!mPBSynchronized) {
+        g_dbus_connection_emit_signal( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                       NULL,
+                                       PHONE_OBJ_PATH,
+                                       PHONE_IFACE,
+                                       "CallHistoryChanged",
+                                       NULL,
+                                       NULL);
+    }
+}
+
+void Phone::pbSynchronizationDone() {
+    LoggerD("PB synchronization DONE");
+    mPBSynchronized = true;
+}
+
+void Phone::handleMethodCall( GDBusConnection       *connection,
+                              const gchar           *sender,
+                              const gchar           *object_path,
+                              const gchar           *interface_name,
+                              const gchar           *method_name,
+                              GVariant              *parameters,
+                              GDBusMethodInvocation *invocation,
+                              gpointer               user_data)
+{
+    LoggerD(sender << ":" << object_path << ":" << interface_name << ":" << method_name);
+
+    Phone *phone = static_cast<Phone*>(user_data);
+    if(!phone) {
+        LoggerE("Failed to cast to Phone");
+        return;
+    }
+
+    if(!strcmp(method_name, "SelectRemoteDevice")) {
+        char *btAddress = NULL;
+        g_variant_get(parameters, "(&s)", &btAddress);
+        if(!btAddress || !isValidMAC(std::string(btAddress))) {
+            LoggerE("Won't select remote device: given MAC address \"" << btAddress << "\" is not valid");
+            g_dbus_connection_emit_signal( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                           NULL,
+                                           PHONE_OBJ_PATH,
+                                           PHONE_IFACE,
+                                           "RemoteDeviceSelected",
+                                           g_variant_new("(s)", "{\"error\":\"Invalid MAC address\"}"),
+                                           NULL);
+        }
+        else {
+            LoggerD("Selecting remote device: " << btAddress);
+
+            // check whether requested device is not yet selected
+            // if so, just check if PB is already synchronized and call callback, if it is
+            if(!strcmp(phone->mSelectedRemoteDevice.c_str(), btAddress)) {
+                if(phone->mPBSynchronized) {
+                    std::string result("");
+                    result += "{\"value\":\"";
+                    result += phone->mSelectedRemoteDevice;
+                    result += "\"}";
+                    g_dbus_connection_emit_signal( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                                   NULL,
+                                                   PHONE_OBJ_PATH,
+                                                   PHONE_IFACE,
+                                                   "RemoteDeviceSelected",
+                                                   g_variant_new("(s)", result.c_str()), // already synchronized/selected
+                                                   NULL);
+                }
+                else {
+                    // the synchronization may be already on-going, but request it anyway
+                    /*Obex::Error err = */phone->syncContacts();
+                    /*Obex::Error err = */phone->syncCallHistory();
+                }
+            }
+            else {
+                //TODO: stop all services and start everything from begining
+                phone->mWantedRemoteDevice = btAddress;
+                phone->storeSelectedRemoteDeviceMAC(btAddress);
+                phone->startServices();
+            }
+        }
+        g_dbus_method_invocation_return_value(invocation, NULL); // just finish the method call - don't return any status (The status is returned via "RemoteDeviceSelected" signal
+    }
+    else if(!strcmp(method_name, "GetSelectedRemoteDevice")) {
+        g_dbus_method_invocation_return_value( invocation,
+                                               g_variant_new("(s)", phone->mSelectedRemoteDevice.c_str()));
+    }
+    if(!strcmp(method_name, "UnselectRemoteDevice")) {
+        phone->mWantedRemoteDevice = "";
+        phone->stopServices();
+        g_dbus_method_invocation_return_value(invocation, NULL);
+    }
+    else if(!strcmp(method_name, "Dial")) {
+        char *number = NULL;
+        char *error = NULL;
+        g_variant_get(parameters, "(&s)", &number);
+        if(phone->invokeCall(number, &error)) {
+            LoggerD("Dialing number: " << number);
+            g_dbus_method_invocation_return_value(invocation, NULL);
+        }
+        else {
+            if(error) { // sanity check
+                LoggerD("Failed to dial number: " << error);
+                GError *err = g_error_new(G_PHONE_ERROR, 1, error);
+                g_dbus_method_invocation_return_gerror(invocation, err);
+                free(error);
+            }
+        }
+    }
+    else if(!strcmp(method_name, "Answer")) {
+        char *error = NULL;
+        if(phone->answerCall(&error)) {
+            LoggerD("Answering incoming call");
+            g_dbus_method_invocation_return_value(invocation, NULL);
+        }
+        else {
+            if(error) { // sanity check
+                LoggerD("Failed to answer the call: " << error);
+                GError *err = g_error_new(G_PHONE_ERROR, 2, error);
+                g_dbus_method_invocation_return_gerror(invocation, err);
+                free(error);
+            }
+        }
+    }
+    else if(!strcmp(method_name, "Hangup")) {
+        char *error = NULL;
+        if(phone->hangupCall(&error)) {
+            LoggerD("Hanging-up active/incoming call");
+            g_dbus_method_invocation_return_value(invocation, NULL);
+        }
+        else {
+            if(error) { // sanity check
+                LoggerD("Failed to hang-up the call: " << error);
+                GError *err = g_error_new(G_PHONE_ERROR, 2, error);
+                g_dbus_method_invocation_return_gerror(invocation, err);
+                free(error);
+            }
+        }
+    }
+    else if(!strcmp(method_name, "Mute")) {
+        bool mute;
+        char *error = NULL;
+        g_variant_get(parameters, "(b)", &mute);
+        if(phone->muteCall(mute, &error)) {
+            LoggerD("Muting MIC: " << mute);
+            g_dbus_method_invocation_return_value(invocation, NULL);
+        }
+        else {
+            if(error) { // sanity check
+                LoggerD("Failed to mute the call: " << error);
+                GError *err = g_error_new(G_PHONE_ERROR, 2, error);
+                g_dbus_method_invocation_return_gerror(invocation, err);
+                free(error);
+            }
+        }
+    }
+    else if(!strcmp(method_name, "ActiveCall")) {
+        LoggerD("constructing ActiveCall response");
+
+        OFono::Call *call = phone->activeCall();
+
+        GVariant *props[8];
+        int nprops = 0;
+
+        GVariant *key, *str, *var;
+        // add state
+        key = g_variant_new_string("state");
+        str = g_variant_new_string((call && call->state)?call->state:"disconnected");
+        var = g_variant_new_variant(str);
+        props[nprops++] = g_variant_new_dict_entry(key, var);
+        // add line_id
+        key = g_variant_new_string("line_id");
+        str = g_variant_new_string((call && call->line_id)?call->line_id:"");
+        var = g_variant_new_variant(str);
+        props[nprops++] = g_variant_new_dict_entry(key, var);
+        // get contact by phone number
+        std::string contact;
+        phone->getContactByPhoneNumber(call?call->line_id:NULL, contact);
+        key = g_variant_new_string("contact");
+        str = g_variant_new_string(contact.c_str());
+        var = g_variant_new_variant(str);
+        props[nprops++] = g_variant_new_dict_entry(key, var);
+
+        GVariant *array = g_variant_new_array(G_VARIANT_TYPE("{sv}"), props, nprops);
+
+        GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("(a{sv})"));
+        g_variant_builder_add_value(builder, array);
+        GVariant *args = g_variant_builder_end(builder);
+        g_variant_builder_unref(builder);
+        g_dbus_method_invocation_return_value(invocation, args);
+
+        if(call)
+            delete call;
+    }
+    else if(!strcmp(method_name, "Synchronize")) {
+        LoggerD("Synchronizing data with the phone");
+        /*Obex::Error err = */phone->syncContacts();
+        /*Obex::Error err = */phone->syncCallHistory();
+        g_dbus_method_invocation_return_value(invocation, NULL);
+    }
+    else if(!strcmp(method_name, "GetContacts")) {
+        unsigned long count;
+        g_variant_get(parameters, "(u)", &count);
+        std::string contacts;
+        phone->getJsonContacts(contacts, count);
+        g_dbus_method_invocation_return_value( invocation,
+                                               g_variant_new("(s)", contacts.c_str()));
+    }
+    else if(!strcmp(method_name, "GetCallHistory")) {
+        unsigned long count;
+        g_variant_get(parameters, "(u)", &count);
+        std::string calls;
+        phone->getJsonCallHistory(calls, count);
+        g_dbus_method_invocation_return_value( invocation,
+                                               g_variant_new("(s)", calls.c_str()));
+    }
+}
+
+gboolean Phone::delayedSyncCallHistory(gpointer user_data) {
+    Phone *ctx = static_cast<Phone*>(user_data);
+    if(!ctx) {
+        LoggerE("Failed to cast to Phone");
+        return G_SOURCE_REMOVE; // single shot timeout
+    }
+    // synchronize given number of latest entries in the list
+    if(Obex::OBEX_ERR_INVALID_SESSION == ctx->syncCallHistory(CALLHISTORY_UPDATED_SYNC_COUNT)) {
+        // if the session is not created, try to create it
+        ctx->createSession(ctx->mWantedRemoteDevice.c_str());
+    }
+    return G_SOURCE_REMOVE; // single shot timeout
+}
+
+void Phone::callChanged(const char* state, const char* phoneNumber) {
+    LoggerD("CallChanged: " << state << "\t" << phoneNumber);
+
+    if(state && !strcmp(state, "disconnected")) {
+        // a call has been made => update call history
+        // use a delayed sync, since the list may not be updated on the phone yet
+        g_timeout_add(DELAYED_SYNC_CALLHISTORY_INTERVAL, delayedSyncCallHistory, this);
+    }
+
+    GVariant *props[8];
+    int nprops = 0;
+    GVariant *key, *str, *var;
+    // add state
+    key = g_variant_new_string("state");
+    str = g_variant_new_string(state?state:"disconnected");
+    var = g_variant_new_variant(str);
+    props[nprops++] = g_variant_new_dict_entry(key, var);
+    // add line_id
+    key = g_variant_new_string("line_id");
+    str = g_variant_new_string(phoneNumber?phoneNumber:"");
+    var = g_variant_new_variant(str);
+    props[nprops++] = g_variant_new_dict_entry(key, var);
+    // get contact by phone number
+    std::string contact;
+    getContactByPhoneNumber(phoneNumber, contact);
+    key = g_variant_new_string("contact");
+    str = g_variant_new_string(contact.c_str());
+    var = g_variant_new_variant(str);
+    props[nprops++] = g_variant_new_dict_entry(key, var);
+
+    GVariant *array = g_variant_new_array(G_VARIANT_TYPE("{sv}"), props, nprops);
+
+    GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("(a{sv})"));
+    g_variant_builder_add_value(builder, array);
+    GVariant *args = g_variant_builder_end(builder);
+    g_variant_builder_unref(builder);
+
+    g_dbus_connection_emit_signal( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                   NULL,
+                                   PHONE_OBJ_PATH,
+                                   PHONE_IFACE,
+                                   "CallChanged",
+                                   args,
+                                   NULL);
+}
+
+bool Phone::storeSelectedRemoteDeviceMAC(const std::string &btAddress) {
+
+    char *home = ::getenv("HOME");
+    if(!home)
+        return false;
+
+    char fileName[32];
+    snprintf(fileName, sizeof(fileName), "%s/.phoned", home);
+
+    LoggerD("storing MAC address " << btAddress << " to " << fileName);
+
+    std::ofstream cfgfile (fileName);
+    if (cfgfile.is_open())
+    {
+        cfgfile << btAddress;
+        cfgfile.close();
+
+        return true;
+    }
+
+    return false;
+}
+
+bool Phone::readSelectedRemoteDeviceMAC(std::string &btAddress) {
+    char *home = ::getenv("HOME");
+    if(!home)
+        return false;
+
+    char fileName[32];
+    snprintf(fileName, sizeof(fileName), "%s/.phoned", home);
+
+    std::ifstream cfgfile (fileName);
+    if (cfgfile.is_open()) {
+        getline(cfgfile, btAddress); // no need to validate MAC, - it's done, when MAC is stored
+        LoggerD("Re-stored MAC address " << btAddress << " from " << fileName);
+        cfgfile.close();
+        return true;
+    }
+
+    return false;
+}
+
+} // PhoneD
+
diff --git a/src/phone.h b/src/phone.h
new file mode 100644 (file)
index 0000000..f5b147b
--- /dev/null
@@ -0,0 +1,187 @@
+#ifndef PHONE_H_
+#define PHONE_H_
+
+#include <gio/gio.h>
+
+#include "connman.h"
+#include "bluez.h"
+#include "ofono.h"
+#include "obex.h"
+#include "utils.h"
+
+namespace PhoneD {
+
+/**
+ * \brief A namespace for all classes used to construct \b phoned daemon.
+ * @namespace PhoneD
+ */
+
+/**
+ * @defgroup phoned phoned daemon
+ * \brief Phoned daemon to provide a single point to access phone functionality.
+ *
+ * A module to provide a signle point to access phone functionality. It provides a phoned daemon, which registers itself on D-Bus as \b org.tizen.phone service and exports phone specific methods on D-Bus, as well as emits a phone specific signals on the bus. See Phone for more details.
+ *
+ * @addtogroup phoned
+ * @{
+ */
+
+/*! \class PhoneD::Phone
+ *  \brief Class which is utilizing ConnMan, Bluez, OFono and Obex D-Bus services to be used for operations with a phone.
+ *
+ * Class which is utilizing ConnMan, Bluez, OFono and Obex D-Bus services and in controlled way exports them via D-Bus to be used by WRT plugin @link wrt-plugins-ivi-phone wrt-plugins-ivi-phone @endlink to provide access to phone functionality from Web application. \n
+ * In the construction phase, it requests to get own name on the D-Bus. Once the bus acquired callback is called, it starts setup procedure. It registers a service \b org.tizen.phone on D-Bus, registers Bluez agent for BT authentication and finaly starts OFono (selects and activates a modem) and Obex (creates a session) instances. ConnMan instance is used for initial powering up BT technology. Bluez is used to subscribe for BT specific notifications, such as notifications about \b Powered state of BT, notifications about paired and selected device being removed/unpaired, and many more.
+ *
+ * When it registers a service on D-Bus, it exports the following methods on \b org.tizen.Phone interface:
+ * <ul>
+ * <li> \b SelectRemoteDevice ( \a \b address ) Selects a remote device/phone, to which the phone operations should be performed at. Will emit \b RemoteDeviceSelected signal, once the device gets selected. </li>
+ *     <ul>
+ *     <li> \a \b address [in] \b 's' MAC address of a remote device to be selected. </li>
+ *     </ul>
+ *
+ * <li> \b GetSelectedRemoteDevice ( \a \b address ) Gets a currently selected remote device used for phone operations. It returns \b "" if no device is currently selected. </li>
+ *     <ul>
+ *     <li> \a \b address [out] \b 's' MAC address of selected remote device. </li>
+ *     </ul>
+ *
+ * <li> \b UnselectRemoteDevice () Unselects selected remote device. Will emit \b RemoteDeviceSelected signal with an address argument \b "". </li>
+ *
+ * <li> \b Dial ( \a \b number ) Dials a given phone number. </li>
+ *     <ul>
+ *     <li> \a \b number [in] \b 's' A phone number to dial. </li>
+ *     </ul>
+ *
+ * <li> \b Answer () Answers an incoming phone call. </li>
+ *
+ * <li> \b Mute ( \a \b muted ) Mutes/unmutes the active phone call. </li>
+ *     <ul>
+ *     <li> \a \b muted [in] \b 'b' Specifies whether to mute, or unmute the call. </li>
+ *     </ul>
+ *
+ * <li> \b Hangup () A method to decline incoming call, or hangup the active one. </li>
+ *
+ * <li> \b ActiveCall ( \a \b call ) Gets the active phone call in JSON format, or \b {} when there is no active call. </li>
+ *     <ul>
+ *     <li> \a \b call [out] \b 'a{sv}' An active call in JSON format. </li>
+ *     </ul>
+ *
+ * <li> \b Synchronize () Synchronizes PB data from the phone (Contacts, CallHistory).
+ *
+ * <li> \b GetContacts ( \a \b count, \a \b contacts ) Gets \a \b count first contacts in \b tizen.Contact JSON format, or \b [] when the data are not synchronized, or there are no contacts on the remote device. </li>
+ *     <ul>
+ *     <li> \a \b count [in] \b 'u' A number to specify how many first contacts should be returned. \a \b 0 means to return all contacts. </li>
+ *     <li> \a \b contacts [out] \b 's' Returned \a \b count countacts in \b tizen.Contact JSON format. </li>
+ *     </ul>
+ *
+ * <li> \b GetCallHistory ( \a \b count, \a \b calls ) Gets \a \b count latest call entries from the history in \b tizen.CallHistoryEntry JSON format, or \b [] when the data are not synchronized, or there are no calls in the history on the remote device. </li>
+ *     <ul>
+ *     <li> \a \b count [in] \b 'u' A number to specify how many latest call entries should be returned. </li>
+ *     <li> \a \b calls [out] \b 's' Returned latest \a \b count call entries in \b tizen.CallHistoryEntry JSON format. </li>
+ *     </ul>
+ *
+ * </ul>
+
+ * And emits the following signals:
+ * <ul>
+ * <li> \b RemoteDeviceSelected ( \a \b device ) A signal which is emitted when the selected remote device has changed, eg. the selected remote device has been selected, or when the remote device has been unselected, eg. due to connection lost with the remote device, or as a result of calling \b UnselectRemoteDevice method.
+ *     <ul>
+ *     <li> \a \b device [in] \b 's' A device in JSON format describing the change. </li>
+ *     </ul>
+ *
+ * <li> \b ContactsChanged () A signal which is emitted when there is a change in synchronized contacts.
+ *
+ * <li> \b CallHistoryChanged () A signal which is emitted when there is a change in synchronized call history, eg. due to made/received phone call.
+ *
+ * <li> \b CallHistoryEntryAdded ( \a \b call ) A signal which is emitted when a call has been added to the call history, eg. due to made/received phone call.
+ *     <ul>
+ *     <li> \a \b call \b 's' A call in \b tizen.CallHistoryEntry fromat that has been added to the call history.
+ *     </ul>
+ *
+ * <li> \b CallChanged ( \a \b call ) A signal which is emitted when there is a change in the active call. It indicates incoming, as well as the individual states that the call may go through, like "alerting", "active", "disconnected".
+ *     <ul>
+ *     <li> \a \b call \b '(a{sv})' A call object which specifies \a \b state of the call, \a \b line \a \b identifier (the phone number) and the \a \b contact if there is a match with the \a \b line_id.
+ *     </ul>
+ * </ul>
+ */
+class Phone : public ConnMan, public Bluez, public OFono, public Obex {
+    public:
+        /**
+         * A default constructor which initializes individual services and sets BT adapter "Powered" ON. It requests a name \b org.tizen.phone to register itself on D-Bus. Once the bus acquired callback is called, it registers Bluez agent for BT authentification and starts OFono and Obex services.
+         */
+        Phone();
+
+        /**
+         * A destructor.
+         * Unregisters the service from D-Bus and destructs the object.
+         */
+        virtual ~Phone();
+
+    private:
+        void setup(); // called once the bus is acquired
+        void startServices(); // starts OFono/Obex services
+        void stopServices(); // stops OFono/Obex services
+        static void busAcquiredCb(GDBusConnection *gdbus, const gchar *name, gpointer data);
+        static void nameAcquiredCb(GDBusConnection *gdbus, const gchar *name, gpointer data);
+        static void nameLostCb(GDBusConnection *gdbus, const gchar *name, gpointer data);
+        static void handleMethodCall( GDBusConnection       *connection,
+                                      const gchar           *sender,
+                                      const gchar           *object_path,
+                                      const gchar           *interface_name,
+                                      const gchar           *method_name,
+                                      GVariant              *parameters,
+                                      GDBusMethodInvocation *invocation,
+                                      gpointer               user_data);
+
+        // Bluez stuff
+        virtual void adapterPowered(bool value); // to handle "Powered" property changed on ADAPTER, due to eg. RF-kill
+        virtual void defaultAdapterAdded();
+        virtual void defaultAdapterRemoved();
+        virtual void deviceCreated(const char *device);
+        virtual void deviceRemoved(const char *device);
+        // OFono stuff
+        virtual void callChanged(const char* state, const char* phoneNumber);
+        virtual void modemAdded(std::string &modem); // MAC address of modem ... from ModemAdded DBUS
+        virtual void modemPowered(bool powered);
+        virtual void setModemPoweredFailed(const char *err);
+        // Obex stuff
+        virtual void contactsChanged();
+        virtual void callHistoryChanged();
+        virtual void pbSynchronizationDone();
+        virtual void createSessionFailed(const char *err);
+        virtual void createSessionDone(const char *s);
+        virtual void removeSessionDone();
+        // NOTE: entry is valid only for the time of call this method
+        virtual void callHistoryEntryAdded(std::string &entry);
+        // active transfer is stalled - ??? session is down ???
+        virtual void transferStalled();
+        static gboolean delayedSyncCallHistory(gpointer user_data);
+
+        // function to store MAC address of selected remote device in persistent storage/file
+        // returns bool indicating result of the operation: false = failed, true = OK
+        bool storeSelectedRemoteDeviceMAC(const std::string &btAddress);
+        // function to read MAC address of selected remote device from persistent storage/file
+        // remote address is returned via 'btAddress' argument to the function
+        // returns bool indicating result of the operation: false = failed, true = OK
+        bool readSelectedRemoteDeviceMAC(std::string &btAddress);
+
+        // sets selected remote device MAC address
+        void setSelectedRemoteDevice(std::string &btAddress);
+
+    private:
+        bool mAdapterPowered; // variable to hold the state of Default adpater - not to try to 'CreateSession' 'cause it will result in Powering up the device, which is unwanted behavior
+        std::string mWantedRemoteDevice; // a remote device that is requested to be 'Selected'
+        std::string mSelectedRemoteDevice; // selected remote device - if initialization of OFono/Obex is successfull
+        bool mPBSynchronized; // indicating whether PB data are synchronized, or not
+        std::vector<CtxCbData*> mSynchronizationDoneListeners; // listeners to be notified once the synchronization finishes
+        guint mNameRequestId;
+        guint mRegistrationId;
+        GDBusNodeInfo *mIntrospectionData;
+        GDBusInterfaceVTable mIfaceVTable;
+};
+
+} // PhoneD
+
+#endif /* PHONE_H_ */
+
+/** @} */
+
diff --git a/src/phoned.cpp b/src/phoned.cpp
new file mode 100644 (file)
index 0000000..39c8f56
--- /dev/null
@@ -0,0 +1,32 @@
+
+#include <stdio.h>
+#include <string.h>
+#include <glib.h>
+#include "phone.h"
+
+#include "Logger.h"
+
+GMainLoop *loop = NULL;
+
+int main (int argc, char *argv[])
+{
+    PhoneD::Phone *phone = new PhoneD::Phone();
+    if(!phone) {
+        LoggerD("Error initializing Phone Service");
+        return -1;
+    }
+
+    LoggerD("Starting GMainLoop");
+    loop = g_main_loop_new(NULL, TRUE);
+    if(!loop) {
+        LoggerD("Failed to create GMainLoop");
+        return -2;
+    }
+    g_main_loop_run(loop);
+    g_main_loop_unref(loop);
+
+    delete phone;
+
+    return 0;
+}
+
diff --git a/src/utils.cpp b/src/utils.cpp
new file mode 100644 (file)
index 0000000..5f6e950
--- /dev/null
@@ -0,0 +1,138 @@
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <algorithm>
+
+#include "utils.h"
+
+#include "Logger.h"
+
+namespace PhoneD {
+
+GQuark g_phone_error_quark(void)
+{
+    return g_quark_from_static_string("g-phone-error-quark");
+}
+
+std::map<std::string, guint> Utils::mSubsIdsMap;
+
+
+bool Utils::setSignalListener(GBusType type, const char *service,
+                              const char *iface, const char *path,
+                              const char *name, GDBusSignalCallback cb,
+                              void *data)
+{
+    // key is of form: BUS_TYPE:SERVICE:IFACE:OBJ_PATH:SIGNAL
+    const char *bus_type = (type == G_BUS_TYPE_SYSTEM) ? "SYSTEM" : (type == G_BUS_TYPE_SESSION) ? "SESSION" : "";
+    std::string key = std::string(bus_type) + ":" + service + ":" + iface + ":" + path + ":" + name;
+    LoggerD("subscribing for DBUS signal: " << key);
+
+    // only one listener (subscription on DBUS signal) allowed for specific signal
+    if(mSubsIdsMap[key] <= 0) { // not yet subscribed for DBUS signal
+        guint id = 0;
+        id = g_dbus_connection_signal_subscribe(g_bus_get_sync(type, NULL, NULL),
+                                                service,
+                                                iface,
+                                                name,
+                                                path,
+                                                NULL,
+                                                G_DBUS_SIGNAL_FLAGS_NONE,
+                                                cb,
+                                                data,
+                                                NULL);
+
+        if(id == 0) {
+            LoggerE("Failed to subscribe to: " << key);
+            return false;
+        }
+
+        mSubsIdsMap[key] = id;
+
+        return true; // success
+    }
+
+    // already subscribed for DBUS signal
+    return false;
+}
+
+void Utils::removeSignalListener(GBusType type, const char *service,
+                                 const char* iface, const char* path,
+                                 const char* name)
+{
+
+    // key is of form: BUS_TYPE:SERVICE:IFACE:OBJ_PATH:SIGNAL
+    const char *bus_type = (type == G_BUS_TYPE_SYSTEM) ? "SYSTEM" : (type == G_BUS_TYPE_SESSION) ? "SESSION" : "";
+    std::string key = std::string(bus_type) + ":" + service + ":" + iface + ":" + path + ":" + name;
+    LoggerD("unsubscribing from DBUS signal: " << key);
+
+    std::map<std::string, guint>::iterator iter = mSubsIdsMap.begin();
+    while(iter != mSubsIdsMap.end()) {
+        if(!strcmp(key.c_str(), (*iter).first.c_str())) {
+            g_dbus_connection_signal_unsubscribe(g_bus_get_sync(type, NULL, NULL), (*iter).second);
+            mSubsIdsMap.erase(iter);
+            break;
+        }
+        iter++;
+    }
+}
+
+// makes AABBCCDDEEFF from AA:BB:CC:DD:EE:FF
+bool makeRawMAC(std::string &address) {
+    address.erase(std::remove_if(address.begin(), address.end(), isnxdigit), address.end());
+    return (address.length() == 12);
+}
+
+// makes AA:BB:CC:DD:EE:FF from AABBCCDDEEFF
+bool makeMACFromRawMAC(std::string &address) {
+    if(address.length() == 12) {
+        address.insert(10,":");
+        address.insert(8,":");
+        address.insert(6,":");
+        address.insert(4,":");
+        address.insert(2,":");
+        return true;
+    }
+    return false;
+}
+
+// makes AA:BB:CC:DD:EE:FF from device path, eg: /org/bluez/223/hci0/dev_AA_BB_CC_DD_EE_FF
+bool makeMACFromDevicePath(std::string &device) {
+    size_t idx = device.find( "dev_" ) + 4;
+    device = device.substr (idx, device.length()-idx);
+    device.erase(std::remove_if(device.begin(), device.end(), isnxdigit), device.end());
+    return makeMACFromRawMAC(device);
+}
+
+// don't use a reference for address - we need a copy
+bool isValidMAC(std::string address) {
+    // expect MAC address in form: AA:BB:CC:DD:EE:FF
+    if( (address[2] != ':') || (address[5] != ':') || (address[8] != ':') ||
+        (address[11] != ':') || (address[14] != ':'))
+        return false;
+
+    // remove all non HEX digits
+    address.erase(std::remove_if(address.begin(), address.end(), isnxdigit), address.end());
+
+    // after removing, there should be exactly 12 remaining characters/digits
+    return (address.length() == 12);
+}
+
+int isndigit(int x) {
+    return !std::isdigit(x);
+}
+
+// will remove all non-digit characters, except leading '+'
+void formatPhoneNumber(std::string &phoneNumber) {
+    bool leadingPlusSign = (phoneNumber[0] == '+');
+    phoneNumber.erase(std::remove_if(phoneNumber.begin(), phoneNumber.end(), isndigit), phoneNumber.end());
+    if(leadingPlusSign)
+        phoneNumber.insert(0, "+");
+}
+
+int isnxdigit(int x) {
+    return !std::isxdigit(x);
+}
+
+} // PhoneD
+
diff --git a/src/utils.h b/src/utils.h
new file mode 100644 (file)
index 0000000..2dbbc5f
--- /dev/null
@@ -0,0 +1,162 @@
+
+#ifndef UTILS_H_
+#define UTILS_H_
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gio/gio.h>
+#include <string>
+#include <map>
+
+namespace PhoneD {
+
+/**
+ * @addtogroup phoned
+ * @{
+ */
+
+/**
+ * @fn bool makeMACFromDevicePath(std::string &device)
+ * Formatter function. Extracts MAC address from device path, eg. from \b /org/bluez/223/hci0/dev_AA_BB_CC_DD_EE_FF makes \b AA:BB:CC:DD:EE:FF.
+ * @param[in,out] device The path of the device.
+ * @return True if the formatted MAC is valid.
+ */
+bool makeMACFromDevicePath(std::string &device);
+
+/**
+ * @fn bool makeMACFromRawMAC(std::string &address)
+ * Formatter function. Formats given MAC address in RAW format, eg. \b AABBCCDDEEFF into 'colon' format: \b AA:BB:CC:DD:EE:FF.
+ * @param[in,out] address MAC address to be formatted.
+ * @return True if the formatted MAC is valid.
+ */
+bool makeMACFromRawMAC(std::string &address);
+
+/**
+ * @fn bool makeRawMAC(std::string &address)
+ * Formatter function. Formats given MAC address in 'colon' format, eg. \b AA:BB:CC:DD:EE:FF into RAW format: \b AABBCCDDEEFF.
+ * @param[in,out] address MAC address to be formatted.
+ * @return True if the formatted MAC is valid.
+ */
+bool makeRawMAC(std::string &address);
+
+/**
+ * @fn void formatPhoneNumber(std::string &phoneNumber)
+ * Formatter function. Formats given phone number to one specific format, ie. it removes all non-HEX digit characters and replaces leadin '00' by '+' character.
+ * \li 00421123456  -> +421123456
+ * \li +421-123-456 -> +421123456
+ * @param[in,out] phoneNumber Phone number to be formatted.
+ */
+void formatPhoneNumber(std::string &phoneNumber);
+
+/**
+ * @fn bool isValidMAC(std::string address)
+ * Function to check wthether given MAC address is valid or not. It expects MAC address to be in 'full' format, eg. \b AA:BB:CC:DD:EE:FF. It doesn't support abbreviated MAC addresses, eg. \b AA:B:C:DD:EE:F. First it check whether ':' are at correct positions (2,5,8,11,14), then removes all non-HEX characters from the string and checks its length. If the length is 12, the given MAC address is valid.
+ * @param[in] address MAC address to perform validation on.
+ * @return \b True, if the MAC address is valid, otherwise returns \b false.
+ */
+bool isValidMAC(std::string address);
+
+/**
+ * int isndigit(int x)
+ * The function to check if the input character is not hexa digit, ie. is not any of: 0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F
+ * @param [in] x Input character to check.
+ * @return The function returns \b true if x is not hexa digit, otherwise it returns \b false.
+ */
+int isndigit(int x);
+
+/**
+ * @fn int isnxdigit(int x)
+ * The function to check if the input character is not digit, ie. is not any of: 0 1 2 3 4 5 6 7 8 9
+ * @param [in] x Input character to check.
+ * @return The function returns \b true if x is not digit, otherwise it returns \b false.
+ */
+int isnxdigit(int x);
+
+/**
+ * A define for g_phone_error_quark().
+ */
+#define G_PHONE_ERROR  g_phone_error_quark()
+
+/**
+ * @fn GQuark g_phone_error_quark(void)
+ * Creates a <a href="https://developer.gnome.org/glib/2.39/glib-Quarks.html">Quark</a> to be used to throw an error on D-Bus. The Quark is created from string \b "g-phone-error-quark".
+ * @return A created Quark.
+ */
+GQuark g_phone_error_quark(void);
+
+/*! \class PhoneD::Utils
+ *  \brief Utility class providing helper functions for operating with Phone.
+ */
+class Utils {
+    public:
+        /**
+         * A default constructor.
+         */
+        Utils () {}
+
+        /**
+         * A destructor.
+         */
+        virtual ~Utils () {}
+
+        /**
+         * A helper method to subscribe for DBUS siganl.
+         * @param[in] type A type of the bus that the signal should be subscribed on. See <a href="https://developer.gnome.org/gio/2.35/GDBusConnection.html#GBusType">GBusType</a> documentation.
+         * @param[in] service Service name to match on (unique or well-known name).
+         * @param[in] iface D-Bus interface name to match on.
+         * @param[in] path Object path to match on.
+         * @param[in] name D-Bus signal name to match on.
+         * @param[in] cb Callback to invoke when there is a signal matching the requested data. See <a href="https://developer.gnome.org/gio/2.35/GDBusConnection.html#GDBusSignalCallback">GDBusSignalCallback</a> documentation.
+         * @param[in] data User data to pass to \b cb.
+         * @return a bool indicating success of setting listener
+         */
+        static bool setSignalListener(GBusType type, const char *service, const char *iface,
+                                      const char *path, const char *name, GDBusSignalCallback cb,
+                                      void *data);
+        /**
+         * A helper method to unsubscribe from DBUS siganl.
+         * @param[in] type A type of the bus that the signal is subscribed to. See <a href="https://developer.gnome.org/gio/2.35/GDBusConnection.html#GBusType">GBusType</a> documentation.
+         * @param[in] service Service name to match on (unique or well-known name).
+         * @param[in] iface D-Bus interface name to match on.
+         * @param[in] path Object path to match on.
+         * @param[in] name D-Bus signal name to match on.
+         */
+        static void removeSignalListener(GBusType type, const char *service, const char *iface,
+                                         const char *path,   const char *name);
+
+    private: // viriables
+        static std::map<std::string, guint> mSubsIdsMap; /*! A map that holds ids of subscriptions to DBUS signals */
+};
+
+/*! \class PhoneD::CtxCbData
+ *  \brief A class to store data for asynchronous operation.
+ *
+ *  A class to store data for asynchronous operation: context, callback function and two pointers to user data.
+ */
+class CtxCbData {
+    public:
+        /**
+         * A default constructor, allowing to pass the data in the construction phase.
+         * @param[in] ctx A pointer to the instance of an object that the asynchronous call was made on.
+         * @param[in] cb A pointer to store callback method. It is usually used when there is a need for multiple level of aynchronous calls.
+         * @param[in] data1 User data to pass to \b cb.
+         * @param[in] data2 User data to pass to \b cb.
+         */
+        CtxCbData(void *ctx, void *cb, void *data1, void *data2) {
+            this->ctx = ctx;
+            this->cb = cb;
+            this->data1 = data1;
+            this->data2 = data2;
+        }
+        void *ctx;     /*!< to store current context */
+        void *cb;      /*!< to store pointer to callback function */
+        void *data1;   /*!< to store user data 1 */
+        void *data2;   /*!< to store user data 2 */
+};
+
+} // PhoneD
+
+#endif /* UTILS_H_ */
+
+/** @} */
+
diff --git a/test/Makefile b/test/Makefile
new file mode 100644 (file)
index 0000000..a7009f4
--- /dev/null
@@ -0,0 +1,31 @@
+
+CC=g++
+OBJ_DIR := .obj
+
+CPP_FILES := $(wildcard ./*.cpp)
+OBJ_FILES := $(addprefix $(OBJ_DIR)/,$(notdir $(CPP_FILES:.cpp=.o)))
+
+GIO_LIBS=`pkg-config --libs gio-2.0`
+GIO_CFLAGS=`pkg-config --cflags gio-2.0`
+DBUS_LIBS=`pkg-config --libs dbus-1`
+DBUS_CFLAGS=`pkg-config --cflags dbus-1`
+GLIB_LIBS=`pkg-config --libs glib-2.0`
+GLIB_CFLAGS=`pkg-config --cflags glib-2.0`
+CXX_CFLAGS = -std=c++11
+
+all: phone
+
+phone: $(OBJ_FILES)
+       $(CC) $(GIO_LIBS) $(DBUS_LIBS) $(GLIB_LIBS) -pthread -o $@ $^
+
+$(OBJ_DIR)/%.o: ./%.cpp $(OBJ_DIR)
+       $(CC) $(GIO_CFLAGS) $(DBUS_CFLAGS) $(GLIB_CFLAGS) $(CXX_CFLAGS) -c -o $@ $<
+
+$(OBJ_DIR):
+       test -d $@ || mkdir $@
+
+clean:
+       rm -f $(OBJ_DIR)/*.o phone
+
+.PHONY: all clean phone
+
diff --git a/test/main.cpp b/test/main.cpp
new file mode 100644 (file)
index 0000000..46c4972
--- /dev/null
@@ -0,0 +1,791 @@
+
+#include <stdio.h>
+#include <string.h>
+#include <gio/gio.h>
+
+#include "../src/Logger.h"
+
+#define TIZEN_PREFIX            "org.tizen"
+#define PHONE_SERVICE           TIZEN_PREFIX ".phone"
+#define PHONE_IFACE             TIZEN_PREFIX ".Phone"
+#define PHONE_OBJ_PATH          "/"
+
+//#define BT_ADDRESS "84:7E:40:31:58:42"
+#define BT_ADDRESS "6C:A7:80:C8:F8:50" //pali
+//#define BT_ADDRESS ""
+
+void * command_handler(void *data);
+
+char *_device = NULL; // to remember device for pairing
+
+static void getBluetoothPowered();
+static void selectRemoteDevice(const char* bt_address);
+static void getSelectedRemoteDevice();
+static void unselectRemoteDevice();
+static void invokeCall(const char* phone_number);
+static void answerCall();
+static void muteCall(bool mute);
+static void hangupCall();
+static void activeCall();
+static void synchronize();
+static void getContacts();
+static void getCallHistory();
+static void restart();
+static void pairDevice(const char* bt_address);
+
+static void handleSignal( GDBusConnection *connection,
+                          const gchar *sender_name,
+                          const gchar *object_path,
+                          const gchar *interface_name,
+                          const gchar *signal_name,
+                          GVariant *parameters,
+                          gpointer user_data);
+
+GMainLoop *loop = NULL;
+
+int main (int argc, char *argv[])
+{
+    pthread_t thread;
+    pthread_create(&thread, NULL, command_handler, NULL);
+
+    g_dbus_connection_signal_subscribe( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL,NULL),
+                                        PHONE_SERVICE,
+                                        PHONE_IFACE,
+                                        "BluetoothPowered",
+                                        NULL,
+                                        NULL,
+                                        G_DBUS_SIGNAL_FLAGS_NONE,
+                                        handleSignal,
+                                        NULL,//this,
+                                        NULL);
+
+    g_dbus_connection_signal_subscribe( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL,NULL),
+                                        PHONE_SERVICE,
+                                        PHONE_IFACE,
+                                        "CallChanged",
+                                        NULL,
+                                        NULL,
+                                        G_DBUS_SIGNAL_FLAGS_NONE,
+                                        handleSignal,
+                                        NULL,//this,
+                                        NULL);
+
+    g_dbus_connection_signal_subscribe( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL,NULL),
+                                        PHONE_SERVICE,
+                                        PHONE_IFACE,
+                                        "CallHistoryEntryAdded",
+                                        NULL,
+                                        NULL,
+                                        G_DBUS_SIGNAL_FLAGS_NONE,
+                                        handleSignal,
+                                        NULL,//this,
+                                        NULL);
+
+    g_dbus_connection_signal_subscribe( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL,NULL),
+                                        PHONE_SERVICE,
+                                        PHONE_IFACE,
+                                        "ContactsChanged",
+                                        NULL,
+                                        NULL,
+                                        G_DBUS_SIGNAL_FLAGS_NONE,
+                                        handleSignal,
+                                        NULL,//this,
+                                        NULL);
+
+    g_dbus_connection_signal_subscribe( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL,NULL),
+                                        PHONE_SERVICE,
+                                        PHONE_IFACE,
+                                        "CallHistoryChanged",
+                                        NULL,
+                                        NULL,
+                                        G_DBUS_SIGNAL_FLAGS_NONE,
+                                        handleSignal,
+                                        NULL,//this,
+                                        NULL);
+
+    g_dbus_connection_signal_subscribe( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL,NULL),
+                                        PHONE_SERVICE,
+                                        PHONE_IFACE,
+                                        "RemoteDeviceSelected",
+                                        NULL,
+                                        NULL,
+                                        G_DBUS_SIGNAL_FLAGS_NONE,
+                                        handleSignal,
+                                        NULL,//this,
+                                        NULL);
+
+    g_dbus_connection_signal_subscribe( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL,NULL),
+                                        PHONE_SERVICE,
+                                        PHONE_IFACE,
+                                        "PairingResult",
+                                        NULL,
+                                        NULL,
+                                        G_DBUS_SIGNAL_FLAGS_NONE,
+                                        handleSignal,
+                                        NULL,//this,
+                                        NULL);
+
+    g_dbus_connection_signal_subscribe( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL,NULL),
+                                        PHONE_SERVICE,
+                                        PHONE_IFACE,
+                                        "RequestPinCode",
+                                        NULL,
+                                        NULL,
+                                        G_DBUS_SIGNAL_FLAGS_NONE,
+                                        handleSignal,
+                                        NULL,//this,
+                                        NULL);
+
+    g_dbus_connection_signal_subscribe( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL,NULL),
+                                        PHONE_SERVICE,
+                                        PHONE_IFACE,
+                                        "RequestPasskey",
+                                        NULL,
+                                        NULL,
+                                        G_DBUS_SIGNAL_FLAGS_NONE,
+                                        handleSignal,
+                                        NULL,//this,
+                                        NULL);
+
+    g_dbus_connection_signal_subscribe( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL,NULL),
+                                        PHONE_SERVICE,
+                                        PHONE_IFACE,
+                                        "RequestConfirmation",
+                                        NULL,
+                                        NULL,
+                                        G_DBUS_SIGNAL_FLAGS_NONE,
+                                        handleSignal,
+                                        NULL,//this,
+                                        NULL);
+
+    loop = g_main_loop_new(NULL, TRUE);
+    g_main_loop_run(loop);
+
+    return 0;
+}
+
+void * command_handler(void *data) {
+    LoggerD("START: command_handler");
+
+    char command[32];
+    char argument[32];
+    char input[128];
+    while(true) {
+        LoggerD("Waiting for command:");
+        fgets(input, sizeof(input), stdin);
+        int validargs = sscanf(input, "%s %s", command, argument);
+        if(validargs == 1) {
+            if(!strncmp(command, "exit", 4) || !strncmp(command, "quit", 4) || !strncmp(command, "q", 1))
+                break;
+            else if(!strncmp(command, "bt", 2))
+                getBluetoothPowered();
+            else if(!strncmp(command, "sel1", 4))
+                selectRemoteDevice(BT_ADDRESS);
+            else if(!strncmp(command, "sel2", 4))
+                selectRemoteDevice("00:A7:80:C8:F8:00");
+            else if(!strncmp(command, "uns", 3))
+                unselectRemoteDevice();
+            else if(!strncmp(command, "gsd", 3))
+                getSelectedRemoteDevice();
+            else if(!strncmp(command, "dial", 4))
+                invokeCall("+421918362985");
+            else if(!strncmp(command, "answer", 6))
+                answerCall();
+            else if(!strncmp(command, "hangup", 6))
+                hangupCall();
+            else if(!strncmp(command, "state", 5))
+                activeCall();
+            else if(!strncmp(command, "synchronize", 11))
+                synchronize();
+            else if(!strncmp(command, "contacts", 8))
+                getContacts();
+            else if(!strncmp(command, "history", 7))
+                getCallHistory();
+            else if(!strncmp(command, "restart", 7))
+                restart();
+            else if(!strncmp(command, "pair1", 5))
+                pairDevice(BT_ADDRESS);
+            else if(!strncmp(command, "pair2", 5))
+                pairDevice("INVALID_MAC");
+            else if(!strncmp(command, "help", 7)) {
+                LoggerD("Commands:");
+                LoggerD("\tdial");
+                LoggerD("\tanswer");
+                LoggerD("\tmute");
+                LoggerD("\thangup");
+                LoggerD("\tstate");
+                LoggerD("\tsynchronize");
+                LoggerD("\tcontacts");
+                LoggerD("\thistory");
+                LoggerD("\trestart");
+            }
+            else
+                LoggerE("Invalid command: " << command);
+        }
+        else if(validargs == 2) {
+            if(!strncmp(command, "bt", 2)) {
+                bool powered = !strncmp(argument, "on", 2);
+                LoggerD("setting BT Powered to: " << powered);
+                GError *error = NULL;
+                g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                             PHONE_SERVICE,
+                                             PHONE_OBJ_PATH,
+                                             PHONE_IFACE,
+                                             "SetBluetoothPowered",
+                                             g_variant_new("(v)", g_variant_new_boolean(powered)),
+                                             NULL,
+                                             G_DBUS_CALL_FLAGS_NONE,
+                                             -1,
+                                             NULL,
+                                             &error);
+                if(error) {
+                    LoggerD("Failed to call 'SetModemPowered': " << error->message);
+                    g_error_free(error);
+                }
+            }
+            if(!strncmp(command, "pin", 3)) {
+                LoggerD("entered pin: " << argument);
+                GError *error = NULL;
+                g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                             PHONE_SERVICE,
+                                             PHONE_OBJ_PATH,
+                                             PHONE_IFACE,
+                                             "RequestPinCodeReply",
+                                             g_variant_new("(ss)", _device, argument), // floating variants are consumed
+                                             NULL,
+                                             G_DBUS_CALL_FLAGS_NONE,
+                                             -1,
+                                             NULL,
+                                             &error);
+                if(error) {
+                    LoggerD("Failed to call 'RequestPinCodeReply'");
+                    g_error_free(error);
+                }
+            }
+            else if(!strncmp(command, "pass", 4)) {
+                unsigned long passkey = atoi(argument);
+                //LoggerD("entered pass: " << argument);
+                printf("entered pass: %lu\n", passkey);
+                GError *error = NULL;
+                g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                             PHONE_SERVICE,
+                                             PHONE_OBJ_PATH,
+                                             PHONE_IFACE,
+                                             "RequestPasskeyReply",
+                                             g_variant_new("(su)", _device, passkey), // floating variants are consumed
+                                             NULL,
+                                             G_DBUS_CALL_FLAGS_NONE,
+                                             -1,
+                                             NULL,
+                                             &error);
+                if(error) {
+                    LoggerD("Failed to call 'RequestPasskeyReply'");
+                    g_error_free(error);
+                }
+            }
+            else if(!strncmp(command, "conf", 4)) {
+                unsigned long passkey = atoi(argument);
+                bool confirmed = !strncmp(argument, "yes", 3);
+                printf("entered confirmed: %s\n", confirmed?"YES":"NO");
+                GError *error = NULL;
+                g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                             PHONE_SERVICE,
+                                             PHONE_OBJ_PATH,
+                                             PHONE_IFACE,
+                                             "RequestConfirmationReply",
+                                             g_variant_new("(sv)", _device, g_variant_new_boolean(confirmed)),
+                                             NULL,
+                                             G_DBUS_CALL_FLAGS_NONE,
+                                             -1,
+                                             NULL,
+                                             &error);
+                if(error) {
+                    LoggerD("Failed to call 'RequestConfirmationReply'");
+                    g_error_free(error);
+                }
+            }
+            else if(!strncmp(command, "mute", 4)) {
+                bool mute = !strncmp(argument, "on", 2);
+                muteCall(mute);
+            }
+        }
+        else
+            LoggerE("Invalid number of arguments");
+    };
+
+    LoggerD("END: command_handler");
+    g_main_loop_quit(loop);
+}
+
+void invokeCall(const char *phone_number) {
+    LoggerD("entered");
+
+    GError *error = NULL;
+    g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                 PHONE_SERVICE,
+                                 PHONE_OBJ_PATH,
+                                 PHONE_IFACE,
+                                 "Dial",
+                                 g_variant_new("(s)", phone_number), // floating variants are consumed
+                                 NULL,
+                                 G_DBUS_CALL_FLAGS_NONE,
+                                 -1,
+                                 NULL,
+                                 &error);
+    if(error) {
+        LoggerE("Failed to dial number: " << error->message);
+        g_error_free(error);
+    }
+}
+
+void selectRemoteDevice(const char *bt_address) {
+    LoggerD("entered");
+
+    GError *error = NULL;
+    g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                 PHONE_SERVICE,
+                                 PHONE_OBJ_PATH,
+                                 PHONE_IFACE,
+                                 "SelectRemoteDevice",
+                                 g_variant_new("(s)", bt_address), // floating variants are consumed
+                                 NULL,
+                                 G_DBUS_CALL_FLAGS_NONE,
+                                 -1,
+                                 NULL,
+                                 &error);
+
+    if(error) {
+        LoggerE("Failed to request 'SelectRemoteDevice': " << error->message);
+        g_error_free(error);
+    }
+}
+
+void unselectRemoteDevice() {
+    LoggerD("entered");
+
+    GError *error = NULL;
+    g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                 PHONE_SERVICE,
+                                 PHONE_OBJ_PATH,
+                                 PHONE_IFACE,
+                                 "UnselectRemoteDevice",
+                                 NULL,
+                                 NULL,
+                                 G_DBUS_CALL_FLAGS_NONE,
+                                 -1,
+                                 NULL,
+                                 &error);
+
+    if(error) {
+        LoggerE("Failed to request 'UnselectRemoteDevice': " << error->message);
+        g_error_free(error);
+    }
+}
+
+void getSelectedRemoteDevice() {
+    LoggerD("entered");
+
+    GError *error = NULL;
+    GVariant *reply = NULL;
+    reply = g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                         PHONE_SERVICE,
+                                         PHONE_OBJ_PATH,
+                                         PHONE_IFACE,
+                                         "GetSelectedRemoteDevice",
+                                         NULL,
+                                         NULL,
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         -1,
+                                         NULL,
+                                         &error);
+    if(error || !reply) {
+        if(error) {
+            LoggerE("Failed to request GetSelectedRemoteDevice: " << error->message);
+            g_error_free(error);
+        }
+        else if(!reply)
+            LoggerE("reply is null");
+
+        return;
+    }
+
+    char *btAddress = NULL;
+    g_variant_get(reply, "(s)", &btAddress);
+    LoggerD("Selected remote device: " << btAddress);
+
+    g_variant_unref(reply);
+}
+
+void answerCall() {
+    LoggerD("entered");
+
+    GError *error = NULL;
+    g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                 PHONE_SERVICE,
+                                 PHONE_OBJ_PATH,
+                                 PHONE_IFACE,
+                                 "Answer",
+                                 NULL,
+                                 NULL,
+                                 G_DBUS_CALL_FLAGS_NONE,
+                                 -1,
+                                 NULL,
+                                 &error);
+    if(error) {
+        LoggerE("Failed to answer call: " << error->message);
+        g_error_free(error);
+    }
+}
+
+void muteCall(bool mute) {
+    LoggerD("entered");
+
+    GError *error = NULL;
+    g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                 PHONE_SERVICE,
+                                 PHONE_OBJ_PATH,
+                                 PHONE_IFACE,
+                                 "Mute",
+                                 g_variant_new("(b)", mute), // floating variants are consumed
+                                 NULL,
+                                 G_DBUS_CALL_FLAGS_NONE,
+                                 -1,
+                                 NULL,
+                                 &error);
+    if(error) {
+        LoggerE("Failed to mute call: " << error->message);
+        g_error_free(error);
+    }
+}
+
+void hangupCall() {
+    LoggerD("entered");
+
+    GError *error = NULL;
+    g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                 PHONE_SERVICE,
+                                 PHONE_OBJ_PATH,
+                                 PHONE_IFACE,
+                                 "Hangup",
+                                 NULL,
+                                 NULL,
+                                 G_DBUS_CALL_FLAGS_NONE,
+                                 -1,
+                                 NULL,
+                                 &error);
+    if(error) {
+        LoggerE("Failed to hangup call: " << error->message);
+        g_error_free(error);
+    }
+}
+
+void activeCall() {
+    LoggerD("entered");
+
+    GError *error = NULL;
+    GVariant *reply = NULL;
+    reply = g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                         PHONE_SERVICE,
+                                         PHONE_OBJ_PATH,
+                                         PHONE_IFACE,
+                                         "ActiveCall",
+                                         NULL,
+                                         NULL,
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         -1,
+                                         NULL,
+                                         &error);
+    if(error || !reply) {
+        if(error) {
+            LoggerE("Failed to get active call: " << error->message);
+            g_error_free(error);
+        }
+        else if(!reply)
+            LoggerE("reply is null");
+
+        return;
+    }
+
+    GVariantIter *iter;
+    g_variant_get(reply, "(a{sv})", &iter);
+    const char *key;
+    GVariant *value;
+    while(g_variant_iter_next(iter, "{sv}", &key, &value)) {
+        if(!strcmp(key, "state")) {
+            const char *state = g_variant_get_string(value, NULL);
+            LoggerD("state: " << state);
+        }
+        else if(!strcmp(key, "line_id")) {
+            const char *line_id = g_variant_get_string(value, NULL);
+            LoggerD("line_id: " << line_id);
+        }
+        else if(!strcmp(key, "contact")) {
+            const char *contact = g_variant_get_string(value, NULL);
+            printf("contact: %s\n", contact);
+        }
+    }
+
+    g_variant_unref(reply);
+}
+
+void synchronize() {
+    LoggerD("entered");
+
+    GError *error = NULL;
+    g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                 PHONE_SERVICE,
+                                 PHONE_OBJ_PATH,
+                                 PHONE_IFACE,
+                                 "Synchronize",
+                                 NULL,
+                                 NULL,
+                                 G_DBUS_CALL_FLAGS_NONE,
+                                 -1,
+                                 NULL,
+                                 &error);
+    if(error) {
+        LoggerE("Failed to synchronize data with the phone: " << error->message);
+        g_error_free(error);
+    }
+}
+
+void getBluetoothPowered() {
+    LoggerD("entered");
+
+    GError *error = NULL;
+    GVariant *reply = NULL;
+    reply = g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                         PHONE_SERVICE,
+                                         PHONE_OBJ_PATH,
+                                         PHONE_IFACE,
+                                         "GetBluetoothPowered",
+                                         NULL,
+                                         NULL,
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         -1,
+                                         NULL,
+                                         &error);
+    if(error || !reply) {
+        if(error) {
+            LoggerE("Failed to call 'GetBluetoothPowered': " << error->message);
+            g_error_free(error);
+        }
+        else if(!reply)
+            LoggerE("reply is null");
+
+        return;
+    }
+
+    GVariant *value = NULL;
+    g_variant_get(reply, "(v)", &value);
+    bool powered = g_variant_get_boolean(value);
+
+    LoggerD("Bluetooth powered: " << (powered?"ON":"OFF"));
+
+    g_variant_unref(reply);
+}
+
+void getContacts() {
+    LoggerD("entered");
+
+    GError *error = NULL;
+    GVariant *reply = NULL;
+    reply = g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                         PHONE_SERVICE,
+                                         PHONE_OBJ_PATH,
+                                         PHONE_IFACE,
+                                         "GetContacts",
+                                         g_variant_new("(u)", 10),
+                                         NULL,
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         -1,
+                                         NULL,
+                                         &error);
+    if(error || !reply) {
+        if(error) {
+            LoggerE("Failed to request Contacts: " << error->message);
+            g_error_free(error);
+        }
+        else if(!reply)
+            LoggerE("reply is null");
+
+        return;
+    }
+
+    char *contacts = NULL;
+    g_variant_get(reply, "(s)", &contacts);
+    //LoggerD("contacts: " << contacts); // !!! Logger is working with a buffer of 128 chars only
+    printf("%s\n", contacts);
+
+    g_variant_unref(reply);
+}
+
+void getCallHistory() {
+    LoggerD("entered");
+
+    GError *error = NULL;
+    GVariant *reply = NULL;
+    reply = g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                         PHONE_SERVICE,
+                                         PHONE_OBJ_PATH,
+                                         PHONE_IFACE,
+                                         "GetCallHistory",
+                                         g_variant_new("(u)", 10),
+                                         NULL,
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         -1,
+                                         NULL,
+                                         &error);
+    if(error || !reply) {
+        if(error) {
+            LoggerE("Failed to request CallHistory: " << error->message);
+            g_error_free(error);
+        }
+        else if(!reply)
+            LoggerE("reply is null");
+
+        return;
+    }
+
+    char *calls = NULL;
+    g_variant_get(reply, "(s)", &calls);
+    //LoggerD("calls: " << calls); // !!! Logger is working with a buffer of 128 chars only
+    printf("%s\n", calls);
+
+    g_variant_unref(reply);
+}
+
+void restart() {
+    LoggerD("entered");
+
+    GError *error = NULL;
+    g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                 PHONE_SERVICE,
+                                 PHONE_OBJ_PATH,
+                                 PHONE_IFACE,
+                                 "Restart",
+                                 NULL,
+                                 NULL,
+                                 G_DBUS_CALL_FLAGS_NONE,
+                                 -1,
+                                 NULL,
+                                 &error);
+    if(error) {
+        LoggerE("Failed to request Restart: " << error->message);
+        g_error_free(error);
+    }
+}
+
+static void handleSignal( GDBusConnection *connection,
+                          const gchar *sender_name,
+                          const gchar *object_path,
+                          const gchar *interface_name,
+                          const gchar *signal_name,
+                          GVariant *parameters,
+                          gpointer user_data)
+{
+    LoggerD("entered");
+    LoggerD("\tsender_name: " << sender_name);
+    LoggerD("\tobject_path: " << object_path);
+    LoggerD("\tinterface_name: " << interface_name);
+    LoggerD("\tsignal_name: " << signal_name);
+
+    if(!strcmp(signal_name, "BluetoothPowered")) {
+        GVariant *value = NULL;
+        g_variant_get(parameters, "(v)", &value);
+        bool powered = g_variant_get_boolean(value);
+        LoggerD("Received POWERED status: " << powered);
+    }
+    else if(!strcmp(signal_name, "CallChanged")) {
+        GVariantIter *iter;
+        g_variant_get(parameters, "(a{sv})", &iter);
+        const char *key;
+        GVariant *value;
+        while(g_variant_iter_next(iter, "{sv}", &key, &value)) {
+            if(!strcmp(key, "state")) {
+                const char *state = g_variant_get_string(value, NULL);
+                LoggerD("\t- state: " << state);
+            }
+            else if(!strcmp(key, "line_id")) {
+                const char *line_id = g_variant_get_string(value, NULL);
+                LoggerD("\t- line_id: " << line_id);
+            }
+            else if(!strcmp(key, "contact")) {
+                const char *contact = g_variant_get_string(value, NULL);
+                LoggerD("\t- contact: " << contact);
+            }
+        }
+    }
+    else if(!strcmp(signal_name, "ContactsChanged")) {
+        LoggerD("ContactsChanged");
+    }
+    else if(!strcmp(signal_name, "CallHistoryChanged")) {
+        LoggerD("CallHistoryChanged");
+    }
+    else if(!strcmp(signal_name, "CallHistoryEntryAdded")) {
+        const char *entry = NULL;
+        g_variant_get(parameters, "(s)", &entry);
+        printf("%s\n", entry);
+    }
+    else if(!strcmp(signal_name, "RemoteDeviceSelected")) {
+        LoggerD("RemoteDeviceSelected");
+        const char *result;
+        g_variant_get(parameters, "(s)", &result);
+        LoggerD("result = " << result);
+    }
+    else if(!strcmp(signal_name, "PairingResult")) {
+        const char *device = NULL, *message = NULL;
+        GVariant *tmp = NULL;
+        g_variant_get(parameters, "(svs)", &device, &tmp, &message);
+        bool success = g_variant_get_boolean(tmp);
+        LoggerD("Pairing result: device=" << device << " success=" << success << " message=" << message);
+    }
+    else if(!strcmp(signal_name, "RequestPinCode")) {
+        LoggerD("RequestPinCode");
+        const char *device;
+        g_variant_get(parameters, "(s)", &device);
+        LoggerD("Enter PinCode for " << device << ": pin <PIN CODE>");
+        if(_device) free(_device);
+        _device = strdup(device);
+    }
+    else if(!strcmp(signal_name, "RequestPasskey")) {
+        LoggerD("RequestPasskey");
+        const char *device;
+        g_variant_get(parameters, "(s)", &device);
+        LoggerD("Enter Passkey for " << device << ": pass <PASS KEY>");
+        if(_device) free(_device);
+        _device = strdup(device);
+    }
+    else if(!strcmp(signal_name, "RequestConfirmation")) {
+        LoggerD("RequestConfirmation");
+        const char *device;
+        unsigned long passkey;
+        g_variant_get(parameters, "(su)", &device, &passkey);
+        LoggerD("Confirm " << device << " with passkey " << passkey << " : conf <yes/no>");
+        if(_device) free(_device);
+        _device = strdup(device);
+    }
+}
+
+void pairDevice(const char *bt_address) {
+    LoggerD("entered");
+
+    GError *error = NULL;
+    g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+                                 PHONE_SERVICE,
+                                 PHONE_OBJ_PATH,
+                                 PHONE_IFACE,
+                                 "PairDevice",
+                                 g_variant_new("(s)", bt_address), // floating variants are consumed
+                                 NULL,
+                                 G_DBUS_CALL_FLAGS_NONE,
+                                 -1,
+                                 NULL,
+                                 &error);
+
+    if(error) {
+        LoggerE("Failed to request 'PairDevice': " << error->message);
+        g_error_free(error);
+    }
+}
+